Skip to content

Commit d1a6e37

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

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-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: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2304,6 +2304,67 @@ 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+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) {
2322+
HashTable *sources;
2323+
2324+
if ((sources = zend_hash_find_ptr(all_method_sources, key)) == NULL) {
2325+
ALLOC_HASHTABLE(sources);
2326+
zend_hash_init(sources, 0, NULL, NULL, 0);
2327+
zend_hash_add_ptr(all_method_sources, key, sources);
2328+
}
2329+
2330+
zend_hash_index_add_ptr(sources, i, traits[i]);
2331+
} ZEND_HASH_FOREACH_END();
2332+
}
2333+
}
2334+
2335+
/* Are all method implementations excluded? */
2336+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(all_method_sources, key, fn) {
2337+
HashTable *sources = (HashTable*)fn;
2338+
bool has_available_impl = false;
2339+
uint32_t trait_index;
2340+
2341+
ZEND_HASH_FOREACH_NUM_KEY_PTR(sources, trait_index, trait) {
2342+
/* Trait's implementation is excluded? */
2343+
if (!exclude_tables[trait_index] ||
2344+
zend_hash_find(exclude_tables[trait_index], key) == NULL) {
2345+
has_available_impl = true;
2346+
break;
2347+
}
2348+
} ZEND_HASH_FOREACH_END();
2349+
2350+
if (!has_available_impl && zend_hash_num_elements(sources) > 1) {
2351+
zend_error_noreturn(E_COMPILE_ERROR,
2352+
"Invalid trait method precedence for %s() - all implementations have been excluded by insteadof rules",
2353+
ZSTR_VAL(key));
2354+
}
2355+
} ZEND_HASH_FOREACH_END();
2356+
2357+
ZEND_HASH_MAP_FOREACH_PTR(all_method_sources, fn) {
2358+
HashTable *sources = (HashTable*)fn;
2359+
zend_hash_destroy(sources);
2360+
FREE_HASHTABLE(sources);
2361+
} ZEND_HASH_FOREACH_END();
2362+
2363+
zend_hash_destroy(all_method_sources);
2364+
FREE_HASHTABLE(all_method_sources);
2365+
}
2366+
/* }}} */
2367+
23072368
static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */
23082369
{
23092370
uint32_t i;
@@ -2589,6 +2650,10 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
25892650
/* complete initialization of trait structures in ce */
25902651
zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases);
25912652

2653+
if (exclude_tables) {
2654+
zend_traits_check_for_mutual_exclusions(ce, traits, exclude_tables);
2655+
}
2656+
25922657
/* first care about all methods to be flattened into the class */
25932658
zend_do_traits_method_binding(ce, traits, exclude_tables, aliases);
25942659

0 commit comments

Comments
 (0)