Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 11 additions & 0 deletions deepclone.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,17 @@ static void dc_copy_array(dc_ctx *ctx, HashTable *src_ht, zval *dst, zval *mask_
zend_hash_real_init_mixed(Z_ARRVAL_P(mask_dst));

ZEND_HASH_FOREACH_KEY_VAL(src_ht, idx, key, src_val) {
/* __serialize() may return the object's raw property table (e.g.
* Random\Randomizer before PHP 8.3), where declared properties are
* IS_INDIRECT slots into the object. Resolve them like the native
* serializer does, or the payload would retain pointers that dangle
* once the source object is released. */
if (UNEXPECTED(Z_TYPE_P(src_val) == IS_INDIRECT)) {
src_val = Z_INDIRECT_P(src_val);
if (Z_TYPE_P(src_val) == IS_UNDEF) {
continue;
}
}
zval undef, null_marker;
ZVAL_UNDEF(&undef);
ZVAL_NULL(&null_marker);
Expand Down
34 changes: 34 additions & 0 deletions tests/deepclone_randomizer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
deepclone round-trips a Random\Randomizer that does not outlive its payload
--EXTENSIONS--
deepclone
--SKIPIF--
<?php if (PHP_VERSION_ID < 80200) die('skip requires PHP 8.2'); ?>
--FILE--
<?php

// Before PHP 8.3, Randomizer::__serialize() returns its raw property table,
// whose "engine" slot is an IS_INDIRECT pointer into the object. The payload
// must not retain it once the source Randomizer is released.
$expected = (new Random\Randomizer(new Random\Engine\Mt19937(42)))->getInt(1, PHP_INT_MAX);

$d = deepclone_to_array(new Random\Randomizer(new Random\Engine\Mt19937(42)));
gc_collect_cycles();
$clone = deepclone_from_array($d);
var_dump($clone instanceof Random\Randomizer);
var_dump($expected === $clone->getInt(1, PHP_INT_MAX));

// Same with the Randomizer nested in a temporary object graph
$g = deepclone_from_array(deepclone_to_array((object) ['list' => [(object) ['r' => new Random\Randomizer(new Random\Engine\Mt19937(9))]]]));
var_dump($g->list[0]->r instanceof Random\Randomizer);

// And behind a shared identity
$r = new Random\Randomizer(new Random\Engine\Mt19937(5));
$c = deepclone_from_array(deepclone_to_array([$r, $r]));
var_dump($c[0] === $c[1]);
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)