Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Zend/tests/lazy_objects/gh15999_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Error: Lazy object was released during initialization
string(13) "C::__destruct"
# Nested error (ghost):
Error: Lazy object was released during initialization
TypeError: Lazy object initializer must return NULL or no value
TypeError: Lazy object initializer must return NULL, no value or true
# Nested error (proxy):
Error: Lazy object was released during initialization
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.
Expand Down
71 changes: 71 additions & 0 deletions Zend/tests/lazy_objects/gh19224.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
--TEST--
GH-19224: Lazy ghosts may be partially initialized
--FILE--
<?php

class C {
public int $x;
public int $y;
}

$rc = new ReflectionClass(C::class);
$initializer = function ($obj, $prop) {
if ($prop === null || $prop === 'x') {
$obj->x = 1;
}
if ($prop === null || $prop === 'y') {
$obj->y = 2;
}
return true;
};

$obj = $rc->newLazyGhost($initializer);
var_dump($obj);
var_dump($obj->x);
var_dump($obj);
var_dump($obj->y);
var_dump($obj);

$obj = $rc->newLazyGhost($initializer);
foreach ($obj as $prop) {}
var_dump($obj);

// Object is realized when no specific prop is requested.
$obj = $rc->newLazyGhost(function () {});
foreach ($obj as $prop) {}
var_dump($obj);

?>
--EXPECTF--
lazy ghost object(C)#%d (0) {
["x"]=>
uninitialized(int)
["y"]=>
uninitialized(int)
}
int(1)
lazy ghost object(C)#%d (1) {
["x"]=>
int(1)
["y"]=>
uninitialized(int)
}
int(2)
object(C)#%d (2) {
["x"]=>
int(1)
["y"]=>
int(2)
}
object(C)#%d (2) {
["x"]=>
int(1)
["y"]=>
int(2)
}
object(C)#%d (0) {
["x"]=>
uninitialized(int)
["y"]=>
uninitialized(int)
}
2 changes: 1 addition & 1 deletion Zend/tests/lazy_objects/init_exception_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ for ($i = 0; $i < 2; $i++) {
--EXPECTF--
Fatal error: Uncaught Exception in %s:%d
Stack trace:
#0 %s(%d): {closure:%s:%d}(Object(C))
#0 %s(%d): {closure:%s:%d}(Object(C), 'c')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ lazy ghost object(C)#%d (0) {
uninitialized(int)
}
string(11) "initializer"
TypeError: Lazy object initializer must return NULL or no value
TypeError: Lazy object initializer must return NULL, no value or true
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
Expand Down
50 changes: 41 additions & 9 deletions Zend/zend_lazy_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,11 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)

/* Initialize a lazy object. */
ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
{
return zend_lazy_object_init_ex(obj, NULL);
}

ZEND_API zend_object *zend_lazy_object_init_ex(zend_object *obj, zend_string *prop)
{
ZEND_ASSERT(zend_object_is_lazy(obj));

Expand Down Expand Up @@ -618,27 +623,52 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)

/* Call initializer */
zval retval;
int argc = 1;
zval zobj;
int argc = 2;
HashTable *named_params = NULL;
zend_object *instance = NULL;

ZVAL_OBJ(&zobj, obj);
zval args[2];
ZVAL_OBJ(&args[0], obj);
if (prop) {
ZVAL_STR(&args[1], prop);
} else {
ZVAL_NULL(&args[1]);
}

zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params);
zend_call_known_fcc(initializer, &retval, argc, args, named_params);

if (EG(exception)) {
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
goto exit;
}

