Coverage Report

Created: 2026-06-13 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_property_hooks.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine                                                          |
4
   +----------------------------------------------------------------------+
5
   | Copyright © Zend Technologies Ltd., a subsidiary company of          |
6
   |     Perforce Software, Inc., and Contributors.                       |
7
   +----------------------------------------------------------------------+
8
   | This source file is subject to the Modified BSD License that is      |
9
   | bundled with this package in the file LICENSE, and is available      |
10
   | through the World Wide Web at <https://www.php.net/license/>.        |
11
   |                                                                      |
12
   | SPDX-License-Identifier: BSD-3-Clause                                |
13
   +----------------------------------------------------------------------+
14
   | Authors: Ilija Tovilo <ilutov@php.net>                               |
15
   +----------------------------------------------------------------------+
16
*/
17
18
#include "zend.h"
19
#include "zend_API.h"
20
#include "zend_hash.h"
21
#include "zend_lazy_objects.h"
22
#include "zend_property_hooks.h"
23
24
typedef struct {
25
  zend_object_iterator it;
26
  bool by_ref;
27
  bool declared_props_done;
28
  zval declared_props;
29
  bool dynamic_props_done;
30
  uint32_t dynamic_prop_offset;
31
  uint32_t dynamic_prop_it;
32
  zval current_key;
33
  zval current_data;
34
} zend_hooked_object_iterator;
35
36
static zend_result zho_it_valid(zend_object_iterator *iter);
37
static void zho_it_move_forward(zend_object_iterator *iter);
38
39
static uint32_t zho_find_dynamic_prop_offset(zend_array *properties)
40
292
{
41
292
  uint32_t offset = 0;
42
292
  zval *value;
43
44
2.11k
  ZEND_HASH_MAP_FOREACH_VAL(properties, value) {
45
2.11k
    if (Z_TYPE_P(value) != IS_INDIRECT) {
46
154
      break;
47
154
    }
48
544
    offset++;
49
544
  } ZEND_HASH_FOREACH_END();
50
51
292
  return offset;
52
292
}
53
54
static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, bool force_ptr, bool include_dynamic_props)
55
670
{
56
670
  zend_class_entry *ce = zobj->ce;
57
670
  zend_array *properties = zend_new_array(include_dynamic_props && zobj->properties
58
670
    ? zend_hash_num_elements(zobj->properties)
59
670
    : ce->default_properties_count);
60
670
  zend_hash_real_init_mixed(properties);
61
62
  /* Build list of parents */
63
670
  int32_t parent_count = 0;
64
1.61k
  for (zend_class_entry *pce = ce; pce; pce = pce->parent) {
65
944
    parent_count++;
66
944
  }
67
670
  zend_class_entry **parents = emalloc(sizeof(zend_class_entry*) * parent_count);
68
670
  int32_t i = 0;
69
1.61k
  for (zend_class_entry *pce = ce; pce; pce = pce->parent) {
70
944
    parents[i++] = pce;
71
944
  }
72
73
  /* Iterate parents top to bottom */
74
670
  i--;
75
1.61k
  for (; i >= 0; i--) {
76
944
    zend_class_entry *pce = parents[i];
77
78
944
    zend_property_info *prop_info;
79
9.56k
    ZEND_HASH_MAP_FOREACH_PTR(&pce->properties_info, prop_info) {
80
9.56k
      if (prop_info->flags & ZEND_ACC_STATIC) {
81
0
        continue;
82
0
      }
83
3.83k
      zend_string *property_name = prop_info->name;
84
      /* When promoting properties from protected to public, use the unmangled name to preserve order. */
85
3.83k
      if (prop_info->flags & ZEND_ACC_PROTECTED) {
86
33
        const char *tmp = zend_get_unmangled_property_name(property_name);
87
33
        zend_string *unmangled_name = zend_string_init(tmp, strlen(tmp), false);
88
33
        zend_property_info *child_prop_info = zend_hash_find_ptr(&ce->properties_info, unmangled_name);
89
33
        if (child_prop_info && (child_prop_info->flags & ZEND_ACC_PUBLIC)) {
90
11
          property_name = unmangled_name;
91
22
        } else {
92
22
          zend_string_release(unmangled_name);
93
22
        }
94
33
      }
95
3.83k
      if (check_access && zend_check_property_access(zobj, property_name, false) == FAILURE) {
96
268
        goto skip_property;
97
268
      }
98
3.56k
      if (prop_info->hooks || force_ptr) {
99
3.20k
        zend_hash_update_ptr(properties, property_name, prop_info);
100
3.20k
      } else {
101
367
        if (UNEXPECTED(Z_TYPE_P(OBJ_PROP(zobj, prop_info->offset)) == IS_UNDEF)) {
102
212
          HT_FLAGS(properties) |= HASH_FLAG_HAS_EMPTY_IND;
103
212
        }
104
367
        zval *tmp = zend_hash_lookup(properties, property_name);
105
367
        ZVAL_INDIRECT(tmp, OBJ_PROP(zobj, prop_info->offset));
106
367
      }
107
3.83k
skip_property:
108
3.83k
      if (property_name != prop_info->name) {
109
11
        zend_string_release(property_name);
110
11
      }
111
3.83k
    } ZEND_HASH_FOREACH_END();
112
944
  }
113
114
670
  efree(parents);
115
116
670
  if (include_dynamic_props && zobj->properties) {
117
349
    zend_string *prop_name;
118
349
    zval *prop_value;
119
3.07k
    ZEND_HASH_FOREACH_STR_KEY_VAL(zobj->properties, prop_name, prop_value) {
120
3.07k
      if (Z_TYPE_P(prop_value) == IS_INDIRECT) {
121
1.31k
        continue;
122
1.31k
      }
123
46
      zval *tmp = zend_hash_add_new(properties, prop_name, prop_value);
124
46
      Z_TRY_ADDREF_P(tmp);
125
46
    } ZEND_HASH_FOREACH_END();
126
349
  }
127
128
670
  return properties;
129
670
}
130
131
ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj)
132
413
{
133
413
  if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
134
54
    zobj = zend_lazy_object_init(zobj);
135
54
    if (UNEXPECTED(!zobj)) {
136
35
      return (zend_array*) &zend_empty_array;
137
35
    }
138
54
  }
