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