From 155350dc1f9dda3466d8e3e3d13558a7b05ba989 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Thu, 27 Mar 2025 23:29:44 +0100 Subject: [PATCH 01/19] initial type tree implementation --- Zend/zend_API.c | 226 +++++++++++++++++++++++++++++++++++++++++++- Zend/zend_API.h | 40 ++++---- Zend/zend_compile.c | 4 + Zend/zend_compile.h | 4 + Zend/zend_execute.c | 81 ++++++++++++++-- Zend/zend_execute.h | 2 +- Zend/zend_types.h | 21 ++++ 7 files changed, 346 insertions(+), 32 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 5bc4b4a04509f..a2a7cdb658eda 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2898,7 +2898,223 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arg_info_toString, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() -static zend_always_inline void zend_normalize_internal_type(zend_type *type) { +static HashTable *interned_type_tree = NULL; + +// todo: move to zend_types.h +#define ADD_TO_TREE(list, count, value) \ + do { \ + list = erealloc(list, sizeof(zend_type) * (count + 1)); \ + list[count++] = value; \ + } while (0) + +static int compare_simple_types(zend_type a, zend_type b) { + uint32_t a_mask = ZEND_TYPE_FULL_MASK(a); + uint32_t b_mask = ZEND_TYPE_FULL_MASK(b); + + if (a_mask != b_mask) { + return a_mask < b_mask ? -1 : 1; + } + + bool a_has_name = ZEND_TYPE_HAS_NAME(a); + bool b_has_name = ZEND_TYPE_HAS_NAME(b); + + if (a_has_name && b_has_name) { + zend_string *a_name = ZEND_TYPE_NAME(a); + zend_string *b_name = ZEND_TYPE_NAME(b); + int cmp = ZSTR_VAL(a_name) == ZSTR_VAL(b_name); + if (cmp != 0) { + return cmp; + } + } + + bool a_nullable = ZEND_TYPE_ALLOW_NULL(a); + bool b_nullable = ZEND_TYPE_ALLOW_NULL(b); + + if (a_nullable != b_nullable) { + return a_nullable ? 1 : -1; + } + + // Types are equal + return 0; +} + +static int compare_type_nodes(const void *a_, const void *b_) { + zend_type_node *a = *(zend_type_node **)a_; + zend_type_node *b = *(zend_type_node **)b_; + + if (a->kind != b->kind) { + return a->kind - b->kind; + } + + if (a->kind == ZEND_TYPE_SIMPLE) { + return compare_simple_types(a->simple_type, b->simple_type); + } + + if (a->compound.num_types != b->compound.num_types) { + return (int)a->compound.num_types - (int)b->compound.num_types; + } + + for (uint32_t i = 0; i < a->compound.num_types; i++) { + const int cmp = compare_type_nodes(&a->compound.types[i], &b->compound.types[i]); + if (cmp != 0) { + return cmp; + } + } + + return 0; +} + +zend_ulong zend_type_node_hash(zend_type_node *node) { + zend_ulong hash = 2166136261u; // FNV-1a offset basis + + hash ^= (zend_ulong)node->kind; + hash *= 16777619; + + switch (node->kind) { + case ZEND_TYPE_SIMPLE: { + zend_type type = node->simple_type; + hash ^= (zend_ulong)ZEND_TYPE_FULL_MASK(type); + hash *= 16777619; + + if (ZEND_TYPE_HAS_NAME(type)) { + zend_string *name = ZEND_TYPE_NAME(type); + hash ^= zend_string_hash_val(name); + hash *= 16777619; + } + + break; + } + + case ZEND_TYPE_UNION: + case ZEND_TYPE_INTERSECTION: { + for (uint32_t i = 0; i < node->compound.num_types; ++i) { + zend_ulong child_hash = zend_type_node_hash(node->compound.types[i]); + hash ^= child_hash; + hash *= 16777619; + } + break; + } + } + + return hash; +} + +bool zend_type_node_equals(zend_type_node *a, zend_type_node *b) { + if (a == b) return true; + if (a->kind != b->kind) return false; + + if (a->kind == ZEND_TYPE_SIMPLE) { + zend_type at = a->simple_type; + zend_type bt = b->simple_type; + + if (ZEND_TYPE_FULL_MASK(at) != ZEND_TYPE_FULL_MASK(bt)) { + return false; + } + + bool a_has_name = ZEND_TYPE_HAS_NAME(at); + bool b_has_name = ZEND_TYPE_HAS_NAME(bt); + if (a_has_name != b_has_name) { + return false; + } + + if (a_has_name) { + zend_string *a_name = ZEND_TYPE_NAME(at); + zend_string *b_name = ZEND_TYPE_NAME(bt); + if (!zend_string_equals(a_name, b_name)) { + return false; + } + } + + return true; + } + + // Compound type: union or intersection + if (a->compound.num_types != b->compound.num_types) { + return false; + } + + for (uint32_t i = 0; i < a->compound.num_types; ++i) { + if (!zend_type_node_equals(a->compound.types[i], b->compound.types[i])) { + return false; + } + } + + return true; +} + + +static zend_type_node *intern_type_node(zend_type_node *node) { + zend_ulong hash = zend_type_node_hash(node); + zend_type_node *existing; + + if (interned_type_tree == NULL) { + interned_type_tree = pemalloc(sizeof(HashTable), 1); + zend_hash_init(interned_type_tree, 64, NULL, NULL, 1); + } + + if ((existing = zend_hash_index_find_ptr(interned_type_tree, hash))) { + if (zend_type_node_equals(existing, node)) { + return existing; // reuse interned node + } + } + + zend_hash_index_add_new_ptr(interned_type_tree, hash, node); + return node; +} + + +ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type) { + if (type.type_mask == 0) { + return NULL; + } + + if (!ZEND_TYPE_HAS_LIST(type)) { + zend_type_node *node = pemalloc(sizeof(zend_type_node), 1); + node->kind = ZEND_TYPE_SIMPLE; + node->simple_type = type; + return intern_type_node(node); + } + + zend_type_list *list = ZEND_TYPE_LIST(type); + zend_type_node_kind kind = ZEND_TYPE_IS_INTERSECTION(type) ? + ZEND_TYPE_INTERSECTION : ZEND_TYPE_UNION; + + zend_type_node **children = NULL; + uint32_t num_children = 0; + + zend_type *subtype; + + ZEND_TYPE_LIST_FOREACH(list, subtype) { + zend_type_node *child = zend_type_to_interned_tree(*subtype); + + if (child->kind == kind) { + for (uint32_t i = 0; child->compound.num_types; i++) { + ADD_TO_TREE(children, num_children, child->compound.types[i]); + } + } else { + ADD_TO_TREE(children, num_children, child); + } + } ZEND_TYPE_LIST_FOREACH_END(); + + qsort(children, num_children, sizeof(zend_type_node*), compare_type_nodes); + + size_t deduped_count = 0; + for (size_t i = 0; i < num_children; i++) { + if (i == 0 || compare_type_nodes(&children[i], &children[i - 1]) != 0) { + children[deduped_count++] = children[i]; + } + } + + zend_type_node *node = pemalloc(sizeof(zend_type_node), 1); + node->kind = kind; + node->compound.num_types = deduped_count; + node->compound.types = pemalloc(sizeof(zend_type_node *) * deduped_count, 1); + memcpy(node->compound.types, children, sizeof(zend_type_node *) * deduped_count); + + return intern_type_node(node); +} + +static zend_always_inline zend_type_node *zend_normalize_internal_type(zend_type *type) { ZEND_ASSERT(!ZEND_TYPE_HAS_LITERAL_NAME(*type)); if (ZEND_TYPE_PURE_MASK(*type) != MAY_BE_ANY) { ZEND_ASSERT(!ZEND_TYPE_CONTAINS_CODE(*type, IS_RESOURCE) && "resource is not allowed in a zend_type"); @@ -2921,6 +3137,8 @@ static zend_always_inline void zend_normalize_internal_type(zend_type *type) { } ZEND_TYPE_FOREACH_END(); } } ZEND_TYPE_FOREACH_END(); + + return zend_type_to_interned_tree(*type); } /* registers all functions in *library_functions in the function hash */ @@ -3190,7 +3408,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend new_arg_info[i].type = legacy_iterable; } - zend_normalize_internal_type(&new_arg_info[i].type); + new_arg_info[i].type_tree = zend_normalize_internal_type(&new_arg_info[i].type); } } @@ -4665,7 +4883,9 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z property_info->type = type; if (is_persistent_class(ce)) { - zend_normalize_internal_type(&property_info->type); + property_info->type_tree = zend_normalize_internal_type(&property_info->type); + } else { + property_info->type_tree = zend_type_to_interned_tree(property_info->type); } zend_hash_update_ptr(&ce->properties_info, name, property_info); diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 6aeffce25d8e5..3beb40408fdcf 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -128,46 +128,46 @@ typedef struct _zend_fcall_info_cache { /* Arginfo structures without type information */ #define ZEND_ARG_INFO(pass_by_ref, name) \ - { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value) \ - { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) \ - { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Arginfo structures with simple type information */ #define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value) \ - { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Arginfo structures with complex type information */ #define ZEND_ARG_TYPE_MASK(pass_by_ref, name, type_mask, default_value) \ - { #name, ZEND_TYPE_INIT_MASK(type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_MASK(type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask, default_value) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Arginfo structures with object type information */ #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, class_name, allow_null) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, class_name, allow_null, default_value) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, class_name, allow_null) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Legacy arginfo structures */ #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX2(name, return_reference, required_num_args, class_name, allow_null, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ { (const char*)(uintptr_t)(required_num_args), \ - ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX2(name, return_reference, required_num_args, class_name, allow_null, 0) @@ -180,7 +180,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX2(name, return_reference, required_num_args, type, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_MASK(type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_MASK(type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(name, return_reference, required_num_args, type) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX2(name, return_reference, required_num_args, type, 0) @@ -190,7 +190,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX2(name, return_reference, required_num_args, class_name, type, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(name, return_reference, required_num_args, class_name, type) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX2(name, return_reference, required_num_args, class_name, type, 0) @@ -200,7 +200,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, 0) @@ -213,7 +213,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0, 0)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0, 0)), NULL, NULL }, #define ZEND_BEGIN_ARG_INFO(name, _unused) \ ZEND_BEGIN_ARG_INFO_EX(name, {}, ZEND_RETURN_VALUE, -1) #define ZEND_END_ARG_INFO() }; @@ -448,6 +448,8 @@ ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const ze ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type); ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type); +ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type); + static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) { if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) { zend_class_mutable_data *mutable_data = diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8e4221673c4cf..ddb9ece5ae5d2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7348,6 +7348,7 @@ static zend_type zend_compile_typename_ex( } ast->attr = orig_ast_attr; + return type; } /* }}} */ @@ -7584,6 +7585,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } else { arg_infos->type = (zend_type) ZEND_TYPE_INIT_CODE(fallback_return_type, 0, 0); } + arg_infos->type_tree = zend_type_to_interned_tree(arg_infos->type); arg_infos++; op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; @@ -7830,6 +7832,8 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER); } } + + arg_info->type_tree = zend_type_to_interned_tree(arg_info->type); } /* These are assigned at the end to avoid uninitialized memory in case of an error */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 224a68be749cb..2ac0e8eb52b3c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -452,6 +452,7 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; + zend_type_node *type_tree; const zend_property_info *prototype; zend_function **hooks; } zend_property_info; @@ -479,6 +480,7 @@ typedef struct _zend_class_constant { typedef struct _zend_internal_arg_info { const char *name; zend_type type; + zend_type_node *type_tree; const char *default_value; } zend_internal_arg_info; @@ -486,6 +488,7 @@ typedef struct _zend_internal_arg_info { typedef struct _zend_arg_info { zend_string *name; zend_type type; + zend_type_node *type_tree; zend_string *default_value; } zend_arg_info; @@ -497,6 +500,7 @@ typedef struct _zend_arg_info { typedef struct _zend_internal_function_info { uintptr_t required_num_args; zend_type type; + zend_type_node *type_tree; const char *default_value; } zend_internal_function_info; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6b6af2c225f79..0639aad380e5d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1165,10 +1165,73 @@ static bool zend_check_intersection_type_from_cache_slot(zend_type_list *interse return status; } +static bool zend_type_node_matches(zend_type_node *node, zval *zv) +{ + switch (node->kind) { + case ZEND_TYPE_SIMPLE: { + zend_type type = node->simple_type; + uint32_t type_mask = ZEND_TYPE_FULL_MASK(type); + + if ((type_mask & MAY_BE_NULL) && Z_TYPE_P(zv) == IS_NULL) { + return true; + } + + if (ZEND_TYPE_HAS_NAME(type)) { + zend_class_entry *ce = zend_lookup_class(ZEND_TYPE_NAME(type)); + if (ce && Z_TYPE_P(zv) == IS_OBJECT && + instanceof_function(Z_OBJCE_P(zv), ce)) { + return true; + } + return false; + } + + if ((type_mask & MAY_BE_CALLABLE) && + zend_is_callable(zv, 0, NULL)) { + return true; + } + + if ((type_mask & MAY_BE_STATIC) && + zend_value_instanceof_static(zv)) { + return true; + } + + // Scalar check + return zend_verify_scalar_type_hint(type_mask, zv, + ZEND_ARG_USES_STRICT_TYPES(), 0); + } + + case ZEND_TYPE_UNION: { + for (uint32_t i = 0; i < node->compound.num_types; i++) { + if (zend_type_node_matches(node->compound.types[i], zv)) { + return true; + } + } + return false; + } + + case ZEND_TYPE_INTERSECTION: { + for (uint32_t i = 0; i < node->compound.num_types; i++) { + if (!zend_type_node_matches(node->compound.types[i], zv)) { + return false; + } + } + return true; + } + + default: + return false; + } +} + + static zend_always_inline bool zend_check_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) { + if (EXPECTED(type_tree != NULL)) { + return zend_type_node_matches(type_tree, arg); + } + uint32_t type_mask; if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { zend_class_entry *ce; @@ -1232,7 +1295,7 @@ static zend_always_inline bool zend_check_type_slow( } static zend_always_inline bool zend_check_type( - zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, + zend_type *type, zend_type_node *type_tree, zval *arg, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal) { zend_reference *ref = NULL; @@ -1247,14 +1310,14 @@ static zend_always_inline bool zend_check_type( return 1; } - return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal); + return zend_check_type_slow(type, type_tree, arg, ref, cache_slot, is_return_type, is_internal); } ZEND_API bool zend_check_user_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type) + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type) { return zend_check_type_slow( - type, arg, ref, cache_slot, is_return_type, /* is_internal */ false); + type, type_tree, arg, ref, cache_slot, is_return_type, /* is_internal */ false); } static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) @@ -1265,7 +1328,7 @@ static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint cur_arg_info = &zf->common.arg_info[arg_num-1]; if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) { + && UNEXPECTED(!zend_check_type(&cur_arg_info->type, cur_arg_info->type_tree, arg, cache_slot, zf->common.scope, 0, 0))) { zend_verify_arg_error(zf, cur_arg_info, arg_num, arg); return 0; } @@ -1277,7 +1340,7 @@ static zend_always_inline bool zend_verify_variadic_arg_type( zend_function *zf, zend_arg_info *arg_info, uint32_t arg_num, zval *arg, void **cache_slot) { ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type)); - if (UNEXPECTED(!zend_check_type(&arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) { + if (UNEXPECTED(!zend_check_type(&arg_info->type, arg_info->type_tree, arg, cache_slot, zf->common.scope, 0, 0))) { zend_verify_arg_error(zf, arg_info, arg_num, arg); return 0; } @@ -1302,7 +1365,7 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ } if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) { + && UNEXPECTED(!zend_check_type(&cur_arg_info->type, cur_arg_info->type_tree, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) { return 0; } arg++; @@ -1508,7 +1571,7 @@ ZEND_API bool zend_verify_internal_return_type(zend_function *zf, zval *ret) return 1; } - if (UNEXPECTED(!zend_check_type(&ret_info->type, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) { + if (UNEXPECTED(!zend_check_type(&ret_info->type, ret_info->type_tree, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) { zend_verify_internal_return_error(zf, ret); return 0; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 3b8ed89ec4f38..5e40229591c67 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -105,7 +105,7 @@ ZEND_API ZEND_COLD void zend_verify_never_error( const zend_function *zf); ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); ZEND_API bool zend_check_user_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type); + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type); #if ZEND_DEBUG ZEND_API bool zend_internal_call_should_throw(zend_function *fbc, zend_execute_data *call); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index f839cec3b3667..89d9c3908cc49 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -142,6 +142,27 @@ typedef struct { zend_type types[1]; } zend_type_list; +typedef struct _zend_type_node zend_type_node; + +typedef enum { + ZEND_TYPE_SIMPLE, + ZEND_TYPE_UNION, + ZEND_TYPE_INTERSECTION, +} zend_type_node_kind; + +struct _zend_type_node { + zend_type_node_kind kind; + + union { + zend_type simple_type; + + struct { + uint32_t num_types; + zend_type_node **types; + } compound; + }; +}; + #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 #define _ZEND_TYPE_MASK ((1u << 25) - 1) /* Only one of these bits may be set. */ From ca4903775cb82a397440265acefad13550fbe3ac Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Thu, 27 Mar 2025 23:32:29 +0100 Subject: [PATCH 02/19] fix jit --- ext/opcache/jit/zend_jit_helpers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 177feea3afa6e..308f06d31bb03 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1902,7 +1902,7 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg const zend_op *opline = EX(opline); void **cache_slot = CACHE_ADDR(opline->extended_value); bool ret = zend_check_user_type_slow( - &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false); + &arg_info->type, arg_info->type_tree, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false); if (UNEXPECTED(!ret)) { zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg); return 0; @@ -1919,7 +1919,7 @@ static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_a } } if (UNEXPECTED(!zend_check_user_type_slow( - &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { + &arg_info->type, arg_info->type_tree, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { zend_verify_return_error((zend_function*)op_array, arg); } } From 6fd613490bb81c919d233cdb4da4c4ba8f6ff26b Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 01:06:41 +0100 Subject: [PATCH 03/19] try to make faster --- Zend/zend_execute.c | 87 ++++++++++----------------------------------- 1 file changed, 18 insertions(+), 69 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 0639aad380e5d..48dba4ad28c8f 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1165,61 +1165,33 @@ static bool zend_check_intersection_type_from_cache_slot(zend_type_list *interse return status; } -static bool zend_type_node_matches(zend_type_node *node, zval *zv) +static int zend_type_node_matches(const zend_type_node *node, zval *zv) { switch (node->kind) { case ZEND_TYPE_SIMPLE: { - zend_type type = node->simple_type; - uint32_t type_mask = ZEND_TYPE_FULL_MASK(type); - - if ((type_mask & MAY_BE_NULL) && Z_TYPE_P(zv) == IS_NULL) { - return true; - } - - if (ZEND_TYPE_HAS_NAME(type)) { - zend_class_entry *ce = zend_lookup_class(ZEND_TYPE_NAME(type)); - if (ce && Z_TYPE_P(zv) == IS_OBJECT && - instanceof_function(Z_OBJCE_P(zv), ce)) { - return true; - } - return false; - } - - if ((type_mask & MAY_BE_CALLABLE) && - zend_is_callable(zv, 0, NULL)) { - return true; - } - - if ((type_mask & MAY_BE_STATIC) && - zend_value_instanceof_static(zv)) { - return true; - } - - // Scalar check - return zend_verify_scalar_type_hint(type_mask, zv, - ZEND_ARG_USES_STRICT_TYPES(), 0); + return 2; } case ZEND_TYPE_UNION: { for (uint32_t i = 0; i < node->compound.num_types; i++) { if (zend_type_node_matches(node->compound.types[i], zv)) { - return true; + return 1; } } - return false; + return 0; } case ZEND_TYPE_INTERSECTION: { for (uint32_t i = 0; i < node->compound.num_types; i++) { if (!zend_type_node_matches(node->compound.types[i], zv)) { - return false; + return 0; } } - return true; + return 1; } default: - return false; + return 0; } } @@ -1228,46 +1200,23 @@ static zend_always_inline bool zend_check_type_slow( zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) { - if (EXPECTED(type_tree != NULL)) { - return zend_type_node_matches(type_tree, arg); + if (EXPECTED(type_tree != NULL) && type_tree->kind != ZEND_TYPE_SIMPLE) { + const int result = zend_type_node_matches(type_tree, arg); + if (result < 2) { + return result; + } } - uint32_t type_mask; if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { - zend_class_entry *ce; - if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { - zend_type *list_type; - if (ZEND_TYPE_IS_INTERSECTION(*type)) { - return zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg), &cache_slot); - } else { - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { - if (zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg), &cache_slot)) { - return true; - } - /* The cache_slot is progressed in zend_check_intersection_type_from_cache_slot() */ - } else { - ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* Instance of a single type part of a union is sufficient to pass the type check */ - if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { - return true; - } - PROGRESS_CACHE_SLOT(); - } - } ZEND_TYPE_LIST_FOREACH_END(); - } - } else { - ce = zend_fetch_ce_from_cache_slot(cache_slot, type); - /* If we have a CE we check if it satisfies the type constraint, - * otherwise it will check if a standard type satisfies it. */ - if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { - return true; - } + const zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, type); + /* If we have a CE we check if it satisfies the type constraint, + * otherwise it will check if a standard type satisfies it. */ + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; } } - type_mask = ZEND_TYPE_FULL_MASK(*type); + const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { return 1; From 8310a4f58c2902b35e069986b8277a272dc25af4 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 01:42:30 +0100 Subject: [PATCH 04/19] remove unused function --- Zend/zend_execute.c | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 48dba4ad28c8f..b9c065f4cc7b3 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1140,31 +1140,6 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( return ce; } -static bool zend_check_intersection_type_from_cache_slot(zend_type_list *intersection_type_list, - zend_class_entry *arg_ce, void ***cache_slot_ptr) -{ - void **cache_slot = *cache_slot_ptr; - zend_class_entry *ce; - zend_type *list_type; - bool status = true; - ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { - /* Only check classes if the type might be valid */ - if (status) { - ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* If type is not an instance of one of the types taking part in the - * intersection it cannot be a valid instance of the whole intersection type. */ - if (!ce || !instanceof_function(arg_ce, ce)) { - status = false; - } - } - PROGRESS_CACHE_SLOT(); - } ZEND_TYPE_LIST_FOREACH_END(); - if (HAVE_CACHE_SLOT) { - *cache_slot_ptr = cache_slot; - } - return status; -} - static int zend_type_node_matches(const zend_type_node *node, zval *zv) { switch (node->kind) { From ff1edb2f3ad79de45b6f6f73979abeb3f141937f Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 02:12:01 +0100 Subject: [PATCH 05/19] fix issue --- Zend/zend_API.c | 55 ++++++++++++++++++++++---------------------- ext/zend_test/test.c | 2 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index a2a7cdb658eda..0e29f3e8f6ce6 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2907,28 +2907,28 @@ static HashTable *interned_type_tree = NULL; list[count++] = value; \ } while (0) -static int compare_simple_types(zend_type a, zend_type b) { - uint32_t a_mask = ZEND_TYPE_FULL_MASK(a); - uint32_t b_mask = ZEND_TYPE_FULL_MASK(b); +static int compare_simple_types(const zend_type a, const zend_type b) { + const uint32_t a_mask = ZEND_TYPE_FULL_MASK(a); + const uint32_t b_mask = ZEND_TYPE_FULL_MASK(b); if (a_mask != b_mask) { return a_mask < b_mask ? -1 : 1; } - bool a_has_name = ZEND_TYPE_HAS_NAME(a); - bool b_has_name = ZEND_TYPE_HAS_NAME(b); + const bool a_has_name = ZEND_TYPE_HAS_NAME(a); + const bool b_has_name = ZEND_TYPE_HAS_NAME(b); if (a_has_name && b_has_name) { - zend_string *a_name = ZEND_TYPE_NAME(a); - zend_string *b_name = ZEND_TYPE_NAME(b); - int cmp = ZSTR_VAL(a_name) == ZSTR_VAL(b_name); + const zend_string *a_name = ZEND_TYPE_NAME(a); + const zend_string *b_name = ZEND_TYPE_NAME(b); + const int cmp = ZSTR_VAL(a_name) == ZSTR_VAL(b_name); if (cmp != 0) { return cmp; } } - bool a_nullable = ZEND_TYPE_ALLOW_NULL(a); - bool b_nullable = ZEND_TYPE_ALLOW_NULL(b); + const bool a_nullable = ZEND_TYPE_ALLOW_NULL(a); + const bool b_nullable = ZEND_TYPE_ALLOW_NULL(b); if (a_nullable != b_nullable) { return a_nullable ? 1 : -1; @@ -2939,11 +2939,11 @@ static int compare_simple_types(zend_type a, zend_type b) { } static int compare_type_nodes(const void *a_, const void *b_) { - zend_type_node *a = *(zend_type_node **)a_; - zend_type_node *b = *(zend_type_node **)b_; + const zend_type_node *a = *(zend_type_node **)a_; + const zend_type_node *b = *(zend_type_node **)b_; if (a->kind != b->kind) { - return a->kind - b->kind; + return (int)a->kind - (int)b->kind; } if (a->kind == ZEND_TYPE_SIMPLE) { @@ -2964,7 +2964,7 @@ static int compare_type_nodes(const void *a_, const void *b_) { return 0; } -zend_ulong zend_type_node_hash(zend_type_node *node) { +zend_ulong zend_type_node_hash(const zend_type_node *node) { zend_ulong hash = 2166136261u; // FNV-1a offset basis hash ^= (zend_ulong)node->kind; @@ -2972,7 +2972,7 @@ zend_ulong zend_type_node_hash(zend_type_node *node) { switch (node->kind) { case ZEND_TYPE_SIMPLE: { - zend_type type = node->simple_type; + const zend_type type = node->simple_type; hash ^= (zend_ulong)ZEND_TYPE_FULL_MASK(type); hash *= 16777619; @@ -2988,7 +2988,7 @@ zend_ulong zend_type_node_hash(zend_type_node *node) { case ZEND_TYPE_UNION: case ZEND_TYPE_INTERSECTION: { for (uint32_t i = 0; i < node->compound.num_types; ++i) { - zend_ulong child_hash = zend_type_node_hash(node->compound.types[i]); + const zend_ulong child_hash = zend_type_node_hash(node->compound.types[i]); hash ^= child_hash; hash *= 16777619; } @@ -2999,27 +2999,27 @@ zend_ulong zend_type_node_hash(zend_type_node *node) { return hash; } -bool zend_type_node_equals(zend_type_node *a, zend_type_node *b) { +bool zend_type_node_equals(const zend_type_node *a, const zend_type_node *b) { if (a == b) return true; if (a->kind != b->kind) return false; if (a->kind == ZEND_TYPE_SIMPLE) { - zend_type at = a->simple_type; - zend_type bt = b->simple_type; + const zend_type at = a->simple_type; + const zend_type bt = b->simple_type; if (ZEND_TYPE_FULL_MASK(at) != ZEND_TYPE_FULL_MASK(bt)) { return false; } - bool a_has_name = ZEND_TYPE_HAS_NAME(at); - bool b_has_name = ZEND_TYPE_HAS_NAME(bt); + const bool a_has_name = ZEND_TYPE_HAS_NAME(at); + const bool b_has_name = ZEND_TYPE_HAS_NAME(bt); if (a_has_name != b_has_name) { return false; } if (a_has_name) { - zend_string *a_name = ZEND_TYPE_NAME(at); - zend_string *b_name = ZEND_TYPE_NAME(bt); + const zend_string *a_name = ZEND_TYPE_NAME(at); + const zend_string *b_name = ZEND_TYPE_NAME(bt); if (!zend_string_equals(a_name, b_name)) { return false; } @@ -3042,9 +3042,8 @@ bool zend_type_node_equals(zend_type_node *a, zend_type_node *b) { return true; } - static zend_type_node *intern_type_node(zend_type_node *node) { - zend_ulong hash = zend_type_node_hash(node); + const zend_ulong hash = zend_type_node_hash(node); zend_type_node *existing; if (interned_type_tree == NULL) { @@ -3054,6 +3053,7 @@ static zend_type_node *intern_type_node(zend_type_node *node) { if ((existing = zend_hash_index_find_ptr(interned_type_tree, hash))) { if (zend_type_node_equals(existing, node)) { + pefree(node, 1); return existing; // reuse interned node } } @@ -3062,8 +3062,7 @@ static zend_type_node *intern_type_node(zend_type_node *node) { return node; } - -ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type) { +ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { if (type.type_mask == 0) { return NULL; } @@ -3076,7 +3075,7 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type) { } zend_type_list *list = ZEND_TYPE_LIST(type); - zend_type_node_kind kind = ZEND_TYPE_IS_INTERSECTION(type) ? + const zend_type_node_kind kind = ZEND_TYPE_IS_INTERSECTION(type) ? ZEND_TYPE_INTERSECTION : ZEND_TYPE_UNION; zend_type_node **children = NULL; diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index e3f87ee1e1636..725396d3421d2 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1202,7 +1202,7 @@ static void register_ZendTestClass_dnf_property(zend_class_entry *ce) { // The types are upgraded to DNF types in `register_dynamic_function_entries()` static zend_internal_arg_info arginfo_zend_test_internal_dnf_arguments[] = { // first entry is a zend_internal_function_info (see zend_compile.h): {argument_count, return_type, unused} - {(const char*)(uintptr_t)(1), {0}, NULL}, + {(const char*)(uintptr_t)(1), {0}, NULL, NULL}, {"arg", {0}, NULL} }; From ff7ab1f9d7954299b102db603ff8d7f3dcf938a8 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 12:41:41 +0100 Subject: [PATCH 06/19] fix bad rebase --- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 6c87b81bc3bd7..5831afd35bf8d 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4427,7 +4427,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 06b4ad6b1494a..2c03b7f252d3c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -10803,7 +10803,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -21540,7 +21540,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -30020,7 +30020,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -37814,7 +37814,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -50615,7 +50615,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } From 024a2726f37e62981a0198edcf7410372640dbcd Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 12:53:29 +0100 Subject: [PATCH 07/19] move some stuff around and clean up memory on shutdown --- Zend/zend.c | 1 + Zend/zend_API.c | 17 +++++++++-------- Zend/zend_API.h | 1 + Zend/zend_types.h | 7 +++++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 2d8a0f455f8b4..7451b9a40dad8 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1195,6 +1195,7 @@ void zend_shutdown(void) /* {{{ */ free(GLOBAL_CONSTANTS_TABLE); zend_shutdown_strtod(); zend_attributes_shutdown(); + zend_type_free_interned_trees(); #ifdef ZTS GLOBAL_FUNCTION_TABLE = NULL; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 0e29f3e8f6ce6..8e4b645a2b3bd 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2900,12 +2900,13 @@ ZEND_END_ARG_INFO() static HashTable *interned_type_tree = NULL; -// todo: move to zend_types.h -#define ADD_TO_TREE(list, count, value) \ - do { \ - list = erealloc(list, sizeof(zend_type) * (count + 1)); \ - list[count++] = value; \ - } while (0) +ZEND_API void zend_type_free_interned_trees(void) { + zend_type_node *tree = NULL; + ZEND_HASH_FOREACH_PTR(interned_type_tree, tree) { + pefree(tree, 1); + } ZEND_HASH_FOREACH_END(); + pefree(interned_type_tree, 1); +} static int compare_simple_types(const zend_type a, const zend_type b) { const uint32_t a_mask = ZEND_TYPE_FULL_MASK(a); @@ -3088,10 +3089,10 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { if (child->kind == kind) { for (uint32_t i = 0; child->compound.num_types; i++) { - ADD_TO_TREE(children, num_children, child->compound.types[i]); + ADD_TO_TYPE_TREE(children, num_children, child->compound.types[i]); } } else { - ADD_TO_TREE(children, num_children, child); + ADD_TO_TYPE_TREE(children, num_children, child); } } ZEND_TYPE_LIST_FOREACH_END(); diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 3beb40408fdcf..7962e75d6c494 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -449,6 +449,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type); ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type); ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type); +ZEND_API void zend_type_free_interned_trees(void); static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) { if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 89d9c3908cc49..47286f74bd1ca 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -814,6 +814,13 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_TYPE_REFCOUNTED (1<<0) #define IS_TYPE_COLLECTABLE (1<<1) +#define ADD_TO_TYPE_TREE(list, count, value) \ +do { \ + list = erealloc(list, sizeof(zend_type) * (count + 1)); \ + list[count++] = value; \ +} while (0) + + #if 1 /* This optimized version assumes that we have a single "type_flag" */ /* IS_TYPE_COLLECTABLE may be used only with IS_TYPE_REFCOUNTED */ From fe66269cffccf152d999c96ad7df6a0f929b82df Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 12:55:27 +0100 Subject: [PATCH 08/19] fix a potential infinite loop --- Zend/zend_API.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 8e4b645a2b3bd..01ccd7fefc73b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3088,7 +3088,7 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { zend_type_node *child = zend_type_to_interned_tree(*subtype); if (child->kind == kind) { - for (uint32_t i = 0; child->compound.num_types; i++) { + for (uint32_t i = 0; i < child->compound.num_types; i++) { ADD_TO_TYPE_TREE(children, num_children, child->compound.types[i]); } } else { From 2336f117cde6b4c52184caf7a1a10c05af52085a Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 13:00:18 +0100 Subject: [PATCH 09/19] compare names case-insensitive --- Zend/zend_API.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 01ccd7fefc73b..ca6b1b77accab 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2922,7 +2922,7 @@ static int compare_simple_types(const zend_type a, const zend_type b) { if (a_has_name && b_has_name) { const zend_string *a_name = ZEND_TYPE_NAME(a); const zend_string *b_name = ZEND_TYPE_NAME(b); - const int cmp = ZSTR_VAL(a_name) == ZSTR_VAL(b_name); + const int cmp = zend_string_equals_ci(a_name, b_name); if (cmp != 0) { return cmp; } @@ -3021,7 +3021,7 @@ bool zend_type_node_equals(const zend_type_node *a, const zend_type_node *b) { if (a_has_name) { const zend_string *a_name = ZEND_TYPE_NAME(at); const zend_string *b_name = ZEND_TYPE_NAME(bt); - if (!zend_string_equals(a_name, b_name)) { + if (!zend_string_equals_ci(a_name, b_name)) { return false; } } From 77d03e9017f94d8d0b26557012dcbc036389ac6b Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 13:12:06 +0100 Subject: [PATCH 10/19] fix a declaration --- ext/zend_test/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 725396d3421d2..4558e5e747cb0 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1203,7 +1203,7 @@ static void register_ZendTestClass_dnf_property(zend_class_entry *ce) { static zend_internal_arg_info arginfo_zend_test_internal_dnf_arguments[] = { // first entry is a zend_internal_function_info (see zend_compile.h): {argument_count, return_type, unused} {(const char*)(uintptr_t)(1), {0}, NULL, NULL}, - {"arg", {0}, NULL} + {"arg", {0}, NULL, NULL} }; static ZEND_NAMED_FUNCTION(zend_test_internal_dnf_arguments) From ed7d7fac18b6c4beb0861dfc732cac098ca31149 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 13:39:08 +0100 Subject: [PATCH 11/19] set to null --- Zend/zend_API.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ca6b1b77accab..ecffebaabb2e3 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2906,6 +2906,7 @@ ZEND_API void zend_type_free_interned_trees(void) { pefree(tree, 1); } ZEND_HASH_FOREACH_END(); pefree(interned_type_tree, 1); + interned_type_tree = NULL; } static int compare_simple_types(const zend_type a, const zend_type b) { From 83c7ed645211d0da42848faf4ea696a6e4a8d35b Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 14:07:26 +0100 Subject: [PATCH 12/19] fix memory leak --- Zend/zend_API.c | 12 ++++++++++-- Zend/zend_types.h | 7 ------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ecffebaabb2e3..53ba7db874206 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2903,6 +2903,9 @@ static HashTable *interned_type_tree = NULL; ZEND_API void zend_type_free_interned_trees(void) { zend_type_node *tree = NULL; ZEND_HASH_FOREACH_PTR(interned_type_tree, tree) { + if (tree->kind != ZEND_TYPE_SIMPLE) { + pefree(tree->compound.types, 1); + } pefree(tree, 1); } ZEND_HASH_FOREACH_END(); pefree(interned_type_tree, 1); @@ -3055,6 +3058,9 @@ static zend_type_node *intern_type_node(zend_type_node *node) { if ((existing = zend_hash_index_find_ptr(interned_type_tree, hash))) { if (zend_type_node_equals(existing, node)) { + if (node->kind != ZEND_TYPE_SIMPLE) { + pefree(node->compound.types, 1); + } pefree(node, 1); return existing; // reuse interned node } @@ -3085,15 +3091,17 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { zend_type *subtype; + children = pemalloc(list->num_types * sizeof(zend_type_node *), 1); + ZEND_TYPE_LIST_FOREACH(list, subtype) { zend_type_node *child = zend_type_to_interned_tree(*subtype); if (child->kind == kind) { for (uint32_t i = 0; i < child->compound.num_types; i++) { - ADD_TO_TYPE_TREE(children, num_children, child->compound.types[i]); + children[num_children++] = child->compound.types[i]; } } else { - ADD_TO_TYPE_TREE(children, num_children, child); + children[num_children++] = child; } } ZEND_TYPE_LIST_FOREACH_END(); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 47286f74bd1ca..89d9c3908cc49 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -814,13 +814,6 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_TYPE_REFCOUNTED (1<<0) #define IS_TYPE_COLLECTABLE (1<<1) -#define ADD_TO_TYPE_TREE(list, count, value) \ -do { \ - list = erealloc(list, sizeof(zend_type) * (count + 1)); \ - list[count++] = value; \ -} while (0) - - #if 1 /* This optimized version assumes that we have a single "type_flag" */ /* IS_TYPE_COLLECTABLE may be used only with IS_TYPE_REFCOUNTED */ From 13e3edeeebc6bb9f99934ed4c55a6c92fb562a59 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 14:18:18 +0100 Subject: [PATCH 13/19] move to EG and CG --- Zend/zend_API.c | 18 ++++++++---------- Zend/zend_execute_API.c | 1 + Zend/zend_globals.h | 2 ++ ext/opcache/ZendAccelerator.c | 1 + 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 53ba7db874206..36ba104f143c9 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2898,18 +2898,16 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arg_info_toString, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() -static HashTable *interned_type_tree = NULL; - ZEND_API void zend_type_free_interned_trees(void) { zend_type_node *tree = NULL; - ZEND_HASH_FOREACH_PTR(interned_type_tree, tree) { + ZEND_HASH_FOREACH_PTR(CG(type_trees), tree) { if (tree->kind != ZEND_TYPE_SIMPLE) { pefree(tree->compound.types, 1); } pefree(tree, 1); } ZEND_HASH_FOREACH_END(); - pefree(interned_type_tree, 1); - interned_type_tree = NULL; + pefree(CG(type_trees), 1); + CG(type_trees) = NULL; } static int compare_simple_types(const zend_type a, const zend_type b) { @@ -3051,12 +3049,12 @@ static zend_type_node *intern_type_node(zend_type_node *node) { const zend_ulong hash = zend_type_node_hash(node); zend_type_node *existing; - if (interned_type_tree == NULL) { - interned_type_tree = pemalloc(sizeof(HashTable), 1); - zend_hash_init(interned_type_tree, 64, NULL, NULL, 1); + if (CG(type_trees) == NULL) { + CG(type_trees) = pemalloc(sizeof(HashTable), 1); + zend_hash_init(CG(type_trees), 64, NULL, NULL, 1); } - if ((existing = zend_hash_index_find_ptr(interned_type_tree, hash))) { + if ((existing = zend_hash_index_find_ptr(CG(type_trees), hash))) { if (zend_type_node_equals(existing, node)) { if (node->kind != ZEND_TYPE_SIMPLE) { pefree(node->compound.types, 1); @@ -3066,7 +3064,7 @@ static zend_type_node *intern_type_node(zend_type_node *node) { } } - zend_hash_index_add_new_ptr(interned_type_tree, hash, node); + zend_hash_index_add_new_ptr(CG(type_trees), hash, node); return node; } diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 1f55521fb72f1..435651dfc4917 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -148,6 +148,7 @@ void init_executor(void) /* {{{ */ EG(function_table) = CG(function_table); EG(class_table) = CG(class_table); + EG(type_trees) = CG(type_trees); EG(in_autoload) = NULL; EG(error_handling) = EH_NORMAL; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 079bfb99caccf..c551ef9f5c48a 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -94,6 +94,7 @@ struct _zend_compiler_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ + HashTable *type_trees; /* type trees table */ HashTable *auto_globals; @@ -191,6 +192,7 @@ struct _zend_executor_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ HashTable *zend_constants; /* constants table */ + HashTable *type_trees /* type trees table */; zval *vm_stack_top; zval *vm_stack_end; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 8caa0cbd4398e..8d2086fab2100 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4803,6 +4803,7 @@ static zend_result accel_finish_startup_preload(bool in_child) #endif php_request_shutdown(NULL); /* calls zend_shared_alloc_unlock(); */ EG(class_table) = NULL; + EG(type_trees) = NULL; EG(function_table) = NULL; PG(report_memleaks) = orig_report_memleaks; #ifdef ZTS From c2d99484a83556adcf1183082c7a31f628f41d20 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 15:28:03 +0100 Subject: [PATCH 14/19] enhance --- Zend/zend_API.c | 5 ++-- Zend/zend_execute.c | 66 +++++++++++++++++++-------------------------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 36ba104f143c9..c9e8df476d252 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2924,7 +2924,7 @@ static int compare_simple_types(const zend_type a, const zend_type b) { if (a_has_name && b_has_name) { const zend_string *a_name = ZEND_TYPE_NAME(a); const zend_string *b_name = ZEND_TYPE_NAME(b); - const int cmp = zend_string_equals_ci(a_name, b_name); + const int cmp = ZSTR_VAL(a_name) - ZSTR_VAL(b_name); if (cmp != 0) { return cmp; } @@ -3107,7 +3107,7 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { size_t deduped_count = 0; for (size_t i = 0; i < num_children; i++) { - if (i == 0 || compare_type_nodes(&children[i], &children[i - 1]) != 0) { + if (i == 0 || children[i] != children[i - 1]) { children[deduped_count++] = children[i]; } } @@ -3117,6 +3117,7 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { node->compound.num_types = deduped_count; node->compound.types = pemalloc(sizeof(zend_type_node *) * deduped_count, 1); memcpy(node->compound.types, children, sizeof(zend_type_node *) * deduped_count); + pefree(children, 1); return intern_type_node(node); } diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index b9c065f4cc7b3..fa74fdaff0f61 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1112,9 +1112,10 @@ static zend_always_inline bool zend_value_instanceof_static(zval *zv) { static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( void **cache_slot, zend_type *type) { - if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { - return (zend_class_entry *) *cache_slot; - } + // todo: work with cache_slot + //if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { + // return (zend_class_entry *) *cache_slot; + //} zend_string *name = ZEND_TYPE_NAME(*type); zend_class_entry *ce; @@ -1140,58 +1141,47 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( return ce; } -static int zend_type_node_matches(const zend_type_node *node, zval *zv) -{ - switch (node->kind) { - case ZEND_TYPE_SIMPLE: { - return 2; - } - case ZEND_TYPE_UNION: { - for (uint32_t i = 0; i < node->compound.num_types; i++) { - if (zend_type_node_matches(node->compound.types[i], zv)) { - return 1; +static bool zend_check_type_slow( + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, + bool is_return_type, bool is_internal) +{ + if (EXPECTED(type_tree != NULL) && type_tree->kind != ZEND_TYPE_SIMPLE) { + switch (type_tree->kind) { + case ZEND_TYPE_UNION: { + for (uint32_t i = 0; i < type_tree->compound.num_types; i++) { + if (zend_check_type_slow(type, type_tree->compound.types[i], arg, ref, cache_slot, is_return_type, is_internal)) { + return true; + } } + return false; } - return 0; - } - case ZEND_TYPE_INTERSECTION: { - for (uint32_t i = 0; i < node->compound.num_types; i++) { - if (!zend_type_node_matches(node->compound.types[i], zv)) { - return 0; + case ZEND_TYPE_INTERSECTION: { + for (uint32_t i = 0; i < type_tree->compound.num_types; i++) { + if (!zend_check_type_slow(type, type_tree->compound.types[i], arg, ref, cache_slot, is_return_type, is_internal)) { + return false; + } } + return true; } - return 1; - } - - default: - return 0; - } -} - -static zend_always_inline bool zend_check_type_slow( - zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, - bool is_return_type, bool is_internal) -{ - if (EXPECTED(type_tree != NULL) && type_tree->kind != ZEND_TYPE_SIMPLE) { - const int result = zend_type_node_matches(type_tree, arg); - if (result < 2) { - return result; + default: + return false; } } - if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { - const zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, type); + if (ZEND_TYPE_IS_COMPLEX(type_tree->simple_type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { + const zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, &type_tree->simple_type); /* If we have a CE we check if it satisfies the type constraint, * otherwise it will check if a standard type satisfies it. */ if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { return true; } + PROGRESS_CACHE_SLOT(); } - const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type); + const uint32_t type_mask = ZEND_TYPE_FULL_MASK(type_tree->simple_type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { return 1; From e630682ebf8e56e09431c6e8ecef56ebb0522840 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Fri, 28 Mar 2025 15:49:34 +0100 Subject: [PATCH 15/19] handle void --- Zend/zend_execute.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index fa74fdaff0f61..7033ca1c9945d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1171,7 +1171,7 @@ static bool zend_check_type_slow( } } - if (ZEND_TYPE_IS_COMPLEX(type_tree->simple_type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { + if (type_tree != NULL && ZEND_TYPE_IS_COMPLEX(type_tree->simple_type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { const zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, &type_tree->simple_type); /* If we have a CE we check if it satisfies the type constraint, * otherwise it will check if a standard type satisfies it. */ @@ -1181,7 +1181,7 @@ static bool zend_check_type_slow( PROGRESS_CACHE_SLOT(); } - const uint32_t type_mask = ZEND_TYPE_FULL_MASK(type_tree->simple_type); + const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { return 1; From 4a85778139980cd533fc0052c1d522ff4ca52504 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Sat, 29 Mar 2025 00:57:50 +0100 Subject: [PATCH 16/19] make sure we destroy the hash --- Zend/zend_API.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index c9e8df476d252..877fa601dc79f 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2906,6 +2906,7 @@ ZEND_API void zend_type_free_interned_trees(void) { } pefree(tree, 1); } ZEND_HASH_FOREACH_END(); + zend_hash_destroy(CG(type_trees)); pefree(CG(type_trees), 1); CG(type_trees) = NULL; } From 43a2f6903ac0f53e432d4ca2aa1cc710d08cc87c Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Sat, 29 Mar 2025 01:27:31 +0100 Subject: [PATCH 17/19] take ownership of the type --- Zend/zend_API.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 877fa601dc79f..0f9417daff561 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2903,6 +2903,10 @@ ZEND_API void zend_type_free_interned_trees(void) { ZEND_HASH_FOREACH_PTR(CG(type_trees), tree) { if (tree->kind != ZEND_TYPE_SIMPLE) { pefree(tree->compound.types, 1); + } else { + if (ZEND_TYPE_HAS_NAME(tree->simple_type)) { + zend_string_release_ex(ZEND_TYPE_NAME(tree->simple_type), 1); + } } pefree(tree, 1); } ZEND_HASH_FOREACH_END(); @@ -3077,7 +3081,13 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { if (!ZEND_TYPE_HAS_LIST(type)) { zend_type_node *node = pemalloc(sizeof(zend_type_node), 1); node->kind = ZEND_TYPE_SIMPLE; - node->simple_type = type; + zend_type new_type = type; + if (ZEND_TYPE_HAS_NAME(type)) { + const zend_string *name = ZEND_TYPE_NAME(type); + zend_string *new_name = zend_string_init_interned(ZSTR_VAL(name), ZSTR_LEN(name), 1); + ZEND_TYPE_SET_PTR(new_type, new_name); + } + node->simple_type = new_type; return intern_type_node(node); } From 8214a9f07993e9a9e5901563d31fe43e9693bb6d Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Sat, 29 Mar 2025 02:02:43 +0100 Subject: [PATCH 18/19] move to only when we are creating new types to be interned --- Zend/zend_API.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 0f9417daff561..1b403011578c4 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3069,6 +3069,14 @@ static zend_type_node *intern_type_node(zend_type_node *node) { } } + if (node->kind == ZEND_TYPE_SIMPLE) { + if (ZEND_TYPE_HAS_NAME(node->simple_type)) { + const zend_string *name = ZEND_TYPE_NAME(node->simple_type); + zend_string *new_name = zend_string_init_interned(ZSTR_VAL(name), ZSTR_LEN(name), 1); + ZEND_TYPE_SET_PTR(node->simple_type, new_name); + } + } + zend_hash_index_add_new_ptr(CG(type_trees), hash, node); return node; } @@ -3081,13 +3089,7 @@ ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { if (!ZEND_TYPE_HAS_LIST(type)) { zend_type_node *node = pemalloc(sizeof(zend_type_node), 1); node->kind = ZEND_TYPE_SIMPLE; - zend_type new_type = type; - if (ZEND_TYPE_HAS_NAME(type)) { - const zend_string *name = ZEND_TYPE_NAME(type); - zend_string *new_name = zend_string_init_interned(ZSTR_VAL(name), ZSTR_LEN(name), 1); - ZEND_TYPE_SET_PTR(new_type, new_name); - } - node->simple_type = new_type; + node->simple_type = type; return intern_type_node(node); } From d1ca6c2fe27cb6823d8799f327b7e804baee6867 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Sat, 29 Mar 2025 09:33:47 +0100 Subject: [PATCH 19/19] only release the string if it is not interned --- Zend/zend_API.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 1b403011578c4..78cfb19a2ba8b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2905,7 +2905,9 @@ ZEND_API void zend_type_free_interned_trees(void) { pefree(tree->compound.types, 1); } else { if (ZEND_TYPE_HAS_NAME(tree->simple_type)) { - zend_string_release_ex(ZEND_TYPE_NAME(tree->simple_type), 1); + if (!ZSTR_IS_INTERNED(ZEND_TYPE_NAME(tree->simple_type))) { + zend_string_release_ex(ZEND_TYPE_NAME(tree->simple_type), 1); + } } } pefree(tree, 1);