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