diff --git a/Zend/tests/lazy_objects/gh15999_001.phpt b/Zend/tests/lazy_objects/gh15999_001.phpt new file mode 100644 index 000000000000..9fa70752d6b4 --- /dev/null +++ b/Zend/tests/lazy_objects/gh15999_001.phpt @@ -0,0 +1,112 @@ +--TEST-- +Lazy Objects: GH-15999 001: Object is released during initialization +--FILE-- +newLazyGhost(function ($obj) { + global $o; + $o = null; +}); +$p = new stdClass; + +try { + $o->s = $p; +} 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(); +}); +$p = new stdClass; + +try { + $o->s = $p; +} 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(); +}); +$p = new stdClass; + +$o->s = $p; +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; +}); +$p = new stdClass; + +try { + $o->s = $p; +} 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; +}); +$p = new stdClass; + +try { + $o->s = $p; +} 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== diff --git a/Zend/tests/lazy_objects/gh15999_002.phpt b/Zend/tests/lazy_objects/gh15999_002.phpt new file mode 100644 index 000000000000..5227e14ba2ac --- /dev/null +++ b/Zend/tests/lazy_objects/gh15999_002.phpt @@ -0,0 +1,55 @@ +--TEST-- +Lazy Objects: GH-15999 002: Assigned value is changed during lazy object initialization +--FILE-- +newLazyGhost(function ($obj) { + global $p; + $p = null; +}); + +$p = new stdClass; +var_dump($o->s = $p); +var_dump($o->s); + +print "# Proxy:\n"; + +$r = new ReflectionClass(C::class); + +$o = $r->newLazyProxy(function ($obj) { + global $p; + $p = null; + return new C(); +}); + +$p = new stdClass; +var_dump($o->s = $p); +var_dump($o->s); + +?> +==DONE== +--EXPECTF-- +# Ghost: +object(stdClass)#%d (0) { +} +object(stdClass)#%d (0) { +} +# Proxy: +string(13) "C::__destruct" +object(stdClass)#%d (0) { +} +object(stdClass)#%d (0) { +} +==DONE== +string(13) "C::__destruct" diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c index 8be8e8b7b773..aea0410f8f5d 100644 --- a/Zend/zend_lazy_objects.c +++ b/Zend/zend_lazy_objects.c @@ -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 */ @@ -440,6 +443,7 @@ 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); @@ -447,7 +451,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj) 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)) { @@ -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))) { @@ -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); @@ -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. */ @@ -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 */ @@ -562,6 +579,7 @@ 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); @@ -569,14 +587,14 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj) 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) { @@ -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) */ diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 64e494200eea..95ed091efb3f 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1183,13 +1183,21 @@ found:; exit: return variable_ptr; -lazy_init: +lazy_init:; + /* backup value as it may change during initialization */ + zval backup; + ZVAL_COPY(&backup, value); + zobj = zend_lazy_object_init(zobj); if (UNEXPECTED(!zobj)) { variable_ptr = &EG(error_zval); + zval_ptr_dtor(&backup); goto exit; } - return zend_std_write_property(zobj, name, value, cache_slot); + + variable_ptr = zend_std_write_property(zobj, name, &backup, cache_slot); + zval_ptr_dtor(&backup); + return variable_ptr; } /* }}} */