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