139
140
378
  return zho_build_properties_ex(zobj, false, false, true);
141
413
}
142
143
static void zho_dynamic_it_init(zend_hooked_object_iterator *hooked_iter)
144
292
{
145
292
  zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data);
146
292
  zend_array *properties = zobj->handlers->get_properties(zobj);
147
292
  hooked_iter->dynamic_props_done = false;
148
292
  hooked_iter->dynamic_prop_offset = zho_find_dynamic_prop_offset(properties);
149
292
  hooked_iter->dynamic_prop_it = zend_hash_iterator_add(properties, hooked_iter->dynamic_prop_offset);
150
292
}
151
152
static void zho_it_get_current_key(zend_object_iterator *iter, zval *key);
153
154
static void zho_declared_it_fetch_current(zend_object_iterator *iter)
155
639
{
156
639
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
157
639
  zend_object *zobj = Z_OBJ_P(&iter->data);
158
639
  zend_array *properties = Z_ARR(hooked_iter->declared_props);
159
160
639
  zend_property_info *prop_info = Z_PTR_P(zend_hash_get_current_data(properties));
161
639
  if (prop_info->hooks) {
162
381
    zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET];
163
381
    if (!get && (prop_info->flags & ZEND_ACC_VIRTUAL)) {
164
53
      return;
165
53
    }
166
328
    if (hooked_iter->by_ref
167
119
     && (get == NULL
168
119
      || !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) {
169
5
      zend_throw_error(NULL, "Cannot create reference to property %s::$%s",
170
5
        ZSTR_VAL(zobj->ce->name), zend_get_unmangled_property_name(prop_info->name));
171
5
      return;
172
5
    }
173
323
    zend_string *unmangled_name = prop_info->name;
174
323
    if (ZSTR_VAL(unmangled_name)[0] == '\0') {
175
22
      const char *tmp = zend_get_unmangled_property_name(unmangled_name);
176
22
      unmangled_name = zend_string_init(tmp, strlen(tmp), false);
177
22
    }
178
323
    zval *value = zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &hooked_iter->current_data);
179
323
    if (unmangled_name != prop_info->name) {
180
22
      zend_string_release(unmangled_name);
181
22
    }
182
323
    if (value == &EG(uninitialized_zval)) {
183
15
      return;
184
308
    } else if (value != &hooked_iter->current_data) {
185
6
      ZVAL_COPY(&hooked_iter->current_data, value);
186
6
    }
187
323
  } else {
188
258
    zval *property = OBJ_PROP(zobj, prop_info->offset);
189
258
    ZVAL_DEINDIRECT(property);
190
258
    if (Z_TYPE_P(property) == IS_UNDEF) {
191
18
      return;
192
18
    }
193
240
    if (!hooked_iter->by_ref) {
194
120
      ZVAL_DEREF(property);
195
120
    } else if (Z_TYPE_P(property) != IS_REFERENCE) {
196
120
      if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
197
9
        zend_throw_error(NULL,
198
9
          "Cannot acquire reference to readonly property %s::$%s",
199
9
          ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name));
200
9
        return;
201
9
      }
