diff --git a/Zend/tests/gh16198.phpt b/Zend/tests/gh16198.phpt new file mode 100644 index 0000000000000..c0992d35a8093 --- /dev/null +++ b/Zend/tests/gh16198.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-16198: Incorrect trait constant conflict when declared via trait +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/gh16198_2.phpt b/Zend/tests/gh16198_2.phpt new file mode 100644 index 0000000000000..7599ea1a11b49 --- /dev/null +++ b/Zend/tests/gh16198_2.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-16198: Usage of super in cloned trait method +--FILE-- + +--EXPECT-- +string(13) "P::__destruct" diff --git a/Zend/tests/traits/constant_015.phpt b/Zend/tests/traits/constant_015.phpt index 24507ea9e59cd..f8a2740d5c315 100644 --- a/Zend/tests/traits/constant_015.phpt +++ b/Zend/tests/traits/constant_015.phpt @@ -15,18 +15,6 @@ class DerivedClass1 extends BaseClass1 { use TestTrait1; } -trait TestTrait2 { - public final const Constant = 123; -} - -class BaseClass2 { - public final const Constant = 456; -} - -class DerivedClass2 extends BaseClass2 { - use TestTrait2; -} - ?> --EXPECTF-- -Fatal error: BaseClass2 and TestTrait2 define the same constant (Constant) in the composition of DerivedClass2. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: DerivedClass1::Constant cannot override final constant BaseClass1::Constant in %s on line %d diff --git a/Zend/tests/traits/error_001.phpt b/Zend/tests/traits/error_001.phpt index a7889da41e2b7..f48207b00bf5e 100644 --- a/Zend/tests/traits/error_001.phpt +++ b/Zend/tests/traits/error_001.phpt @@ -25,4 +25,4 @@ class A extends foo { ?> --EXPECTF-- -Fatal error: Class A cannot extend trait foo in %s on line %d +Fatal error: Required Trait foo2 wasn't added to A in %s on line %d diff --git a/Zend/tests/traits/gh15753.phpt b/Zend/tests/traits/gh15753.phpt new file mode 100644 index 0000000000000..39a717b2dc7aa --- /dev/null +++ b/Zend/tests/traits/gh15753.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-15753: Overriding readonly properties from traits don't allow assignment from the child +--FILE-- +prop = 20; + } +} + +$c = new C(); +var_dump($c); + +?> +--EXPECT-- +object(C)#1 (1) { + ["prop":protected]=> + int(20) +} diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 88f1d3447c2a7..2344d63282e94 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1112,7 +1112,10 @@ static inheritance_status do_inheritance_check_on_method( #define SEPARATE_METHOD() do { \ if ((flags & ZEND_INHERITANCE_LAZY_CHILD_CLONE) \ - && child_scope != ce && child->type == ZEND_USER_FUNCTION) { \ + && child_scope != ce \ + /* Trait scopes are fixed after inheritance. However, they are always duplicated. */ \ + && !(child_scope->ce_flags & ZEND_ACC_TRAIT) \ + && child->type == ZEND_USER_FUNCTION) { \ /* op_array wasn't duplicated yet */ \ zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); \ memcpy(new_function, child, sizeof(zend_op_array)); \ @@ -2612,7 +2615,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e } /* }}} */ -static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */ +static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases, bool verify_abstract, bool *contains_abstract_methods) /* {{{ */ { uint32_t i; zend_string *key; @@ -2623,6 +2626,10 @@ static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry if (traits[i]) { /* copies functions, applies defined aliasing, and excludes unused trait methods */ ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) { + if (verify_abstract != (bool) (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { + *contains_abstract_methods = true; + continue; + } zend_traits_copy_functions(key, fn, ce, exclude_tables[i], aliases); } ZEND_HASH_FOREACH_END(); @@ -2637,15 +2644,15 @@ static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry for (i = 0; i < ce->num_traits; i++) { if (traits[i]) { ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) { + if (verify_abstract != (bool) (fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { + *contains_abstract_methods = true; + continue; + } zend_traits_copy_functions(key, fn, ce, NULL, aliases); } ZEND_HASH_FOREACH_END(); } } } - - ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) { - zend_fixup_trait_method(fn, ce); - } ZEND_HASH_FOREACH_END(); } /* }}} */ @@ -2927,33 +2934,6 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } /* }}} */ -static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */ -{ - HashTable **exclude_tables; - zend_class_entry **aliases; - - ZEND_ASSERT(ce->num_traits > 0); - - /* complete initialization of trait structures in ce */ - zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases); - - /* first care about all methods to be flattened into the class */ - zend_do_traits_method_binding(ce, traits, exclude_tables, aliases); - - if (aliases) { - efree(aliases); - } - - if (exclude_tables) { - efree(exclude_tables); - } - - /* then flatten the constants and properties into it, to, mostly to notify developer about problems */ - zend_do_traits_constant_binding(ce, traits); - zend_do_traits_property_binding(ce, traits); -} -/* }}} */ - #define MAX_ABSTRACT_INFO_CNT 3 #define MAX_ABSTRACT_INFO_FMT "%s%s%s%s" #define DISPLAY_ABSTRACT_FN(idx) \ @@ -3562,6 +3542,15 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string zend_enum_register_funcs(ce); } + HashTable **trait_exclude_tables; + zend_class_entry **trait_aliases; + bool trait_contains_abstract_methods = false; + if (ce->num_traits) { + zend_traits_init_trait_structures(ce, traits_and_interfaces, &trait_exclude_tables, &trait_aliases); + zend_do_traits_method_binding(ce, traits_and_interfaces, trait_exclude_tables, trait_aliases, false, &trait_contains_abstract_methods); + zend_do_traits_constant_binding(ce, traits_and_interfaces); + zend_do_traits_property_binding(ce, traits_and_interfaces); + } if (parent) { if (!(parent->ce_flags & ZEND_ACC_LINKED)) { add_dependency_obligation(ce, parent); @@ -3569,7 +3558,29 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string zend_do_inheritance(ce, parent); } if (ce->num_traits) { - zend_do_bind_traits(ce, traits_and_interfaces); + if (trait_contains_abstract_methods) { + zend_do_traits_method_binding(ce, traits_and_interfaces, trait_exclude_tables, trait_aliases, true, &trait_contains_abstract_methods); + } + + if (trait_exclude_tables) { + for (i = 0; i < ce->num_traits; i++) { + if (traits_and_interfaces[i]) { + if (trait_exclude_tables[i]) { + zend_hash_destroy(trait_exclude_tables[i]); + FREE_HASHTABLE(trait_exclude_tables[i]); + } + } + } + efree(trait_exclude_tables); + } + if (trait_aliases) { + efree(trait_aliases); + } + + zend_function *fn; + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) { + zend_fixup_trait_method(fn, ce); + } ZEND_HASH_FOREACH_END(); } if (ce->num_interfaces) { /* Also copy the parent interfaces here, so we don't need to reallocate later. */