Skip to content

Fix GH-18223: Meaningful error on wrong trait method exclusion #19404

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

Open
wants to merge 1 commit into
base: PHP-8.3
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
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ PHP NEWS
. Fixed bug GH-18736 (Circumvented type check with return by ref + finally).
(ilutov)
. Fixed zend call stack size for macOs/arm64. (David Carlier)
. Fixed GH-18223 (Traits methods precedence). (alexandre-daubois)

- FTP:
. Fix theoretical issues with hrtime() not being available. (nielsdos)
Expand Down
36 changes: 36 additions & 0 deletions Zend/tests/traits/precedence_circular_exclusion.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Invalid trait insteadof list causing circular exclusion with three traits
--FILE--
<?php

trait A {
public function test() {
return 'A';
}
}

trait B {
public function test() {
return 'B';
}
}

trait C {
public function test() {
return 'C';
}
}

class D {
use A, B, C {
A::test insteadof B;
B::test insteadof C;
C::test insteadof A;
}
}

$d = new D();
var_dump($d->test());
?>
--EXPECTF--
Fatal error: Invalid trait method precedence for test() - all implementations have been excluded by insteadof rules in %s on line %d
29 changes: 29 additions & 0 deletions Zend/tests/traits/precedence_mutual_exclusion.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Invalid trait insteadof list causing mutual exclusion
--FILE--
<?php

trait A {
public function test() {
return 'A';
}
}

trait B {
public function test() {
return 'B';
}
}

class C {
use A, B {
A::test insteadof B;
B::test insteadof A;
}
}

$c = new C();
var_dump($c->test());
?>
--EXPECTF--
Fatal error: Invalid trait method precedence for test() - all implementations have been excluded by insteadof rules in %s on line %d
67 changes: 67 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -2304,6 +2304,69 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
}
/* }}} */

static void zend_traits_check_for_mutual_exclusions(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables) /* {{{ */
{
uint32_t i;
zend_string *key;
zend_function *fn;
HashTable *all_method_sources;
zend_class_entry *trait;
(void) trait; /* Silence unused variable warning */

ALLOC_HASHTABLE(all_method_sources);
zend_hash_init(all_method_sources, 0, NULL, NULL, 0);

for (i = 0; i < ce->num_traits; i++) {
if (!traits[i]) {
continue;
}

ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) {
HashTable *sources;

if ((sources = zend_hash_find_ptr(all_method_sources, key)) == NULL) {
ALLOC_HASHTABLE(sources);
zend_hash_init(sources, 0, NULL, NULL, 0);
zend_hash_add_ptr(all_method_sources, key, sources);
}

zend_hash_index_add_ptr(sources, i, traits[i]);
} ZEND_HASH_FOREACH_END();
}

/* Are all method implementations excluded? */
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(all_method_sources, key, fn) {
HashTable *sources = (HashTable*)fn;
bool has_available_impl = false;
uint32_t trait_index;

ZEND_HASH_FOREACH_NUM_KEY_PTR(sources, trait_index, trait) {
/* Trait's implementation is excluded? */
if (!exclude_tables[trait_index] ||
zend_hash_find(exclude_tables[trait_index], key) == NULL) {
has_available_impl = true;
break;
}
} ZEND_HASH_FOREACH_END();

if (!has_available_impl && zend_hash_num_elements(sources) > 1) {
zend_error_noreturn(E_COMPILE_ERROR,
"Invalid trait method precedence for %s() - all implementations have been excluded by insteadof rules",
ZSTR_VAL(key));
}
} ZEND_HASH_FOREACH_END();

ZEND_HASH_MAP_FOREACH_PTR(all_method_sources, fn) {
HashTable *sources = (HashTable*)fn;
zend_hash_destroy(sources);
FREE_HASHTABLE(sources);
} ZEND_HASH_FOREACH_END();

zend_hash_destroy(all_method_sources);
FREE_HASHTABLE(all_method_sources);
}
/* }}} */

static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */
{
uint32_t i;
Expand Down Expand Up @@ -2589,6 +2652,10 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
/* complete initialization of trait structures in ce */
zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases);

if (exclude_tables) {
zend_traits_check_for_mutual_exclusions(ce, traits, exclude_tables);
}

/* first care about all methods to be flattened into the class */
zend_do_traits_method_binding(ce, traits, exclude_tables, aliases);

Expand Down
Loading