/src/php-src/Zend/zend_lazy_objects.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: Arnaud Le Blanc <arnaud.lb@gmail.com> | |
16 | | +----------------------------------------------------------------------+ |
17 | | */ |
18 | | |
19 | | /* Lazy objects are standard zend_object whose initialization is deferred until |
20 | | * one of their properties backing store is accessed for the first time. |
21 | | * |
22 | | * This is implemented by using the same fallback mechanism as __get and __set |
23 | | * magic methods that is triggered when an undefined property is accessed. |
24 | | * |
25 | | * Execution of methods or virtual property hooks do not trigger initialization |
26 | | * until they access properties. |
27 | | * |
28 | | * A lazy object can be created via the Reflection API. The user specifies an |
29 | | * initializer function that is called when initialization is required. |
30 | | * |
31 | | * There are two kinds of lazy objects: |
32 | | * |
33 | | * - Ghosts: These are initialized in-place by the initializer function |
34 | | * - Proxy: The initializer returns a new instance. After initialization, |
35 | | * interaction with the proxy object are proxied to the instance. |
36 | | * |
37 | | * Internal objects are not supported. |
38 | | */ |
39 | | |
40 | | #include "zend_API.h" |
41 | | #include "zend_compile.h" |
42 | | #include "zend_execute.h" |
43 | | #include "zend_gc.h" |
44 | | #include "zend_hash.h" |
45 | | #include "zend_object_handlers.h" |
46 | | #include "zend_objects_API.h" |
47 | | #include "zend_operators.h" |
48 | | #include "zend_types.h" |
49 | | #include "zend_variables.h" |
50 | | #include "zend_lazy_objects.h" |
51 | | |
52 | | /** |
53 | | * Information about each lazy object is stored outside of zend_objects, in |
54 | | * EG(lazy_objects_store). For ghost objects, we can release this after the |
55 | | * object is initialized. |
56 | | */ |
57 | | typedef struct _zend_lazy_object_info { |
58 | | union { |
59 | | struct { |
60 | | zend_fcall_info_cache fcc; |
61 | | zval zv; /* ReflectionClass::getLazyInitializer() */ |
62 | | } initializer; |
63 | | zend_object *instance; /* For initialized lazy proxy objects */ |
64 | | } u; |
65 | | zend_lazy_object_flags_t flags; |
66 | | int lazy_properties_count; |
67 | | } zend_lazy_object_info; |
68 | | |
69 | | /* zend_hash dtor_func_t for zend_lazy_objects_store.infos */ |
70 | | static void zend_lazy_object_info_dtor_func(zval *pElement) |
71 | 2.29k | { |
72 | 2.29k | zend_lazy_object_info *info = (zend_lazy_object_info*) Z_PTR_P(pElement); |
73 | | |
74 | 2.29k | if (info->flags & ZEND_LAZY_OBJECT_INITIALIZED) { |
75 | 719 | ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_STRATEGY_PROXY); |
76 | 719 | zend_object_release(info->u.instance); |
77 | 1.57k | } else { |
78 | 1.57k | zval_ptr_dtor(&info->u.initializer.zv); |
79 | 1.57k | zend_fcc_dtor(&info->u.initializer.fcc); |
80 | 1.57k | } |
81 | | |
82 | 2.29k | efree(info); |
83 | 2.29k | } |
84 | | |
85 | | void zend_lazy_objects_init(zend_lazy_objects_store *store) |
86 | 300k | { |
87 | 300k | zend_hash_init(&store->infos, 8, NULL, zend_lazy_object_info_dtor_func, false); |
88 | 300k | } |
89 | | |
90 | | void zend_lazy_objects_destroy(zend_lazy_objects_store *store) |
91 | 300k | { |
92 | 300k | ZEND_ASSERT(zend_hash_num_elements(&store->infos) == 0 || CG(unclean_shutdown)); |
93 | 300k | zend_hash_destroy(&store->infos); |
94 | 300k | } |
95 | | |
96 | | static void zend_lazy_object_set_info(zend_object *obj, zend_lazy_object_info *info) |
97 | 2.29k | { |
98 | 2.29k | ZEND_ASSERT(zend_object_is_lazy(obj)); |
99 | | |
100 | 2.29k | zval *zv = zend_hash_index_add_new_ptr(&EG(lazy_objects_store).infos, obj->handle, info); |
101 | 2.29k | ZEND_ASSERT(zv); |
102 | 2.29k | (void)zv; |
103 | 2.29k | } |
104 | | |
105 | | static zend_lazy_object_info* zend_lazy_object_get_info(zend_object *obj) |
106 | 5.54k | { |
107 | 5.54k | ZEND_ASSERT(zend_object_is_lazy(obj)); |
108 | | |
109 | 5.54k | zend_lazy_object_info *info = zend_hash_index_find_ptr(&EG(lazy_objects_store).infos, obj->handle); |
110 | 5.54k | ZEND_ASSERT(info); |
111 | | |
112 | 5.54k | return info; |
113 | 5.54k | } |
114 | | |
115 | | static bool zend_lazy_object_has_stale_info(zend_object *obj) |
116 | 318 | { |
117 | 318 | return zend_hash_index_find_ptr(&EG(lazy_objects_store).infos, obj->handle); |
118 | 318 | } |
119 | | |
120 | | zval* zend_lazy_object_get_initializer_zv(zend_object *obj) |
121 | 35 | { |
122 | 35 | ZEND_ASSERT(!zend_lazy_object_initialized(obj)); |
123 | | |
124 | 35 | zend_lazy_object_info *info = zend_lazy_object_get_info(obj); |
125 | | |
126 | 35 | ZEND_ASSERT(!(info->flags & ZEND_LAZY_OBJECT_INITIALIZED)); |
127 | | |
128 | 35 | return &info->u.initializer.zv; |
129 | 35 | } |
130 | | |
131 | | static zend_fcall_info_cache* zend_lazy_object_get_initializer_fcc(zend_object *obj) |
132 | 822 | { |
133 | 822 | ZEND_ASSERT(!zend_lazy_object_initialized(obj)); |
134 | | |
135 | 822 | zend_lazy_object_info *info = zend_lazy_object_get_info(obj); |
136 | | |
137 | 822 | ZEND_ASSERT(!(info->flags & ZEND_LAZY_OBJECT_INITIALIZED)); |
138 | | |
139 | 822 | return &info->u.initializer.fcc; |
140 | 822 | } |
141 | | |
142 | | zend_object* zend_lazy_object_get_instance(zend_object *obj) |
143 | 916 | { |
144 | 916 | ZEND_ASSERT(zend_lazy_object_initialized(obj)); |
145 | | |
146 | 916 | if (zend_object_is_lazy_proxy(obj)) { |
147 | 722 | zend_lazy_object_info *info = zend_lazy_object_get_info(obj); |
148 | | |
149 | 722 | ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_INITIALIZED); |
150 | | |
151 | 722 | return info->u.instance; |
152 | 722 | } |
153 | | |
154 | 194 | return obj; |
155 | 916 | } |
156 | | |
157 | | zend_lazy_object_flags_t zend_lazy_object_get_flags(zend_object *obj) |
158 | 119 | { |
159 | 119 | return zend_lazy_object_get_info(obj)->flags; |
160 | 119 | } |
161 | | |
162 | | void zend_lazy_object_del_info(zend_object *obj) |
163 | 2.29k | { |
164 | 2.29k | zend_result res = zend_hash_index_del(&EG(lazy_objects_store).infos, obj->handle); |
165 | 2.29k | ZEND_ASSERT(res == SUCCESS); |
166 | 2.29k | } |
167 | | |
168 | | bool zend_lazy_object_decr_lazy_props(zend_object *obj) |
169 | 670 | { |
170 | 670 | ZEND_ASSERT(zend_object_is_lazy(obj)); |
171 | 670 | ZEND_ASSERT(!zend_lazy_object_initialized(obj)); |
172 | | |
173 | 670 | zend_lazy_object_info *info = zend_lazy_object_get_info(obj); |
174 | | |
175 | 670 | ZEND_ASSERT(info->lazy_properties_count > 0); |
176 | | |
177 | 670 | info->lazy_properties_count--; |
178 | | |
179 | 670 | return info->lazy_properties_count == 0; |
180 | 670 | } |
181 | | |
182 | | /** |
183 | | * Making objects lazy |
184 | | */ |
185 | | |
186 | | ZEND_API bool zend_class_can_be_lazy(zend_class_entry *ce) |
187 | 0 | { |
188 | | /* Internal classes are not supported */ |
189 | 0 | if (UNEXPECTED(ce->type == ZEND_INTERNAL_CLASS && ce != zend_standard_class_def)) { |
190 | 0 | return false; |
191 | 0 | } |
192 | | |
193 | 0 | for (zend_class_entry *parent = ce->parent; parent; parent = parent->parent) { |
194 | 0 | if (UNEXPECTED(parent->type == ZEND_INTERNAL_CLASS && parent != zend_standard_class_def)) { |
195 | 0 | return false; |
196 | 0 | } |
197 | 0 | } |
198 | | |
199 | 0 | return true; |
200 | 0 | } |
201 | | |
202 | | static int zlo_hash_remove_dyn_props_func(zval *pDest) |
203 | 106 | { |
204 | 106 | if (Z_TYPE_P(pDest) == IS_INDIRECT) { |
205 | 52 | return ZEND_HASH_APPLY_STOP; |
206 | 52 | } |
207 | | |
208 | 54 | return ZEND_HASH_APPLY_REMOVE; |
209 | 106 | } |
210 | | |
211 | | static bool zlo_is_iterating(zend_object *object) |
212 | 348 | { |
213 | 348 | if (object->properties && HT_ITERATORS_COUNT(object->properties)) { |
214 | 0 | return true; |
215 | 0 | } |
216 | 348 | if (zend_object_is_lazy_proxy(object) |
217 | 348 | && zend_lazy_object_initialized(object)) { |
218 | 15 | return zlo_is_iterating(zend_lazy_object_get_instance(object)); |
219 | 15 | } |
220 | 333 | return false; |
221 | 348 | } |
222 | | |
223 | | /* Make object 'obj' lazy. If 'obj' is NULL, create a lazy instance of |
224 | | * class 'reflection_ce' */ |
225 | | ZEND_API zend_object *zend_object_make_lazy(zend_object *obj, |
226 | | zend_class_entry *reflection_ce, zval *initializer_zv, |
227 | | zend_fcall_info_cache *initializer_fcc, zend_lazy_object_flags_t flags) |
228 | 2.42k | { |
229 | 2.42k | ZEND_ASSERT(!(flags & ~(ZEND_LAZY_OBJECT_USER_MASK|ZEND_LAZY_OBJECT_STRATEGY_MASK))); |
230 | 2.42k | ZEND_ASSERT((flags & ZEND_LAZY_OBJECT_STRATEGY_MASK) == ZEND_LAZY_OBJECT_STRATEGY_GHOST |
231 | 2.42k | || (flags & ZEND_LAZY_OBJECT_STRATEGY_MASK) == ZEND_LAZY_OBJECT_STRATEGY_PROXY); |
232 | | |
233 | 2.42k | ZEND_ASSERT(!obj || (!zend_object_is_lazy(obj) || zend_lazy_object_initialized(obj))); |
234 | 2.42k | ZEND_ASSERT(!obj || instanceof_function(obj->ce, reflection_ce)); |
235 | | |
236 | | /* Internal classes are not supported */ |
237 | 2.42k | if (UNEXPECTED(reflection_ce->type == ZEND_INTERNAL_CLASS && reflection_ce != zend_standard_class_def)) { |
238 | 13 | zend_throw_error(NULL, "Cannot make instance of internal class lazy: %s is internal", ZSTR_VAL(reflection_ce->name)); |
239 | 13 | return NULL; |
240 | 13 | } |
241 | | |
242 | 2.72k | for (zend_class_entry *parent = reflection_ce->parent; parent; parent = parent->parent) { |
243 | 327 | if (UNEXPECTED(parent->type == ZEND_INTERNAL_CLASS && parent != zend_standard_class_def)) { |
244 | 15 | zend_throw_error(NULL, "Cannot make instance of internal class lazy: %s inherits internal class %s", |
245 | 15 | ZSTR_VAL(reflection_ce->name), ZSTR_VAL(parent->name)); |
246 | 15 | return NULL; |
247 | 15 | } |
248 | 327 | } |
249 | | |
250 | 2.40k | int lazy_properties_count = 0; |
251 | | |
252 | 2.40k | if (!obj) { |
253 | 2.06k | if (UNEXPECTED(reflection_ce->ce_flags & ZEND_ACC_UNINSTANTIABLE)) { |
254 | 0 | zval zobj; |
255 | | /* Call object_init_ex() for the generated exception */ |
256 | 0 | zend_result result = object_init_ex(&zobj, reflection_ce); |
257 | 0 | ZEND_ASSERT(result == FAILURE && EG(exception)); |
258 | 0 | (void)result; |
259 | 0 | return NULL; |
260 | 0 | } |
261 | | |
262 | 2.06k | if (UNEXPECTED(!(reflection_ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { |
263 | 5 | if (UNEXPECTED(zend_update_class_constants(reflection_ce) != SUCCESS)) { |
264 | 5 | ZEND_ASSERT(EG(exception)); |
265 | 5 | return NULL; |
266 | 5 | } |
267 | 5 | } |
268 | | |
269 | 2.06k | obj = zend_objects_new(reflection_ce); |
270 | | |
271 | | /* Iterate in reverse to avoid overriding Z_PROP_FLAG_P() of child props with added hooks (GH-17870). */ |
272 | 6.08k | for (int i = obj->ce->default_properties_count - 1; i >= 0; i--) { |
273 | 4.02k | zval *p = &obj->properties_table[i]; |
274 | 4.02k | ZVAL_UNDEF(p); |
275 | 4.02k | Z_PROP_FLAG_P(p) = 0; |
276 | | |
277 | 4.02k | zend_property_info *prop_info = obj->ce->properties_info_table[i]; |
278 | 4.02k | if (prop_info) { |
279 | 3.98k | zval *p = &obj->properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]; |
280 | 3.98k | Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY; |
281 | 3.98k | lazy_properties_count++; |
282 | 3.98k | } |
283 | 4.02k | } |
284 | 2.06k | } else { |
285 | 333 | if (zlo_is_iterating(obj)) { |
286 | 0 | zend_throw_error(NULL, "Can not reset an object during property iteration"); |
287 | 0 | return NULL; |
288 | 0 | } |
289 | 333 | if (zend_object_is_lazy(obj)) { |
290 | 15 | ZEND_ASSERT(zend_object_is_lazy_proxy(obj) && zend_lazy_object_initialized(obj)); |
291 | 15 | OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY); |
292 | 15 | zend_lazy_object_del_info(obj); |
293 | 318 | } else { |
294 | 318 | if (zend_lazy_object_has_stale_info(obj)) { |
295 | 22 | zend_throw_error(NULL, "Can not reset an object while it is being initialized"); |
296 | 22 | return NULL; |
297 | 22 | } |
298 | | |
299 | 296 | if (!(flags & ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR) |
300 | 296 | && !(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { |
301 | 281 | if (obj->handlers->dtor_obj != zend_objects_destroy_object |
302 | 281 | || obj->ce->destructor) { |
303 | 39 | GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); |
304 | 39 | GC_ADDREF(obj); |
305 | 39 | obj->handlers->dtor_obj(obj); |
306 | 39 | GC_DELREF(obj); |
307 | 39 | if (EG(exception)) { |
308 | 19 | return NULL; |
309 | 19 | } |
310 | 39 | } |
311 | 281 | } |
312 | 296 | } |
313 | | |
314 | 292 | GC_DEL_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); |
315 | | |
316 | | /* unset() dynamic properties. Do not NULL out obj->properties, as this |
317 | | * would be unexpected. */ |
318 | 292 | if (obj->properties) { |
319 | 67 | if (UNEXPECTED(GC_REFCOUNT(obj->properties) > 1)) { |
320 | 0 | if (EXPECTED(!(GC_FLAGS(obj->properties) & IS_ARRAY_IMMUTABLE))) { |
321 | 0 | GC_DELREF(obj->properties); |
322 | 0 | } |
323 | 0 | obj->properties = zend_array_dup(obj->properties); |
324 | 0 | } |
325 | 67 | zend_hash_reverse_apply(obj->properties, zlo_hash_remove_dyn_props_func); |
326 | 67 | } |
327 | | |
328 | | /* unset() declared properties */ |
329 | 612 | for (int i = 0; i < reflection_ce->default_properties_count; i++) { |
330 | 320 | zend_property_info *prop_info = obj->ce->properties_info_table[i]; |
331 | 320 | if (EXPECTED(prop_info)) { |
332 | 320 | zval *p = &obj->properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]; |
333 | 320 | if (Z_TYPE_P(p) != IS_UNDEF) { |
334 | 228 | if ((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(p) & IS_PROP_REINITABLE) |
335 | | /* TODO: test final property */ |
336 | 228 | && ((obj->ce->ce_flags & ZEND_ACC_FINAL) || (prop_info->flags & ZEND_ACC_FINAL))) { |
337 | 5 | continue; |
338 | 5 | } |
339 | 223 | zend_object_dtor_property(obj, p); |
340 | 223 | ZVAL_UNDEF(p); |
341 | 223 | } |
342 | 315 | Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY; |
343 | 315 | lazy_properties_count++; |
344 | 315 | } |
345 | 320 | } |
346 | 292 | } |
347 | | |
348 | | /* Objects become non-lazy if all properties are made non-lazy before |
349 | | * initialization is triggered. If the object has no properties to begin |
350 | | * with, this happens immediately. */ |
351 | 2.35k | if (UNEXPECTED(!lazy_properties_count)) { |
352 | 92 | return obj; |
353 | 92 | } |
354 | | |
355 | 2.26k | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED; |
356 | | |
357 | 2.26k | if (flags & ZEND_LAZY_OBJECT_STRATEGY_PROXY) { |
358 | 1.07k | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_PROXY; |
359 | 1.18k | } else { |
360 | 1.18k | ZEND_ASSERT(flags & ZEND_LAZY_OBJECT_STRATEGY_GHOST); |
361 | 1.18k | } |
362 | | |
363 | 2.26k | zend_lazy_object_info *info = emalloc(sizeof(*info)); |
364 | 2.26k | zend_fcc_dup(&info->u.initializer.fcc, initializer_fcc); |
365 | 2.26k | ZVAL_COPY(&info->u.initializer.zv, initializer_zv); |
366 | 2.26k | info->flags = flags; |
367 | 2.26k | info->lazy_properties_count = lazy_properties_count; |
368 | 2.26k | zend_lazy_object_set_info(obj, info); |
369 | | |
370 | 2.26k | return obj; |
371 | 2.26k | } |
372 | | |
373 | | /** |
374 | | * Initialization of lazy objects |
375 | | */ |
376 | | |
377 | | /* Mark object as initialized. Lazy properties are initialized to their default |
378 | | * value and the initializer is not called. */ |
379 | | ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj) |
380 | 15 | { |
381 | 15 | ZEND_ASSERT(zend_object_is_lazy(obj)); |
382 | 15 | ZEND_ASSERT(!zend_lazy_object_initialized(obj)); |
383 | | |
384 | 15 | zend_class_entry *ce = obj->ce; |
385 | | |
386 | 15 | ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED); |
387 | | |
388 | 15 | zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce); |
389 | 15 | zval *properties_table = obj->properties_table; |
390 | | |
391 | 15 | OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY); |
392 | | |
393 | 30 | for (int i = 0; i < ce->default_properties_count; i++) { |
394 | 15 | if (Z_PROP_FLAG_P(&properties_table[i]) & IS_PROP_LAZY) { |
395 | 15 | ZVAL_COPY_PROP(&properties_table[i], &default_properties_table[i]); |
396 | 15 | } |
397 | 15 | } |
398 | | |
399 | 15 | zend_lazy_object_del_info(obj); |
400 | | |
401 | 15 | return obj; |
402 | 15 | } |
403 | | |
404 | | /* Revert initializer effects */ |
405 | | static void zend_lazy_object_revert_init(zend_object *obj, zval *properties_table_snapshot, HashTable *properties_snapshot) |
406 | 201 | { |
407 | 201 | zend_class_entry *ce = obj->ce; |
408 | | |
409 | 201 | if (ce->default_properties_count) { |
410 | 201 | ZEND_ASSERT(properties_table_snapshot); |
411 | 201 | zval *properties_table = obj->properties_table; |
412 | | |
413 | 562 | for (int i = 0; i < ce->default_properties_count; i++) { |
414 | 361 | zend_property_info *prop_info = ce->properties_info_table[i]; |
415 | 361 | if (!prop_info) { |
416 | 8 | continue; |
417 | 8 | } |
418 | | |
419 | 353 | zval *p = &properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]; |
420 | 353 | zend_object_dtor_property(obj, p); |
421 | 353 | ZVAL_COPY_VALUE_PROP(p, &properties_table_snapshot[OBJ_PROP_TO_NUM(prop_info->offset)]); |
422 | | |
423 | 353 | if (Z_ISREF_P(p) && ZEND_TYPE_IS_SET(prop_info->type)) { |
424 | 22 | ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(p), prop_info); |
425 | 22 | } |
426 | 353 | } |
427 | | |
428 | 201 | efree(properties_table_snapshot); |
429 | 201 | } |
430 | 201 | if (properties_snapshot) { |
431 | 82 | if (obj->properties != properties_snapshot) { |
432 | 5 | ZEND_ASSERT((GC_FLAGS(properties_snapshot) & IS_ARRAY_IMMUTABLE) || GC_REFCOUNT(properties_snapshot) >= 1); |
433 | 5 | zend_release_properties(obj->properties); |
434 | 5 | obj->properties = properties_snapshot; |
435 | 77 | } else { |
436 | 77 | ZEND_ASSERT((GC_FLAGS(properties_snapshot) & IS_ARRAY_IMMUTABLE) || GC_REFCOUNT(properties_snapshot) > 1); |
437 | 77 | zend_release_properties(properties_snapshot); |
438 | 77 | } |
439 | 119 | } else if (obj->properties) { |
440 | 5 | zend_release_properties(obj->properties); |
441 | 5 | obj->properties = NULL; |
442 | 5 | } |
443 | | |
444 | 201 | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED; |
445 | 201 | } |
446 | | |
447 | | static bool zend_lazy_object_compatible(zend_object *real_object, zend_object *lazy_object) |
448 | 728 | { |
449 | 728 | if (EXPECTED(real_object->ce == lazy_object->ce)) { |
450 | 641 | return true; |
451 | 641 | } |
452 | | |
453 | 87 | if (!instanceof_function(lazy_object->ce, real_object->ce)) { |
454 | 20 | return false; |
455 | 20 | } |
456 | | |
457 | | /* zend_hash_num_elements(ce.properties_info) reports the actual number of |
458 | | * properties. ce.default_properties_count is off by the number of property |
459 | | * overrides. */ |
460 | 67 | if (zend_hash_num_elements(&lazy_object->ce->properties_info) != zend_hash_num_elements(&real_object->ce->properties_info)) { |
461 | 0 | return false; |
462 | 0 | } |
463 | | |
464 | 67 | return lazy_object->ce->destructor == real_object->ce->destructor |
465 | 67 | && lazy_object->ce->clone == real_object->ce->clone; |
466 | 67 | } |
467 | | |
468 | | /* Initialize a lazy proxy object */ |
469 | | static zend_object *zend_lazy_object_init_proxy(zend_object *obj) |
470 | 874 | { |
471 | 874 | ZEND_ASSERT(zend_object_is_lazy_proxy(obj)); |
472 | 874 | ZEND_ASSERT(!zend_lazy_object_initialized(obj)); |
473 | | |
474 | | /* Prevent object from being released during initialization */ |
475 | 874 | GC_ADDREF(obj); |
476 | | |
477 | 874 | zend_lazy_object_info *info = zend_lazy_object_get_info(obj); |
478 | | |
479 | | /* prevent reentrant initialization */ |
480 | 874 | OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY); |
481 | | |
482 | | /* Call factory */ |
483 | 874 | zval retval; |
484 | 874 | int argc = 1; |
485 | 874 | zval zobj; |
486 | 874 | HashTable *named_params = NULL; |
487 | 874 | zend_fcall_info_cache *initializer = &info->u.initializer.fcc; |
488 | 874 | zend_object *instance = NULL; |
489 | | |
490 | 874 | ZVAL_OBJ(&zobj, obj); |
491 | | |
492 | 874 | zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params); |
493 | | |
494 | 874 | if (UNEXPECTED(EG(exception))) { |
495 | 138 | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; |
496 | 138 | goto exit; |
497 | 138 | } |
498 | | |
499 | 736 | if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT)) { |
500 | 8 | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; |
501 | 8 | zend_type_error("Lazy proxy factory must return an instance of a class compatible with %s, %s returned", |
502 | 8 | ZSTR_VAL(obj->ce->name), |
503 | 8 | zend_zval_value_name(&retval)); |
504 | 8 | zval_ptr_dtor(&retval); |
505 | 8 | goto exit; |
506 | 8 | } |
507 | | |
508 | 728 | if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || !zend_lazy_object_compatible(Z_OBJ(retval), obj))) { |
509 | 30 | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; |
510 | 30 | zend_type_error("The real instance class %s is not compatible with the proxy class %s. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.", |
511 | 30 | zend_zval_value_name(&retval), |
512 | 30 | ZSTR_VAL(obj->ce->name)); |
513 | 30 | zval_ptr_dtor(&retval); |
514 | 30 | goto exit; |
515 | 30 | } |
516 | | |
517 | 698 | if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) { |
518 | 7 | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; |
519 | 7 | zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object"); |
520 | 7 | zval_ptr_dtor(&retval); |
521 | 7 | goto exit; |
522 | 7 | } |
523 | | |
524 | 691 | zend_fcc_dtor(&info->u.initializer.fcc); |
525 | 691 | zval_ptr_dtor(&info->u.initializer.zv); |
526 | 691 | info->u.instance = Z_OBJ(retval); |
527 | 691 | info->flags |= ZEND_LAZY_OBJECT_INITIALIZED; |
528 | 691 | OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_PROXY; |
529 | | |
530 | | /* unset() properties of the proxy. This ensures that all accesses are be |
531 | | * delegated to the backing instance from now on. */ |
532 | 691 | zend_object_dtor_dynamic_properties(obj); |
533 | 691 | obj->properties = NULL; |
534 | | |
535 | 1.68k | for (int i = 0; i < Z_OBJ(retval)->ce->default_properties_count; i++) { |
536 | 991 | zend_property_info *prop_info = Z_OBJ(retval)->ce->properties_info_table[i]; |
537 | 991 | if (EXPECTED(prop_info)) { |
538 | 991 | zval *prop = &obj->properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]; |
539 | 991 | zend_object_dtor_property(obj, prop); |
540 | 991 | ZVAL_UNDEF(prop); |
541 | 991 | Z_PROP_FLAG_P(prop) = IS_PROP_UNINIT | IS_PROP_LAZY; |
542 | 991 | } |
543 | 991 | } |
544 | | |
545 | 691 | instance = Z_OBJ(retval); |
546 | | |
547 | 874 | exit: |
548 | 874 | if (UNEXPECTED(GC_DELREF(obj) == 0)) { |
549 | 16 | zend_throw_error(NULL, "Lazy object was released during initialization"); |
550 | 16 | zend_objects_store_del(obj); |
551 | 16 | instance = NULL; |
552 | 858 | } else { |
553 | 858 | gc_check_possible_root((zend_refcounted*) obj); |
554 | 858 | } |
555 | | |
556 | 874 | return instance; |
557 | 691 | } |
558 | | |
559 | | /* Initialize a lazy object. */ |
560 | | ZEND_API zend_object *zend_lazy_object_init(zend_object *obj) |
561 | 2.18k | { |
562 | 2.18k | ZEND_ASSERT(zend_object_is_lazy(obj)); |
563 | | |
564 | | /* If obj is an initialized lazy proxy, return the real instance. This |
565 | | * supports the following pattern: |
566 | | * if (zend_lazy_object_must_init(obj)) { |
567 | | * instance = zend_lazy_object_init(obj); |
568 | | * } |
569 | | */ |
570 | 2.18k | if (zend_lazy_object_initialized(obj)) { |
571 | 486 | ZEND_ASSERT(zend_object_is_lazy_proxy(obj)); |
572 | 486 | zend_lazy_object_info *info = zend_lazy_object_get_info(obj); |
573 | 486 | ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_INITIALIZED); |
574 | 486 | if (zend_object_is_lazy(info->u.instance)) { |
575 | 55 | return zend_lazy_object_init(info->u.instance); |
576 | 55 | } |
577 | 431 | return info->u.instance; |
578 | 486 | } |
579 | | |
580 | 1.69k | zend_class_entry *ce = obj->ce; |
581 | | |
582 | 1.69k | ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED); |
583 | | |
584 | 1.69k | if (zend_object_is_lazy_proxy(obj)) { |
585 | 874 | return zend_lazy_object_init_proxy(obj); |
586 | 874 | } |
587 | | |
588 | | /* Prevent object from being released during initialization */ |
589 | 822 | GC_ADDREF(obj); |
590 | | |
591 | 822 | zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj); |
592 | | |
593 | | /* Prevent reentrant initialization */ |
594 | 822 | OBJ_EXTRA_FLAGS(obj) &= ~IS_OBJ_LAZY_UNINITIALIZED; |
595 | | |
596 | | /* Snapshot dynamic properties */ |
597 | 822 | HashTable *properties_snapshot = obj->properties; |
598 | 822 | if (properties_snapshot) { |
599 | 285 | GC_TRY_ADDREF(properties_snapshot); |
600 | 285 | } |
601 | | |
602 | 822 | zval *properties_table_snapshot = NULL; |
603 | | |
604 | | /* Snapshot declared properties and initialize lazy properties to their |
605 | | * default value */ |
606 | 822 | if (ce->default_properties_count) { |
607 | 822 | zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce); |
608 | 822 | zval *properties_table = obj->properties_table; |
609 | 822 | properties_table_snapshot = emalloc(sizeof(*properties_table_snapshot) * ce->default_properties_count); |
610 | | |
611 | 2.13k | for (int i = 0; i < ce->default_properties_count; i++) { |
612 | 1.31k | ZVAL_COPY_PROP(&properties_table_snapshot[i], &properties_table[i]); |
613 | 1.31k | if (Z_PROP_FLAG_P(&properties_table[i]) & IS_PROP_LAZY) { |
614 | 1.15k | ZVAL_COPY_PROP(&properties_table[i], &default_properties_table[i]); |
615 | 1.15k | } |
616 | 1.31k | } |
617 | 822 | } |
618 | | |
619 | | /* Call initializer */ |
620 | 822 | zval retval; |
621 | 822 | int argc = 1; |
622 | 822 | zval zobj; |
623 | 822 | HashTable *named_params = NULL; |
624 | 822 | zend_object *instance = NULL; |
625 | | |
626 | 822 | ZVAL_OBJ(&zobj, obj); |
627 | | |
628 | 822 | zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params); |
629 | | |
630 | 822 | if (EG(exception)) { |
631 | 186 | zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot); |
632 | 186 | goto exit; |
633 | 186 | } |
634 | | |
635 | 636 | if (Z_TYPE(retval) != IS_NULL) { |
636 | 15 | zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot); |
637 | 15 | zval_ptr_dtor(&retval); |
638 | 15 | zend_type_error("Lazy object initializer must return NULL or no value"); |
639 | 15 | goto exit; |
640 | 15 | } |
641 | | |
642 | 621 | if (properties_table_snapshot) { |
643 | 1.57k | for (int i = 0; i < obj->ce->default_properties_count; i++) { |
644 | 955 | zval *p = &properties_table_snapshot[i]; |
645 | | /* Use zval_ptr_dtor directly here (not zend_object_dtor_property), |
646 | | * as any reference type_source will have already been deleted in |
647 | | * case the prop is not bound to this value anymore. */ |
648 | 955 | i_zval_ptr_dtor(p); |
649 | 955 | } |
650 | 621 | efree(properties_table_snapshot); |
651 | 621 | } |
652 | | |
653 | 621 | if (properties_snapshot) { |
654 | 203 | zend_release_properties(properties_snapshot); |
655 | 203 | } |
656 | | |
657 | | /* Must be very last in this function, for the |
658 | | * zend_lazy_object_has_stale_info() check */ |
659 | 621 | zend_lazy_object_del_info(obj); |
660 | | |
661 | 621 | instance = obj; |
662 | | |
663 | 822 | exit: |
664 | 822 | if (UNEXPECTED(GC_DELREF(obj) == 0)) { |
665 | 14 | zend_throw_error(NULL, "Lazy object was released during initialization"); |
666 | 14 | zend_objects_store_del(obj); |
667 | 14 | instance = NULL; |
668 | 808 | } else { |
669 | 808 | gc_check_possible_root((zend_refcounted*) obj); |
670 | 808 | } |
671 | | |
672 | 822 | return instance; |
673 | 621 | } |
674 | | |
675 | | /* Mark an object as non-lazy (after all properties were initialized) */ |
676 | | void zend_lazy_object_realize(zend_object *obj) |
677 | 121 | { |
678 | 121 | ZEND_ASSERT(zend_object_is_lazy(obj)); |
679 | 121 | ZEND_ASSERT(!zend_lazy_object_initialized(obj)); |
680 | | |
681 | 121 | zend_lazy_object_del_info(obj); |
682 | | |
683 | 121 | #if ZEND_DEBUG |
684 | 397 | for (int i = 0; i < obj->ce->default_properties_count; i++) { |
685 | 276 | ZEND_ASSERT(!(Z_PROP_FLAG_P(&obj->properties_table[i]) & IS_PROP_LAZY)); |
686 | 276 | } |
687 | 121 | #endif |
688 | | |
689 | 121 | OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED | IS_OBJ_LAZY_PROXY); |
690 | 121 | } |
691 | | |
692 | | ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object) |
693 | 323 | { |
694 | 323 | ZEND_ASSERT(zend_object_is_lazy(object)); |
695 | | |
696 | 323 | zend_object *tmp = zend_lazy_object_init(object); |
697 | 323 | if (UNEXPECTED(!tmp)) { |
698 | 61 | if (object->properties) { |
699 | 13 | return object->properties; |
700 | 13 | } |
701 | 48 | return object->properties = zend_new_array(0); |
702 | 61 | } |
703 | | |
704 | 262 | object = tmp; |
705 | 262 | ZEND_ASSERT(!zend_lazy_object_must_init(object)); |
706 | | |
707 | 262 | return zend_std_get_properties_ex(object); |
708 | 262 | } |
709 | | |
710 | | /* Initialize object and clone it. For proxies, we clone both the proxy and its |
711 | | * real instance, and we don't call __clone() on the proxy. */ |
712 | | zend_object *zend_lazy_object_clone(zend_object *old_obj) |
713 | 67 | { |
714 | 67 | ZEND_ASSERT(zend_object_is_lazy(old_obj)); |
715 | | |
716 | 67 | if (UNEXPECTED(!zend_lazy_object_initialized(old_obj) && !zend_lazy_object_init(old_obj))) { |
717 | 16 | ZEND_ASSERT(EG(exception)); |
718 | | /* Clone handler must always return an object. It is discarded later due |
719 | | * to the exception. */ |
720 | 16 | zval zv; |
721 | 16 | object_init_ex(&zv, old_obj->ce); |
722 | 16 | GC_ADD_FLAGS(Z_OBJ(zv), IS_OBJ_DESTRUCTOR_CALLED); |
723 | 16 | return Z_OBJ(zv); |
724 | 16 | } |
725 | | |
726 | 51 | if (!zend_object_is_lazy_proxy(old_obj)) { |
727 | 23 | return zend_objects_clone_obj(old_obj); |
728 | 23 | } |
729 | | |
730 | 28 | zend_lazy_object_info *info = zend_lazy_object_get_info(old_obj); |
731 | 28 | zend_class_entry *ce = old_obj->ce; |
732 | 28 | zend_object *new_proxy = zend_objects_new(ce); |
733 | | |
734 | | /* Iterate in reverse to avoid overriding Z_PROP_FLAG_P() of child props with added hooks (GH-17870). */ |
735 | 66 | for (int i = ce->default_properties_count - 1; i >= 0; i--) { |
736 | 38 | zval *p = &new_proxy->properties_table[i]; |
737 | 38 | ZVAL_UNDEF(p); |
738 | 38 | Z_PROP_FLAG_P(p) = 0; |
739 | | |
740 | 38 | zend_property_info *prop_info = ce->properties_info_table[i]; |
741 | 38 | if (prop_info) { |
742 | 38 | zval *p = &new_proxy->properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]; |
743 | 38 | Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY; |
744 | 38 | } |
745 | 38 | } |
746 | | |
747 | 28 | OBJ_EXTRA_FLAGS(new_proxy) = OBJ_EXTRA_FLAGS(old_obj); |
748 | | |
749 | 28 | zend_lazy_object_info *new_info = emalloc(sizeof(*info)); |
750 | 28 | *new_info = *info; |
751 | 28 | new_info->u.instance = zend_objects_clone_obj(info->u.instance); |
752 | | |
753 | 28 | zend_lazy_object_set_info(new_proxy, new_info); |
754 | | |
755 | 28 | return new_proxy; |
756 | 51 | } |
757 | | |
758 | | HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp) |
759 | 975 | { |
760 | 975 | ZEND_ASSERT(zend_object_is_lazy(object)); |
761 | | |
762 | 975 | if (zend_object_is_lazy_proxy(object)) { |
763 | 579 | if (zend_lazy_object_initialized(object)) { |
764 | 295 | HashTable *properties = zend_new_array(0); |
765 | 295 | zval instance; |
766 | 295 | ZVAL_OBJ(&instance, zend_lazy_object_get_instance(object)); |
767 | 295 | Z_ADDREF(instance); |
768 | 295 | zend_hash_str_add(properties, "instance", strlen("instance"), &instance); |
769 | 295 | *is_temp = 1; |
770 | 295 | return properties; |
771 | 295 | } |
772 | 579 | } |
773 | | |
774 | 680 | *is_temp = 0; |
775 | 680 | return zend_get_properties_no_lazy_init(object); |
776 | 975 | } |
777 | | |
778 | | HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n) |
779 | 1.78k | { |
780 | 1.78k | ZEND_ASSERT(zend_object_is_lazy(zobj)); |
781 | | |
782 | 1.78k | zend_lazy_object_info *info = zend_lazy_object_get_info(zobj); |
783 | 1.78k | zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); |
784 | | |
785 | 1.78k | if (zend_lazy_object_initialized(zobj)) { |
786 | 1.01k | ZEND_ASSERT(zend_object_is_lazy_proxy(zobj)); |
787 | 1.01k | zend_get_gc_buffer_add_obj(gc_buffer, info->u.instance); |
788 | 1.01k | zend_get_gc_buffer_use(gc_buffer, table, n); |
789 | | /* Initialized proxy object can not have properties */ |
790 | 1.01k | return NULL; |
791 | 1.01k | } |
792 | | |
793 | 770 | zend_fcall_info_cache *fcc = &info->u.initializer.fcc; |
794 | 770 | if (fcc->object) { |
795 | 4 | zend_get_gc_buffer_add_obj(gc_buffer, fcc->object); |
796 | 4 | } |
797 | 770 | if (fcc->closure) { |
798 | 770 | zend_get_gc_buffer_add_obj(gc_buffer, fcc->closure); |
799 | 770 | } |
800 | 770 | zend_get_gc_buffer_add_zval(gc_buffer, &info->u.initializer.zv); |
801 | | |
802 | | /* Uninitialized lazy objects can not have dynamic properties, so we can |
803 | | * ignore zobj->properties. */ |
804 | 770 | zval *prop = zobj->properties_table; |
805 | 770 | zval *end = prop + zobj->ce->default_properties_count; |
806 | 2.06k | for ( ; prop < end; prop++) { |
807 | 1.29k | zend_get_gc_buffer_add_zval(gc_buffer, prop); |
808 | 1.29k | } |
809 | | |
810 | 770 | zend_get_gc_buffer_use(gc_buffer, table, n); |
811 | 770 | return NULL; |
812 | 1.78k | } |
813 | | |
814 | | zend_property_info *zend_lazy_object_get_property_info_for_slot(zend_object *obj, zval *slot) |
815 | 502 | { |
816 | 502 | ZEND_ASSERT(zend_object_is_lazy_proxy(obj)); |
817 | | |
818 | 502 | zend_property_info **table = obj->ce->properties_info_table; |
819 | 502 | intptr_t prop_num = slot - obj->properties_table; |
820 | 502 | if (prop_num >= 0 && prop_num < obj->ce->default_properties_count) { |
821 | 465 | if (table[prop_num]) { |
822 | 465 | return table[prop_num]; |
823 | 465 | } else { |
824 | 0 | return zend_get_property_info_for_slot_slow(obj, slot); |
825 | 0 | } |
826 | 465 | } |
827 | | |
828 | 37 | if (!zend_lazy_object_initialized(obj)) { |
829 | 0 | return NULL; |
830 | 0 | } |
831 | | |
832 | 37 | obj = zend_lazy_object_get_instance(obj); |
833 | 37 | return zend_get_property_info_for_slot(obj, slot); |
834 | 37 | } |