Coverage Report

Created: 2026-10-28 06:20

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