202
111
      ZVAL_MAKE_REF(property);
203
111
      if (ZEND_TYPE_IS_SET(prop_info->type)) {
204
95
        ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(property), prop_info);
205
95
      }
206
111
    }
207
231
    ZVAL_COPY(&hooked_iter->current_data, property);
208
231
  }
209
210
539
  if (ZSTR_VAL(prop_info->name)[0] == '\0') {
211
33
    const char *tmp = zend_get_unmangled_property_name(prop_info->name);
212
33
    ZVAL_STR(&hooked_iter->current_key, zend_string_init(tmp, strlen(tmp), false));
213
506
  } else {
214
506
    ZVAL_STR_COPY(&hooked_iter->current_key, prop_info->name);
215
506
  }
216
539
}
217
218
static void zho_dynamic_it_fetch_current(zend_object_iterator *iter)
219
581
{
220
581
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
221
581
  zend_array *properties = Z_OBJ(iter->data)->properties;
222
581
  HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties);
223
224
581
  if (pos >= properties->nNumUsed) {
225
267
    hooked_iter->dynamic_props_done = true;
226
267
    return;
227
267
  }
228
229
314
  Bucket *bucket = properties->arData + pos;
230
231
314
  if (UNEXPECTED(Z_TYPE(bucket->val) == IS_UNDEF)) {
232
133
    return;
233
133
  }
234
235
181
  zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data);
236
181
  if (bucket->key && zend_check_property_access(zobj, bucket->key, true) != SUCCESS) {
237
11
    return;
238
11
  }
239
240
170
  if (hooked_iter->by_ref && Z_TYPE(bucket->val) != IS_REFERENCE) {
241
76
    ZVAL_MAKE_REF(&bucket->val);
242
76
  }
243
170
  ZVAL_COPY(&hooked_iter->current_data, &bucket->val);
244
245
170
  if (bucket->key) {
246
170
    ZVAL_STR_COPY(&hooked_iter->current_key, bucket->key);
247
170
  } else {
248
0
    ZVAL_LONG(&hooked_iter->current_key, bucket->h);
249
0
  }
250
170
}
251
252
static void zho_it_fetch_current(zend_object_iterator *iter)
253
2.38k
{
254
2.38k
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
255
2.38k
  if (Z_TYPE(hooked_iter->current_data) != IS_UNDEF) {
256
1.38k
    return;
257
1.38k
  }
258
259
1.48k
  while (true) {
260
1.48k
    if (!hooked_iter->declared_props_done) {
261
639
      zho_declared_it_fetch_current(iter);
262
848
    } else if (!hooked_iter->dynamic_props_done) {
263
581
      zho_dynamic_it_fetch_current(iter);
264
581
    } else {
265
267
      break;
266
267
    }
267
1.22k
    if (Z_TYPE(hooked_iter->current_data) != IS_UNDEF || EG(exception)) {
268
729
      break;
269
729
    }
270
491
    zho_it_move_forward(iter);
271
491
  }
272
996
}
273
274
static void zho_it_dtor(zend_object_iterator *iter)
275
292
{
276
292
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
277
292
  zval_ptr_dtor(&iter->data);
278
292
  zval_ptr_dtor(&hooked_iter->declared_props);
279
292
  zval_ptr_dtor_nogc(&hooked_iter->current_key);
280
292
  zval_ptr_dtor(&hooked_iter->current_data);
281
292
  zend_hash_iterator_del(hooked_iter->dynamic_prop_it);
282
292
}
283
284
static zend_result zho_it_valid(zend_object_iterator *iter)
285
996
{
286
996
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
287
996
  zho_it_fetch_current(iter);
288
996
  return Z_TYPE(hooked_iter->current_data) != IS_UNDEF ? SUCCESS : FAILURE;
289
996
}
290
291
static zval *zho_it_get_current_data(zend_object_iterator *iter)
292
709
{
293
709
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
294
709
  zho_it_fetch_current(iter);
295
709
  return &hooked_iter->current_data;
296
709
}
297
298
static void zho_it_get_current_key(zend_object_iterator *iter, zval *key)
299
679
{
300
679
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
301
679
  zho_it_fetch_current(iter);
302
679
  ZVAL_COPY(key, &hooked_iter->current_key);
303
679
}
304
305
static void zho_it_move_forward(zend_object_iterator *iter)
306
1.19k
{
307
1.19k
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
308
309
1.19k
  zval_ptr_dtor(&hooked_iter->current_data);
310
1.19k
  ZVAL_UNDEF(&hooked_iter->current_data);
311
1.19k
  zval_ptr_dtor_nogc(&hooked_iter->current_key);
312
1.19k
  ZVAL_UNDEF(&hooked_iter->current_key);
313
314
1.19k
  if (!hooked_iter->declared_props_done) {
315
614
    zend_array *properties = Z_ARR(hooked_iter->declared_props);
316
614
    zend_hash_move_forward(properties);
317
614
    if (zend_hash_has_more_elements(properties) != SUCCESS) {
318
267
      hooked_iter->declared_props_done = true;
319
267
    }
320
614
  } else if (!hooked_iter->dynamic_props_done) {
321
314
    zend_array *properties = Z_OBJ(iter->data)->properties;
322
314
    HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties);
323
314
    pos++;
324
314
    EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = pos;
325
314
  }
