Skip to content

Commit 4e95403

Browse files
author
gron
committed
Fixed Bug #60217 (Requiring the same method from different traits)
- also added test to check for inconsistent abstract method definitions, they need to be compatible git-svn-id: http://svn.php.net/repository/php/php-src/trunk@318793 c90b9560-bf6c-de11-be94-00142212c4b1
1 parent ba04b0c commit 4e95403

File tree

4 files changed

+110
-19
lines changed

4 files changed

+110
-19
lines changed

Zend/tests/traits/bug60217a.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #60217 (Requiring the same method from different traits.)
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
public abstract function foo();
8+
}
9+
10+
trait T2 {
11+
public abstract function foo();
12+
}
13+
14+
class C {
15+
use T1, T2;
16+
17+
public function foo() {
18+
echo "C::foo() works.\n";
19+
}
20+
}
21+
22+
$o = new C;
23+
$o->foo();
24+
25+
--EXPECTF--
26+
C::foo() works.

Zend/tests/traits/bug60217b.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #60217 (Requiring the same method from different traits and abstract methods have to be compatible)
3+
--FILE--
4+
<?php
5+
6+
trait TBroken1 {
7+
public abstract function foo($a);
8+
}
9+
10+
trait TBroken2 {
11+
public abstract function foo($a, $b = 0);
12+
}
13+
14+
class CBroken {
15+
use TBroken1, TBroken2;
16+
17+
public function foo($a) {
18+
echo 'FOO';
19+
}
20+
}
21+
22+
$o = new CBroken;
23+
$o->foo(1);
24+
25+
--EXPECTF--
26+
Fatal error: Declaration of TBroken1::foo($a) must be compatible with TBroken2::foo($a, $b = 0) in %s on line %d

Zend/tests/traits/bug60217c.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #60217 (Requiring the same method from different traits and abstract methods have to be compatible, in both directions.)
3+
--FILE--
4+
<?php
5+
6+
trait TBroken1 {
7+
public abstract function foo($a, $b = 0);
8+
}
9+
10+
trait TBroken2 {
11+
public abstract function foo($a);
12+
}
13+
14+
class CBroken {
15+
use TBroken1, TBroken2;
16+
17+
public function foo($a) {
18+
echo 'FOO';
19+
}
20+
}
21+
22+
$o = new CBroken;
23+
$o->foo(1);
24+
25+
--EXPECTF--
26+
Fatal error: Declaration of TBroken1::foo($a, $b = 0) must be compatible with TBroken2::foo($a) in %s on line %d

