Skip to content

Commit 3a21b1f

Browse files
committed
Fix GH-24715: Lazy ghost reset by a destructor while releasing its initializer
1 parent d5ab360 commit 3a21b1f

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

‎NEWS‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ PHP NEWS
3030
for grouping. (Klaas Skelte van der Werf)
3131
. Fixed bug GH-24265 (Unwind/GracefulExit discarded in
3232
zend_fiber_execute()). (David Carlier)
33+
. Fixed bug GH-24715 (Assertion failure in zend_lazy_object_get_properties()
34+
when releasing the initializer resets the object as lazy).
35+
(Lazizbek Ergashev)
3336

3437
- Date:
3538
. Fixed bug GH-20503 (Assertion failure with ext/date DateInterval property
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
GH-24715: Object reset as lazy by a destructor triggered when releasing the initializer
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public $a;
8+
}
9+
10+
class D {
11+
public $self;
12+
public function __construct() {
13+
$this->self = $this;
14+
}
15+
public function __destruct() {
16+
global $obj, $reflector;
17+
try {
18+
$reflector->resetAsLazyGhost($obj, function () {});
19+
} catch (Error $e) {
20+
printf("%s: %s\n", $e::class, $e->getMessage());
21+
}
22+
}
23+
}
24+
25+
new D();
26+
27+
$reflector = new ReflectionClass(C::class);
28+
$obj = $reflector->newLazyGhost(new class {
29+
public function __invoke($obj) {
30+
$obj->a = 1;
31+
}
32+
public function __destruct() {
33+
gc_collect_cycles();
34+
}
35+
});
36+
37+
var_dump(serialize($obj));
38+
var_dump($reflector->isUninitializedLazyObject($obj));
39+
40+
?>
41+
--EXPECT--
42+
Error: Can not reset an object while it is being initialized
43+
string(24) "O:1:"C":1:{s:1:"a";i:1;}"
44+
bool(false)

‎Zend/zend_lazy_objects.c‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ static void zend_lazy_object_info_dtor_func(zval *pElement)
7676
zend_object_release(info->u.instance);
7777
} else {
7878
zval_ptr_dtor(&info->u.initializer.zv);
79-
zend_fcc_dtor(&info->u.initializer.fcc);
79+
if (ZEND_FCC_INITIALIZED(info->u.initializer.fcc)) {
80+
zend_fcc_dtor(&info->u.initializer.fcc);
81+
}
8082
}
8183

8284
efree(info);
@@ -670,6 +672,15 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
670672
zend_release_properties(properties_snapshot);
671673
}
672674

675+
/* Release the initializer while the info is still registered, so that
676+
* destructors triggered by this can not reset the object */
677+
zend_lazy_object_info *info = zend_hash_index_find_ptr(&EG(lazy_objects_store).infos, obj->handle);
678+
zval initializer_zv;
679+
ZVAL_COPY_VALUE(&initializer_zv, &info->u.initializer.zv);
680+
ZVAL_UNDEF(&info->u.initializer.zv);
681+
zend_fcc_dtor(&info->u.initializer.fcc);
682+
zval_ptr_dtor(&initializer_zv);
683+
673684
/* Must be very last in this function, for the
674685
* zend_lazy_object_has_stale_info() check */
675686
zend_lazy_object_del_info(obj);

0 commit comments

Comments
 (0)