/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 | } |