Skip to content

Commit c8ce478

Browse files
committed
Allow lazy ghosts to remain partially uninitialized
Fixes GH-19224
1 parent 747ecce commit c8ce478

File tree

9 files changed

+161
-57
lines changed

9 files changed

+161
-57
lines changed

Zend/tests/lazy_objects/gh15999_001.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ Error: Lazy object was released during initialization
105105
string(13) "C::__destruct"
106106
# Nested error (ghost):
107107
Error: Lazy object was released during initialization
108-
TypeError: Lazy object initializer must return NULL or no value
108+
TypeError: Lazy object initializer must return NULL, no value or true
109109
# Nested error (proxy):
110110
Error: Lazy object was released during initialization
111111
TypeError: The real instance class stdClass is not compatible with the proxy class C. 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.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
--TEST--
2+
GH-19224: Lazy ghosts may be partially initialized
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public int $x;
8+
public int $y;
9+
}
10+
11+
$rc = new ReflectionClass(C::class);
12+
$initializer = function ($obj, $prop) {
13+
if ($prop === null || $prop === 'x') {
14+
$obj->x = 1;
15+
}
16+
if ($prop === null || $prop === 'y') {
17+
$obj->y = 2;
18+
}
19+
return true;
20+
};
21+
22+
$obj = $rc->newLazyGhost($initializer);
23+
var_dump($obj);
24+
var_dump($obj->x);
25+
var_dump($obj);
26+
var_dump($obj->y);
27+
var_dump($obj);
28+
29+
$obj = $rc->newLazyGhost($initializer);
30+
foreach ($obj as $prop) {}
31+
var_dump($obj);
32+
33+
// Object is realized when no specific prop is requested.
34+
$obj = $rc->newLazyGhost(function () {});
35+
foreach ($obj as $prop) {}
36+
var_dump($obj);
37+
38+
?>
39+
--EXPECTF--
40+
lazy ghost object(C)#%d (0) {
41+
["x"]=>
42+
uninitialized(int)
43+
["y"]=>
44+
uninitialized(int)
45+
}
46+
int(1)
47+
lazy ghost object(C)#%d (1) {
48+
["x"]=>
49+
int(1)
50+
["y"]=>
51+
uninitialized(int)
52+
}
53+
int(2)
54+
object(C)#%d (2) {
55+
["x"]=>
56+
int(1)
57+
["y"]=>
58+
int(2)
59+
}
60+
object(C)#%d (2) {
61+
["x"]=>
62+
int(1)
63+
["y"]=>
64+
int(2)
65+
}
66+
object(C)#%d (0) {
67+
["x"]=>
68+
uninitialized(int)
69+
["y"]=>
70+
uninitialized(int)
71+
}

Zend/tests/lazy_objects/init_exception_001.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ for ($i = 0; $i < 2; $i++) {
2424
--EXPECTF--
2525
Fatal error: Uncaught Exception in %s:%d
2626
Stack trace:
27-
#0 %s(%d): {closure:%s:%d}(Object(C))
27+
#0 %s(%d): {closure:%s:%d}(Object(C), 'c')
2828
#1 {main}
2929
thrown in %s on line %d

Zend/tests/lazy_objects/initializer_must_return_the_right_type.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ lazy ghost object(C)#%d (0) {
106106
uninitialized(int)
107107
}
108108
string(11) "initializer"
109-
TypeError: Lazy object initializer must return NULL or no value
109+
TypeError: Lazy object initializer must return NULL, no value or true
110110
lazy ghost object(C)#%d (0) {
111111
["b"]=>
112112
uninitialized(int)

Zend/zend_lazy_objects.c

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,11 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
558558

559559
/* Initialize a lazy object. */
560560
ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
561+
{
562+
return zend_lazy_object_init_ex(obj, NULL);
563+
}
564+
565+
ZEND_API zend_object *zend_lazy_object_init_ex(zend_object *obj, zend_string *prop)
561566
{
562567
ZEND_ASSERT(zend_object_is_lazy(obj));
563568

@@ -618,27 +623,52 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
618623

619624
/* Call initializer */
620625
zval retval;
621-
int argc = 1;
622-
zval zobj;
626+
int argc = 2;
623627
HashTable *named_params = NULL;
624628
zend_object *instance = NULL;
625629

626-
ZVAL_OBJ(&zobj, obj);
630+
zval args[2];
631+
ZVAL_OBJ(&args[0], obj);
632+
if (prop) {
633+
ZVAL_STR(&args[1], prop);
634+
} else {
635+
ZVAL_NULL(&args[1]);
636+
}
627637

628-
zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params);
638+
zend_call_known_fcc(initializer, &retval, argc, args, named_params);
629639

630640
if (EG(exception)) {
631641
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
632642
goto exit;
633643
}
634644

