Skip to content

Commit 011795b

Browse files
committed
Bind traits before parent class
This more accurately matches the "copy & paste" semantics described in the documentation. Abstract trait methods diverge from this behavior, given that a parent method can satisfy trait methods used in the child. In that case, the method is not copied, but the check is performed after the parent has been bound. Fixes GH-15753 Fixes GH-16198 Close GH-15878
1 parent 2ede200 commit 011795b

File tree

7 files changed

+141
-64
lines changed

7 files changed

+141
-64
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ PHP NEWS
4141
. Added the (void) cast to indicate that not using a value is intentional.
4242
(timwolla)
4343
. Added get_error_handler(), get_exception_handler() functions. (Arnaud)
44+
. Fixed bug GH-15753 and GH-16198 (Bind traits before parent class). (ilutov)
4445

4546
- Curl:
4647
. Added curl_multi_get_handles(). (timwolla)

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ PHP 8.5 UPGRADE NOTES
4141
. The tick handlers are now deactivated after all shutdown functions, destructors
4242
have run and the output handlers have been cleaned up.
4343
This is a consequence of fixing GH-18033.
44+
. Traits are now bound before the parent class. This is a subtle behavioral
45+
change, but should closer match user expectations, demonstrated by GH-15753
46+
and GH-16198.
4447

4548
- Intl:
4649
. The extension now requires at least ICU 57.1.

Zend/tests/traits/constant_015.phpt

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
The same name constant of a trait used in a class that inherits a constant defined in a parent can be defined only if they are compatible.
2+
Final class constant in parent may not be overridden by child through trait
33
--FILE--
44
<?php
55

@@ -15,18 +15,6 @@ class DerivedClass1 extends BaseClass1 {
1515
use TestTrait1;
1616
}
1717

18-
trait TestTrait2 {
19-
public final const Constant = 123;
20-
}
21-
22-
class BaseClass2 {
23-
public final const Constant = 456;
24-
}
25-
26-
class DerivedClass2 extends BaseClass2 {
27-
use TestTrait2;
28-
}
29-
3018
?>
3119
--EXPECTF--
32-
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
20+
Fatal error: DerivedClass1::Constant cannot override final constant BaseClass1::Constant in %s on line %d

Zend/tests/traits/gh15753.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
GH-15753: Trait can override property declared in parent class of used class
3+
--FILE--
4+
<?php
5+
6+
class P {
7+
public $prop = 1;
8+
}
9+
10+
trait T {
11+
public $prop = 2;
12+
}
13+
14+
class C extends P {
15+
use T;
16+
}
17+
18+
$c = new C();
19+
var_dump($c->prop);
20+
21+
?>
22+
--EXPECT--
23+
int(2)

Zend/tests/traits/gh16198.phpt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GH-16198: Incorrect trait constant conflict when declared via trait
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
final public const string C1 = 'T1';
8+
}
9+
10+
interface I1 {
11+
public const ?string C1 = null;
12+
public const ?string C2 = null;
13+
}
14+
15+
final class O1 implements I1 {
16+
final public const string C2 = 'O1';
17+
}
18+
19+
final class O2 implements I1 {
20+
use T1;
21+
}
22+
23+
abstract class A1 implements I1 {}
24+
25+
final class O3 extends A1 {
26+
final public const string C2 = 'O3';
27+
}
28+
29+
final class O4 extends A1 {
30+
use T1;
31+
}
32+
33+
?>
34+
===DONE===
35+
--EXPECT--
36+
===DONE===

Zend/tests/traits/gh16198_2.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
GH-16198: Usage of super in cloned trait method
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
public function __destruct() {
8+
parent::__destruct();
9+
}
10+
}
11+
12+
class P {
13+
public function __destruct() {
14+
var_dump(__METHOD__);
15+
}
16+
}
17+
18+
class C extends P {
19+
use T;
20+
}
21+
22+
$c = new C();
23+
unset($c);
24+
25+
?>
26+
--EXPECT--
27+
string(13) "P::__destruct"

