Skip to content

Commit 60f87f2

Browse files
authored
Fix various hooked object iterator issues (phpGH-15394)
Fixes phpGH-15187
1 parent 36b1977 commit 60f87f2

File tree

7 files changed

+165
-64
lines changed

7 files changed

+165
-64
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PHP NEWS
99
visibility are ignored). (ilutov)
1010
. Fixed bug GH-15419 (Missing readonly+hook incompatibility check for readonly
1111
classes). (ilutov)
12+
. Fixed bug GH-15187 (Various hooked object iterator issues). (ilutov)
1213

1314
15 Aug 2024, PHP 8.4.0beta3
1415

Zend/tests/property_hooks/foreach.phpt

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ class ByRef {
1717
$this->_virtualByRef = $value;
1818
}
1919
}
20+
public $virtualSetOnly {
21+
set {
22+
echo __METHOD__, "\n";
23+
}
24+
}
2025
public function __construct() {
2126
$this->dynamic = 'dynamic';
2227
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
GH-15187: Crash in hooked iterators on properties array without dynamic properties
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public $prop { set => $value; }
8+
}
9+
10+
$test = new Test();
11+
var_dump($test);
12+
foreach ($test as $key => $value) {
13+
var_dump($key, $value);
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
object(Test)#%d (1) {
19+
["prop"]=>
20+
NULL
21+
}
22+
string(4) "prop"
23+
NULL
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
GH-15187: Crash in hooked iterators on properties array without dynamic properties
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public int $prop { set => $value; }
8+
}
9+
10+
$test = new Test();
11+
var_dump($test);
12+
foreach ($test as $key => $value) {
13+
var_dump($key, $value);
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
object(Test)#%d (0) {
19+
["prop"]=>
20+
uninitialized(int)
21+
}
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
GH-15187: Hooked iterator typed property ref tracking
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public $a { set {} }
8+
public int $b;
9+
public int $c = 1;
10+
public $d = 2;
11+
}
12+
13+
$c = new C();
14+
15+
foreach ($c as $k => &$v) {
16+
var_dump($v);
17+
if ($k === 'c') {
18+
try {
19+
$v = 'foo';
20+
} catch (Error $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
}
24+
if ($k === 'd') {
25+
$v = 'foo';
26+
}
27+
}
28+
29+
var_dump($c);
30+
31+
?>
32+
--EXPECTF--
33+
int(1)
34+
Cannot assign string to reference held by property C::$c of type int
35+
int(2)
36+
object(C)#%d (2) {
37+
["b"]=>
38+
uninitialized(int)
39+
["c"]=>
40+
int(1)
41+
["d"]=>
42+
&string(3) "foo"
43+
}

Zend/zend_compile.h

+2
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ typedef struct _zend_property_info {
438438
((uint32_t)(XtOffsetOf(zend_object, properties_table) + sizeof(zval) * (num)))
439439
#define OBJ_PROP_TO_NUM(offset) \
440440
((offset - OBJ_PROP_TO_OFFSET(0)) / sizeof(zval))
441+
#define OBJ_PROP_PTR_TO_NUM(obj, prop) \
442+
(((char*)prop - (char*)obj->properties_table) / sizeof(zval))
441443

442444
typedef struct _zend_class_constant {
443445
zval value; /* flags are stored in u2 */

Zend/zend_property_hooks.c

+70-64
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ typedef struct {
2525
bool by_ref;
2626
bool declared_props_done;
2727
zval declared_props;
28+
bool dynamic_props_done;
2829
uint32_t dynamic_prop_it;
2930
zval current_key;
3031
zval current_data;
3132
} zend_hooked_object_iterator;
3233

3334
static zend_result zho_it_valid(zend_object_iterator *iter);
35+
static void zho_it_move_forward(zend_object_iterator *iter);
3436

3537
// FIXME: This should probably be stored on zend_class_entry somewhere (e.g. through num_virtual_props).
3638
static uint32_t zho_num_backed_props(zend_object *zobj)
@@ -87,13 +89,16 @@ ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj)
8789

8890
static bool zho_dynamic_it_init(zend_hooked_object_iterator *hooked_iter)
8991
{
90-
zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data);
91-
if (!zobj->properties) {
92-
return false;
93-
}
9492
if (hooked_iter->dynamic_prop_it != (uint32_t) -1) {
9593
return true;
9694
}
95+
96+
zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data);
97+
if (!zobj->properties || zho_num_backed_props(zobj) == zobj->properties->nNumUsed) {
98+
hooked_iter->dynamic_props_done = true;
99+
return false;
100+
}
101+
97102
hooked_iter->dynamic_prop_it = zend_hash_iterator_add(zobj->properties, zho_num_backed_props(zobj));
98103
return true;
99104
}
@@ -102,39 +107,45 @@ static void zho_it_get_current_key(zend_object_iterator *iter, zval *key);
102107

