Skip to content

Commit 8254e8d

Browse files
committedMar 8, 2025
Fix lazy proxy calling set hook twice
Writing to an uninitialized lazy proxy will initialize the underlying object and then call zend_std_write_property() on it. If this happens inside a hook, zend_std_write_property() should not call the hook again but directly write to the property slot. This didn't previously work because zend_should_call_hook() would compare the parent frame containing the proxy to the underlying object. This is now handled explicitly. Fixes GH-18000 Closes GH-18001
1 parent 9acfe6e commit 8254e8d

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed
 

‎NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ PHP NEWS
1717
get_object_vars()). (ilutov)
1818
. Fixed bug GH-17998 (Skipped lazy object initialization on primed
1919
SIMPLE_WRITE cache). (ilutov)
20+
. Fixed bug GH-17998 (Assignment to backing value in set hook of lazy proxy
21+
calls hook again). (ilutov)
2022

2123
- DOM:
2224
. Fixed bug GH-17991 (Assertion failure dom_attr_value_write). (nielsdos)
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
GH-18000: Lazy proxy calls set hook twice
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public $prop {
8+
set {
9+
echo "set\n";
10+
$this->prop = $value * 2;
11+
}
12+
}
13+
}
14+
15+
$rc = new ReflectionClass(C::class);
16+
17+
$obj = $rc->newLazyProxy(function () {
18+
echo "init\n";
19+
return new C;
20+
});
21+
22+
function foo(C $c) {
23+
$c->prop = 1;
24+
var_dump($c->prop);
25+
}
26+
27+
foo($obj);
28+
29+
?>
30+
--EXPECT--
31+
set
32+
init
33+
int(2)

‎Zend/zend_object_handlers.c

+17-3
Original file line numberDiff line numberDiff line change
@@ -673,9 +673,23 @@ static bool zend_is_in_hook(const zend_property_info *prop_info)
673673

674674
static bool zend_should_call_hook(const zend_property_info *prop_info, const zend_object *obj)
675675
{
676-
return !zend_is_in_hook(prop_info)
677-
/* execute_data and This are guaranteed to be set if zend_is_in_hook() returns true. */
678-
|| Z_OBJ(EG(current_execute_data)->This) != obj;
676+
if (!zend_is_in_hook(prop_info)) {
677+
return true;
678+
}
679+
680+
/* execute_data and This are guaranteed to be set if zend_is_in_hook() returns true. */
681+
zend_object *parent_obj = Z_OBJ(EG(current_execute_data)->This);
682+
if (parent_obj == obj) {
683+
return false;
684+
}
685+
686+
if (zend_object_is_lazy_proxy(parent_obj)
687+
&& zend_lazy_object_initialized(parent_obj)
688+
&& zend_lazy_object_get_instance(parent_obj) == obj) {
689+
return false;
690+
}
691+
692+
return true;
679693
}
680694

681695
static ZEND_COLD void zend_throw_no_prop_backing_value_access(zend_string *class_name, zend_string *prop_name, bool is_read)

0 commit comments

Comments
 (0)
Failed to load comments.