diff --git a/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_class_using_trait.phpt b/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_class_using_trait.phpt index 0c0e64cdec86..41d9705cb3e4 100644 --- a/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_class_using_trait.phpt +++ b/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_class_using_trait.phpt @@ -4,6 +4,7 @@ Eval Class definition should not leak memory when using compiled traits bar(); var_dump($a2); +$methodInfo = ReflectionMethod::createFromMethodName("A::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('A', 'foo'); +var_dump($property->getType()->getName()); + +$methodInfo = ReflectionMethod::createFromMethodName("TraitCompiled::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('TraitCompiled', 'foo'); +var_dump($property->getType()->getName()); ?> DONE --EXPECT-- object(A)#2 (0) { + ["foo"]=> + uninitialized(A) } +string(1) "A" +string(1) "A" +string(4) "self" +string(4) "self" DONE diff --git a/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_trait.phpt b/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_trait.phpt index 74e71f6c324e..c0b4774d6e08 100644 --- a/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_trait.phpt +++ b/Zend/tests/type_declarations/relative_types/relative_type_in_evaled_trait.phpt @@ -5,6 +5,7 @@ Eval code should not leak memory when using traits const EVAL_CODE = <<<'CODE' trait TraitEval { + public self $foo; public function bar(): self { return new self; } } CODE; @@ -19,9 +20,25 @@ $a1 = new A(); $a2 = $a1->bar(); var_dump($a2); +$methodInfo = ReflectionMethod::createFromMethodName("A::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('A', 'foo'); +var_dump($property->getType()->getName()); + +$methodInfo = ReflectionMethod::createFromMethodName("TraitEval::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('TraitEval', 'foo'); +var_dump($property->getType()->getName()); + ?> DONE --EXPECT-- object(A)#2 (0) { + ["foo"]=> + uninitialized(A) } +string(1) "A" +string(1) "A" +string(4) "self" +string(4) "self" DONE diff --git a/Zend/tests/type_declarations/relative_types/relative_type_in_trait.phpt b/Zend/tests/type_declarations/relative_types/relative_type_in_trait.phpt new file mode 100644 index 000000000000..dfd53753ae50 --- /dev/null +++ b/Zend/tests/type_declarations/relative_types/relative_type_in_trait.phpt @@ -0,0 +1,40 @@ +--TEST-- +Relative class types from traits should be resolved when used inside a class +--FILE-- +bar(); +var_dump($a2); + +$methodInfo = ReflectionMethod::createFromMethodName("A::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('A', 'foo'); +var_dump($property->getType()->getName()); + +$methodInfo = ReflectionMethod::createFromMethodName("TraitCompiled::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('TraitCompiled', 'foo'); +var_dump($property->getType()->getName()); + +?> +DONE +--EXPECT-- +object(A)#2 (0) { + ["foo"]=> + uninitialized(A) +} +string(1) "A" +string(1) "A" +string(4) "self" +string(4) "self" +DONE diff --git a/Zend/tests/type_declarations/relative_types/relative_type_in_trait_used_from_other_trait.phpt b/Zend/tests/type_declarations/relative_types/relative_type_in_trait_used_from_other_trait.phpt new file mode 100644 index 000000000000..ea04e2d7b3fb --- /dev/null +++ b/Zend/tests/type_declarations/relative_types/relative_type_in_trait_used_from_other_trait.phpt @@ -0,0 +1,32 @@ +--TEST-- +Relative class types from traits MUST NOT be resolved when used inside a trait +--FILE-- +getReturnType()->getName()); +$property = new ReflectionProperty('TraitCompiled', 'foo'); +var_dump($property->getType()->getName()); + +$methodInfo = ReflectionMethod::createFromMethodName("SecondTrait::bar"); +var_dump($methodInfo->getReturnType()->getName()); +$property = new ReflectionProperty('SecondTrait', 'foo'); +var_dump($property->getType()->getName()); + +?> +DONE +--EXPECT-- +string(4) "self" +string(4) "self" +string(4) "self" +string(4) "self" +DONE diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt index 1959ab7f802c..17088824f424 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt @@ -11,5 +11,5 @@ class A { } ?> DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt index 0d5b9eefc441..e8cf4c0d4171 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt @@ -11,5 +11,5 @@ class A { } ?> DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt index eeb50a2abef4..27936c8eae90 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt @@ -11,5 +11,5 @@ class A { } ?> DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt index 02605b28bdcc..24681b660066 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt @@ -1,9 +1,12 @@ --TEST-- -Cannot use a trait which references parent as a type in a class with no parent, DNF type +Cannot use a trait which references parent as a type in a class with no parent, non-simple union type --FILE-- DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt new file mode 100644 index 000000000000..29d885a2792b --- /dev/null +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot use a trait which references parent as a type in a class with no parent, DNF type +--FILE-- + +DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee83ee75ff6e..42d59a7cb0a6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1709,7 +1709,7 @@ static bool zend_try_ct_eval_const(zval *zv, zend_string *name, bool is_fully_qu } /* }}} */ -static inline bool zend_is_scope_known(void) /* {{{ */ +static inline bool zend_is_scope_known_ex(bool allow_traits) /* {{{ */ { if (!CG(active_op_array)) { /* This can only happen when evaluating a default value string. */ @@ -1728,10 +1728,14 @@ static inline bool zend_is_scope_known(void) /* {{{ */ } /* For traits self etc refers to the using class, not the trait itself */ - return (CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT) == 0; + return allow_traits || (CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT) == 0; } /* }}} */ +static inline bool zend_is_scope_known(void) { + return zend_is_scope_known_ex(false); +} + static inline bool class_name_refers_to_active_ce(const zend_string *class_name, uint32_t fetch_type) /* {{{ */ { if (!CG(active_class_entry)) { @@ -7420,6 +7424,10 @@ static zend_type zend_compile_single_typename(zend_ast *ast) ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time"); } } + if (zend_is_scope_known_ex(true)) { + zend_op_array *op_array = CG(active_op_array); + op_array->fn_flags2 = ZEND_ACC_RESOLVE_RELATIVE_TYPE; + } zend_string_addref(class_name); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 5414467f3f87..45c1e4419ba7 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -416,7 +416,8 @@ typedef struct _zend_oparray_context { /* Function Flags 2 (fn_flags2) (unused: 0-31) | | | */ /* ============================ | | | */ /* | | | */ -/* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */ +/* method had self or parent type from trait | | | */ +#define ZEND_ACC_RESOLVE_RELATIVE_TYPE (1 << 1) /* | X | | */ #define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) #define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index ba13a3233ed2..b0a1bd45d31d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2361,6 +2361,78 @@ static zend_class_entry *fixup_trait_scope(const zend_function *fn, zend_class_e return fn->common.scope->ce_flags & ZEND_ACC_TRAIT ? ce : fn->common.scope; } +static void zend_resolve_type(zend_type *type, const zend_class_entry *const ce) +{ + /* We are adding trait methods to another trait, delay resolution */ + if (ce->ce_flags & ZEND_ACC_TRAIT) { + return; + } + /* Only built-in types + static */ + if (!ZEND_TYPE_IS_COMPLEX(*type)) { + return; + } + /* Intersection types cannot have un-resolved relative class types */ + if (ZEND_TYPE_IS_INTERSECTION(*type)) { + return; + } + + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type) || (ZEND_TYPE_HAS_LIST(*type))); + if (ZEND_TYPE_HAS_NAME(*type)) { + if (zend_string_equals_ci(ZEND_TYPE_NAME(*type), ZSTR_KNOWN(ZEND_STR_SELF))) { + ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->name)); + } else if (zend_string_equals_ci(ZEND_TYPE_NAME(*type), ZSTR_KNOWN(ZEND_STR_PARENT))) { + if (!ce->parent) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use trait which has \"parent\" as a type when current class scope has no parent"); + } + + if (ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) { + ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->parent->name)); + } else { + ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->parent_name)); + } + } + return; + } + + zend_type_list *union_type_list = ZEND_TYPE_LIST(*type); + zend_type *list_type; + ZEND_TYPE_LIST_FOREACH_MUTABLE(union_type_list, list_type) { + zend_resolve_type(list_type, ce); + } ZEND_TYPE_LIST_FOREACH_END(); +} + +static void zend_resolve_trait_relative_class_types(zend_function *const fn, const zend_class_entry *const ce) +{ + /* No type info */ + if (!fn->common.arg_info) { + return; + } + + bool has_return_type = fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE; + /* Variadic parameters are not counted as part of the standard number of arguments */ + bool has_variadic_type = fn->common.fn_flags & ZEND_ACC_VARIADIC; + uint32_t num_args = fn->common.num_args + has_variadic_type; + size_t allocated_size = sizeof(zend_arg_info) * (has_return_type + num_args); + + zend_arg_info *new_arg_infos = fn->common.arg_info - has_return_type; + + /* We can allocate the arg_infos on the arena as the fn is also allocated on the arena */ + if (fn->op_array.refcount && !(fn->op_array.fn_flags & ZEND_ACC_IMMUTABLE)) { + new_arg_infos = safe_emalloc(num_args + has_return_type, sizeof(zend_arg_info), 0); + } else { + new_arg_infos = zend_arena_alloc(&CG(arena), allocated_size); + } + memcpy(new_arg_infos, fn->common.arg_info - has_return_type, allocated_size); + fn->common.arg_info = new_arg_infos + has_return_type; + + for (uint32_t i = 0; i < num_args + has_return_type; i++) { + zend_type *type = &new_arg_infos[i].type; + zend_type_copy_ctor(type, true, ce->type == ZEND_INTERNAL_CLASS); + zend_resolve_type(type, ce); + } +} + static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn) /* {{{ */ { zend_function *existing_fn = NULL; @@ -2415,6 +2487,9 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_ /* Reassign method name, in case it is an alias. */ new_fn->common.function_name = name; function_add_ref(new_fn); + if (new_fn->op_array.fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) { + zend_resolve_trait_relative_class_types(new_fn, ce); + } fn = zend_hash_update_ptr(&ce->function_table, key, new_fn); zend_add_magic_method(ce, fn, key); } @@ -2959,6 +3034,8 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_type type = property_info->type; /* Assumption: only userland classes can use traits, as such the type must be arena allocated */ zend_type_copy_ctor(&type, /* use arena */ true, /* persistent */ false); + /* Resolve possible self/parent types */ + zend_resolve_type(&type, ce); zend_property_info *new_prop = zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, type); if (property_info->attributes) { @@ -2989,6 +3066,10 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_fixup_trait_method(new_fn, ce); + if (new_fn->op_array.fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) { + zend_resolve_trait_relative_class_types(new_fn, ce); + } + hooks[j] = new_fn; } } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 24b480ad71e6..8273c7f28bf6 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -561,6 +561,30 @@ ZEND_API void zend_destroy_static_vars(zend_op_array *op_array) } } +static void zend_destroy_arg_infos_from_op_array(zend_op_array *op_array, bool free) { + + uint32_t num_args = op_array->num_args; + zend_arg_info *arg_info = op_array->arg_info; + + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + arg_info--; + num_args++; + } + if (op_array->fn_flags & ZEND_ACC_VARIADIC) { + num_args++; + } + for (uint32_t i = 0 ; i < num_args; i++) { + if (arg_info[i].name) { + zend_string_release_ex(arg_info[i].name, 0); + } + zend_type_release(arg_info[i].type, /* persistent */ false); + } + if (free) { + efree(arg_info); + } + op_array->arg_info = NULL; +} + ZEND_API void destroy_op_array(zend_op_array *op_array) { uint32_t i; @@ -574,6 +598,10 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) zend_string_release_ex(op_array->function_name, 0); } + if (op_array->fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) { + zend_destroy_arg_infos_from_op_array(op_array, op_array->refcount && !(op_array->fn_flags & ZEND_ACC_IMMUTABLE)); + } + if (!op_array->refcount || --(*op_array->refcount) > 0) { return; } @@ -634,23 +662,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) } } if (op_array->arg_info) { - uint32_t num_args = op_array->num_args; - zend_arg_info *arg_info = op_array->arg_info; - - if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { - arg_info--; - num_args++; - } - if (op_array->fn_flags & ZEND_ACC_VARIADIC) { - num_args++; - } - for (i = 0 ; i < num_args; i++) { - if (arg_info[i].name) { - zend_string_release_ex(arg_info[i].name, 0); - } - zend_type_release(arg_info[i].type, /* persistent */ false); - } - efree(arg_info); + zend_destroy_arg_infos_from_op_array(op_array, true); } if (op_array->static_variables) { zend_array_destroy(op_array->static_variables);