Skip to content
Merged
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
107 changes: 107 additions & 0 deletions Zend/tests/lazy_objects/gh15999.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
--TEST--
Lazy Objects: GH-15999: Object is released during initialization
--FILE--
<?php

class C {
public $s;
public function __destruct() {
var_dump(__METHOD__);
}
}

print "# Ghost:\n";

$r = new ReflectionClass(C::class);

$o = $r->newLazyGhost(function ($obj) {
global $o;
$o = null;
});

try {
$o->s = $o;
} catch (Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}

print "# Proxy:\n";

$o = $r->newLazyProxy(function ($obj) {
global $o;
$o = null;
return new C();
});

try {
$o->s = $o;
} catch (Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}

print "# GC cycle:\n";

$o = $r->newLazyGhost(function ($obj) {
global $o;
$o->s = $o;
$o = null;
gc_collect_cycles();
});

$o->s = $o;
gc_collect_cycles();

print "# Nested error (ghost):\n";

$r = new ReflectionClass(C::class);

$o = $r->newLazyGhost(function ($obj) {
global $o;
$o = null;
return new stdClass;
});

try {
$o->s = $o;
} catch (Error $e) {
do {
printf("%s: %s\n", $e::class, $e->getMessage());
} while ($e = $e->getPrevious());
}

print "# Nested error (proxy):\n";

$r = new ReflectionClass(C::class);

$o = $r->newLazyProxy(function ($obj) {
global $o;
$o = null;
return new stdClass;
});

try {
$o->s = $o;
} catch (Error $e) {
do {
printf("%s: %s\n", $e::class, $e->getMessage());
} while ($e = $e->getPrevious());
}

?>
==DONE==
--EXPECT--
# Ghost:
string(13) "C::__destruct"
Error: Lazy object was released during initialization
# Proxy:
string(13) "C::__destruct"
Error: Lazy object was released during initialization
# GC cycle:
string(13) "C::__destruct"
# Nested error (ghost):
Error: Lazy object was released during initialization
TypeError: Lazy object initializer must return NULL or no value
# 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.
==DONE==
47 changes: 38 additions & 9 deletions Zend/zend_lazy_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
ZEND_ASSERT(!zend_lazy_object_initialized(obj));

/* Prevent object from being released during initialization */
GC_ADDREF(obj);

zend_lazy_object_info *info = zend_lazy_object_get_info(obj);

/* prevent reentrant initialization */
Expand All @@ -440,14 +443,15 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
zval zobj;
HashTable *named_params = NULL;
zend_fcall_info_cache *initializer = &info->u.initializer.fcc;
zend_object *instance = NULL;

ZVAL_OBJ(&zobj, obj);

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

if (UNEXPECTED(EG(exception))) {
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
return NULL;
goto exit;
}

if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT)) {
Expand All @@ -456,8 +460,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
ZSTR_VAL(obj->ce->name),
zend_zval_value_name(&retval));
zval_ptr_dtor(&retval);
return NULL;

goto exit;
}

if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || !zend_lazy_object_compatible(Z_OBJ(retval), obj))) {
Expand All @@ -466,14 +469,14 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
zend_zval_value_name(&retval),
ZSTR_VAL(obj->ce->name));
zval_ptr_dtor(&retval);
return NULL;
goto exit;
}

if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) {
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object");
zval_ptr_dtor(&retval);
return NULL;
goto exit;
}

zend_fcc_dtor(&info->u.initializer.fcc);
Expand All @@ -495,7 +498,18 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
}
}

return Z_OBJ(retval);
instance = Z_OBJ(retval);

exit:
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
zend_throw_error(NULL, "Lazy object was released during initialization");
zend_objects_store_del(obj);
instance = NULL;
} else {
gc_check_possible_root((zend_refcounted*) obj);
}

return instance;
}

/* Initialize a lazy object. */
Expand Down Expand Up @@ -529,6 +543,9 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
return zend_lazy_object_init_proxy(obj);
}

/* Prevent object from being released during initialization */
GC_ADDREF(obj);

zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj);

/* Prevent reentrant initialization */
Expand Down Expand Up @@ -562,21 +579,22 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
int argc = 1;
zval zobj;
HashTable *named_params = NULL;
zend_object *instance = NULL;

ZVAL_OBJ(&zobj, obj);

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

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

if (Z_TYPE(retval) != IS_NULL) {
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");
return NULL;
goto exit;
}

if (properties_table_snapshot) {
Expand All @@ -598,7 +616,18 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
* zend_lazy_object_has_stale_info() check */
zend_lazy_object_del_info(obj);

return obj;
instance = obj;

exit:
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
zend_throw_error(NULL, "Lazy object was released during initialization");
zend_objects_store_del(obj);
instance = NULL;
} else {
gc_check_possible_root((zend_refcounted*) obj);
}

return instance;
}

/* Mark an object as non-lazy (after all properties were initialized) */
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,10 @@ found:;
variable_ptr = &EG(error_zval);
goto exit;
}
/* value may have changed during initialization */
if (UNEXPECTED(Z_ISREF_P(value))) {
value = Z_REFVAL_P(value);
}
return zend_std_write_property(zobj, name, value, cache_slot);
}
/* }}} */
Expand Down
Loading