Skip to content

Commit e55098e

Browse files
Fix GH-18223: Meaningful error on wrong trait method exclusion
1 parent 983be08 commit e55098e

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Invalid trait insteadof list causing circular exclusion with three traits
3+
--FILE--
4+
<?php
5+
6+
trait A {
7+
public function test() {
8+
return 'A';
9+
}
10+
}
11+
12+
trait B {
13+
public function test() {
14+
return 'B';
15+
}
16+
}
17+
18+
trait C {
19+
public function test() {
20+
return 'C';
21+
}
22+
}
23+
24+
class D {
25+
use A, B, C {
26+
A::test insteadof B;
27+
B::test insteadof C;
28+
C::test insteadof A;
29+
}
30+
}
31+
32+
$d = new D();
33+
var_dump($d->test());
34+
?>
35+
--EXPECTF--
36+
Fatal error: Invalid trait method precedence for test() - all implementations have been excluded by insteadof rules in %s on line %d
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Invalid trait insteadof list causing mutual exclusion
3+
--FILE--
4+
<?php
5+
6+
trait A {
7+
public function test() {
8+
return 'A';
9+
}
10+
}
11+
12+
trait B {
13+
public function test() {
14+
return 'B';
15+
}
16+
}
17+
18+
class C {
19+
use A, B {
20+
A::test insteadof B;
21+
B::test insteadof A;
22+
}
23+
}
24+
25+
$c = new C();
26+
var_dump($c->test());
27+
?>
28+
--EXPECTF--
29+
Fatal error: Invalid trait method precedence for test() - all implementations have been excluded by insteadof rules in %s on line %d

Zend/zend_inheritance.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,69 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
23062306
}
23072307
/* }}} */
23082308

2309+
static void zend_traits_check_for_mutual_exclusions(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables) /* {{{ */
2310+
{
2311+
uint32_t i;
2312+
zend_string *key;
2313+
zend_function *fn;
2314+
HashTable *all_method_sources;
2315+
zend_class_entry *trait;
2316+
(void) trait; /* Silence unused variable warning */
2317+
2318+
ALLOC_HASHTABLE(all_method_sources);
2319+
zend_hash_init(all_method_sources, 0, NULL, NULL, 0);
2320+
2321+
for (i = 0; i < ce->num_traits; i++) {
2322+
if (!traits[i]) {
2323+
continue;
2324+
}
2325+
2326+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) {
2327+
HashTable *sources;
2328+
2329+
if ((sources = zend_hash_find_ptr(all_method_sources, key)) == NULL) {
2330+
ALLOC_HASHTABLE(sources);
2331+
zend_hash_init(sources, 0, NULL, NULL, 0);
2332+
zend_hash_add_ptr(all_method_sources, key, sources);
2333+
}
2334+
2335+
zend_hash_index_add_ptr(sources, i, traits[i]);
2336+
} ZEND_HASH_FOREACH_END();
2337+
}
2338+
2339+
/* Are all method implementations excluded? */
2340+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(all_method_sources, key, fn) {
2341+
HashTable *sources = (HashTable*)fn;
2342+
bool has_available_impl = false;
2343+
uint32_t trait_index;
2344+
2345+
ZEND_HASH_FOREACH_NUM_KEY_PTR(sources, trait_index, trait) {
2346+
/* Trait's implementation is excluded? */
2347+
if (!exclude_tables[trait_index] ||
2348+
zend_hash_find(exclude_tables[trait_index], key) == NULL) {
2349+
has_available_impl = true;
2350+
break;
2351+
}
2352+
} ZEND_HASH_FOREACH_END();
2353+
2354+
if (!has_available_impl && zend_hash_num_elements(sources) > 1) {
2355+
zend_error_noreturn(E_COMPILE_ERROR,
2356+
"Invalid trait method precedence for %s() - all implementations have been excluded by insteadof rules",
2357+
ZSTR_VAL(key));
2358+
}
2359+
} ZEND_HASH_FOREACH_END();
2360+
2361+
ZEND_HASH_MAP_FOREACH_PTR(all_method_sources, fn) {
2362+
HashTable *sources = (HashTable*)fn;
2363+
zend_hash_destroy(sources);
2364+
FREE_HASHTABLE(sources);
2365+
} ZEND_HASH_FOREACH_END();
2366+
2367+
zend_hash_destroy(all_method_sources);
2368+
FREE_HASHTABLE(all_method_sources);
2369+
}
2370+
/* }}} */
2371+
23092372
static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */
23102373
{
23112374
uint32_t i;
@@ -2591,6 +2654,10 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
25912654
/* complete initialization of trait structures in ce */
25922655
zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases);
25932656

2657+
if (exclude_tables) {
2658+
zend_traits_check_for_mutual_exclusions(ce, traits, exclude_tables);
2659+
}
2660+
25942661
/* first care about all methods to be flattened into the class */
25952662
zend_do_traits_method_binding(ce, traits, exclude_tables, aliases);
25962663

0 commit comments

Comments
 (0)