Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bind trait props/consts before parent class #15878

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Zend/tests/gh16198.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
GH-16198: Incorrect trait constant conflict when declared via trait
--FILE--
<?php

trait T1
{
final public const string C1 = 'T1';
}

interface I1
{
public const ?string C1 = null;
public const ?string C2 = null;
}

final class O1 implements I1
{
final public const string C2 = 'O1';
}

final class O2 implements I1
{
use T1;
}

abstract class A1 implements I1
{
}

final class O3 extends A1
{
final public const string C2 = 'O3';
}

final class O4 extends A1
{
use T1;
}

?>
===DONE===
--EXPECT--
===DONE===
27 changes: 27 additions & 0 deletions Zend/tests/gh16198_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-16198: Usage of super in cloned trait method
--FILE--
<?php

trait T {
public function __destruct() {
parent::__destruct();
}
}

class P {
public function __destruct() {
var_dump(__METHOD__);
}
}

class C extends P {
use T;
}

$c = new C();
unset($c);

?>
--EXPECT--
string(13) "P::__destruct"
14 changes: 1 addition & 13 deletions Zend/tests/traits/constant_015.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines -18 to +20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a behavior change, that may break some PHP code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. This is (IMO) clearly a bug, but it may be better to commit to master anyway.

2 changes: 1 addition & 1 deletion Zend/tests/traits/error_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 30 additions & 0 deletions Zend/tests/traits/gh15753.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
GH-15753: Overriding readonly properties from traits don't allow assignment from the child
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test works fine on unpatched master, but fails on PHP-8.2. Is this a backport?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this works on master because of the changes to readonly in https://wiki.php.net/rfc/asymmetric-visibility-v2#relationship_with_readonly. Namely, readonly properties can, by default, now be initialized from all sub-classes. For master, we should add a better test.

--FILE--
<?php

class P {
protected readonly int $prop;
}

trait T {
protected readonly int $prop;
}

class C extends P {
use T;

public function __construct() {
$this->prop = 20;
}
}

$c = new C();
var_dump($c);

?>
--EXPECT--
object(C)#1 (1) {
["prop":protected]=>
int(20)
}
79 changes: 45 additions & 34 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)); \
Expand Down Expand Up @@ -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;
Expand All @@ -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();

Expand All @@ -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();
}
/* }}} */

Expand Down Expand Up @@ -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) \
Expand Down Expand Up @@ -3562,14 +3542,45 @@ 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);
}
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. */
Expand Down