326
1.19k
}
327
328
static void zho_it_rewind(zend_object_iterator *iter)
329
292
{
330
292
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
331
332
292
  zval_ptr_dtor(&hooked_iter->current_data);
333
292
  ZVAL_UNDEF(&hooked_iter->current_data);
334
292
  zval_ptr_dtor_nogc(&hooked_iter->current_key);
335
292
  ZVAL_UNDEF(&hooked_iter->current_key);
336
337
292
  zend_array *properties = Z_ARR(hooked_iter->declared_props);
338
292
  zend_hash_internal_pointer_reset(properties);
339
292
  hooked_iter->declared_props_done = !zend_hash_num_elements(properties);
340
292
  hooked_iter->dynamic_props_done = false;
341
292
  EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = hooked_iter->dynamic_prop_offset;
342
292
}
343
344
static HashTable *zho_it_get_gc(zend_object_iterator *iter, zval **table, int *n)
345
0
{
346
0
  zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter;
347
0
  zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
348
0
  zend_get_gc_buffer_add_zval(gc_buffer, &iter->data);
349
0
  zend_get_gc_buffer_add_zval(gc_buffer, &hooked_iter->declared_props);
350
0
  zend_get_gc_buffer_add_zval(gc_buffer, &hooked_iter->current_data);
351
0
  zend_get_gc_buffer_use(gc_buffer, table, n);
352
0
  return NULL;
353
0
}
354
355
static const zend_object_iterator_funcs zend_hooked_object_it_funcs = {
356
  zho_it_dtor,
357
  zho_it_valid,
358
  zho_it_get_current_data,
359
  zho_it_get_current_key,
360
  zho_it_move_forward,
361
  zho_it_rewind,
362
  NULL,
363
  zho_it_get_gc,
364
};
365
366
ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry *ce, zval *object, int by_ref)
367
303
{
368
303
  zend_object *zobj = Z_OBJ_P(object);
369
303
  if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
370
147
    zobj = zend_lazy_object_init(zobj);
371
147
    if (UNEXPECTED(!zobj)) {
372
11
      return NULL;
373
11
    }
374
147
  }
375
376
292
  zend_hooked_object_iterator *iterator = emalloc(sizeof(zend_hooked_object_iterator));
377
292
  zend_iterator_init(&iterator->it);
378
379
292
  ZVAL_OBJ_COPY(&iterator->it.data, zobj);
380
292
  iterator->it.funcs = &zend_hooked_object_it_funcs;
381
292
  iterator->by_ref = by_ref;
382
292
  zend_array *properties = zho_build_properties_ex(zobj, true, true, false);
383
292
  ZVAL_ARR(&iterator->declared_props, properties);
384
292
  iterator->declared_props_done = !zend_hash_num_elements(properties);
385
292
  zho_dynamic_it_init(iterator);
386
292
  ZVAL_UNDEF(&iterator->current_key);
387
292
  ZVAL_UNDEF(&iterator->current_data);
388
389
292
  return &iterator->it;
390
303
}