Add support for generating optimizer function info from stubs (#7367)

This commit is contained in:
Máté Kocsis 2021-08-24 16:35:33 +02:00 committed by GitHub
parent 3be94217f4
commit b1822899fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 548 additions and 161 deletions

View file

@ -47,6 +47,8 @@ typedef struct _func_info_t {
#define FC(name, callback) \
{name, sizeof(name)-1, 0, callback}
#include "zend_func_infos.h"
static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
{
if (!call_info->send_unpack
@ -85,34 +87,21 @@ static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa
}
}
static const func_info_t func_infos[] = {
static const func_info_t old_func_infos[] = {
/* zend */
F1("zend_version", MAY_BE_STRING),
FN("func_get_args", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY),
F1("get_class_vars", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF),
F1("get_class_methods", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("get_included_files", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
FN("set_error_handler", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT),
F0("restore_error_handler", MAY_BE_TRUE),
F0("restore_exception_handler", MAY_BE_TRUE),
F1("get_declared_traits", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("get_declared_classes", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("get_declared_interfaces", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("get_defined_functions", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
F1("get_defined_vars", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF),
F1("get_resource_type", MAY_BE_STRING),
F1("get_defined_constants", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
F1("debug_backtrace", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
F1("get_loaded_extensions", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("get_extension_funcs", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
/* ext/standard */
FN("constant", MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
F1("bin2hex", MAY_BE_STRING),
F1("hex2bin", MAY_BE_FALSE | MAY_BE_STRING),
#if HAVE_NANOSLEEP
F1("time_nanosleep", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG),
#endif
#if HAVE_STRPTIME
F1("strptime", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
#endif
@ -191,7 +180,6 @@ static const func_info_t func_infos[] = {
F0("passthru", MAY_BE_NULL | MAY_BE_FALSE),
F1("shell_exec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
#ifdef PHP_CAN_SUPPORT_PROC_OPEN
F1("proc_open", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("proc_get_status", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
#endif
F1("random_bytes", MAY_BE_STRING),
@ -266,30 +254,14 @@ static const func_info_t func_infos[] = {
#ifdef HAVE_GETHOSTNAME
F1("gethostname", MAY_BE_FALSE | MAY_BE_STRING),
#endif
#if defined(PHP_WIN32) || HAVE_DNS_SEARCH_FUNC
# if defined(PHP_WIN32) || HAVE_FULL_DNS_FUNCS
F1("dns_get_record", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
# endif
#endif
F1("popen", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("fgets", MAY_BE_FALSE | MAY_BE_STRING),
F1("fread", MAY_BE_FALSE | MAY_BE_STRING),
F1("fopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("fstat", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG),
F1("tempnam", MAY_BE_FALSE | MAY_BE_STRING),
F1("tmpfile", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("file", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("file_get_contents", MAY_BE_FALSE | MAY_BE_STRING),
F1("stream_context_create", MAY_BE_RESOURCE),
F1("stream_context_get_params", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
FN("stream_context_get_options", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
FN("stream_context_get_default", MAY_BE_RESOURCE),
FN("stream_context_set_default", MAY_BE_RESOURCE),
FN("stream_filter_prepend", MAY_BE_FALSE | MAY_BE_RESOURCE),
FN("stream_filter_append", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("stream_socket_client", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("stream_socket_server", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("stream_socket_accept", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("stream_socket_get_name", MAY_BE_FALSE | MAY_BE_STRING),
F1("stream_socket_recvfrom", MAY_BE_FALSE | MAY_BE_STRING),
#if HAVE_SOCKETPAIR
@ -306,13 +278,10 @@ static const func_info_t func_infos[] = {
F1("get_headers", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
F1("socket_get_status", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
F1("realpath", MAY_BE_FALSE | MAY_BE_STRING),
F1("fsockopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
FN("pfsockopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("pack", MAY_BE_STRING),
F1("unpack", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
F1("get_browser", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
F1("crypt", MAY_BE_STRING),
FN("opendir", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("getcwd", MAY_BE_FALSE | MAY_BE_STRING),
F1("readdir", MAY_BE_FALSE | MAY_BE_STRING),
F1("dir", MAY_BE_FALSE | MAY_BE_OBJECT),
@ -542,7 +511,6 @@ static const func_info_t func_infos[] = {
F1("utf8_decode", MAY_BE_STRING),
/* ext/zlib */
F1("gzopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("gzfile", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
F1("gzcompress", MAY_BE_FALSE | MAY_BE_STRING),
F1("gzuncompress", MAY_BE_FALSE | MAY_BE_STRING),
@ -686,7 +654,6 @@ static const func_info_t func_infos[] = {
F1("pg_get_result", MAY_BE_FALSE | MAY_BE_OBJECT),
F1("pg_result_status", MAY_BE_LONG | MAY_BE_STRING),
F1("pg_get_notify", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
F1("pg_socket", MAY_BE_FALSE | MAY_BE_RESOURCE),
F1("pg_meta_data", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
F1("pg_convert", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
F1("pg_insert", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_OBJECT | MAY_BE_STRING),
@ -889,25 +856,33 @@ ZEND_API uint32_t zend_get_func_info(
return ret;
}
int zend_func_info_startup(void)
void zend_func_info_add(const func_info_t *func_infos, size_t n)
{
size_t i;
for (i = 0; i < n; i++) {
zend_string *key = zend_string_init_interned(func_infos[i].name, func_infos[i].name_len, 1);
if (zend_hash_add_ptr(&func_info, key, (void**)&func_infos[i]) == NULL) {
fprintf(stderr, "ERROR: Duplicate function info for \"%s\"\n", func_infos[i].name);
}
zend_string_release_ex(key, 1);
}
}
int zend_func_info_startup(void)
{
if (zend_func_info_rid == -1) {
zend_func_info_rid = zend_get_resource_handle("Zend Optimizer");
if (zend_func_info_rid < 0) {
return FAILURE;
}
zend_hash_init(&func_info, sizeof(func_infos)/sizeof(func_info_t), NULL, NULL, 1);
for (i = 0; i < sizeof(func_infos)/sizeof(func_info_t); i++) {
zend_string *key = zend_string_init_interned(func_infos[i].name, func_infos[i].name_len, 1);
zend_hash_init(&func_info, sizeof(old_func_infos)/sizeof(func_info_t) + sizeof(func_infos)/sizeof(func_info_t), NULL, NULL, 1);
if (zend_hash_add_ptr(&func_info, key, (void**)&func_infos[i]) == NULL) {
fprintf(stderr, "ERROR: Duplicate function info for \"%s\"\n", func_infos[i].name);
}
zend_string_release_ex(key, 1);
}
zend_func_info_add(old_func_infos, sizeof(old_func_infos)/sizeof(func_info_t));
zend_func_info_add(func_infos, sizeof(func_infos)/sizeof(func_info_t));
}
return SUCCESS;

View file

@ -0,0 +1,49 @@
/* This is a generated file, edit the .stub.php files instead. */
static const func_info_t func_infos[] = {
F1("zend_version", MAY_BE_STRING),
FN("func_get_args", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ANY),
F1("get_class_methods", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
F1("get_included_files", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
FN("set_error_handler", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT|MAY_BE_NULL),
F0("restore_error_handler", MAY_BE_TRUE),
FN("set_exception_handler", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT|MAY_BE_NULL),
F0("restore_exception_handler", MAY_BE_TRUE),
F1("get_resource_type", MAY_BE_STRING),
F1("get_defined_constants", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY),
FN("socket_export_stream", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("gzopen", MAY_BE_RESOURCE|MAY_BE_FALSE),
#if HAVE_NANOSLEEP
F1("time_nanosleep", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_BOOL),
#endif
#if defined(PHP_WIN32) || HAVE_DNS_SEARCH_FUNC
F1("dns_get_record", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE),
#endif
FN("opendir", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("popen", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("fopen", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("tmpfile", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("fsockopen", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("pfsockopen", MAY_BE_RESOURCE|MAY_BE_FALSE),
#if defined(PHP_CAN_SUPPORT_PROC_OPEN)
F1("proc_open", MAY_BE_RESOURCE|MAY_BE_FALSE),
#endif
F1("stream_context_create", MAY_BE_RESOURCE),
FN("stream_context_get_default", MAY_BE_RESOURCE),
FN("stream_context_set_default", MAY_BE_RESOURCE),
FN("stream_filter_prepend", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("stream_filter_append", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("stream_socket_client", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("stream_socket_server", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("stream_socket_accept", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("zip_open", MAY_BE_RESOURCE|MAY_BE_LONG|MAY_BE_FALSE),
FN("zip_read", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("oci_new_connect", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("oci_connect", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("oci_pconnect", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("oci_parse", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("oci_get_implicit_resultset", MAY_BE_RESOURCE|MAY_BE_FALSE),
FN("oci_password_change", MAY_BE_RESOURCE|MAY_BE_BOOL),
FN("oci_new_cursor", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("pg_socket", MAY_BE_RESOURCE|MAY_BE_FALSE),
};

View file

@ -6,12 +6,14 @@ class stdClass
{
}
/** @refcount 1 */
function zend_version(): string {}
function func_num_args(): int {}
function func_get_arg(int $position): mixed {}
/** @return array<int, mixed> */
function func_get_args(): array {}
function strlen(string $string): int {}
@ -48,6 +50,10 @@ function get_object_vars(object $object): array {}
function get_mangled_object_vars(object $object): array {}
/**
* @return array<int, string>
* @refcount 1
*/
function get_class_methods(object|string $object_or_class): array {}
/** @param object|string $object_or_class */
@ -68,9 +74,16 @@ function function_exists(string $function): bool {}
function class_alias(string $class, string $alias, bool $autoload = true): bool {}
/**
* @return array<int, string>
* @refcount 1
*/
function get_included_files(): array {}
/** @alias get_included_files */
/**
* @return array<int, string>
* @alias get_included_files
*/
function get_required_files(): array {}
function trigger_error(string $message, int $error_level = E_USER_NOTICE): bool {}
@ -78,14 +91,16 @@ function trigger_error(string $message, int $error_level = E_USER_NOTICE): bool
/** @alias trigger_error */
function user_error(string $message, int $error_level = E_USER_NOTICE): bool {}
/** @return string|array|object|null */
/** @return string|array<int, string|object>|object|null */
function set_error_handler(?callable $callback, int $error_levels = E_ALL) {}
/** @return true */
function restore_error_handler(): bool {}
/** @return string|array|object|null */
/** @return string|array<int, string|object>|object|null */
function set_exception_handler(?callable $callback) {}
/** @return true */
function restore_exception_handler(): bool {}
function get_declared_classes(): array {}
@ -98,7 +113,10 @@ function get_defined_functions(bool $exclude_disabled = true): array {}
function get_defined_vars(): array {}
/** @param resource $resource */
/**
* @param resource $resource
* @refcount 1
*/
function get_resource_type($resource): string {}
/** @param resource $resource */
@ -108,6 +126,10 @@ function get_resources(?string $type = null): array {}
function get_loaded_extensions(bool $zend_extensions = false): array {}
/**
* @return array<string, mixed>
* @refcount 1
*/
function get_defined_constants(bool $categorize = false): array {}
function debug_backtrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): array {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 38936b472d60d9eeb2cc8e35c1276e9f707837bf */
* Stub hash: c8a0a1e4cfece42832f737b33e317ba88b91fab5 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_version, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()

View file

@ -107,22 +107,59 @@ class Context {
public $forceRegeneration = false;
}
class ArrayType extends SimpleType {
/** @var Type */
public $keyType;
/** @var Type */
public $valueType;
public static function createGenericArray(): self
{
return new ArrayType(Type::fromString("int|string"), Type::fromString("mixed"));
}
public function __construct(Type $keyType, Type $valueType)
{
parent::__construct("array", true);
$this->keyType = $keyType;
$this->valueType = $valueType;
}
public function toOptimizerTypeMask(): string {
$typeMasks = [
parent::toOptimizerTypeMask(),
$this->keyType->toOptimizerTypeMaskForArrayKey(),
$this->valueType->toOptimizerTypeMaskForArrayValue(),
];
return implode("|", $typeMasks);
}
public function equals(SimpleType $other): bool {
if (!parent::equals($other)) {
return false;
}
assert(get_class($other) === self::class);
return Type::equals($this->keyType, $other->keyType) &&
Type::equals($this->valueType, $other->valueType);
}
}
class SimpleType {
/** @var string */
public $name;
/** @var bool */
public $isBuiltin;
public function __construct(string $name, bool $isBuiltin) {
$this->name = $name;
$this->isBuiltin = $isBuiltin;
}
public static function fromNode(Node $node): SimpleType {
if ($node instanceof Node\Name) {
if ($node->toLowerString() === 'static') {
// PHP internally considers "static" a builtin type.
return new SimpleType($node->toString(), true);
return new SimpleType($node->toLowerString(), true);
}
if ($node->toLowerString() === 'self') {
@ -132,39 +169,55 @@ class SimpleType {
assert($node->isFullyQualified());
return new SimpleType($node->toString(), false);
}
if ($node instanceof Node\Identifier) {
return new SimpleType($node->toString(), true);
if ($node->toLowerString() === 'array') {
return ArrayType::createGenericArray();
}
return new SimpleType($node->toLowerString(), true);
}
throw new Exception("Unexpected node type");
}
public static function fromPhpDoc(string $type): SimpleType
public static function fromString(string $typeString): SimpleType
{
switch (strtolower($type)) {
switch (strtolower($typeString)) {
case "void":
case "null":
case "false":
case "true":
case "bool":
case "int":
case "float":
case "string":
case "array":
case "iterable":
case "object":
case "resource":
case "mixed":
case "static":
case "never":
return new SimpleType(strtolower($type), true);
return new SimpleType(strtolower($typeString), true);
case "array":
return ArrayType::createGenericArray();
case "self":
throw new Exception('The exact class name must be used instead of "self"');
}
if (strpos($type, "[]") !== false) {
return new SimpleType("array", true);
$matches = [];
$isArray = preg_match("/(.*)\s*\[\s*\]/", $typeString, $matches);
if ($isArray) {
return new ArrayType(Type::fromString("int"), Type::fromString($matches[1]));
}
return new SimpleType($type, false);
$matches = [];
$isArray = preg_match("/array\s*<\s*([A-Za-z0-9_-|]+)\s*,\s*([A-Za-z0-9_-|]+)\s*>/i", $typeString, $matches);
if ($isArray) {
return new ArrayType(Type::fromString($matches[1]), Type::fromString($matches[2]));
}
return new SimpleType($typeString, false);
}
public static function null(): SimpleType
@ -177,13 +230,22 @@ class SimpleType {
return new SimpleType("void", true);
}
protected function __construct(string $name, bool $isBuiltin) {
$this->name = $name;
$this->isBuiltin = $isBuiltin;
}
public function isScalar(): bool {
return $this->isBuiltin && in_array($this->name, ["null", "false", "true", "bool", "int", "float"], true);
}
public function isNull(): bool {
return $this->isBuiltin && $this->name === 'null';
}
public function toTypeCode(): string {
assert($this->isBuiltin);
switch (strtolower($this->name)) {
switch ($this->name) {
case "bool":
return "_IS_BOOL";
case "int":
@ -213,9 +275,10 @@ class SimpleType {
}
}
public function toTypeMask() {
public function toTypeMask(): string {
assert($this->isBuiltin);
switch (strtolower($this->name)) {
switch ($this->name) {
case "null":
return "MAY_BE_NULL";
case "false":
@ -245,20 +308,86 @@ class SimpleType {
}
}
public function toOptimizerTypeMaskForArrayKey(): string {
assert($this->isBuiltin);
switch ($this->name) {
case "int":
return "MAY_BE_ARRAY_KEY_LONG";
case "string":
return "MAY_BE_ARRAY_KEY_STRING";
default:
throw new Exception("Type $this->name cannot be an array key");
}
}
public function toOptimizerTypeMaskForArrayValue(): string {
if (!$this->isBuiltin) {
return "MAY_BE_ARRAY_OF_OBJECT";
}
switch ($this->name) {
case "null":
return "MAY_BE_ARRAY_OF_NULL";
case "false":
return "MAY_BE_ARRAY_OF_FALSE";
case "bool":
return "MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE";
case "int":
return "MAY_BE_ARRAY_OF_LONG";
case "float":
return "MAY_BE_ARRAY_OF_DOUBLE";
case "string":
return "MAY_BE_ARRAY_OF_STRING";
case "array":
return "MAY_BE_ARRAY_OF_ARRAY";
case "object":
return "MAY_BE_ARRAY_OF_OBJECT";
case "resource":
return "MAY_BE_ARRAY_OF_RESOURCE";
case "mixed":
return "MAY_BE_ARRAY_OF_ANY";
default:
throw new Exception("Type $this->name cannot be an array value");
}
}
public function toOptimizerTypeMask(): string {
if (!$this->isBuiltin) {
return "MAY_BE_OBJECT";
}
if ($this->name === "resource") {
return "MAY_BE_RESOURCE";
}
if ($this->name === "true") {
return "MAY_BE_TRUE";
}
if ($this->name === "mixed") {
return "MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY";
}
return $this->toTypeMask();
}
public function toEscapedName(): string {
return str_replace('\\', '\\\\', $this->name);
}
public function equals(SimpleType $other) {
return $this->name === $other->name
&& $this->isBuiltin === $other->isBuiltin;
public function equals(SimpleType $other): bool {
return $this->name === $other->name && $this->isBuiltin === $other->isBuiltin;
}
}
class Type {
/** @var SimpleType[] $types */
/** @var SimpleType[] */
public $types;
/**
* @param SimpleType[] $types
*/
public function __construct(array $types) {
$this->types = $types;
}
@ -276,23 +405,58 @@ class Type {
return new Type([SimpleType::fromNode($node)]);
}
public static function fromPhpDoc(string $phpDocType) {
$types = explode("|", $phpDocType);
public static function fromString(string $typeString): self {
$typeString .= "|";
$simpleTypes = [];
foreach ($types as $type) {
$simpleTypes[] = SimpleType::fromPhpDoc($type);
$simpleTypeOffset = 0;
$inArray = false;
$typeStringLength = strlen($typeString);
for ($i = 0; $i < $typeStringLength; $i++) {
$char = $typeString[$i];
if ($char === "<") {
$inArray = true;
continue;
}
if ($char === ">") {
$inArray = false;
continue;
}
if ($inArray) {
continue;
}
if ($char === "|") {
$simpleTypeName = trim(substr($typeString, $simpleTypeOffset, $i - $simpleTypeOffset));
$simpleTypes[] = SimpleType::fromString($simpleTypeName);
$simpleTypeOffset = $i + 1;
}
}
return new Type($simpleTypes);
}
public function isScalar(): bool {
foreach ($this->types as $type) {
if (!$type->isScalar()) {
return false;
}
}
return true;
}
public function isNullable(): bool {
foreach ($this->types as $type) {
if ($type->isNull()) {
return true;
}
}
return false;
}
@ -323,6 +487,36 @@ class Type {
return new ArginfoType($classTypes, $builtinTypes);
}
public function toOptimizerTypeMask(): string {
$optimizerTypes = [];
foreach ($this->types as $type) {
$optimizerTypes[] = $type->toOptimizerTypeMask();
}
return implode("|", $optimizerTypes);
}
public function toOptimizerTypeMaskForArrayKey(): string {
$typeMasks = [];
foreach ($this->types as $type) {
$typeMasks[] = $type->toOptimizerTypeMaskForArrayKey();
}
return implode("|", $typeMasks);
}
public function toOptimizerTypeMaskForArrayValue(): string {
$typeMasks = [];
foreach ($this->types as $type) {
$typeMasks[] = $type->toOptimizerTypeMaskForArrayValue();
}
return implode("|", $typeMasks);
}
public function getTypeForDoc(DOMDocument $doc): DOMElement {
if (count($this->types) > 1) {
$typeElement = $doc->createElement('type');
@ -333,7 +527,14 @@ class Type {
$typeElement->appendChild($unionTypeElement);
}
} else {
$typeElement = $doc->createElement('type', $this->types[0]->name);
$type = $this->types[0];
if ($type->isBuiltin && strtolower($type->name) === "true") {
$name = "bool";
} else {
$name = $type->name;
}
$typeElement = $doc->createElement('type', $name);
}
return $typeElement;
@ -423,8 +624,7 @@ class ArgInfo {
$this->name = $name;
$this->sendBy = $sendBy;
$this->isVariadic = $isVariadic;
$this->type = $type;
$this->phpDocType = $phpDocType;
$this->setTypes($type, $phpDocType);
$this->defaultValue = $defaultValue;
}
@ -488,6 +688,16 @@ class ArgInfo {
return $this->defaultValue;
}
private function setTypes(?Type $type, ?Type $phpDocType): void
{
if ($phpDocType !== null && Type::equals($type, $phpDocType)) {
throw new Exception('PHPDoc param type "' . $phpDocType->__toString() . '" is unnecessary');
}
$this->type = $type;
$this->phpDocType = $phpDocType;
}
}
class PropertyName {
@ -619,6 +829,16 @@ class MethodName implements FunctionOrMethodName {
}
class ReturnInfo {
const REFCOUNT_0 = "0";
const REFCOUNT_1 = "1";
const REFCOUNT_N = "N";
const REFCOUNTS = [
self::REFCOUNT_0,
self::REFCOUNT_1,
self::REFCOUNT_N,
];
/** @var bool */
public $byRef;
/** @var Type|null */
@ -627,15 +847,16 @@ class ReturnInfo {
public $phpDocType;
/** @var bool */
public $tentativeReturnType;
/** @var string */
public $refcount;
public function __construct(bool $byRef, ?Type $type, ?Type $phpDocType, bool $tentativeReturnType) {
public function __construct(bool $byRef, ?Type $type, ?Type $phpDocType, bool $tentativeReturnType, ?string $refcount) {
$this->byRef = $byRef;
$this->type = $type;
$this->phpDocType = $phpDocType;
$this->tentativeReturnType = $tentativeReturnType;
$this->setTypes($type, $phpDocType, $tentativeReturnType);
$this->setRefcount($refcount);
}
public function equals(ReturnInfo $other): bool {
public function equalsApartFromPhpDocAndRefcount(ReturnInfo $other): bool {
return $this->byRef === $other->byRef
&& Type::equals($this->type, $other->type)
&& $this->tentativeReturnType === $other->tentativeReturnType;
@ -644,6 +865,42 @@ class ReturnInfo {
public function getMethodSynopsisType(): ?Type {
return $this->type ?? $this->phpDocType;
}
private function setTypes(?Type $type, ?Type $phpDocType, bool $tentativeReturnType): void
{
if ($phpDocType !== null && Type::equals($type, $phpDocType)) {
throw new Exception('PHPDoc return type "' . $phpDocType->__toString() . '" is unnecessary');
}
$this->type = $type;
$this->phpDocType = $phpDocType;
$this->tentativeReturnType = $tentativeReturnType;
}
private function setRefcount(?string $refcount): void
{
$type = $this->phpDocType ?? $this->type;
$isScalarType = $type !== null && $type->isScalar();
if ($refcount === null) {
$this->refcount = $isScalarType ? self::REFCOUNT_0 : self::REFCOUNT_N;
return;
}
if (!in_array($refcount, ReturnInfo::REFCOUNTS, true)) {
throw new Exception("@refcount must have one of the following values: \"0\", \"1\", \"N\", $refcount given");
}
if ($isScalarType && $refcount !== self::REFCOUNT_0) {
throw new Exception('A scalar return type of "' . $type->__toString() . '" must have a refcount of "' . self::REFCOUNT_0 . '"');
}
if (!$isScalarType && $refcount === self::REFCOUNT_0) {
throw new Exception('A non-scalar return type of "' . $type->__toString() . '" cannot have a refcount of "' . self::REFCOUNT_0 . '"');
}
$this->refcount = $refcount;
}
}
class FuncInfo {
@ -752,7 +1009,7 @@ class FuncInfo {
return false;
}
public function equalsApartFromName(FuncInfo $other): bool {
public function equalsApartFromNameAndRefcount(FuncInfo $other): bool {
if (count($this->args) !== count($other->args)) {
return false;
}
@ -763,7 +1020,7 @@ class FuncInfo {
}
}
return $this->return->equals($other->return)
return $this->return->equalsApartFromPhpDocAndRefcount($other->return)
&& $this->numRequiredArgs === $other->numRequiredArgs
&& $this->cond === $other->cond;
}
@ -860,6 +1117,27 @@ class FuncInfo {
}
}
public function getOptimizerInfo(): ?string {
if ($this->isMethod()) {
return null;
}
if ($this->alias !== null) {
return null;
}
if ($this->return->refcount !== ReturnInfo::REFCOUNT_1 && $this->return->phpDocType === null) {
return null;
}
$type = $this->return->phpDocType ?? $this->return->type;
if ($type === null) {
return null;
}
return " F" . $this->return->refcount . '("' . $this->name->__toString() . '", ' . $type->toOptimizerTypeMask() . "),\n";
}
public function discardInfoForOldPhpVersions(): void {
$this->return->type = null;
foreach ($this->args as $arg) {
@ -1733,12 +2011,12 @@ class DocCommentTag {
$matches = [];
if ($this->name === "param") {
preg_match('/^\s*([\w\|\\\\\[\]]+)\s*\$\w+.*$/', $value, $matches);
preg_match('/^\s*([\w\|\\\\\[\]<>, ]+)\s*\$\w+.*$/', $value, $matches);
} elseif ($this->name === "return") {
preg_match('/^\s*([\w\|\\\\\[\]]+)(\s+|$)/', $value, $matches);
preg_match('/^\s*([\w\|\\\\\[\]<>, ]+)(\s+|$)/', $value, $matches);
}
if (isset($matches[1]) === false) {
if (!isset($matches[1])) {
throw new Exception("@$this->name doesn't contain a type or has an invalid format \"$value\"");
}
@ -1759,7 +2037,7 @@ class DocCommentTag {
preg_match('/^\s*\$(\w+).*$/', $value, $matches);
}
if (isset($matches[1]) === false) {
if (!isset($matches[1])) {
throw new Exception("@$this->name doesn't contain a variable name or has an invalid format \"$value\"");
}
@ -1799,6 +2077,7 @@ function parseFunctionLike(
$docReturnType = null;
$tentativeReturnType = false;
$docParamTypes = [];
$refcount = null;
if ($comment) {
$tags = parseDocComment($comment);
@ -1827,6 +2106,8 @@ function parseFunctionLike(
$docReturnType = $tag->getType();
} else if ($tag->name === 'param') {
$docParamTypes[$tag->getVariableName()] = $tag->getType();
} else if ($tag->name === 'refcount') {
$refcount = $tag->getValue();
}
}
}
@ -1883,7 +2164,7 @@ function parseFunctionLike(
$sendBy,
$param->variadic,
$type,
isset($docParamTypes[$varName]) ? Type::fromPhpDoc($docParamTypes[$varName]) : null,
isset($docParamTypes[$varName]) ? Type::fromString($docParamTypes[$varName]) : null,
$param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null
);
if (!$param->default && !$param->variadic) {
@ -1903,8 +2184,9 @@ function parseFunctionLike(
$return = new ReturnInfo(
$func->returnsByRef(),
$returnType ? Type::fromNode($returnType) : null,
$docReturnType ? Type::fromPhpDoc($docReturnType) : null,
$tentativeReturnType
$docReturnType ? Type::fromString($docReturnType) : null,
$tentativeReturnType,
$refcount
);
return new FuncInfo(
@ -2302,7 +2584,7 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
/** @param FuncInfo[] $generatedFuncInfos */
function findEquivalentFuncInfo(array $generatedFuncInfos, FuncInfo $funcInfo): ?FuncInfo {
foreach ($generatedFuncInfos as $generatedFuncInfo) {
if ($generatedFuncInfo->equalsApartFromName($funcInfo)) {
if ($generatedFuncInfo->equalsApartFromNameAndRefcount($funcInfo)) {
return $generatedFuncInfo;
}
}
@ -2416,6 +2698,22 @@ function generateFunctionEntries(?Name $className, array $funcInfos): string {
return $code;
}
/** @param FuncInfo[] $funcInfos */
function generateOptimizerInfo(array $funcInfos): string {
$code = "/* This is a generated file, edit the .stub.php files instead. */\n\n";
$code .= "static const func_info_t func_infos[] = {\n";
$code .= generateCodeWithConditions($funcInfos, "", function (FuncInfo $funcInfo) {
return $funcInfo->getOptimizerInfo();
});
$code .= "};\n";
return $code;
}
/**
* @param ClassInfo[] $classMap
* @return array<string, string>
@ -2433,7 +2731,6 @@ function generateClassSynopses(array $classMap): array {
return $result;
}
/**
* @param ClassInfo[] $classMap
* @return array<string, string>
@ -2812,7 +3109,7 @@ $options = getopt(
"fh",
[
"force-regeneration", "parameter-stats", "help", "verify", "generate-classsynopses", "replace-classsynopses",
"generate-methodsynopses", "replace-methodsynopses"
"generate-methodsynopses", "replace-methodsynopses", "generate-optimizer-info"
],
$optind
);
@ -2824,8 +3121,9 @@ $generateClassSynopses = isset($options["generate-classsynopses"]);
$replaceClassSynopses = isset($options["replace-classsynopses"]);
$generateMethodSynopses = isset($options["generate-methodsynopses"]);
$replaceMethodSynopses = isset($options["replace-methodsynopses"]);
$generateOptimizerInfo = isset($options["generate-optimizer-info"]);
$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $generateClassSynopses || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
$targetSynopses = $argv[$argc - 1] ?? null;
if ($replaceClassSynopses && $targetSynopses === null) {
@ -2961,13 +3259,22 @@ if ($verify) {
$aliasArgs, $aliasedArgs
);
$aliasedReturn = $aliasedFunc->return;
$aliasReturn = $aliasFunc->return;
if (!$aliasedFunc->name->isConstructor() && !$aliasFunc->name->isConstructor()) {
$aliasedReturnType = $aliasedFunc->return->type ?? $aliasedFunc->return->phpDocType;
$aliasReturnType = $aliasFunc->return->type ?? $aliasFunc->return->phpDocType;
$aliasedReturnType = $aliasedReturn->type ?? $aliasedReturn->phpDocType;
$aliasReturnType = $aliasReturn->type ?? $aliasReturn->phpDocType;
if ($aliasReturnType != $aliasedReturnType) {
$errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
}
}
$aliasedPhpDocReturnType = $aliasedReturn->phpDocType;
$aliasPhpDocReturnType = $aliasReturn->phpDocType;
if ($aliasedPhpDocReturnType != $aliasPhpDocReturnType && $aliasedPhpDocReturnType != $aliasReturn->type && $aliasPhpDocReturnType != $aliasedReturn->type) {
$errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same PHPDoc return type";
}
}
echo implode("\n", $errors);
@ -3004,7 +3311,6 @@ if ($replaceClassSynopses) {
}
}
if ($generateMethodSynopses) {
$methodSynopsesDirectory = getcwd() . "/methodsynopses";
@ -3031,3 +3337,12 @@ if ($replaceMethodSynopses) {
}
}
}
if ($generateOptimizerInfo) {
$filename = dirname(__FILE__, 2) . "/Zend/Optimizer/zend_func_infos.h";
$optimizerInfo = generateOptimizerInfo($funcMap);
if (file_put_contents($filename, $optimizerInfo)) {
echo "Saved $filename\n";
}
}

View file

@ -401,7 +401,10 @@ namespace {
function pg_get_pid(PgSql\Connection $connection): int {}
/** @return resource|false */
/**
* @return resource|false
* @refcount 1
*/
function pg_socket(PgSql\Connection $connection) {}
function pg_consume_input(PgSql\Connection $connection): bool {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 37814767f8290b717a4facbdf55ef3bfc1fc24c8 */
* Stub hash: 09b927fbe51265aa3895c6fadbbe66eb3087d4df */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0)

View file

@ -288,6 +288,10 @@ function sleep(int $seconds): int {}
function usleep(int $microseconds): void {}
#if HAVE_NANOSLEEP
/**
* @return array<string, int>|bool
* @refcount 1
*/
function time_nanosleep(int $seconds, int $nanoseconds): array|bool {}
function time_sleep_until(float $timestamp): bool {}
@ -421,6 +425,8 @@ function checkdnsrr(string $hostname, string $type = "MX"): bool {}
/**
* @param array $authoritative_name_servers
* @param array $additional_records
* @return array<int, array>|false
* @refcount 1
*/
function dns_get_record(string $hostname, int $type = DNS_ANY, &$authoritative_name_servers = null, &$additional_records = null, bool $raw = false): array|false {}
@ -772,7 +778,10 @@ function get_meta_tags(string $filename, bool $use_include_path = false): array|
/** @param resource $handle */
function pclose($handle): int {}
/** @return resource|false */
/**
* @return resource|false
* @refcount 1
*/
function popen(string $command, string $mode) {}
/** @param resource|null $context */
@ -804,6 +813,7 @@ function fread($stream, int $length): string|false {}
/**
* @param resource|null $context
* @return resource|false
* @refcount 1
*/
function fopen(string $filename, string $mode, bool $use_include_path = false, $context = null) {}
@ -854,7 +864,10 @@ function copy(string $from, string $to, $context = null): bool {}
function tempnam(string $directory, string $prefix): string|false {}
/** @return resource|false */
/**
* @return resource|false
* @refcount 1
*/
function tmpfile() {}
/** @param resource|null $context */
@ -975,6 +988,7 @@ function vfprintf($stream, string $format, array $values): int {}
* @param int $error_code
* @param string $error_message
* @return resource|false
* @refcount 1
*/
function fsockopen(string $hostname, int $port = -1, &$error_code = null, &$error_message = null, ?float $timeout = null) {}
@ -1165,6 +1179,7 @@ function password_algos(): array {}
/**
* @param array $pipes
* @return resource|false
* @refcount 1
*/
function proc_open(array|string $command, array $descriptor_spec, &$pipes, ?string $cwd = null, ?array $env_vars = null, ?array $options = null) {}
@ -1214,7 +1229,10 @@ function soundex(string $string): string {}
function stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null): int|false {}
/** @return resource */
/**
* @return resource
* @refcount 1
*/
function stream_context_create(?array $options = null, ?array $params = null) {}
/** @param resource $context */
@ -1255,6 +1273,7 @@ function stream_filter_remove($stream_filter): bool {}
* @param string $error_message
* @param resource|null $context
* @return resource|false
* @refcount 1
*/
function stream_socket_client(string $address, &$error_code = null, &$error_message = null, ?float $timeout = null, int $flags = STREAM_CLIENT_CONNECT, $context = null) {}
@ -1263,14 +1282,15 @@ function stream_socket_client(string $address, &$error_code = null, &$error_mess
* @param string $error_message
* @param resource|null $context
* @return resource|false
* @refcount 1
*/
function stream_socket_server(string $address, &$error_code = null, &$error_message = null, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context = null) {}
/**
* @param resource $socket
* @param float $timeout
* @param string $peer_name
* @return resource|false
* @refcount 1
*/
function stream_socket_accept($socket, ?float $timeout = null, &$peer_name = null) {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 82d7b69362e6dc0ccd8b7cb953ec0bdcb494137d */
* Stub hash: a7e7f1f40e8ea4f8a532bce27b4841329a5a9663 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)

View file

@ -24,7 +24,10 @@ function zlib_get_coding_type(): string|false {}
function gzfile(string $filename, int $use_include_path = 0): array|false {}
/** @return resource|false */
/**
* @return resource|false
* @refcount 1
*/
function gzopen(string $filename, string $mode, int $use_include_path = 0) {}
function readgzfile(string $filename, int $use_include_path = 0): int|false {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 2e3212e4cbb2fdeb5fc1342b19590bc89ae5d31a */
* Stub hash: 89215fe1b85a38feee3926e25e9b8eac2365c4cb */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ob_gzhandler, 0, 2, MAY_BE_STRING|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)