Skip to content

Commit 5eb7a5a

Browse files
Fix GH-18223: Meaningful error on wrong trait method exclusion
1 parent bd2766c commit 5eb7a5a

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ PHP NEWS
2020
. Fixed bug GH-18736 (Circumvented type check with return by ref + finally).
2121
(ilutov)
2222
. Fixed zend call stack size for macOs/arm64. (David Carlier)
23+
. Fixed GH-18223 (Traits methods precedence). (alexandre-daubois)
2324

2425
- FTP:
2526
. Fix theoretical issues with hrtime() not being available. (nielsdos)
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
@@ -2304,6 +2304,69 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
23042304
}
23052305
/* }}} */
23062306

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

2655+
if (exclude_tables) {
2656+
zend_traits_check_for_mutual_exclusions(ce, traits, exclude_tables);
2657+
}
2658+
25922659
/* first care about all methods to be flattened into the class */
25932660
zend_do_traits_method_binding(ce, traits, exclude_tables, aliases);
25942661

0 commit comments

Comments
 (0)