Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings

Commit fb8777b

Browse filesBrowse the repository at this point in the historyBrowse files
iliaalnicolas-grekas
authored andcommitted
[VarExporter] Fix export of string parameter defaults containing escaped quotes
Reflection renders string defaults lossily: inner single quotes are emitted unescaped and backslashes are double-escaped, so re-tokenizing the rendered default in exportDefault() produces broken PHP in the generated lazy-proxy code (parse error at best). For any rendering that looks like a pure quoted string, export the real value returned by ReflectionParameter::getDefaultValue() instead, keeping the token-based path as a fallback when the value cannot be resolved.
1 parent 1d8a783 commit fb8777b
Copy full SHA for fb8777b

2 files changed

+63Lines changed: 63 additions & 0 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/Symfony/Component/VarExporter/ProxyHelper.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/VarExporter/ProxyHelper.php
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,20 @@ private static function exportDefault(\ReflectionParameter $param, $namespace):
510510
if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) {
511511
return VarExporter::export($param->getDefaultValue());
512512
}
513+
if (str_starts_with($default, "'") && str_ends_with($default, "'")) {
514+
// Reflection renders string literals without escaping quotes, which makes them
515+
// impossible to re-tokenize. Export the value instead, but only when it renders
516+
// back identically, which also tells literals apart from concatenations.
517+
try {
518+
$value = $param->getDefaultValue();
519+
} catch (\Throwable) {
520+
$value = null;
521+
}
522+
523+
if (\is_string($value) && $default === self::renderString($value)) {
524+
return VarExporter::export($value);
525+
}
526+
}
513527

514528
$regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/";
515529
$parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
@@ -550,4 +564,18 @@ private static function exportSymbol(string $symbol, bool $mightBeRootConst, str
550564

551565
return '\\'.substr($symbol, $ns + 1);
552566
}
567+
568+
private static function renderString(string $value): string
569+
{
570+
return "'".preg_replace_callback('/[\x00-\x1F\x7F-\xFF\\\\]/', static fn ($m) => match ($m[0]) {
571+
'\\' => '\\\\',
572+
"\t" => '\t',
573+
"\n" => '\n',
574+
"\v" => '\v',
575+
"\f" => '\f',
576+
"\r" => '\r',
577+
"\e" => '\e',
578+
default => \sprintf('\x%02X', \ord($m[0])),
579+
}, $value)."'";
580+
}
553581
}
Collapse file

‎src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,24 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
166166
$this->assertSame($expected, ProxyHelper::generateLazyProxy(null, [new \ReflectionClass(TestForProxyHelperInterface1::class), new \ReflectionClass(TestForProxyHelperInterface2::class)]));
167167
}
168168

169+
public function testGenerateLazyProxyWithStringParameterDefaults()
170+
{
171+
$code = ProxyHelper::generateLazyProxy(null, [new \ReflectionClass(TestForProxyHelperStringDefaults::class)]);
172+
173+
$this->assertStringContainsString('public function singleQuote($a = \'it\\\'s a "quote" \\\\ back\')', $code);
174+
$this->assertStringContainsString('public function doubleQuote($a = \'a"b\\\\c\')', $code);
175+
$this->assertStringContainsString('public function concat($a = \'pre-\' . \\'.TestForProxyHelperStringDefaults::class.'::SUFFIX . \'-post\')', $code);
176+
177+
eval('class TestForProxyHelperStringDefaultsImpl'.$code);
178+
179+
foreach ((new \ReflectionClass(TestForProxyHelperStringDefaults::class))->getMethods() as $method) {
180+
$expected = $method->getParameters()[0]->getDefaultValue();
181+
$actual = (new \ReflectionParameter([\TestForProxyHelperStringDefaultsImpl::class, $method->name], 'a'))->getDefaultValue();
182+
183+
$this->assertSame($expected, $actual, $method->name);
184+
}
185+
}
186+
169187
/**
170188
* @dataProvider classWithUnserializeMagicMethodProvider
171189
*/
@@ -329,6 +347,23 @@ public function foo2(?Bar $b, ...$d): self;
329347
public static function foo3(): string;
330348
}
331349

350+
interface TestForProxyHelperStringDefaults
351+
{
352+
public const SUFFIX = 'suffix';
353+
354+
public function singleQuote($a = 'it\'s a "quote" \ back');
355+
356+
public function doubleQuote($a = 'a"b\\c');
357+
358+
public function newLine($a = "line1\nline2");
359+
360+
public function nullByte($a = "nul\0byte");
361+
362+
public function emoji($a = "emoji \u{1F600} end");
363+
364+
public function concat($a = 'pre-'.self::SUFFIX.'-post');
365+
}
366+
332367
class TestSignatureFQ extends \stdClass
333368
{
334369
public function bar(

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.