Coverage Report

Created: 2026-07-25 06:39

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