Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Zend/tests/asymmetric_visibility/cache_slot_magic_set.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Asymmetric visibility: cache slot primed by a __set() fallback must not skip the set visibility check
--FILE--
<?php
class C {
public private(set) int $x = 0;
public function __set($name, $value) { echo "__set($name)\n"; }
public function unsetX() { unset($this->x); }
}
function w(C $c, int $v) {
try {
$c->x = $v; // same opline every call
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
}
$a = new C();
$a->unsetX();
w($a, 1); // unset property: goes to __set(), cache slot stays primed
$b = new C();
w($b, 2); // initialized property, must fail: private(set) from global scope
var_dump($b->x);

?>
--EXPECT--
__set(x)
Cannot modify private(set) property C::$x from global scope
int(0)
26 changes: 26 additions & 0 deletions Zend/tests/readonly_props/cache_slot_clone_reinit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Readonly: cache slot primed by a failed modification must not skip the set visibility check on reinitialization
--FILE--
<?php
class P {
public readonly int $x;
public function __construct() { $this->x = 1; }
public function __clone() { w($this, 3); }
}
function w(P $p, int $v) {
try {
$p->x = $v; // same opline every call
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
}
$p = new P();
w($p, 2); // readonly modification error, cache slot stays primed
$q = clone $p; // __clone -> w(): global scope must not be able to reinitialize
var_dump($q->x);

?>
--EXPECT--
Cannot modify readonly property P::$x
Cannot modify protected(set) readonly property P::$x from global scope
int(1)
24 changes: 17 additions & 7 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1131,18 +1131,19 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
&& !(Z_PROP_FLAG_P(variable_ptr) & IS_PROP_REINITABLE)) {
zend_readonly_property_modification_error(prop_info);
variable_ptr = &EG(error_zval);
goto exit;
/* The set visibility was not checked */
goto exit_reset_cache_slot;
}
if ((prop_info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(prop_info)) {
zend_asymmetric_visibility_property_modification_error(prop_info, "modify");
variable_ptr = &EG(error_zval);
if (cache_slot) {
/* Reset cache slot to dodge fast path in next execution. */
CACHE_POLYMORPHIC_PTR_EX(cache_slot, NULL, NULL);
CACHE_PTR_EX(cache_slot + 2, NULL);
}
goto exit;
goto exit_reset_cache_slot;
}
} else if (cache_slot) {
/* __set() will be called without checking the set visibility. The ZEND_ASSIGN_OBJ
* fast path skips that check when the cache slot is primed, so reset it. */
CACHE_POLYMORPHIC_PTR_EX(cache_slot, NULL, NULL);
CACHE_PTR_EX(cache_slot + 2, NULL);
}
}

Expand Down Expand Up @@ -1347,6 +1348,15 @@ found:;
exit:
return variable_ptr;

exit_reset_cache_slot:
if (cache_slot) {
/* Reset cache slot to dodge the fast path, which doesn't check the set visibility,
* in the next execution. */
CACHE_POLYMORPHIC_PTR_EX(cache_slot, NULL, NULL);
CACHE_PTR_EX(cache_slot + 2, NULL);
}
return variable_ptr;

lazy_init:
return forward_write_to_lazy_object(zobj, name, value, cache_slot,
/* guarded */ false);
Expand Down
Loading