Coverage Report

Created: 2026-04-01 06:49

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