xref: /PHP-8.4/Zend/zend_lazy_objects.c (revision c9dfb774)
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 		obj = zend_objects_new(reflection_ce);
263 
264 		for (int i = 0; i < obj->ce->default_properties_count; i++) {
265 			zval *p = &obj->properties_table[i];
266 			ZVAL_UNDEF(p);
267 			if (EXPECTED(obj->ce->properties_info_table[i])) {
268 				Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY;
269 				lazy_properties_count++;
270 			} else {
271 				Z_PROP_FLAG_P(p) = 0;
272 			}
273 		}
274 	} else {
275 		if (zlo_is_iterating(obj)) {
276 			zend_throw_error(NULL, "Can not reset an object during property iteration");
277 			return NULL;
278 		}
279 		if (zend_object_is_lazy(obj)) {
280 			ZEND_ASSERT(zend_object_is_lazy_proxy(obj) && zend_lazy_object_initialized(obj));
281 			OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY);
282 			zend_lazy_object_del_info(obj);
283 		} else {
284 			if (zend_lazy_object_has_stale_info(obj)) {
285 				zend_throw_error(NULL, "Can not reset an object while it is being initialized");
286 				return NULL;
287 			}
288 
289 			if (!(flags & ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR)
290 				&& !(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) {
291 				if (obj->handlers->dtor_obj != zend_objects_destroy_object
292 						|| obj->ce->destructor) {
293 					GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
294 					GC_ADDREF(obj);
295 					obj->handlers->dtor_obj(obj);
296 					GC_DELREF(obj);
297 					if (EG(exception)) {
298 						return NULL;
299 					}
300 				}
301 			}
302 		}
303 
304 		GC_DEL_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
305 
306 		/* unset() dynamic properties. Do not NULL out obj->properties, as this
307 		 * would be unexpected. */
308 		if (obj->properties) {
309 			if (UNEXPECTED(GC_REFCOUNT(obj->properties) > 1)) {
310 				if (EXPECTED(!(GC_FLAGS(obj->properties) & IS_ARRAY_IMMUTABLE))) {
311 					GC_DELREF(obj->properties);
312 				}
313 				obj->properties = zend_array_dup(obj->properties);
314 			}
315 			zend_hash_reverse_apply(obj->properties, zlo_hash_remove_dyn_props_func);
316 		}
317 
318 		/* unset() declared properties */
319 		for (int i = 0; i < reflection_ce->default_properties_count; i++) {
320 			zend_property_info *prop_info = obj->ce->properties_info_table[i];
321 			if (EXPECTED(prop_info)) {
322 				zval *p = &obj->properties_table[i];
323 				if (Z_TYPE_P(p) != IS_UNDEF) {
324 					if ((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(p) & IS_PROP_REINITABLE)
325 							/* TODO: test final property */
326 							&& ((obj->ce->ce_flags & ZEND_ACC_FINAL) || (prop_info->flags & ZEND_ACC_FINAL))) {
327 						continue;
328 					}
329 					zend_object_dtor_property(obj, p);
330 					ZVAL_UNDEF(p);
331 				}
332 				Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY;
333 				lazy_properties_count++;
334 			}
335 		}
336 	}
337 
338 	/* Objects become non-lazy if all properties are made non-lazy before
339 	 * initialization is triggered. If the object has no properties to begin
340 	 * with, this happens immediately. */
341 	if (UNEXPECTED(!lazy_properties_count)) {
342 		return obj;
343 	}
344 
345 	OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED;
346 
347 	if (flags & ZEND_LAZY_OBJECT_STRATEGY_PROXY) {
348 		OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_PROXY;
349 	} else {
350 		ZEND_ASSERT(flags & ZEND_LAZY_OBJECT_STRATEGY_GHOST);
351 	}
352 
353 	zend_lazy_object_info *info = emalloc(sizeof(*info));
354 	zend_fcc_dup(&info->u.initializer.fcc, initializer_fcc);
355 	ZVAL_COPY(&info->u.initializer.zv, initializer_zv);
356 	info->flags = flags;
357 	info->lazy_properties_count = lazy_properties_count;
358 	zend_lazy_object_set_info(obj, info);
359 
360 	return obj;
361 }
362 
363 /**
364  * Initialization of lazy objects
365  */
366 
367 /* Mark object as initialized. Lazy properties are initialized to their default
368  * value and the initializer is not called. */
zend_lazy_object_mark_as_initialized(zend_object * obj)369 ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj)
370 {
371 	ZEND_ASSERT(zend_object_is_lazy(obj));
372 	ZEND_ASSERT(!zend_lazy_object_initialized(obj));
373 
374 	zend_class_entry *ce = obj->ce;
375 
376 	if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) {
377 		if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) {
378 			ZEND_ASSERT(EG(exception));
379 			return NULL;
380 		}
381 	}
382 
383 	zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce);
384 	zval *properties_table = obj->properties_table;
385 
386 	OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY);
387 
388 	for (int i = 0; i < ce->default_properties_count; i++) {
389 		if (Z_PROP_FLAG_P(&properties_table[i]) & IS_PROP_LAZY) {
390 			ZVAL_COPY_PROP(&properties_table[i], &default_properties_table[i]);
391 		}
392 	}
393 
394 	zend_lazy_object_del_info(obj);
395 
396 	return obj;
397 }
398 
399 /* Revert initializer effects */
zend_lazy_object_revert_init(zend_object * obj,zval * properties_table_snapshot,HashTable * properties_snapshot)400 static void zend_lazy_object_revert_init(zend_object *obj, zval *properties_table_snapshot, HashTable *properties_snapshot)
401 {
402 	zend_class_entry *ce = obj->ce;
403 
404 	if (ce->default_properties_count) {
405 		ZEND_ASSERT(properties_table_snapshot);
406 		zval *properties_table = obj->properties_table;
407 
408 		for (int i = 0; i < ce->default_properties_count; i++) {
409 			zval *p = &properties_table[i];
410 			zend_object_dtor_property(obj, p);
411 			ZVAL_COPY_VALUE_PROP(p, &properties_table_snapshot[i]);
412 
413 			zend_property_info *prop_info = ce->properties_info_table[i];
414 			if (Z_ISREF_P(p) && prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
415 				ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(p), prop_info);
416 			}
417 		}
418 
419 		efree(properties_table_snapshot);
420 	}
421 	if (properties_snapshot) {
422 		if (obj->properties != properties_snapshot) {
423 			ZEND_ASSERT((GC_FLAGS(properties_snapshot) & IS_ARRAY_IMMUTABLE) || GC_REFCOUNT(properties_snapshot) >= 1);
424 			zend_release_properties(obj->properties);
425 			obj->properties = properties_snapshot;
426 		} else {
427 			ZEND_ASSERT((GC_FLAGS(properties_snapshot) & IS_ARRAY_IMMUTABLE) || GC_REFCOUNT(properties_snapshot) > 1);
428 			zend_release_properties(properties_snapshot);
429 		}
430 	} else if (obj->properties) {
431 		zend_release_properties(obj->properties);
432 		obj->properties = NULL;
433 	}
434 
435 	OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED;
436 }
437 
zend_lazy_object_compatible(zend_object * real_object,zend_object * lazy_object)438 static bool zend_lazy_object_compatible(zend_object *real_object, zend_object *lazy_object)
439 {
440 	if (EXPECTED(real_object->ce == lazy_object->ce)) {
441 		return true;
442 	}
443 
444 	if (!instanceof_function(lazy_object->ce, real_object->ce)) {
445 		return false;
446 	}
447 
448 	/* zend_hash_num_elements(ce.properties_info) reports the actual number of
449 	 * properties. ce.default_properties_count is off by the number of property
450 	 * overrides. */
451 	if (zend_hash_num_elements(&lazy_object->ce->properties_info) != zend_hash_num_elements(&real_object->ce->properties_info)) {
452 		return false;
453 	}
454 
455 	return lazy_object->ce->destructor == real_object->ce->destructor
456 		&& lazy_object->ce->clone == real_object->ce->clone;
457 }
458 
459 /* Initialize a lazy proxy object */
zend_lazy_object_init_proxy(zend_object * obj)460 static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
461 {
462 	ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
463 	ZEND_ASSERT(!zend_lazy_object_initialized(obj));
464 
465 	/* Prevent object from being released during initialization */
466 	GC_ADDREF(obj);
467 
468 	zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
469 
470 	/* prevent reentrant initialization */
471 	OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY);
472 
473 	/* Call factory */
474 	zval retval;
475 	int argc = 1;
476 	zval zobj;
477 	HashTable *named_params = NULL;
478 	zend_fcall_info_cache *initializer = &info->u.initializer.fcc;
479 	zend_object *instance = NULL;
480 
481 	ZVAL_OBJ(&zobj, obj);
482 
483 	zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params);
484 
485 	if (UNEXPECTED(EG(exception))) {
486 		OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
487 		goto exit;
488 	}
489 
490 	if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT)) {
491 		OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
492 		zend_type_error("Lazy proxy factory must return an instance of a class compatible with %s, %s returned",
493 				ZSTR_VAL(obj->ce->name),
494 				zend_zval_value_name(&retval));
495 		zval_ptr_dtor(&retval);
496 		goto exit;
497 	}
498 
499 	if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || !zend_lazy_object_compatible(Z_OBJ(retval), obj))) {
500 		OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
501 		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.",
502 				zend_zval_value_name(&retval),
503 				ZSTR_VAL(obj->ce->name));
504 		zval_ptr_dtor(&retval);
505 		goto exit;
506 	}
507 
508 	if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) {
509 		OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
510 		zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object");
511 		zval_ptr_dtor(&retval);
512 		goto exit;
513 	}
514 
515 	zend_fcc_dtor(&info->u.initializer.fcc);
516 	zval_ptr_dtor(&info->u.initializer.zv);
517 	info->u.instance = Z_OBJ(retval);
518 	info->flags |= ZEND_LAZY_OBJECT_INITIALIZED;
519 	OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_PROXY;
520 
521 	/* unset() properties of the proxy. This ensures that all accesses are be
522 	 * delegated to the backing instance from now on. */
523 	zend_object_dtor_dynamic_properties(obj);
524 	obj->properties = NULL;
525 
526 	for (int i = 0; i < Z_OBJ(retval)->ce->default_properties_count; i++) {
527 		if (EXPECTED(Z_OBJ(retval)->ce->properties_info_table[i])) {
528 			zend_object_dtor_property(obj, &obj->properties_table[i]);
529 			ZVAL_UNDEF(&obj->properties_table[i]);
530 			Z_PROP_FLAG_P(&obj->properties_table[i]) = IS_PROP_UNINIT | IS_PROP_LAZY;
531 		}
532 	}
533 
534 	instance = Z_OBJ(retval);
535 
536 exit:
537 	if (UNEXPECTED(GC_DELREF(obj) == 0)) {
538 		zend_throw_error(NULL, "Lazy object was released during initialization");
539 		zend_objects_store_del(obj);
540 		instance = NULL;
541 	} else {
542 		gc_check_possible_root((zend_refcounted*) obj);
543 	}
544 
545 	return instance;
546 }
547 
548 /* Initialize a lazy object. */
zend_lazy_object_init(zend_object * obj)549 ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
550 {
551 	ZEND_ASSERT(zend_object_is_lazy(obj));
552 
553 	/* If obj is an initialized lazy proxy, return the real instance. This
554 	 * supports the following pattern:
555 	 * if (zend_lazy_object_must_init(obj)) {
556 	 *     instance = zend_lazy_object_init(obj);
557 	 * }
558 	 */
559 	if (zend_lazy_object_initialized(obj)) {
560 		ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
561 		zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
562 		ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_INITIALIZED);
563 		if (zend_object_is_lazy(info->u.instance)) {
564 			return zend_lazy_object_init(info->u.instance);
565 		}
566 		return info->u.instance;
567 	}
568 
569 	zend_class_entry *ce = obj->ce;
570 
571 	if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) {
572 		if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) {
573 			ZEND_ASSERT(EG(exception));
574 			return NULL;
575 		}
576 	}
577 
578 	if (zend_object_is_lazy_proxy(obj)) {
579 		return zend_lazy_object_init_proxy(obj);
580 	}
581 
582 	/* Prevent object from being released during initialization */
583 	GC_ADDREF(obj);
584 
585 	zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj);
586 
587 	/* Prevent reentrant initialization */
588 	OBJ_EXTRA_FLAGS(obj) &= ~IS_OBJ_LAZY_UNINITIALIZED;
589 
590 	/* Snapshot dynamic properties */
591 	HashTable *properties_snapshot = obj->properties;
592 	if (properties_snapshot) {
593 		GC_TRY_ADDREF(properties_snapshot);
594 	}
595 
596 	zval *properties_table_snapshot = NULL;
597 
598 	/* Snapshot declared properties and initialize lazy properties to their
599 	 * default value */
600 	if (ce->default_properties_count) {
601 		zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce);
602 		zval *properties_table = obj->properties_table;
603 		properties_table_snapshot = emalloc(sizeof(*properties_table_snapshot) * ce->default_properties_count);
604 
605 		for (int i = 0; i < ce->default_properties_count; i++) {
606 			ZVAL_COPY_PROP(&properties_table_snapshot[i], &properties_table[i]);
607 			if (Z_PROP_FLAG_P(&properties_table[i]) & IS_PROP_LAZY) {
608 				ZVAL_COPY_PROP(&properties_table[i], &default_properties_table[i]);
609 			}
610 		}
611 	}
612 
613 	/* Call initializer */
614 	zval retval;
615 	int argc = 1;
616 	zval zobj;
617 	HashTable *named_params = NULL;
618 	zend_object *instance = NULL;
619 
620 	ZVAL_OBJ(&zobj, obj);
621 
622 	zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params);
623 
624 	if (EG(exception)) {
625 		zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
626 		goto exit;
627 	}
628 
629 	if (Z_TYPE(retval) != IS_NULL) {
630 		zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
631 		zval_ptr_dtor(&retval);
632 		zend_type_error("Lazy object initializer must return NULL or no value");
633 		goto exit;
634 	}
635 
636 	if (properties_table_snapshot) {
637 		for (int i = 0; i < obj->ce->default_properties_count; i++) {
638 			zval *p = &properties_table_snapshot[i];
639 			/* Use zval_ptr_dtor directly here (not zend_object_dtor_property),
640 			 * as any reference type_source will have already been deleted in
641 			 * case the prop is not bound to this value anymore. */
642 			i_zval_ptr_dtor(p);
643 		}
644 		efree(properties_table_snapshot);
645 	}
646 
647 	if (properties_snapshot) {
648 		zend_release_properties(properties_snapshot);
649 	}
650 
651 	/* Must be very last in this function, for the
652 	 * zend_lazy_object_has_stale_info() check */
653 	zend_lazy_object_del_info(obj);
654 
655 	instance = obj;
656 
657 exit:
658 	if (UNEXPECTED(GC_DELREF(obj) == 0)) {
659 		zend_throw_error(NULL, "Lazy object was released during initialization");
660 		zend_objects_store_del(obj);
661 		instance = NULL;
662 	} else {
663 		gc_check_possible_root((zend_refcounted*) obj);
664 	}
665 
666 	return instance;
667 }
668 
669 /* Mark an object as non-lazy (after all properties were initialized) */
zend_lazy_object_realize(zend_object * obj)670 void zend_lazy_object_realize(zend_object *obj)
671 {
672 	ZEND_ASSERT(zend_object_is_lazy(obj));
673 	ZEND_ASSERT(!zend_lazy_object_initialized(obj));
674 
675 	zend_lazy_object_del_info(obj);
676 
677 #if ZEND_DEBUG
678 	for (int i = 0; i < obj->ce->default_properties_count; i++) {
679 		ZEND_ASSERT(!(Z_PROP_FLAG_P(&obj->properties_table[i]) & IS_PROP_LAZY));
680 	}
681 #endif
682 
683 	OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED | IS_OBJ_LAZY_PROXY);
684 }
685 
zend_lazy_object_get_properties(zend_object * object)686 ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object)
687 {
688 	ZEND_ASSERT(zend_object_is_lazy(object));
689 
690 	zend_object *tmp = zend_lazy_object_init(object);
691 	if (UNEXPECTED(!tmp)) {
692 		if (object->properties) {
693 			return object->properties;
694 		}
695 		return object->properties = zend_new_array(0);
696 	}
697 
698 	object = tmp;
699 	ZEND_ASSERT(!zend_lazy_object_must_init(object));
700 
701 	return zend_std_get_properties_ex(object);
702 }
703 
704 /* Initialize object and clone it. For proxies, we clone both the proxy and its
705  * real instance, and we don't call __clone() on the proxy. */
zend_lazy_object_clone(zend_object * old_obj)706 zend_object *zend_lazy_object_clone(zend_object *old_obj)
707 {
708 	ZEND_ASSERT(zend_object_is_lazy(old_obj));
709 
710 	if (UNEXPECTED(!zend_lazy_object_initialized(old_obj) && !zend_lazy_object_init(old_obj))) {
711 		ZEND_ASSERT(EG(exception));
712 		/* Clone handler must always return an object. It is discarded later due
713 		 * to the exception. */
714 		zval zv;
715 		object_init_ex(&zv, old_obj->ce);
716 		GC_ADD_FLAGS(Z_OBJ(zv), IS_OBJ_DESTRUCTOR_CALLED);
717 		return Z_OBJ(zv);
718 	}
719 
720 	if (!zend_object_is_lazy_proxy(old_obj)) {
721 		return zend_objects_clone_obj(old_obj);
722 	}
723 
724 	zend_lazy_object_info *info = zend_lazy_object_get_info(old_obj);
725 	zend_class_entry *ce = old_obj->ce;
726 	zend_object *new_proxy = zend_objects_new(ce);
727 
728 	for (int i = 0; i < ce->default_properties_count; i++) {
729 		zval *p = &new_proxy->properties_table[i];
730 		ZVAL_UNDEF(p);
731 		if (EXPECTED(ce->properties_info_table[i])) {
732 			Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY;
733 		} else {
734 			Z_PROP_FLAG_P(p) = 0;
735 		}
736 	}
737 
738 	OBJ_EXTRA_FLAGS(new_proxy) = OBJ_EXTRA_FLAGS(old_obj);
739 
740 	zend_lazy_object_info *new_info = emalloc(sizeof(*info));
741 	*new_info = *info;
742 	new_info->u.instance = zend_objects_clone_obj(info->u.instance);
743 
744 	zend_lazy_object_set_info(new_proxy, new_info);
745 
746 	return new_proxy;
747 }
748 
zend_lazy_object_debug_info(zend_object * object,int * is_temp)749 HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp)
750 {
751 	ZEND_ASSERT(zend_object_is_lazy(object));
752 
753 	if (zend_object_is_lazy_proxy(object)) {
754 		if (zend_lazy_object_initialized(object)) {
755 			HashTable *properties = zend_new_array(0);
756 			zval instance;
757 			ZVAL_OBJ(&instance, zend_lazy_object_get_instance(object));
758 			Z_ADDREF(instance);
759 			zend_hash_str_add(properties, "instance", strlen("instance"), &instance);
760 			*is_temp = 1;
761 			return properties;
762 		}
763 	}
764 
765 	*is_temp = 0;
766 	return zend_get_properties_no_lazy_init(object);
767 }
768 
zend_lazy_object_get_gc(zend_object * zobj,zval ** table,int * n)769 HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n)
770 {
771 	ZEND_ASSERT(zend_object_is_lazy(zobj));
772 
773 	zend_lazy_object_info *info = zend_lazy_object_get_info(zobj);
774 	zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
775 
776 	if (zend_lazy_object_initialized(zobj)) {
777 		ZEND_ASSERT(zend_object_is_lazy_proxy(zobj));
778 		zend_get_gc_buffer_add_obj(gc_buffer, info->u.instance);
779 		zend_get_gc_buffer_use(gc_buffer, table, n);
780 		/* Initialized proxy object can not have properties */
781 		return NULL;
782 	}
783 
784 	zend_fcall_info_cache *fcc = &info->u.initializer.fcc;
785 	if (fcc->object) {
786 		zend_get_gc_buffer_add_obj(gc_buffer, fcc->object);
787 	}
788 	if (fcc->closure) {
789 		zend_get_gc_buffer_add_obj(gc_buffer, fcc->closure);
790 	}
791 	zend_get_gc_buffer_add_zval(gc_buffer, &info->u.initializer.zv);
792 
793 	/* Uninitialized lazy objects can not have dynamic properties, so we can
794 	 * ignore zobj->properties. */
795 	zval *prop = zobj->properties_table;
796 	zval *end = prop + zobj->ce->default_properties_count;
797 	for ( ; prop < end; prop++) {
798 		zend_get_gc_buffer_add_zval(gc_buffer, prop);
799 	}
800 
801 	zend_get_gc_buffer_use(gc_buffer, table, n);
802 	return NULL;
803 }
804 
zend_lazy_object_get_property_info_for_slot(zend_object * obj,zval * slot)805 zend_property_info *zend_lazy_object_get_property_info_for_slot(zend_object *obj, zval *slot)
806 {
807 	ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
808 
809 	zend_property_info **table = obj->ce->properties_info_table;
810 	intptr_t prop_num = slot - obj->properties_table;
811 	if (prop_num >= 0 && prop_num < obj->ce->default_properties_count) {
812 		return table[prop_num];
813 	}
814 
815 	if (!zend_lazy_object_initialized(obj)) {
816 		return NULL;
817 	}
818 
819 	obj = zend_lazy_object_get_instance(obj);
820 	return zend_get_property_info_for_slot(obj, slot);
821 }
822