Skip to content

Commit c6266d8

Browse files
committed
Fix GC inconsistency
1 parent e88e049 commit c6266d8

3 files changed

Lines changed: 102 additions & 7 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
GH-21999: GC inconsistency with lazy object, var_dump(), and object comparison
3+
--CREDITS--
4+
kid-lxy
5+
--FILE--
6+
<?php
7+
8+
class C {
9+
public function __construct() {
10+
printf("%s\n". __METHOD__);
11+
}
12+
public $a;
13+
public $b;
14+
}
15+
16+
function test(string $name, object $obj) {
17+
$reflector = new ReflectionClass(C::class);
18+
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass);
19+
var_dump(!$reflector->isUninitializedLazyObject($obj));
20+
var_dump($obj); // builds obj->properties
21+
}
22+
23+
$reflector = new ReflectionClass(C::class);
24+
$obj = $reflector->newLazyGhost(function ($obj) {
25+
$obj->__construct(); // throws: initialization fails
26+
});
27+
test('Ghost', $obj);
28+
try {
29+
// zend_std_compare() fetches obj->properties. zend_compare_symbol_tables()
30+
// adds obj->properties to GC roots.
31+
$obj > $reflector->newLazyProxy(function () {
32+
return new C();
33+
});
34+
} catch (\Throwable $e) {
35+
echo $e::class, ": ", $e->getMessage(), "\n";
36+
gc_collect_cycles();
37+
}
38+
39+
?>
40+
--EXPECTF--
41+
bool(false)
42+
lazy ghost object(C)#%d (1) {
43+
["a"]=>
44+
object(stdClass)#%d (0) {
45+
}
46+
}
47+
ArgumentCountError: 2 arguments are required, 1 given
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GH-21999: GC inconsistency with lazy object, var_dump(), and object comparison
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public function __construct() {
8+
printf("%s\n". __METHOD__);
9+
}
10+
public $a;
11+
public $b;
12+
}
13+
14+
function test(string $name, object $obj) {
15+
$reflector = new ReflectionClass(C::class);
16+
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass);
17+
var_dump(!$reflector->isUninitializedLazyObject($obj));
18+
var_dump($obj);
19+
}
20+
21+
$reflector = new ReflectionClass(C::class);
22+
$obj = $reflector->newLazyGhost(function ($obj) {
23+
$obj->__construct();
24+
});
25+
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, $obj);
26+
27+
try {
28+
var_dump($obj < $reflector->newLazyGhost(function () {}));
29+
} catch (\Throwable $e) {
30+
echo $e::class, ": ", $e->getMessage(), "\n";
31+
gc_collect_cycles();
32+
}
33+
34+
?>
35+
--EXPECT--
36+
ArgumentCountError: 2 arguments are required, 1 given

Zend/zend_lazy_objects.c

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -797,16 +797,28 @@ HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n)
797797
}
798798
zend_get_gc_buffer_add_zval(gc_buffer, &info->u.initializer.zv);
799799

800-
/* Uninitialized lazy objects can not have dynamic properties, so we can
801-
* ignore zobj->properties. */
802-
zval *prop = zobj->properties_table;
803-
zval *end = prop + zobj->ce->default_properties_count;
804-
for ( ; prop < end; prop++) {
805-
zend_get_gc_buffer_add_zval(gc_buffer, prop);
800+
/* Lazy objects may have a properties ht in two cases:
801+
* - After fetching debug infos
802+
* - After lazy init failed in zend_std_get_properties()
803+
*
804+
* In the latter case zobj->properties is an empty ht. We should ignore it,
805+
* otherwise we may mask references to the GC. In the former case we must
806+
* return it, otherwise the GC may traverse properties twice in case the ht
807+
* it a root by itself. */
808+
zend_array *ht;
809+
if (zobj->properties && zend_hash_num_elements(zobj->properties) > 0) {
810+
ht = zobj->properties;
811+
} else {
812+
zval *prop = zobj->properties_table;
813+
zval *end = prop + zobj->ce->default_properties_count;
814+
for ( ; prop < end; prop++) {
815+
zend_get_gc_buffer_add_zval(gc_buffer, prop);
816+
}
817+
ht = NULL;
806818
}
807819

808820
zend_get_gc_buffer_use(gc_buffer, table, n);
809-
return NULL;
821+
return ht;
810822
}
811823

812824
zend_property_info *zend_lazy_object_get_property_info_for_slot(zend_object *obj, zval *slot)

0 commit comments

Comments
 (0)