/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 | 132 | { |
41 | 132 | uint32_t offset = 0; |
42 | 132 | zval *value; |
43 | | |
44 | 981 | ZEND_HASH_MAP_FOREACH_VAL(properties, value) { |
45 | 981 | if (Z_TYPE_P(value) != IS_INDIRECT) { |
46 | 75 | break; |
47 | 75 | } |
48 | 252 | offset++; |
49 | 252 | } ZEND_HASH_FOREACH_END(); |
50 | | |
51 | 132 | return offset; |
52 | 132 | } |
53 | | |
54 | | static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, bool force_ptr, bool include_dynamic_props) |
55 | 360 | { |
56 | 360 | zend_class_entry *ce = zobj->ce; |
57 | 360 | zend_array *properties = zend_new_array(include_dynamic_props && zobj->properties |
58 | 360 | ? zend_hash_num_elements(zobj->properties) |
59 | 360 | : ce->default_properties_count); |
60 | 360 | zend_hash_real_init_mixed(properties); |
61 | | |
62 | | /* Build list of parents */ |
63 | 360 | int32_t parent_count = 0; |
64 | 903 | for (zend_class_entry *pce = ce; pce; pce = pce->parent) { |
65 | 543 | parent_count++; |
66 | 543 | } |
67 | 360 | zend_class_entry **parents = emalloc(sizeof(zend_class_entry*) * parent_count); |
68 | 360 | int32_t i = 0; |
69 | 903 | for (zend_class_entry *pce = ce; pce; pce = pce->parent) { |
70 | 543 | parents[i++] = pce; |
71 | 543 | } |
72 | | |
73 | | /* Iterate parents top to bottom */ |
74 | 360 | i--; |
75 | 903 | for (; i >= 0; i--) { |
76 | 543 | zend_class_entry *pce = parents[i]; |
77 | | |
78 | 543 | zend_property_info *prop_info; |
79 | 5.82k | ZEND_HASH_MAP_FOREACH_PTR(&pce->properties_info, prop_info) { |
80 | 5.82k | if (prop_info->flags & ZEND_ACC_STATIC) { |
81 | 0 | continue; |
82 | 0 | } |
83 | 2.36k | zend_string *property_name = prop_info->name; |
84 | | /* When promoting properties from protected to public, use the unmangled name to preserve order. */ |
85 | 2.36k | if (prop_info->flags & ZEND_ACC_PROTECTED) { |
86 | 27 | const char *tmp = zend_get_unmangled_property_name(property_name); |
87 | 27 | zend_string *unmangled_name = zend_string_init(tmp, strlen(tmp), false); |
88 | 27 | zend_property_info *child_prop_info = zend_hash_find_ptr(&ce->properties_info, unmangled_name); |
89 | 27 | if (child_prop_info && (child_prop_info->flags & ZEND_ACC_PUBLIC)) { |
90 | 9 | property_name = unmangled_name; |
91 | 18 | } else { |
92 | 18 | zend_string_release(unmangled_name); |
93 | 18 | } |
94 | 27 | } |
95 | 2.36k | if (check_access && zend_check_property_access(zobj, property_name, false) == FAILURE) { |
96 | 123 | goto skip_property; |
97 | 123 | } |
98 | 2.24k | if (prop_info->hooks || force_ptr) { |
99 | 2.01k | zend_hash_update_ptr(properties, property_name, prop_info); |
100 | 2.01k | } else { |
101 | 228 | if (UNEXPECTED(Z_TYPE_P(OBJ_PROP(zobj, prop_info->offset)) == IS_UNDEF)) { |
102 | 147 | HT_FLAGS(properties) |= HASH_FLAG_HAS_EMPTY_IND; |
103 | 147 | } |
104 | 228 | zval *tmp = zend_hash_lookup(properties, property_name); |
105 | 228 | ZVAL_INDIRECT(tmp, OBJ_PROP(zobj, prop_info->offset)); |
106 | 228 | } |
107 | 2.36k | skip_property: |
108 | 2.36k | if (property_name != prop_info->name) { |
109 | 9 | zend_string_release(property_name); |
110 | 9 | } |
111 | 2.36k | } ZEND_HASH_FOREACH_END(); |
112 | 543 | } |
113 | | |
114 | 360 | efree(parents); |
115 | | |
116 | 360 | if (include_dynamic_props && zobj->properties) { |
117 | 219 | zend_string *prop_name; |
118 | 219 | zval *prop_value; |
119 | 1.97k | ZEND_HASH_FOREACH_STR_KEY_VAL(zobj->properties, prop_name, prop_value) { |
120 | 1.97k | if (Z_TYPE_P(prop_value) == IS_INDIRECT) { |
121 | 858 | continue; |
122 | 858 | } |
123 | 21 | zval *tmp = zend_hash_add_new(properties, prop_name, prop_value); |
124 | 21 | Z_TRY_ADDREF_P(tmp); |
125 | 21 | } ZEND_HASH_FOREACH_END(); |
126 | 219 | } |
127 | | |
128 | 360 | return properties; |
129 | 360 | } |
130 | | |
131 | | ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj) |
132 | 249 | { |
133 | 249 | if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { |
134 | 30 | zobj = zend_lazy_object_init(zobj); |
135 | 30 | if (UNEXPECTED(!zobj)) { |
136 | 21 | return (zend_array*) &zend_empty_array; |
137 | 21 | } |
138 | 30 | } |
139 | | |
140 | 228 | return zho_build_properties_ex(zobj, false, false, true); |
141 | 249 | } |
142 | | |
143 | | static void zho_dynamic_it_init(zend_hooked_object_iterator *hooked_iter) |
144 | 132 | { |
145 | 132 | zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data); |
146 | 132 | zend_array *properties = zobj->handlers->get_properties(zobj); |
147 | 132 | hooked_iter->dynamic_props_done = false; |
148 | 132 | hooked_iter->dynamic_prop_offset = zho_find_dynamic_prop_offset(properties); |
149 | 132 | hooked_iter->dynamic_prop_it = zend_hash_iterator_add(properties, hooked_iter->dynamic_prop_offset); |
150 | 132 | } |
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 | 321 | { |
156 | 321 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
157 | 321 | zend_object *zobj = Z_OBJ_P(&iter->data); |
158 | 321 | zend_array *properties = Z_ARR(hooked_iter->declared_props); |
159 | | |
160 | 321 | zend_property_info *prop_info = Z_PTR_P(zend_hash_get_current_data(properties)); |
161 | 321 | if (prop_info->hooks) { |
162 | 198 | zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET]; |
163 | 198 | if (!get && (prop_info->flags & ZEND_ACC_VIRTUAL)) { |
164 | 36 | return; |
165 | 36 | } |
166 | 162 | if (hooked_iter->by_ref |
167 | 51 | && (get == NULL |
168 | 51 | || !(get->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) { |
169 | 3 | zend_throw_error(NULL, "Cannot create reference to property %s::$%s", |
170 | 3 | ZSTR_VAL(zobj->ce->name), zend_get_unmangled_property_name(prop_info->name)); |
171 | 3 | return; |
172 | 3 | } |
173 | 159 | zend_string *unmangled_name = prop_info->name; |
174 | 159 | if (ZSTR_VAL(unmangled_name)[0] == '\0') { |
175 | 18 | const char *tmp = zend_get_unmangled_property_name(unmangled_name); |
176 | 18 | unmangled_name = zend_string_init(tmp, strlen(tmp), false); |
177 | 18 | } |
178 | 159 | zval *value = zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &hooked_iter->current_data); |
179 | 159 | if (unmangled_name != prop_info->name) { |
180 | 18 | zend_string_release(unmangled_name); |
181 | 18 | } |
182 | 159 | if (value == &EG(uninitialized_zval)) { |
183 | 3 | return; |
184 | 156 | } else if (value != &hooked_iter->current_data) { |
185 | 3 | ZVAL_COPY(&hooked_iter->current_data, value); |
186 | 3 | } |
187 | 159 | } else { |
188 | 123 | zval *property = OBJ_PROP(zobj, prop_info->offset); |
189 | 123 | ZVAL_DEINDIRECT(property); |
190 | 123 | if (Z_TYPE_P(property) == IS_UNDEF) { |
191 | 9 | return; |
192 | 9 | } |
193 | 114 | if (!hooked_iter->by_ref) { |
194 | 57 | ZVAL_DEREF(property); |
195 | 57 | } else if (Z_TYPE_P(property) != IS_REFERENCE) { |
196 | 57 | if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) { |
197 | 6 | zend_throw_error(NULL, |
198 | 6 | "Cannot acquire reference to readonly property %s::$%s", |
199 | 6 | ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name)); |
200 | 6 | return; |
201 | 6 | } |
202 | 51 | ZVAL_MAKE_REF(property); |
203 | 51 | if (ZEND_TYPE_IS_SET(prop_info->type)) { |
204 | 39 | ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(property), prop_info); |
205 | 39 | } |
206 | 51 | } |
207 | 108 | ZVAL_COPY(&hooked_iter->current_data, property); |
208 | 108 | } |
209 | | |
210 | 264 | if (ZSTR_VAL(prop_info->name)[0] == '\0') { |
211 | 27 | const char *tmp = zend_get_unmangled_property_name(prop_info->name); |
212 | 27 | ZVAL_STR(&hooked_iter->current_key, zend_string_init(tmp, strlen(tmp), false)); |
213 | 237 | } else { |
214 | 237 | ZVAL_STR_COPY(&hooked_iter->current_key, prop_info->name); |
215 | 237 | } |
216 | 264 | } |
217 | | |
218 | | static void zho_dynamic_it_fetch_current(zend_object_iterator *iter) |
219 | 276 | { |
220 | 276 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
221 | 276 | zend_array *properties = Z_OBJ(iter->data)->properties; |
222 | 276 | HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties); |
223 | | |
224 | 276 | if (pos >= properties->nNumUsed) { |
225 | 120 | hooked_iter->dynamic_props_done = true; |
226 | 120 | return; |
227 | 120 | } |
228 | | |
229 | 156 | Bucket *bucket = properties->arData + pos; |
230 | | |
231 | 156 | if (UNEXPECTED(Z_TYPE(bucket->val) == IS_UNDEF)) { |
232 | 63 | return; |
233 | 63 | } |
234 | | |
235 | 93 | zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data); |
236 | 93 | if (bucket->key && zend_check_property_access(zobj, bucket->key, true) != SUCCESS) { |
237 | 9 | return; |
238 | 9 | } |
239 | | |
240 | 84 | if (hooked_iter->by_ref && Z_TYPE(bucket->val) != IS_REFERENCE) { |
241 | 33 | ZVAL_MAKE_REF(&bucket->val); |
242 | 33 | } |
243 | 84 | ZVAL_COPY(&hooked_iter->current_data, &bucket->val); |
244 | | |
245 | 84 | if (bucket->key) { |
246 | 84 | ZVAL_STR_COPY(&hooked_iter->current_key, bucket->key); |
247 | 84 | } else { |
248 | 0 | ZVAL_LONG(&hooked_iter->current_key, bucket->h); |
249 | 0 | } |
250 | 84 | } |
251 | | |
252 | | static void zho_it_fetch_current(zend_object_iterator *iter) |
253 | 1.16k | { |
254 | 1.16k | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
255 | 1.16k | if (Z_TYPE(hooked_iter->current_data) != IS_UNDEF) { |
256 | 687 | return; |
257 | 687 | } |
258 | | |
259 | 717 | while (true) { |
260 | 717 | if (!hooked_iter->declared_props_done) { |
261 | 321 | zho_declared_it_fetch_current(iter); |
262 | 396 | } else if (!hooked_iter->dynamic_props_done) { |
263 | 276 | zho_dynamic_it_fetch_current(iter); |
264 | 276 | } else { |
265 | 120 | break; |
266 | 120 | } |
267 | 597 | if (Z_TYPE(hooked_iter->current_data) != IS_UNDEF || EG(exception)) { |
268 | 357 | break; |
269 | 357 | } |
270 | 240 | zho_it_move_forward(iter); |
271 | 240 | } |
272 | 477 | } |
273 | | |
274 | | static void zho_it_dtor(zend_object_iterator *iter) |
275 | 132 | { |
276 | 132 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
277 | 132 | zval_ptr_dtor(&iter->data); |
278 | 132 | zval_ptr_dtor(&hooked_iter->declared_props); |
279 | 132 | zval_ptr_dtor_nogc(&hooked_iter->current_key); |
280 | 132 | zval_ptr_dtor(&hooked_iter->current_data); |
281 | 132 | zend_hash_iterator_del(hooked_iter->dynamic_prop_it); |
282 | 132 | } |
283 | | |
284 | | static zend_result zho_it_valid(zend_object_iterator *iter) |
285 | 477 | { |
286 | 477 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
287 | 477 | zho_it_fetch_current(iter); |
288 | 477 | return Z_TYPE(hooked_iter->current_data) != IS_UNDEF ? SUCCESS : FAILURE; |
289 | 477 | } |
290 | | |
291 | | static zval *zho_it_get_current_data(zend_object_iterator *iter) |
292 | 348 | { |
293 | 348 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
294 | 348 | zho_it_fetch_current(iter); |
295 | 348 | return &hooked_iter->current_data; |
296 | 348 | } |
297 | | |
298 | | static void zho_it_get_current_key(zend_object_iterator *iter, zval *key) |
299 | 339 | { |
300 | 339 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
301 | 339 | zho_it_fetch_current(iter); |
302 | 339 | ZVAL_COPY(key, &hooked_iter->current_key); |
303 | 339 | } |
304 | | |
305 | | static void zho_it_move_forward(zend_object_iterator *iter) |
306 | 585 | { |
307 | 585 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
308 | | |
309 | 585 | zval_ptr_dtor(&hooked_iter->current_data); |
310 | 585 | ZVAL_UNDEF(&hooked_iter->current_data); |
311 | 585 | zval_ptr_dtor_nogc(&hooked_iter->current_key); |
312 | 585 | ZVAL_UNDEF(&hooked_iter->current_key); |
313 | | |
314 | 585 | if (!hooked_iter->declared_props_done) { |
315 | 309 | zend_array *properties = Z_ARR(hooked_iter->declared_props); |
316 | 309 | zend_hash_move_forward(properties); |
317 | 309 | if (zend_hash_has_more_elements(properties) != SUCCESS) { |
318 | 120 | hooked_iter->declared_props_done = true; |
319 | 120 | } |
320 | 309 | } else if (!hooked_iter->dynamic_props_done) { |
321 | 156 | zend_array *properties = Z_OBJ(iter->data)->properties; |
322 | 156 | HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties); |
323 | 156 | pos++; |
324 | 156 | EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = pos; |
325 | 156 | } |
326 | 585 | } |
327 | | |
328 | | static void zho_it_rewind(zend_object_iterator *iter) |
329 | 132 | { |
330 | 132 | zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; |
331 | | |
332 | 132 | zval_ptr_dtor(&hooked_iter->current_data); |
333 | 132 | ZVAL_UNDEF(&hooked_iter->current_data); |
334 | 132 | zval_ptr_dtor_nogc(&hooked_iter->current_key); |
335 | 132 | ZVAL_UNDEF(&hooked_iter->current_key); |
336 | | |
337 | 132 | zend_array *properties = Z_ARR(hooked_iter->declared_props); |
338 | 132 | zend_hash_internal_pointer_reset(properties); |
339 | 132 | hooked_iter->declared_props_done = !zend_hash_num_elements(properties); |
340 | 132 | hooked_iter->dynamic_props_done = false; |
341 | 132 | EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = hooked_iter->dynamic_prop_offset; |
342 | 132 | } |
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 | 135 | { |
368 | 135 | zend_object *zobj = Z_OBJ_P(object); |
369 | 135 | if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { |
370 | 57 | zobj = zend_lazy_object_init(zobj); |
371 | 57 | if (UNEXPECTED(!zobj)) { |
372 | 3 | return NULL; |
373 | 3 | } |
374 | 57 | } |
375 | | |
376 | 132 | zend_hooked_object_iterator *iterator = emalloc(sizeof(zend_hooked_object_iterator)); |
377 | 132 | zend_iterator_init(&iterator->it); |
378 | | |
379 | 132 | ZVAL_OBJ_COPY(&iterator->it.data, zobj); |
380 | 132 | iterator->it.funcs = &zend_hooked_object_it_funcs; |
381 | 132 | iterator->by_ref = by_ref; |
382 | 132 | zend_array *properties = zho_build_properties_ex(zobj, true, true, false); |
383 | 132 | ZVAL_ARR(&iterator->declared_props, properties); |
384 | 132 | iterator->declared_props_done = !zend_hash_num_elements(properties); |
385 | 132 | zho_dynamic_it_init(iterator); |
386 | 132 | ZVAL_UNDEF(&iterator->current_key); |
387 | 132 | ZVAL_UNDEF(&iterator->current_data); |
388 | | |
389 | 132 | return &iterator->it; |
390 | 135 | } |