103108
static void zho_declared_it_fetch_current(zend_object_iterator *iter)
104109
{
105-
ZEND_ASSERT(zho_it_valid(iter) == SUCCESS);
106-
107110
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
108-
zval_ptr_dtor(&hooked_iter->current_data);
109-
if (EG(exception)) {
110-
return;
111-
}
112-
ZVAL_UNDEF(&hooked_iter->current_data);
113-
zval_ptr_dtor(&hooked_iter->current_key);
114-
ZVAL_UNDEF(&hooked_iter->current_key);
115111
zend_object *zobj = Z_OBJ_P(&iter->data);
116112
zend_array *properties = Z_ARR(hooked_iter->declared_props);
117113

118-
zval_ptr_dtor_nogc(&hooked_iter->current_key);
119-
zend_hash_get_current_key_zval(properties, &hooked_iter->current_key);
120-
121114
zval *property = zend_hash_get_current_data(properties);
122115
if (Z_TYPE_P(property) == IS_PTR) {
123116
zend_property_info *prop_info = Z_PTR_P(property);
124117
zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET];
118+
if (!get && (prop_info->flags & ZEND_ACC_VIRTUAL)) {
119+
return;
120+
}
125121
if (hooked_iter->by_ref
126122
&& (get == NULL
127123
|| !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) {
128124
zend_throw_error(NULL, "Cannot create reference to property %s::$%s",
129125
ZSTR_VAL(zobj->ce->name), zend_get_unmangled_property_name(prop_info->name));
130126
return;
131127
}
132-
zend_read_property_ex(prop_info->ce, zobj, prop_info->name, /* silent */ true, &hooked_iter->current_data);
128+
zval *value = zend_read_property_ex(prop_info->ce, zobj, prop_info->name, /* silent */ true, &hooked_iter->current_data);
129+
if (value == &EG(uninitialized_zval)) {
130+
return;
131+
} else if (value != &hooked_iter->current_data) {
132+
ZVAL_COPY(&hooked_iter->current_data, value);
133+
}
133134
} else {
134135
ZVAL_DEINDIRECT(property);
135-
if (hooked_iter->by_ref && Z_TYPE_P(property) != IS_REFERENCE) {
136-
ZEND_ASSERT(Z_TYPE_P(property) != IS_UNDEF);
136+
if (Z_TYPE_P(property) == IS_UNDEF) {
137+
return;
138+
}
139+
if (!hooked_iter->by_ref) {
140+
ZVAL_DEREF(property);
141+
} else if (Z_TYPE_P(property) != IS_REFERENCE) {
137142
ZVAL_MAKE_REF(property);
143+
144+
zend_class_entry *ce = zobj->ce;
145+
zend_property_info *prop_info = ce->properties_info_table[OBJ_PROP_PTR_TO_NUM(zobj, property)];
146+
if (ZEND_TYPE_IS_SET(prop_info->type)) {
147+
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(property), prop_info);
148+
}
138149
}
139150
ZVAL_COPY(&hooked_iter->current_data, property);
140151
}
@@ -154,10 +165,8 @@ static void zho_dynamic_it_fetch_current(zend_object_iterator *iter)
154165
ZEND_ASSERT(Z_TYPE(bucket->val) != IS_UNDEF);
155166
ZVAL_MAKE_REF(&bucket->val);
156167
}
157-
zval_ptr_dtor(&hooked_iter->current_data);
158168
ZVAL_COPY(&hooked_iter->current_data, &bucket->val);
159169

160-
zval_ptr_dtor_nogc(&hooked_iter->current_key);
161170
if (bucket->key) {
162171
ZVAL_STR_COPY(&hooked_iter->current_key, bucket->key);
163172
} else {
@@ -168,13 +177,22 @@ static void zho_dynamic_it_fetch_current(zend_object_iterator *iter)
168177
static void zho_it_fetch_current(zend_object_iterator *iter)
169178
{
170179
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
180+
if (Z_TYPE(hooked_iter->current_data) != IS_UNDEF) {
181+
return;
182+
}
171183

172-
if (!hooked_iter->declared_props_done) {
173-
zho_declared_it_fetch_current(iter);
174-
} else if (zho_dynamic_it_init(hooked_iter)) {
175-
zho_dynamic_it_fetch_current(iter);
176-
} else {
177-
ZEND_UNREACHABLE();
184+
while (true) {
185+
if (!hooked_iter->declared_props_done) {
186+
zho_declared_it_fetch_current(iter);
187+
} else if (!hooked_iter->dynamic_props_done && zho_dynamic_it_init(hooked_iter)) {
188+
zho_dynamic_it_fetch_current(iter);
189+
} else {
190+
break;
191+
}
192+
if (Z_TYPE(hooked_iter->current_data) != IS_UNDEF || EG(exception)) {
193+
break;
194+
}
195+
zho_it_move_forward(iter);
178196
}
179197
}
180198

@@ -185,7 +203,6 @@ static void zho_it_dtor(zend_object_iterator *iter)
185203
zval_ptr_dtor(&hooked_iter->declared_props);
186204
zval_ptr_dtor_nogc(&hooked_iter->current_key);
187205
zval_ptr_dtor(&hooked_iter->current_data);
188-
zval_ptr_dtor(&hooked_iter->current_key);
189206
if (hooked_iter->dynamic_prop_it != (uint32_t) -1) {
190207
zend_hash_iterator_del(hooked_iter->dynamic_prop_it);
191208
}
@@ -194,78 +211,66 @@ static void zho_it_dtor(zend_object_iterator *iter)
194211
static zend_result zho_it_valid(zend_object_iterator *iter)
195212
{
196213
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
197-
if (!hooked_iter->declared_props_done) {
198-
if (zend_hash_has_more_elements(Z_ARR(hooked_iter->declared_props)) == SUCCESS) {
199-
return SUCCESS;
200-
}
201-
}
202-
203-
if (zho_dynamic_it_init(hooked_iter)) {
204-
zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data);
205-
HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, zobj->properties);
206-
return pos < zobj->properties->nNumUsed ? SUCCESS : FAILURE;
207-
}
208-
209-
return FAILURE;
214+
zho_it_fetch_current(iter);
215+
return Z_TYPE(hooked_iter->current_data) != IS_UNDEF ? SUCCESS : FAILURE;
210216
}
211217