Zend/zend_inheritance.c

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,12 @@ static inheritance_status do_inheritance_check_on_method(
11351135

11361136
#define SEPARATE_METHOD() do { \
11371137
if ((flags & ZEND_INHERITANCE_LAZY_CHILD_CLONE) \
1138-
&& child_scope != ce && child->type == ZEND_USER_FUNCTION) { \
1138+
&& child_scope != ce \
1139+
/* Trait methods have already been separated at this point. However, their */ \
1140+
/* scope isn't fixed until after inheritance checks to preserve the scope */ \
1141+
/* in error messages. Skip them here explicitly. */ \
1142+
&& !(child_scope->ce_flags & ZEND_ACC_TRAIT) \
1143+
&& child->type == ZEND_USER_FUNCTION) { \
11391144
/* op_array wasn't duplicated yet */ \
11401145
zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); \
11411146
memcpy(new_function, child, sizeof(zend_op_array)); \
@@ -2350,7 +2355,6 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
23502355
{
23512356
zend_function *existing_fn = NULL;
23522357
zend_function *new_fn;
2353-
bool check_inheritance = false;
23542358

23552359
if ((existing_fn = zend_hash_find_ptr(&ce->function_table, key)) != NULL) {
23562360
/* if it is the same function with the same visibility and has not been assigned a class scope yet, regardless
@@ -2384,8 +2388,6 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
23842388
ZSTR_VAL(fn->common.scope->name), ZSTR_VAL(fn->common.function_name),
23852389
ZSTR_VAL(ce->name), ZSTR_VAL(name),
23862390
ZSTR_VAL(existing_fn->common.scope->name), ZSTR_VAL(existing_fn->common.function_name));
2387-
} else {
2388-
check_inheritance = true;
23892391
}
23902392
}
23912393

@@ -2405,19 +2407,6 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
24052407
function_add_ref(new_fn);
24062408
fn = zend_hash_update_ptr(&ce->function_table, key, new_fn);
24072409
zend_add_magic_method(ce, fn, key);
2408-
2409-
if (check_inheritance) {
2410-
/* Inherited members are overridden by members inserted by traits.
2411-
* Check whether the trait method fulfills the inheritance requirements. */
2412-
uint32_t flags = ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY;
2413-
if (!(existing_fn->common.scope->ce_flags & ZEND_ACC_TRAIT)) {
2414-
flags |= ZEND_INHERITANCE_SET_CHILD_CHANGED |ZEND_INHERITANCE_SET_CHILD_PROTO |
2415-
ZEND_INHERITANCE_RESET_CHILD_OVERRIDE;
2416-
}
2417-
do_inheritance_check_on_method(
2418-
fn, fixup_trait_scope(fn, ce), existing_fn, fixup_trait_scope(existing_fn, ce),
2419-
ce, NULL, flags);
2420-
}
24212410
}
24222411
/* }}} */
24232412

@@ -2695,7 +2684,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
26952684
}
26962685
/* }}} */
26972686

2698-
static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */
2687+
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) /* {{{ */
26992688
{
27002689
uint32_t i;
27012690
zend_string *key;
@@ -2706,6 +2695,11 @@ static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry
27062695
if (traits[i]) {
27072696
/* copies functions, applies defined aliasing, and excludes unused trait methods */
27082697
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) {
2698+
bool is_abstract = (bool) (fn->common.fn_flags & ZEND_ACC_ABSTRACT);
2699+
*contains_abstract_methods |= is_abstract;
2700+
if (verify_abstract != is_abstract) {
2701+
continue;
2702+
}
27092703
zend_traits_copy_functions(key, fn, ce, exclude_tables[i], aliases);
27102704
} ZEND_HASH_FOREACH_END();
27112705

@@ -2720,15 +2714,16 @@ static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry
27202714
for (i = 0; i < ce->num_traits; i++) {
27212715
if (traits[i]) {
27222716
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) {
2717+
bool is_abstract = (bool) (fn->common.fn_flags & ZEND_ACC_ABSTRACT);
2718+
*contains_abstract_methods |= is_abstract;
2719+
if (verify_abstract != is_abstract) {
2720+
continue;
2721+
}
27232722
zend_traits_copy_functions(key, fn, ce, NULL, aliases);
27242723
} ZEND_HASH_FOREACH_END();
27252724
}
27262725
}
27272726
}
2728-
2729-
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) {
2730-
zend_fixup_trait_method(fn, ce);
2731-
} ZEND_HASH_FOREACH_END();
27322727
}
27332728
/* }}} */
27342729

