diff --git a/NEWS b/NEWS index 5ea9ecbafb0b..075825c2ba53 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,9 @@ PHP NEWS for grouping. (Klaas Skelte van der Werf) . Fixed bug GH-24265 (Unwind/GracefulExit discarded in zend_fiber_execute()). (David Carlier) + . Fixed bug GH-24715 (Assertion failure in zend_lazy_object_get_properties() + when releasing the initializer resets the object as lazy). + (Lazizbek Ergashev) - Date: . Fixed bug GH-20503 (Assertion failure with ext/date DateInterval property diff --git a/Zend/tests/lazy_objects/gh24715-001.phpt b/Zend/tests/lazy_objects/gh24715-001.phpt new file mode 100644 index 000000000000..4d7cefa1857e --- /dev/null +++ b/Zend/tests/lazy_objects/gh24715-001.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-24715: Object reset as lazy by a destructor triggered when releasing the initializer +--FILE-- +self = $this; + } + public function __destruct() { + global $obj, $reflector; + try { + $reflector->resetAsLazyGhost($obj, function () {}); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + } +} + +new D(); + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(new class { + public function __invoke($obj) { + $obj->a = 1; + } + public function __destruct() { + gc_collect_cycles(); + } +}); + +var_dump(serialize($obj)); +var_dump($reflector->isUninitializedLazyObject($obj)); + +?> +--EXPECT-- +Error: Can not reset an object while it is being initialized +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +bool(false) diff --git a/Zend/tests/lazy_objects/gh24715-002.phpt b/Zend/tests/lazy_objects/gh24715-002.phpt new file mode 100644 index 000000000000..a7aa165a4633 --- /dev/null +++ b/Zend/tests/lazy_objects/gh24715-002.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-24715 002: Object reset as lazy by a destructor triggered when releasing the initializer in markLazyObjectAsInitialized() +--FILE-- +resetAsLazyGhost($obj, function () {}); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + } +} + +$reflector = new ReflectionClass(C::class); +$d = new D(); +$obj = $reflector->newLazyGhost(function ($obj) use ($d) {}); +unset($d); + +var_dump($reflector->markLazyObjectAsInitialized($obj) === $obj); +var_dump($reflector->isUninitializedLazyObject($obj)); + +?> +--EXPECT-- +Error: Can not reset an object while it is being initialized +bool(true) +bool(false) diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c index eb8b5f300eeb..5a9800da3d14 100644 --- a/Zend/zend_lazy_objects.c +++ b/Zend/zend_lazy_objects.c @@ -76,7 +76,9 @@ static void zend_lazy_object_info_dtor_func(zval *pElement) zend_object_release(info->u.instance); } else { zval_ptr_dtor(&info->u.initializer.zv); - zend_fcc_dtor(&info->u.initializer.fcc); + if (ZEND_FCC_INITIALIZED(info->u.initializer.fcc)) { + zend_fcc_dtor(&info->u.initializer.fcc); + } } efree(info); @@ -159,6 +161,20 @@ zend_lazy_object_flags_t zend_lazy_object_get_flags(zend_object *obj) return zend_lazy_object_get_info(obj)->flags; } +/* Release the initializer while the info is still registered, so that + * destructors triggered by this can not reset the object */ +static void zend_lazy_object_release_initializer(zend_object *obj) +{ + zend_lazy_object_info *info = zend_hash_index_find_ptr(&EG(lazy_objects_store).infos, obj->handle); + ZEND_ASSERT(info && !(info->flags & ZEND_LAZY_OBJECT_INITIALIZED)); + + zval initializer_zv; + ZVAL_COPY_VALUE(&initializer_zv, &info->u.initializer.zv); + ZVAL_UNDEF(&info->u.initializer.zv); + zend_fcc_dtor(&info->u.initializer.fcc); + zval_ptr_dtor(&initializer_zv); +} + void zend_lazy_object_del_info(zend_object *obj) { zend_result res = zend_hash_index_del(&EG(lazy_objects_store).infos, obj->handle); @@ -410,6 +426,7 @@ ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj) } } + zend_lazy_object_release_initializer(obj); zend_lazy_object_del_info(obj); return obj; @@ -670,6 +687,8 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj) zend_release_properties(properties_snapshot); } + zend_lazy_object_release_initializer(obj); + /* Must be very last in this function, for the * zend_lazy_object_has_stale_info() check */ zend_lazy_object_del_info(obj);