Skip to content

Commit 0295615

Browse files
committed
Fix prototype for trait methods
Fixes GH-14009
1 parent b3e26c3 commit 0295615

7 files changed

+194
-16
lines changed

Zend/tests/gh14009_001.phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-14009: Traits inherit prototype
3+
--FILE--
4+
<?php
5+
6+
class P {
7+
protected function common() {
8+
throw new Exception('Unreachable');
9+
}
10+
}
11+
12+
class A extends P {
13+
public function test(P $sibling) {
14+
$sibling->common();
15+
}
16+
}
17+
18+
class B extends P {
19+
protected function common() {
20+
echo __METHOD__, "\n";
21+
}
22+
}
23+
24+
trait T {
25+
protected function common() {
26+
echo __METHOD__, "\n";
27+
}
28+
}
29+
30+
class C extends P {
31+
use T;
32+
}
33+
34+
$a = new A();
35+
$a->test(new B());
36+
$a->test(new C());
37+
38+
?>
39+
--EXPECT--
40+
B::common
41+
T::common

Zend/tests/gh14009_002.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-14009: Traits inherit prototype
3+
--FILE--
4+
<?php
5+
6+
class P {
7+
protected function common() {}
8+
}
9+
10+
class A extends P {}
11+
12+
trait T {
13+
private abstract function common(int $param);
14+
}
15+
16+
class B extends P {
17+
use T;
18+
}
19+
20+
?>
21+
--EXPECTF--
22+
Fatal error: Declaration of P::common() must be compatible with T::common(int $param) in %s on line %d

Zend/tests/gh14009_003.phpt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
GH-14009: Traits inherit prototype
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
private function test() {}
8+
}
9+
10+
trait T {
11+
private function test() {}
12+
}
13+
14+
class B extends A {
15+
use T;
16+
}
17+
18+
?>
19+
--EXPECT--

Zend/tests/gh14009_004.phpt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
GH-14009: Traits inherit prototype
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
protected function test() {
8+
throw new Exception('Unreachable');
9+
}
10+
}
11+
12+
class B extends A {
13+
// Prototype is A::test().
14+
protected function test() {
15+
echo __METHOD__, "\n";
16+
}
17+
}
18+
19+
trait T {
20+
protected abstract function test();
21+
}
22+
23+
class C extends B {
24+
use T;
25+
}
26+
27+
class D extends A {
28+
public static function callTest($sibling) {
29+
$sibling->test();
30+
}
31+
}
32+
33+
D::callTest(new C());
34+
35+
?>
36+
--EXPECT--
37+
B::test
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Interfaces don't set prototypes to their parent method
3+
--FILE--
4+
<?php
5+
6+
interface A {
7+
public function __construct(int $param);
8+
}
9+
interface B extends A {
10+
public function __construct(int|float $param);
11+
}
12+
class Test implements B {
13+
public function __construct(int $param) {}
14+
}
15+
new Test(42);
16+
17+
?>
18+
--EXPECTF--
19+
Fatal error: Declaration of Test::__construct(int $param) must be compatible with B::__construct(int|float $param) in %s on line %d
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Interfaces don't set prototypes to their parent method
3+
--XFAIL--
4+
X::__constructor()'s prototype is set to B::__construct(). Y::__construct() then
5+
uses prototype to verify LSP, but misses A::__construct() which has a stricter
6+
signature.
7+
--FILE--
8+
<?php
9+
10+
interface A {
11+
public function __construct(int|float $param);
12+
}
13+
interface B {
14+
public function __construct(int $param);
15+
}
16+
class X implements A, B {
17+
public function __construct(int|float $param) {}
18+
}
19+
class Y extends X {
20+
public function __construct(int $param) {}
21+
}
22+
new Y(42);
23+
24+
?>
25+
--EXPECTF--
26+
Fatal error: Declaration of Y::__construct(int $param) must be compatible with A::__construct(int|float $param) in %s on line %d

Zend/zend_inheritance.c

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,19 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
10851085
uint32_t parent_flags = parent->common.fn_flags;
10861086
zend_function *proto;
10871087

1088+
#define SEPARATE_METHOD() do { \
1089+
if (child_scope != ce && child->type == ZEND_USER_FUNCTION && child_zv) { \
1090+
/* op_array wasn't duplicated yet */ \
1091+
zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); \
1092+
memcpy(new_function, child, sizeof(zend_op_array)); \
1093+
Z_PTR_P(child_zv) = child = new_function; \
1094+
child_zv = NULL; \
1095+
} \
1096+
} while(0)
1097+
10881098
if (UNEXPECTED((parent_flags & ZEND_ACC_PRIVATE) && !(parent_flags & ZEND_ACC_ABSTRACT) && !(parent_flags & ZEND_ACC_CTOR))) {
10891099
if (!check_only) {
1100+
SEPARATE_METHOD();
10901101
child->common.fn_flags |= ZEND_ACC_CHANGED;
10911102
}
10921103
/* The parent method is private and not an abstract so we don't need to check any inheritance rules */
@@ -1130,7 +1141,12 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
11301141
ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child));
11311142
}
11321143

1133-
if (!check_only && (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))) {
1144+
if (!check_only
1145+
&& (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))
1146+
/* In the case of abstract traits, don't mark the method as changed. We don't actually have a
1147+
* private parent method. */
1148+
&& !(parent->common.scope->ce_flags & ZEND_ACC_TRAIT)) {
1149+
SEPARATE_METHOD();
11341150
child->common.fn_flags |= ZEND_ACC_CHANGED;
11351151
}
11361152

@@ -1146,21 +1162,16 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
11461162
parent = proto;
11471163
}
11481164

1149-
if (!check_only && child->common.prototype != proto && child_zv) {
1150-
do {
1151-
if (child->common.scope != ce && child->type == ZEND_USER_FUNCTION) {
1152-
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
1153-
/* Few parent interfaces contain the same method */
1154-
break;
1155-
} else {
1156-
/* op_array wasn't duplicated yet */
1157-
zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
1158-
memcpy(new_function, child, sizeof(zend_op_array));
1159-
Z_PTR_P(child_zv) = child = new_function;
1160-
}
1161-
}
1162-
child->common.prototype = proto;
1163-
} while (0);
1165+
if (!check_only
1166+
&& child->common.prototype != proto
1167+
/* We are not setting the prototype of overridden interface methods because of abstract
1168+
* constructors. See Zend/tests/interface_constructor_prototype_001.phpt. */
1169+
&& !(ce->ce_flags & ZEND_ACC_INTERFACE)
1170+
/* Parent is a trait when its method is abstract. We only need to verify its signature then,
1171+
* don't actually use it as a prototype. */
1172+
&& !(parent->common.scope->ce_flags & ZEND_ACC_TRAIT)) {
1173+
SEPARATE_METHOD();
1174+
child->common.prototype = proto;
11641175
}
11651176

11661177
/* Prevent derived classes from restricting access that was available in parent classes (except deriving from non-abstract ctors) */
@@ -1180,6 +1191,9 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
11801191
}
11811192
perform_delayable_implementation_check(ce, child, child_scope, parent, parent_scope);
11821193
}
1194+
1195+
#undef SEPARATE_METHOD
1196+
11831197
return INHERITANCE_SUCCESS;
11841198
}
11851199
/* }}} */

0 commit comments

Comments
 (0)