if (Z_TYPE(retval) != IS_NULL) {
if (Z_TYPE(retval) != IS_NULL && Z_TYPE(retval) != IS_TRUE) {
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
zval_ptr_dtor(&retval);
zend_type_error("Lazy object initializer must return NULL or no value");
zend_type_error("Lazy object initializer must return NULL, no value or true");
goto exit;
}

/* Restore IS_PROP_LAZY flags for flags that remain uninitialized. */
int lazy_properties_count = 0;
if (Z_TYPE(retval) == IS_TRUE && prop) {
if (ce->default_properties_count) {
for (int i = 0; i < ce->default_properties_count; i++) {
zval *prop = &obj->properties_table[i];
if (Z_TYPE_P(prop) == IS_UNDEF
&& (Z_PROP_FLAG_P(&properties_table_snapshot[i]) & IS_PROP_LAZY)) {
lazy_properties_count++;
Z_PROP_FLAG_P(prop) = IS_PROP_UNINIT | IS_PROP_LAZY;
}
}
}
if (lazy_properties_count != 0) {
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED;
zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
info->lazy_properties_count = lazy_properties_count;
}
}

if (properties_table_snapshot) {
for (int i = 0; i < obj->ce->default_properties_count; i++) {
zval *p = &properties_table_snapshot[i];
Expand All @@ -654,9 +684,11 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
zend_release_properties(properties_snapshot);
}

/* Must be very last in this function, for the
* zend_lazy_object_has_stale_info() check */
zend_lazy_object_del_info(obj);
if (!(OBJ_EXTRA_FLAGS(obj) & IS_OBJ_LAZY_UNINITIALIZED)) {
/* Must be very last in this function, for the
* zend_lazy_object_has_stale_info() check */
zend_lazy_object_del_info(obj);
}

instance = obj;

Expand Down
1 change: 1 addition & 0 deletions Zend/zend_lazy_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ ZEND_API zend_object *zend_object_make_lazy(zend_object *obj,
zend_class_entry *class_type, zval *initializer_zv,
zend_fcall_info_cache *initializer_fcc, zend_lazy_object_flags_t flags);
ZEND_API zend_object *zend_lazy_object_init(zend_object *obj);
ZEND_API zend_object *zend_lazy_object_init_ex(zend_object *obj, zend_string *prop_name);
ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj);

void zend_lazy_objects_init(zend_lazy_objects_store *store);
Expand Down
14 changes: 7 additions & 7 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
uninit_error:
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
if (!prop_info || (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY)) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
retval = &EG(uninitialized_zval);
goto exit;
Expand Down Expand Up @@ -1004,7 +1004,7 @@ static zval *forward_write_to_lazy_object(zend_object *zobj,
zval backup;
ZVAL_COPY(&backup, value);

zend_object *instance = zend_lazy_object_init(zobj);
zend_object *instance = zend_lazy_object_init_ex(zobj, name);
if (UNEXPECTED(!instance)) {
zval_ptr_dtor(&backup);
return &EG(error_zval);
Expand Down Expand Up @@ -1395,7 +1395,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
UNEXPECTED((*zend_get_property_guard(zobj, name)) & IN_GET) ||
UNEXPECTED(prop_info && (Z_PROP_FLAG_P(retval) & IS_PROP_UNINIT))) {
if (UNEXPECTED(zend_lazy_object_must_init(zobj) && (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY))) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
return &EG(error_zval);
}
Expand Down Expand Up @@ -1453,7 +1453,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
}
}
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
return &EG(error_zval);
}
Expand Down Expand Up @@ -1527,7 +1527,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
}
if (UNEXPECTED(Z_PROP_FLAG_P(slot) & IS_PROP_UNINIT)) {
if (UNEXPECTED(zend_lazy_object_must_init(zobj) && (Z_PROP_FLAG_P(slot) & IS_PROP_LAZY))) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
return;
}
Expand Down Expand Up @@ -1580,7 +1580,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
}

if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
return;
}
Expand Down Expand Up @@ -2396,7 +2396,7 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has
lazy_init:
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
if (!value || (Z_PROP_FLAG_P(value) & IS_PROP_LAZY)) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
result = 0;
goto exit;
Expand Down
4 changes: 2 additions & 2 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2513,7 +2513,7 @@ ZEND_VM_C_LABEL(fast_assign_obj):
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) {
name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R));
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
zobj = zend_lazy_object_init(zobj);
zobj = zend_lazy_object_init_ex(zobj, name);
if (!zobj) {
value = &EG(uninitialized_zval);
ZEND_VM_C_GOTO(free_and_exit_assign_obj);
Expand Down Expand Up @@ -6162,7 +6162,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
}

bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
if (c->ce->type == ZEND_USER_CLASS) {
/* Recursion protection only applied to user constants, GH-18463 */
CONST_PROTECT_RECURSION(c);
Expand Down
Loading