635-
if (Z_TYPE(retval) != IS_NULL) {
645+
if (Z_TYPE(retval) != IS_NULL && Z_TYPE(retval) != IS_TRUE) {
636646
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
637647
zval_ptr_dtor(&retval);
638-
zend_type_error("Lazy object initializer must return NULL or no value");
648+
zend_type_error("Lazy object initializer must return NULL, no value or true");
639649
goto exit;
640650
}
641651

652+
/* Restore IS_PROP_LAZY flags for flags that remain uninitialized. */
653+
int lazy_properties_count = 0;
654+
if (Z_TYPE(retval) == IS_TRUE && prop) {
655+
if (ce->default_properties_count) {
656+
for (int i = 0; i < ce->default_properties_count; i++) {
657+
zval *prop = &obj->properties_table[i];
658+
if (Z_TYPE_P(prop) == IS_UNDEF
659+
&& (Z_PROP_FLAG_P(&properties_table_snapshot[i]) & IS_PROP_LAZY)) {
660+
lazy_properties_count++;
661+
Z_PROP_FLAG_P(prop) = IS_PROP_UNINIT | IS_PROP_LAZY;
662+
}
663+
}
664+
}
665+
if (lazy_properties_count != 0) {
666+
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED;
667+
zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
668+
info->lazy_properties_count = lazy_properties_count;
669+
}
670+
}
671+
642672
if (properties_table_snapshot) {
643673
for (int i = 0; i < obj->ce->default_properties_count; i++) {
644674
zval *p = &properties_table_snapshot[i];
@@ -654,9 +684,11 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
654684
zend_release_properties(properties_snapshot);
655685
}
656686

657-
/* Must be very last in this function, for the
658-
* zend_lazy_object_has_stale_info() check */
659-
zend_lazy_object_del_info(obj);
687+
if (!(OBJ_EXTRA_FLAGS(obj) & IS_OBJ_LAZY_UNINITIALIZED)) {
688+
/* Must be very last in this function, for the
689+
* zend_lazy_object_has_stale_info() check */
690+
zend_lazy_object_del_info(obj);
691+
}
660692

661693
instance = obj;
662694

Zend/zend_lazy_objects.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ ZEND_API zend_object *zend_object_make_lazy(zend_object *obj,
6262
zend_class_entry *class_type, zval *initializer_zv,
6363
zend_fcall_info_cache *initializer_fcc, zend_lazy_object_flags_t flags);
6464
ZEND_API zend_object *zend_lazy_object_init(zend_object *obj);
65+
ZEND_API zend_object *zend_lazy_object_init_ex(zend_object *obj, zend_string *prop_name);
6566
ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj);
6667

6768
void zend_lazy_objects_init(zend_lazy_objects_store *store);

Zend/zend_object_handlers.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
953953
uninit_error:
954954
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
955955
if (!prop_info || (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY)) {
956-
zobj = zend_lazy_object_init(zobj);
956+
zobj = zend_lazy_object_init_ex(zobj, name);
957957
if (!zobj) {
958958
retval = &EG(uninitialized_zval);
959959
goto exit;
@@ -1004,7 +1004,7 @@ static zval *forward_write_to_lazy_object(zend_object *zobj,
10041004
zval backup;
10051005
ZVAL_COPY(&backup, value);
10061006

1007-
zend_object *instance = zend_lazy_object_init(zobj);
1007+
zend_object *instance = zend_lazy_object_init_ex(zobj, name);
10081008
if (UNEXPECTED(!instance)) {
10091009
zval_ptr_dtor(&backup);
10101010
return &EG(error_zval);
@@ -1395,7 +1395,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
13951395
UNEXPECTED((*zend_get_property_guard(zobj, name)) & IN_GET) ||
13961396
UNEXPECTED(prop_info && (Z_PROP_FLAG_P(retval) & IS_PROP_UNINIT))) {
13971397
if (UNEXPECTED(zend_lazy_object_must_init(zobj) && (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY))) {
1398-
zobj = zend_lazy_object_init(zobj);
1398+
zobj = zend_lazy_object_init_ex(zobj, name);
13991399
if (!zobj) {
14001400
return &EG(error_zval);
14011401
}
@@ -1453,7 +1453,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
14531453
}
14541454
}
14551455
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
1456-
zobj = zend_lazy_object_init(zobj);
1456+
zobj = zend_lazy_object_init_ex(zobj, name);
14571457
if (!zobj) {
14581458
return &EG(error_zval);
14591459
}
@@ -1527,7 +1527,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
15271527
}
15281528
if (UNEXPECTED(Z_PROP_FLAG_P(slot) & IS_PROP_UNINIT)) {
15291529
if (UNEXPECTED(zend_lazy_object_must_init(zobj) && (Z_PROP_FLAG_P(slot) & IS_PROP_LAZY))) {
1530-
zobj = zend_lazy_object_init(zobj);
1530+
zobj = zend_lazy_object_init_ex(zobj, name);
15311531
if (!zobj) {
15321532
return;
15331533
}
@@ -1580,7 +1580,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
15801580
}
15811581

15821582
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
1583-
zobj = zend_lazy_object_init(zobj);
1583+
zobj = zend_lazy_object_init_ex(zobj, name);
15841584
if (!zobj) {
15851585
return;
15861586
}
@@ -2396,7 +2396,7 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has
23962396
lazy_init:
23972397
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
23982398
if (!value || (Z_PROP_FLAG_P(value) & IS_PROP_LAZY)) {
2399-
zobj = zend_lazy_object_init(zobj);
2399+
zobj = zend_lazy_object_init_ex(zobj, name);
24002400
if (!zobj) {
24012401
result = 0;
24022402
goto exit;

Zend/zend_vm_def.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,7 +2513,7 @@ ZEND_VM_C_LABEL(fast_assign_obj):
25132513
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) {
25142514
name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R));
25152515
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
2516-
zobj = zend_lazy_object_init(zobj);
2516+
zobj = zend_lazy_object_init_ex(zobj, name);
25172517
if (!zobj) {
25182518
value = &EG(uninitialized_zval);
25192519
ZEND_VM_C_GOTO(free_and_exit_assign_obj);
@@ -6162,7 +6162,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
61626162
}
61636163

61646164
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
6165-
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
6165+
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
61666166
if (c->ce->type == ZEND_USER_CLASS) {
61676167
/* Recursion protection only applied to user constants, GH-18463 */
61686168
CONST_PROTECT_RECURSION(c);

0 commit comments

Comments
 (0)