Coverage Report

Created: 2025-06-13 06:43

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