Skip to content

Commit c9dfb77

Browse files
committed
Deny resetting an object as lazy during property iteration
Supporting object reset while its properties are being iterated would increase complexity for little benefit. Furthermore it may not be possible to ensure a consistent behavior between ghosts and proxies (wrt to iteration position). Iteration is detected by checking if the object's properties ht has iterators. This requires refactoring the hooked get_iterator() implementation to ensure that it creates a properties ht iterator immediately. Closes GH-15960
1 parent 3151117 commit c9dfb77

5 files changed

+265
-63
lines changed

Zend/tests/lazy_objects/init_trigger_foreach.phpt

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ Lazy objects: Foreach initializes object
55

66
class C {
77
public int $a;
8+
public int $b;
89
public function __construct() {
910
var_dump(__METHOD__);
1011
$this->a = 1;
12+
$this->b = 2;
1113
}
1214
}
1315

@@ -24,6 +26,17 @@ foreach ($obj as $prop => $value) {
2426
var_dump($prop, $value);
2527
}
2628

29+
print "# Ghost (by ref):\n";
30+
31+
$obj = $reflector->newLazyGhost(function ($obj) {
32+
var_dump("initializer");
33+
$obj->__construct();
34+
});
35+
36+
foreach ($obj as $prop => &$value) {
37+
var_dump($prop, $value);
38+
}
39+
2740
print "# Proxy:\n";
2841

