Skip to content

Commit 8254e8d

Browse files
committed
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

Lines changed: 2 additions & 0 deletions
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)
Lines changed: 33 additions & 0 deletions
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

Lines changed: 17 additions & 3 deletions
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)