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 5bc4b4a04509f..78cfb19a2ba8b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2898,7 +2898,246 @@ 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) { +ZEND_API void zend_type_free_interned_trees(void) { + zend_type_node *tree = NULL; + 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)) { + if (!ZSTR_IS_INTERNED(ZEND_TYPE_NAME(tree->simple_type))) { + zend_string_release_ex(ZEND_TYPE_NAME(tree->simple_type), 1); + } + } + } + pefree(tree, 1); + } ZEND_HASH_FOREACH_END(); + zend_hash_destroy(CG(type_trees)); + pefree(CG(type_trees), 1); + CG(type_trees) = NULL; +} + +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; + } + + 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) { + 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; + } + } + + 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; + } + + // Types are equal + return 0; +} + +static int compare_type_nodes(const void *a_, const void *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 (int)a->kind - (int)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(const 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: { + const 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) { + const 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(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) { + 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; + } + + 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) { + const zend_string *a_name = ZEND_TYPE_NAME(at); + const zend_string *b_name = ZEND_TYPE_NAME(bt); + if (!zend_string_equals_ci(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) { + const zend_ulong hash = zend_type_node_hash(node); + zend_type_node *existing; + + 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(CG(type_trees), 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 + } + } + + 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; +} + +ZEND_API zend_type_node *zend_type_to_interned_tree(const 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); + const 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; + + 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++) { + children[num_children++] = child->compound.types[i]; + } + } else { + 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 || children[i] != children[i - 1]) { + 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); + pefree(children, 1); + + 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 +3160,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 +3431,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 +4906,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..7962e75d6c494 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,9 @@ 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); +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)) { 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..7033ca1c9945d 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,71 +1141,47 @@ 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 zend_always_inline bool zend_check_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, +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) { - 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(); + 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; } - } ZEND_TYPE_LIST_FOREACH_END(); + } + return false; } - } 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)) { + + 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; } + + default: + return false; } } - type_mask = ZEND_TYPE_FULL_MASK(*type); + 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. */ + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; + } + PROGRESS_CACHE_SLOT(); + } + + 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; @@ -1232,7 +1209,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 +1224,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 +1242,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 +1254,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 +1279,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 +1485,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_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/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. */ 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(); } 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 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); } } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index e3f87ee1e1636..4558e5e747cb0 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1202,8 +1202,8 @@ 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}, - {"arg", {0}, NULL} + {(const char*)(uintptr_t)(1), {0}, NULL, NULL}, + {"arg", {0}, NULL, NULL} }; static ZEND_NAMED_FUNCTION(zend_test_internal_dnf_arguments)