diff --git a/Zend/tests/type_declarations/dnf_types/internal_dnf_property.phpt b/Zend/tests/type_declarations/dnf_types/internal_dnf_property.phpt new file mode 100644 index 000000000000..89edb1f14ec7 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/internal_dnf_property.phpt @@ -0,0 +1,25 @@ +--TEST-- +DNF typed properties in internal classes +--EXTENSIONS-- +zend_test +spl +reflection +--FILE-- +getType(); +var_dump((string)$type); + +$obj = new _ZendTestClass(); +$obj->dnfProperty = new \ArrayIterator([1, 2, 3]); +try { + $obj->dnfProperty = []; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +string(32) "Iterator|(Traversable&Countable)" +Cannot assign array to property _ZendTestClass::$dnfProperty of type Iterator|(Traversable&Countable) + diff --git a/Zend/tests/type_declarations/typed_properties_095.phpt b/Zend/tests/type_declarations/typed_properties_095.phpt index 5caf862e72da..321b07e34c73 100644 --- a/Zend/tests/type_declarations/typed_properties_095.phpt +++ b/Zend/tests/type_declarations/typed_properties_095.phpt @@ -75,6 +75,8 @@ object(_ZendTestClass)#1 (3) { uninitialized(Traversable&Countable) ["readonlyProp"]=> uninitialized(int) + ["dnfProperty"]=> + uninitialized(Iterator|(Traversable&Countable)) } int(123) Cannot assign string to property _ZendTestClass::$intProp of type int @@ -91,6 +93,8 @@ object(Test)#4 (3) { uninitialized(Traversable&Countable) ["readonlyProp"]=> uninitialized(int) + ["dnfProperty"]=> + uninitialized(Iterator|(Traversable&Countable)) } int(123) Cannot assign string to property _ZendTestClass::$staticIntProp of type int diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 34ab803321aa..2a5ba9245ae6 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3496,7 +3496,7 @@ ZEND_API zend_result zend_disable_class(const char *class_name, size_t class_nam ZEND_HASH_MAP_FOREACH_PTR(&disabled_class->properties_info, prop) { if (prop->ce == disabled_class) { zend_string_release(prop->name); - zend_type_release(prop->type, /* persistent */ 1); + zend_type_release_internal(prop->type); free(prop); } } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 724b5b8c8a57..bf5c5e2d5a59 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -862,7 +862,14 @@ ZEND_API void zend_destroy_static_vars(zend_op_array *op_array); ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle); ZEND_API void zend_cleanup_mutable_class_data(zend_class_entry *ce); ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce); +/** + * Used to release userland types. + */ ZEND_API void zend_type_release(zend_type type, bool persistent); +/** + * Used to release types associated with internal functions or classes. + */ +ZEND_API void zend_type_release_internal(zend_type type); ZEND_API zend_string *zend_create_member_string(zend_string *class_name, zend_string *member_name); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index dc968bc39530..0df796171518 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -130,6 +130,28 @@ ZEND_API void zend_type_release(zend_type type, bool persistent) { } } +ZEND_API void zend_type_release_internal(zend_type type) { + ZEND_ASSERT(!ZEND_TYPE_USES_ARENA(type)); + if (ZEND_TYPE_HAS_LIST(type)) { + zend_type *list_type, *sublist_type; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { + if (ZEND_TYPE_HAS_LIST(*list_type)) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*list_type), sublist_type) { + if (ZEND_TYPE_HAS_NAME(*sublist_type)) { + zend_string_release(ZEND_TYPE_NAME(*sublist_type)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + free(ZEND_TYPE_LIST(*list_type)); + } else if (ZEND_TYPE_HAS_NAME(*list_type)) { + zend_string_release(ZEND_TYPE_NAME(*list_type)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + free(ZEND_TYPE_LIST(type)); + } else if (ZEND_TYPE_HAS_NAME(type)) { + zend_string_release(ZEND_TYPE_NAME(type)); + } +} + void zend_free_internal_arg_info(zend_internal_function *function) { if ((function->fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS)) && function->arg_info) { @@ -142,7 +164,7 @@ void zend_free_internal_arg_info(zend_internal_function *function) { num_args++; } for (i = 0 ; i < num_args; i++) { - zend_type_release(arg_info[i].type, /* persistent */ 1); + zend_type_release_internal(arg_info[i].type); } free(arg_info); } @@ -454,7 +476,7 @@ ZEND_API void destroy_zend_class(zval *zv) ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop_info) { if (prop_info->ce == ce) { zend_string_release(prop_info->name); - zend_type_release(prop_info->type, /* persistent */ 1); + zend_type_release_internal(prop_info->type); free(prop_info); } } ZEND_HASH_FOREACH_END(); @@ -489,6 +511,7 @@ ZEND_API void destroy_zend_class(zval *zv) } else { zval_internal_ptr_dtor(&c->value); } + zend_type_release_internal(c->type); if (c->doc_comment) { zend_string_release_ex(c->doc_comment, 1); } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index b4ea2b27c9e9..5731703627b4 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -884,11 +884,42 @@ static void le_throwing_resource_dtor(zend_resource *rsrc) zend_throw_exception(NULL, "Throwing resource destructor called", 0); } +// We need to "manually" generate this property because gen_stubs.php +// does not support codegen for DNF types. +static void register_ZendTestClass_dnf_property(zend_class_entry *ce) { + zend_string *class_Iterator = zend_string_init_interned("Iterator", sizeof("Iterator") - 1, true); + zend_alloc_ce_cache(class_Iterator); + zend_string *class_Traversable = zend_string_init_interned("Traversable", sizeof("Traversable") - 1, true); + zend_alloc_ce_cache(class_Traversable); + zend_string *class_Countable = zend_string_init_interned("Countable", sizeof("Countable") - 1, true); + zend_alloc_ce_cache(class_Countable); + // + zend_type_list *intersection_type_list = malloc(ZEND_TYPE_LIST_SIZE(2)); + intersection_type_list->num_types = 2; + intersection_type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(class_Traversable, 0, 0); + intersection_type_list->types[1] = (zend_type) ZEND_TYPE_INIT_CLASS(class_Countable, 0, 0); + zend_type_list *union_type_list = malloc(ZEND_TYPE_LIST_SIZE(2)); + union_type_list->num_types = 2; + union_type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(class_Iterator, 0, 0); + union_type_list->types[1] = (zend_type) ZEND_TYPE_INIT_INTERSECTION(intersection_type_list, 0); + zend_type prop_type = (zend_type) ZEND_TYPE_INIT_UNION(union_type_list, 0); + // + zend_string *prop_name = zend_string_init_interned("dnfProperty", sizeof("dnfProperty") - 1, true); + zval default_value; + ZVAL_UNDEF(&default_value); + // We need this hack because some debug assertions incorrectly reject valid DNF types. + // see https://github.com/php/php-src/issues/10120#issuecomment-1360682804 + zend_type hack = {0}; + zend_property_info *prop = zend_declare_typed_property(ce, prop_name, &default_value, ZEND_ACC_PUBLIC, NULL, hack); + prop->type = prop_type; +} + PHP_MINIT_FUNCTION(zend_test) { zend_test_interface = register_class__ZendTestInterface(); zend_test_class = register_class__ZendTestClass(zend_test_interface); + register_ZendTestClass_dnf_property(zend_test_class); zend_test_class->create_object = zend_test_class_new; zend_test_class->get_static_method = zend_test_class_static_method_get;