Zend/zend_compile.c

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3617,7 +3617,19 @@ static int zend_traits_merge_functions(zend_function *fn TSRMLS_DC, int num_args
36173617
/* if it is an abstract method, there is no collision */
36183618
if (other_trait_fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
36193619
/* Make sure they are compatible */
3620-
do_inheritance_check_on_method(fn, other_trait_fn TSRMLS_CC);
3620+
if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
3621+
/* In case both are abstract, just check prototype, but need to do that in both directions */
3622+
if ( !zend_do_perform_implementation_check(fn, other_trait_fn TSRMLS_CC)
3623+
|| !zend_do_perform_implementation_check(other_trait_fn, fn TSRMLS_CC)) {
3624+
zend_error(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", //ZEND_FN_SCOPE_NAME(fn), fn->common.function_name, //::%s()
3625+
zend_get_function_declaration(fn TSRMLS_CC),
3626+
zend_get_function_declaration(other_trait_fn TSRMLS_CC));
3627+
}
3628+
}
3629+
else {
3630+
/* otherwise, do the full check */
3631+
do_inheritance_check_on_method(fn, other_trait_fn TSRMLS_CC);
3632+
}
36213633

36223634
/* we can savely free and remove it from other table */
36233635
zend_function_dtor(other_trait_fn);
@@ -3626,7 +3638,8 @@ static int zend_traits_merge_functions(zend_function *fn TSRMLS_DC, int num_args
36263638
/* if it is not an abstract method, there is still no collision */
36273639
/* if fn is an abstract method */
36283640
if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
3629-
/* Make sure they are compatible */
3641+
/* Make sure they are compatible.
3642+
Here, we already know other_trait_fn cannot be abstract, full check ok. */
36303643
do_inheritance_check_on_method(other_trait_fn, fn TSRMLS_CC);
36313644

36323645
/* just mark as solved, will be added if its own trait is processed */
@@ -3843,39 +3856,39 @@ static int zend_traits_merge_functions_to_class(zend_function *fn TSRMLS_DC, int
38433856
zend_function* existing_fn = NULL;
38443857
zend_function fn_copy, *fn_copy_p;
38453858
zend_function* prototype = NULL; /* is used to determine the prototype according to the inheritance chain */
3846-
3859+
38473860
if (zend_hash_quick_find(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void**) &existing_fn) == FAILURE) {
38483861
add = 1; /* not found */
38493862
} else if (existing_fn->common.scope != ce) {
38503863
add = 1; /* or inherited from other class or interface */
38513864
}
3852-
3865+
38533866
if (add) {
38543867
zend_function* parent_function;
38553868
if (ce->parent && zend_hash_quick_find(&ce->parent->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void**) &parent_function) != FAILURE) {
38563869
prototype = parent_function; /* ->common.fn_flags |= ZEND_ACC_ABSTRACT; */
38573870

38583871
/* we got that method in the parent class, and are going to override it,
3859-
except, if the trait is just asking to have an abstract method implemented. */
3872+
except, if the trait is just asking to have an abstract method implemented. */
38603873
if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
38613874
/* then we clean up an skip this method */
38623875
zend_function_dtor(fn);
38633876
return ZEND_HASH_APPLY_REMOVE;
38643877
}
38653878
}
3866-
3879+
38673880
fn->common.scope = ce;
38683881
fn->common.prototype = prototype;
3869-
3882+
38703883
if (prototype
3871-
&& (prototype->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT
3872-
|| prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
3873-
fn->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT;
3874-
} else if (fn->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT) {
3875-
/* remove ZEND_ACC_IMPLEMENTED_ABSTRACT flag, think it shouldn't be copied to class */
3876-
fn->common.fn_flags = fn->common.fn_flags - ZEND_ACC_IMPLEMENTED_ABSTRACT;
3877-
}
3878-
3884+
&& (prototype->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT
3885+
|| prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
3886+
fn->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT;
3887+
} else if (fn->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT) {
3888+
/* remove ZEND_ACC_IMPLEMENTED_ABSTRACT flag, think it shouldn't be copied to class */
3889+
fn->common.fn_flags = fn->common.fn_flags - ZEND_ACC_IMPLEMENTED_ABSTRACT;
3890+
}
3891+
38793892
/* check whether the trait method fullfills the inheritance requirements */
38803893
if (prototype) {
38813894
do_inheritance_check_on_method(fn, prototype TSRMLS_CC);
@@ -3906,18 +3919,18 @@ static int zend_traits_merge_functions_to_class(zend_function *fn TSRMLS_DC, int
39063919
}
39073920
fn_copy = *fn;
39083921
zend_traits_duplicate_function(&fn_copy, ce, estrdup(fn->common.function_name) TSRMLS_CC);
3909-
3922+
39103923
if (zend_hash_quick_update(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &fn_copy, sizeof(zend_function), (void**)&fn_copy_p)==FAILURE) {
39113924
zend_error(E_COMPILE_ERROR, "Trait method %s has not been applied, because failure occured during updating class method table", hash_key->arKey);
39123925
}
3913-
3926+
39143927
zend_add_magic_methods(ce, hash_key->arKey, hash_key->nKeyLength, fn_copy_p TSRMLS_CC);
3915-
3928+
39163929
zend_function_dtor(fn);
39173930
} else {
39183931
zend_function_dtor(fn);
39193932
}
3920-
3933+
39213934
return ZEND_HASH_APPLY_REMOVE;
39223935
}
39233936
/* }}} */

0 commit comments

Comments
 (0)