Skip to content

Commit 825ee3f

Browse files
committed
Adjust after review
1 parent 1fa44f7 commit 825ee3f

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-19044: Protected properties must be scoped according to their prototype (protected(set) on non-hooked property)
3+
--FILE--
4+
<?php
5+
6+
class P {
7+
public mixed $foo { get => 42; }
8+
}
9+
10+
class C1 extends P {
11+
public protected(set) mixed $foo = 1;
12+
}
13+
14+
class C2 extends P {
15+
public protected(set) mixed $foo;
16+
17+
static function foo($c) { return $c->foo += 1; }
18+
}
19+
20+
var_dump(C2::foo(new C1));
21+
22+
?>
23+
--EXPECTF--
24+
Fatal error: Uncaught Error: Cannot modify protected(set) property C1::$foo from scope C2 in %s:%d
25+
Stack trace:
26+
#0 %s(%d): C2::foo(Object(C1))
27+
#1 {main}
28+
thrown in %s on line %d
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-19044: Protected properties must be scoped according to their prototype (abstract parent has implicit set hook)
3+
--FILE--
4+
<?php
5+
6+
abstract class GP {
7+
public abstract mixed $foo { get; }
8+
}
9+
10+
class P extends GP {
11+
public protected(set) mixed $foo { get => $this->foo; }
12+
}
13+
14+
class C1 extends P {
15+
public protected(set) mixed $foo = 1;
16+
}
17+
18+
class C2 extends P {
19+
public protected(set) mixed $foo;
20+
21+
static function foo($c) { return $c->foo += 1; }
22+
}
23+
24+
var_dump(C2::foo(new C1));
25+
26+
?>
27+
--EXPECT--
28+
int(2)

Zend/zend_object_handlers.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,25 @@ static zend_never_inline int is_protected_compatible_scope(const zend_class_entr
286286
}
287287
/* }}} */
288288

289-
static zend_never_inline int is_asymmetric_set_protected_property_compatible_scope(const zend_property_info *info, const zend_class_entry *scope) /* {{{ */
289+
static int is_asymmetric_set_protected_property_compatible_scope(const zend_property_info *info, const zend_class_entry *scope) /* {{{ */
290290
{
291291
zend_class_entry *ce;
292-
if (!(info->prototype->flags & ZEND_ACC_PROTECTED_SET) && info->hooks && info->hooks[ZEND_PROPERTY_HOOK_SET]) {
293-
zend_function *hookfn = info->hooks[ZEND_PROPERTY_HOOK_SET];
294-
ce = hookfn->common.prototype ? hookfn->common.prototype->common.scope : hookfn->common.scope;
292+
/* we need to identify the common protected(set) ancestor: if the prototype has the protected(set), it's straightforward */
293+
if (info->prototype->flags & ZEND_ACC_PROTECTED_SET) {
294+
ce = info->prototype->ce;
295+
} else if (info->hooks && info->hooks[ZEND_PROPERTY_HOOK_SET]) {
296+
/* shortcut: the visibility of hooks cannot be overwritten */
297+
zend_function *hook = info->hooks[ZEND_PROPERTY_HOOK_SET];
298+
ce = zend_get_function_root_class(hook);
295299
} else {
296-
ce = info->prototype->ce;
300+
/* we do not have an easy way to find the ancestor which introduces the protected(set), let's iterate */
301+
do {
302+
ce = info->ce;
303+
if (!ce->parent->properties_info_table) {
304+
break;
305+
}
306+
info = ce->parent->properties_info_table[OBJ_PROP_TO_NUM(info->offset)];
307+
} while (info->flags & ZEND_ACC_PROTECTED_SET);
297308
}
298309
return is_protected_compatible_scope(ce, scope);
299310
}

0 commit comments

Comments
 (0)