2942
$obj = $reflector->newLazyProxy(function ($obj) {
@@ -35,14 +48,110 @@ foreach ($obj as $prop => $value) {
3548
var_dump($prop, $value);
3649
}
3750

38-
--EXPECTF--
51+
print "# Proxy (by ref):\n";
52+
53+
$obj = $reflector->newLazyProxy(function ($obj) {
54+
var_dump("initializer");
55+
return new C();
56+
});
57+
58+
foreach ($obj as $prop => &$value) {
59+
var_dump($prop, $value);
60+
}
61+
62+
print "# Ghost (init failure)\n";
63+
64+
$fail = true;
65+
$obj = $reflector->newLazyGhost(function ($obj) use (&$fail) {
66+
if ($fail) {
67+
throw new Exception("initializer");
68+
} else {
69+
var_dump("initializer");
70+
$obj->__construct();
71+
}
72+
});
73+
74+
try {
75+
foreach ($obj as $prop => $value) {
76+
var_dump($prop, $value);
77+
}
78+
} catch (Exception $e) {
79+
printf("%s: %s\n", $e::class, $e->getMessage());
80+
}
81+
82+
$fail = false;
83+
foreach ($obj as $prop => $value) {
84+
var_dump($prop, $value);
85+
}
86+
87+
print "# Ghost (init failure, by ref)\n";
88+
89+
$fail = true;
90+
$obj = $reflector->newLazyGhost(function ($obj) use (&$fail) {
91+
if ($fail) {
92+
throw new Exception("initializer");
93+
} else {
94+
var_dump("initializer");
95+
$obj->__construct();
96+
}
97+
});
98+
99+
try {
100+
foreach ($obj as $prop => &$value) {
101+
var_dump($prop, $value);
102+
}
103+
} catch (Exception $e) {
104+
printf("%s: %s\n", $e::class, $e->getMessage());
105+
}
106+
107+
$fail = false;
108+
foreach ($obj as $prop => &$value) {
109+
var_dump($prop, $value);
110+
}
111+
112+
?>
113+
--EXPECT--
39114
# Ghost:
40115
string(11) "initializer"
41116
string(14) "C::__construct"
42117
string(1) "a"
43118
int(1)
119+
string(1) "b"
120+
int(2)
121+
# Ghost (by ref):
122+
string(11) "initializer"
123+
string(14) "C::__construct"
124+
string(1) "a"
125+
int(1)
126+
string(1) "b"
127+
int(2)
44128
# Proxy:
45129
string(11) "initializer"
46130
string(14) "C::__construct"
47131
string(1) "a"
48132
int(1)
133+
string(1) "b"
134+
int(2)
135+
# Proxy (by ref):
136+
string(11) "initializer"
137+
string(14) "C::__construct"
138+
string(1) "a"
139+
int(1)
140+
string(1) "b"
141+
int(2)
142+
# Ghost (init failure)
143+
Exception: initializer
144+
string(11) "initializer"
145+
string(14) "C::__construct"
146+
string(1) "a"
147+
int(1)
148+
string(1) "b"
149+
int(2)
150+
# Ghost (init failure, by ref)
151+
Exception: initializer
152+
string(11) "initializer"
153+
string(14) "C::__construct"
154+
string(1) "a"
155+
int(1)
156+
string(1) "b"
157+
int(2)

Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,79 @@ Lazy objects: Foreach initializes object
66
#[AllowDynamicProperties]
77
class C {
88
public int $a;
9+
private int $_b;
910
public int $b {
10-
get { return $this->b; }
11-
set(int $value) { $this->b = $value; }
11+
&get { $ref = &$this->_b; return $ref; }
1212
}
13-
public int $c {
14-
get { return $this->a + 2; }
15-
}
16-
public function __construct() {
13+
public function __construct(bool $addDynamic = true) {
1714
var_dump(__METHOD__);
1815
$this->a = 1;
19-
$this->b = 2;
20-
$this->d = 4;
16+
$this->_b = 2;
17+
if ($addDynamic) {
18+
$this->c = 3;
19+
$this->d = 4;
20+
unset($this->c);
21+
}
2122
}
2223
}
2324

2425
$reflector = new ReflectionClass(C::class);
2526

26-
print "# Ghost:\n";
27+
function test(string $name, object $obj) {
28+
printf("# %s:\n", $name);
29+
foreach ($obj as $prop => $value) {
30+
var_dump($prop, $value);
31+
}
32+
foreach ($obj as $prop => &$value) {
33+
var_dump($prop, $value);
34+
}
35+
}
2736

2837
$obj = $reflector->newLazyGhost(function ($obj) {
2938
var_dump("initializer");
3039
$obj->__construct();
3140
});
3241

33-
foreach ($obj as $prop => $value) {
34-
var_dump($prop, $value);
35-
}
42+
test('Ghost', $obj);
43+
44+
$obj = $reflector->newLazyProxy(function ($obj) {
45+
var_dump("initializer");
46+
return new C();
47+
});
48+
49+
test('Proxy', $obj);
3650

37-
print "# Proxy:\n";
51+
$obj = $reflector->newLazyGhost(function ($obj) {
52+
var_dump("initializer");
53+
$obj->__construct(addDynamic: false);
54+
});
55+
56+
test('Ghost (no dynamic)', $obj);
3857

3958
$obj = $reflector->newLazyProxy(function ($obj) {
4059
var_dump("initializer");
60+
return new C(addDynamic: false);
61+
});
62+
63+
test('Proxy (no dynamic)', $obj);
64+
65+
print "# Proxy of proxy (initialization)\n";
66+
67+
$obj = $reflector->newLazyProxy(function ($obj) use (&$obj2, $reflector) {
68+
var_dump("initializer");
69+
return $obj2 = new C();
70+
});
71+
$reflector->initializeLazyObject($obj);
72+
$reflector->resetAsLazyProxy($obj2, function () {
4173
return new C();
4274
});
4375

44-
foreach ($obj as $prop => $value) {
45-
var_dump($prop, $value);
46-
}
76+
test('Proxy of proxy', $obj);
4777

4878
print "# Ghost (init exception):\n";
4979

5080
$obj = $reflector->newLazyGhost(function ($obj) {
51-
throw new \Exception();
81+
throw new \Exception("initializer");
5282
});
5383

5484
try {
@@ -60,7 +90,7 @@ try {
6090
print "# Proxy (init exception):\n";
6191

6292
$obj = $reflector->newLazyProxy(function ($obj) {
63-
throw new \Exception();
93+
throw new \Exception("initializer");
6494
});
6595

6696
try {
@@ -77,8 +107,12 @@ string(1) "a"
77107
int(1)
78108
string(1) "b"
79109
int(2)
80-
string(1) "c"
81-
int(3)
110+
string(1) "d"
111+
int(4)
112+
string(1) "a"
113+
int(1)
114+
string(1) "b"
115+
int(2)
82116
string(1) "d"
83117
int(4)
84118
# Proxy:
@@ -88,9 +122,54 @@ string(1) "a"
88122
int(1)
89123
string(1) "b"
90124
int(2)
91-
string(1) "c"
92-
int(3)
125+
string(1) "d"
126+
int(4)
127+
string(1) "a"
128+
int(1)
129+
string(1) "b"
130+
int(2)
131+
string(1) "d"
132+
int(4)
133+
# Ghost (no dynamic):
134+
string(11) "initializer"
135+
string(14) "C::__construct"
136+
string(1) "a"
137+
int(1)
138+
string(1) "b"
139+
int(2)
140+
string(1) "a"
141+
int(1)
142+
string(1) "b"
143+
int(2)
144+
# Proxy (no dynamic):
145+
string(11) "initializer"
146+
string(14) "C::__construct"
147+
string(1) "a"
148+
int(1)
149+
string(1) "b"
150+
int(2)
151+
string(1) "a"
152+
int(1)
153+
string(1) "b"
154+
int(2)
155+
# Proxy of proxy (initialization)
156+
string(11) "initializer"
157+
string(14) "C::__construct"
158+
# Proxy of proxy:
159+
string(14) "C::__construct"
160+
string(1) "a"
161+
int(1)
162+
string(1) "b"
163+
int(2)
164+
string(1) "d"
165+
int(4)
166+
string(1) "a"
167+
int(1)
168+
string(1) "b"
169+
int(2)
170+
string(1) "d"
171+
int(4)
93172
# Ghost (init exception):
94-
Exception:
173+
Exception: initializer
95174
# Proxy (init exception):
96-
Exception:
175+
Exception: initializer

Zend/tests/lazy_objects/reset_as_lazy_resets_dynamic_props.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ var_dump($obj);
4747
--EXPECTF--
4848
# Ghost:
4949
string(18) "Canary::__destruct"
50-
lazy ghost object(C)#%d (0) {
50+
lazy ghost object(C)#%d (%d) {
5151
}
5252
string(11) "initializer"
5353
object(Canary)#%d (0) {
@@ -62,7 +62,7 @@ object(C)#%d (2) {
6262
# Proxy:
6363
string(18) "Canary::__destruct"
6464
string(18) "Canary::__destruct"
65-
lazy proxy object(C)#%d (0) {
65+
lazy proxy object(C)#%d (%d) {
6666
}
6767
string(11) "initializer"
6868
object(Canary)#%d (0) {

Zend/zend_lazy_objects.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,18 @@ static int zlo_hash_remove_dyn_props_func(zval *pDest)
208208
return ZEND_HASH_APPLY_REMOVE;
209209
}
210210

211+
static bool zlo_is_iterating(zend_object *object)
212+
{
213+
if (object->properties && HT_ITERATORS_COUNT(object->properties)) {
214+
return true;
215+
}
216+
if (zend_object_is_lazy_proxy(object)
217+
&& zend_lazy_object_initialized(object)) {
218+
return zlo_is_iterating(zend_lazy_object_get_instance(object));
219+
}
220+
return false;
221+
}
222+
211223
/* Make object 'obj' lazy. If 'obj' is NULL, create a lazy instance of
212224
* class 'reflection_ce' */
213225
ZEND_API zend_object *zend_object_make_lazy(zend_object *obj,
@@ -260,6 +272,10 @@ ZEND_API zend_object *zend_object_make_lazy(zend_object *obj,
260272
}
261273
}
262274
} else {
275+
if (zlo_is_iterating(obj)) {
276+
zend_throw_error(NULL, "Can not reset an object during property iteration");
277+
return NULL;
278+
}
263279
if (zend_object_is_lazy(obj)) {
264280
ZEND_ASSERT(zend_object_is_lazy_proxy(obj) && zend_lazy_object_initialized(obj));
265281
OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY);
@@ -544,6 +560,9 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
544560
ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
545561
zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
546562
ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_INITIALIZED);
563+
if (zend_object_is_lazy(info->u.instance)) {
564+
return zend_lazy_object_init(info->u.instance);
565+
}
547566
return info->u.instance;
548567
}
549568

0 commit comments

Comments
 (0)