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
8 changes: 6 additions & 2 deletions src/DeepClone/DeepClone.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ private static function prepare($values, &$objectsPool, &$refsPool, &$objectsCou
// method of that name, so it is gated behind
// allow_named_closures, which both ends must enable.
if (!$allowNamedClosures) {
throw new \ValueError('deepclone_to_array(): serializing a closure over the named callable "'.$r->name.'" requires enabling the allow_named_closures option');
throw new \ValueError('deepclone_to_array(): serializing a closure over the named callable "'.$r->name.'" requires enabling the "allow_named_closures" option; do it only if you trust the input; alternatively, install the "deepclone" extension, which can reference callables declared in constant expressions');
}
if (null !== $allowedSet && !isset($allowedSet['closure'])) {
throw new \ValueError('deepclone_to_array(): class "Closure" is not allowed');
Expand Down Expand Up @@ -1655,7 +1655,11 @@ private static function resolveConstExprClosureScalar($value, ?array $allowedCla
if (null === $found) {
throw new \ValueError('deepclone_from_array(): Argument #1 ($data) malformed payload, const-expr-closure references unknown closure index '.$closureIndex);
}
if ($line !== $foundLine = (new \ReflectionFunction($found))->getStartLine()) {
// Internal functions (e.g. a global strlen(...) reference) have no
// start line; getStartLine() returns false, which the extension encodes
// as 0. Normalize so such a reference does not look stale.
$foundLine = (new \ReflectionFunction($found))->getStartLine() ?: 0;
if ($line !== $foundLine) {
throw new \ValueError('deepclone_from_array(): Argument #1 ($data) stale payload, const-expr-closure moved from line '.$line.' to line '.$foundLine);
}

Expand Down
21 changes: 20 additions & 1 deletion tests/DeepClone/DeepCloneTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,9 @@ public function testClosurePrivateMethodWireFormatAndRoundTrip()
public function testToArrayNamedClosureRequiresOptIn()
{
$this->expectException(\ValueError::class);
$this->expectExceptionMessage('serializing a closure over the named callable "strlen" requires enabling the allow_named_closures option');
// Substring common to the polyfill and extension messages (the polyfill
// quotes the option name and appends an extension hint).
$this->expectExceptionMessage('serializing a closure over the named callable "strlen" requires enabling the');
deepclone_to_array(\Closure::fromCallable('strlen'));
}

Expand Down Expand Up @@ -2554,4 +2556,21 @@ public function testToArrayConstExprClosureFirstClassCallableUsesDeclarationSite
$this->assertSame(ConstExprFccFixture::class, $d['prepared'][0]);
$this->assertTrue(deepclone_from_array($d)());
}

/**
* @requires PHP 8.5
*/
public function testFromArrayConstExprClosureGlobalInternalFunction()
{
// The extension can reference a global internal function declared in an
// attribute (e.g. #[ConstExprAttr(strlen(...))]); such a reference has
// no start line and is encoded with line 0. The polyfill cannot produce
// these (no reflection hook), but must decode them: ReflectionFunction
// reports no start line for an internal function, so the line-0
// reference must not be treated as stale.
$payload = ['classes' => '', 'objectMeta' => 0, 'prepared' => [ConstExprGlobalFccFixture::class, '$p', 0, 0, 0], 'mask' => 1];

$r = deepclone_from_array($payload);
$this->assertSame(5, $r('hello'));
}
}
6 changes: 6 additions & 0 deletions tests/DeepClone/fixtures85.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,9 @@ public static function helper(): bool
return true;
}
}

class ConstExprGlobalFccFixture
{
#[ConstExprAttr(strlen(...))]
public string $p = '';
}
Loading