From 6576122f661bf2528967d964ff67c26a4a090a70 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 11 Feb 2025 15:37:04 +0000 Subject: [PATCH 1/4] Zend: Resolve "self" and "parent" types when using traits --- ...trait_parent_type_in_class_no_parent1.phpt | 4 +- ...trait_parent_type_in_class_no_parent2.phpt | 4 +- ...trait_parent_type_in_class_no_parent3.phpt | 4 +- ...trait_parent_type_in_class_no_parent4.phpt | 11 +-- ...trait_parent_type_in_class_no_parent5.phpt | 15 ++++ Zend/zend_compile.c | 12 +++- Zend/zend_compile.h | 3 +- Zend/zend_inheritance.c | 72 ++++++++++++++++++- Zend/zend_opcode.c | 43 ++++++----- 9 files changed, 136 insertions(+), 32 deletions(-) create mode 100644 Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt 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 1959ab7f802cd..17088824f4249 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 0d5b9eefc4414..e8cf4c0d4171e 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 eeb50a2abef46..27936c8eae905 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 02605b28bdcc3..24681b6600666 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 0000000000000..29d885a2792b7 --- /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 ee83ee75ff6ea..42d59a7cb0a6f 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 5414467f3f874..45c1e4419ba7f 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 ba13a3233ed2a..9f0914e90e827 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2361,6 +2361,67 @@ 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) +{ + /* 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"); + } + 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; + + new_arg_infos = safe_emalloc(num_args + has_return_type, sizeof(zend_arg_info), 0); + 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); + /* New CE is a concrete class, resolve */ + if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { + 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 +2476,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); } @@ -2957,8 +3021,8 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_string *doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; 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, copy otherwise */ + 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 +3053,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 24b480ad71e66..7e10a4554ad11 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -561,6 +561,28 @@ 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) { + + 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); + } + efree(arg_info); + op_array->arg_info = NULL; +} + ZEND_API void destroy_op_array(zend_op_array *op_array) { uint32_t i; @@ -574,6 +596,9 @@ 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); + } if (!op_array->refcount || --(*op_array->refcount) > 0) { return; } @@ -634,23 +659,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); } if (op_array->static_variables) { zend_array_destroy(op_array->static_variables); From 99c800e5b2d8924d064ce24d8f1674264174fbd1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 14 Mar 2026 15:55:14 +0000 Subject: [PATCH 2/4] Fix properties? --- Zend/zend_inheritance.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9f0914e90e827..6686510602b66 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2363,6 +2363,10 @@ static zend_class_entry *fixup_trait_scope(const zend_function *fn, zend_class_e 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; @@ -2415,10 +2419,7 @@ static void zend_resolve_trait_relative_class_types(zend_function *const fn, con 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); - /* New CE is a concrete class, resolve */ - if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { - zend_resolve_type(&type, ce); - } + zend_resolve_type(&type, ce); } } @@ -3021,7 +3022,9 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_string *doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; zend_type type = property_info->type; - /* Resolve possible self/parent types, copy otherwise */ + /* 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); From f1936fe41a5843197ab4d487ceb0c2700445856a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 16 Mar 2026 15:41:49 +0000 Subject: [PATCH 3/4] Some fixes with help of Arnaud --- Zend/zend_inheritance.c | 10 ++++++++-- Zend/zend_opcode.c | 10 +++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 6686510602b66..9849b51977c74 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2385,7 +2385,12 @@ static void zend_resolve_type(zend_type *type, const zend_class_entry *const ce) zend_error_noreturn(E_COMPILE_ERROR, "Cannot use trait which has \"parent\" as a type when current class scope has no parent"); } - ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->parent->name)); + + 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; } @@ -2412,7 +2417,8 @@ static void zend_resolve_trait_relative_class_types(zend_function *const fn, con zend_arg_info *new_arg_infos = fn->common.arg_info - has_return_type; - new_arg_infos = safe_emalloc(num_args + has_return_type, sizeof(zend_arg_info), 0); + /* We can allocate the arg_infos on the arena as the fn is also allocated on the arena */ + 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; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 7e10a4554ad11..9dd0ee10fabd3 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -596,12 +596,16 @@ 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); - } + //if (op_array->refcount == NULL) { + // return; + //} + if (!op_array->refcount || --(*op_array->refcount) > 0) { return; } + if (op_array->fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) { + zend_destroy_arg_infos_from_op_array(op_array); + } efree_size(op_array->refcount, sizeof(*(op_array->refcount))); From 046bb022feeacb8b1c69689ffd16c91c0eb2b96b Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 17 Mar 2026 18:09:23 +0000 Subject: [PATCH 4/4] fix traits? --- Zend/zend_inheritance.c | 6 +++++- Zend/zend_opcode.c | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9849b51977c74..1aee35780babf 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2418,7 +2418,11 @@ static void zend_resolve_trait_relative_class_types(zend_function *const fn, con 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 */ - new_arg_infos = zend_arena_alloc(&CG(arena), allocated_size); + 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; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 9dd0ee10fabd3..353bbe2c3096c 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -561,7 +561,7 @@ 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) { +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; @@ -579,7 +579,9 @@ static void zend_destroy_arg_infos_from_op_array(zend_op_array *op_array) { } zend_type_release(arg_info[i].type, /* persistent */ false); } - efree(arg_info); + if (free) { + efree(arg_info); + } op_array->arg_info = NULL; } @@ -599,13 +601,13 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) //if (op_array->refcount == NULL) { // return; //} + 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; } - if (op_array->fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) { - zend_destroy_arg_infos_from_op_array(op_array); - } efree_size(op_array->refcount, sizeof(*(op_array->refcount))); @@ -663,7 +665,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) } } if (op_array->arg_info) { - zend_destroy_arg_infos_from_op_array(op_array); + zend_destroy_arg_infos_from_op_array(op_array, true); } if (op_array->static_variables) { zend_array_destroy(op_array->static_variables);