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