@@ -3010,33 +3005,6 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
30103005
}
30113006
/* }}} */
30123007

3013-
static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */
3014-
{
3015-
HashTable **exclude_tables;
3016-
zend_class_entry **aliases;
3017-
3018-
ZEND_ASSERT(ce->num_traits > 0);
3019-
3020-
/* complete initialization of trait structures in ce */
3021-
zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases);
3022-
3023-
/* first care about all methods to be flattened into the class */
3024-
zend_do_traits_method_binding(ce, traits, exclude_tables, aliases);
3025-
3026-
if (aliases) {
3027-
efree(aliases);
3028-
}
3029-
3030-
if (exclude_tables) {
3031-
efree(exclude_tables);
3032-
}
3033-
3034-
/* then flatten the constants and properties into it, to, mostly to notify developer about problems */
3035-
zend_do_traits_constant_binding(ce, traits);
3036-
zend_do_traits_property_binding(ce, traits);
3037-
}
3038-
/* }}} */
3039-
30403008
#define MAX_ABSTRACT_INFO_CNT 3
30413009
#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
30423010
#define DISPLAY_ABSTRACT_FN(idx) \
@@ -3649,14 +3617,45 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
36493617
zend_link_hooked_object_iter(ce);
36503618
#endif
36513619

3620+
HashTable **trait_exclude_tables;
3621+
zend_class_entry **trait_aliases;
3622+
bool trait_contains_abstract_methods = false;
3623+
if (ce->num_traits) {
3624+
zend_traits_init_trait_structures(ce, traits_and_interfaces, &trait_exclude_tables, &trait_aliases);
3625+
zend_do_traits_method_binding(ce, traits_and_interfaces, trait_exclude_tables, trait_aliases, false, &trait_contains_abstract_methods);
3626+
zend_do_traits_constant_binding(ce, traits_and_interfaces);
3627+
zend_do_traits_property_binding(ce, traits_and_interfaces);
3628+
}
36523629
if (parent) {
36533630
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
36543631
add_dependency_obligation(ce, parent);
36553632
}
36563633
zend_do_inheritance(ce, parent);
36573634
}
36583635
if (ce->num_traits) {
3659-
zend_do_bind_traits(ce, traits_and_interfaces);
3636+
if (trait_contains_abstract_methods) {
3637+
zend_do_traits_method_binding(ce, traits_and_interfaces, trait_exclude_tables, trait_aliases, true, &trait_contains_abstract_methods);
3638+
}
3639+
3640+
if (trait_exclude_tables) {
3641+
for (i = 0; i < ce->num_traits; i++) {
3642+
if (traits_and_interfaces[i]) {
3643+
if (trait_exclude_tables[i]) {
3644+
zend_hash_destroy(trait_exclude_tables[i]);
3645+
FREE_HASHTABLE(trait_exclude_tables[i]);
3646+
}
3647+
}
3648+
}
3649+
efree(trait_exclude_tables);
3650+
}
3651+
if (trait_aliases) {
3652+
efree(trait_aliases);
3653+
}
3654+
3655+
zend_function *fn;
3656+
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) {
3657+
zend_fixup_trait_method(fn, ce);
3658+
} ZEND_HASH_FOREACH_END();
36603659
}
36613660
if (ce->num_interfaces) {
36623661
/* Also copy the parent interfaces here, so we don't need to reallocate later. */

0 commit comments

Comments
 (0)