212218
static zval *zho_it_get_current_data(zend_object_iterator *iter)
213219
{
214220
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
221+
zho_it_fetch_current(iter);
215222
return &hooked_iter->current_data;
216223
}
217224

218225
static void zho_it_get_current_key(zend_object_iterator *iter, zval *key)
219226
{
220227
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
228+
zho_it_fetch_current(iter);
221229
ZVAL_COPY(key, &hooked_iter->current_key);
222230
}
223231

224232
static void zho_it_move_forward(zend_object_iterator *iter)
225233
{
226234
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
227-
zend_array *properties = Z_ARR(hooked_iter->declared_props);
235+
236+
zval_ptr_dtor(&hooked_iter->current_data);
237+
ZVAL_UNDEF(&hooked_iter->current_data);
238+
zval_ptr_dtor_nogc(&hooked_iter->current_key);
239+
ZVAL_UNDEF(&hooked_iter->current_key);
240+
228241
if (!hooked_iter->declared_props_done) {
242+
zend_array *properties = Z_ARR(hooked_iter->declared_props);
229243
zend_hash_move_forward(properties);
230-
if (zend_hash_has_more_elements(properties) == SUCCESS) {
231-
zho_declared_it_fetch_current(iter);
232-
} else {
244+
if (zend_hash_has_more_elements(properties) != SUCCESS) {
233245
hooked_iter->declared_props_done = true;
234-
if (zho_dynamic_it_init(hooked_iter)) {
235-
zho_dynamic_it_fetch_current(iter);
236-
}
237246
}
238-
} else if (zho_dynamic_it_init(hooked_iter)) {
247+
} else if (!hooked_iter->dynamic_props_done && zho_dynamic_it_init(hooked_iter)) {
239248
zend_array *properties = Z_OBJ(iter->data)->properties;
240249
HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties);
241-
Bucket *bucket = properties->arData + pos;
242-
while (true) {
243-
pos++;
244-
bucket++;
245-
if (pos >= properties->nNumUsed) {
246-
break;
247-
}
248-
if (Z_TYPE_INFO_P(&bucket->val) != IS_UNDEF) {
249-
zho_dynamic_it_fetch_current(iter);
250-
break;
251-
}
252-
}
250+
pos++;
253251
EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = pos;
252+
if (pos >= properties->nNumUsed) {
253+
hooked_iter->dynamic_props_done = true;
254+
}
254255
}
255256
}
256257

257258
static void zho_it_rewind(zend_object_iterator *iter)
258259
{
259260
zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
261+
262+
zval_ptr_dtor(&hooked_iter->current_data);
263+
ZVAL_UNDEF(&hooked_iter->current_data);
264+
zval_ptr_dtor_nogc(&hooked_iter->current_key);
265+
ZVAL_UNDEF(&hooked_iter->current_key);
266+
260267
hooked_iter->declared_props_done = false;
261268
zend_array *properties = Z_ARR(hooked_iter->declared_props);
262269
zend_hash_internal_pointer_reset(properties);
270+
hooked_iter->dynamic_props_done = false;
263271
if (hooked_iter->dynamic_prop_it != (uint32_t) -1) {
264272
EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = zho_num_backed_props(Z_OBJ(iter->data));
265273
}
266-
if (zho_it_valid(iter) == SUCCESS) {
267-
zho_it_fetch_current(iter);
268-
}
269274
}
270275

271276
static HashTable *zho_it_get_gc(zend_object_iterator *iter, zval **table, int *n)
@@ -301,6 +306,7 @@ ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry
301306
iterator->declared_props_done = false;
302307
zend_array *properties = zho_build_properties_ex(Z_OBJ_P(object), true, false);
303308
ZVAL_ARR(&iterator->declared_props, properties);
309+
iterator->dynamic_props_done = false;
304310
iterator->dynamic_prop_it = (uint32_t) -1;
305311
ZVAL_UNDEF(&iterator->current_key);
306312
ZVAL_UNDEF(&iterator->current_data);

0 commit comments

Comments
 (0)