From 7a451f1d773ec4cb61b448a6cf2d0d874b558ff7 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Feb 2026 15:22:05 +0700 Subject: [PATCH 1/5] [Php72] Handle crash on hex backreference on PregReplaceEModifierRector --- .../call_function_variable_numeric.php.inc | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc diff --git a/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc b/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc new file mode 100644 index 00000000000..92b5e966e87 --- /dev/null +++ b/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc @@ -0,0 +1,29 @@ + +----- + From 8eec3997cf353f7d7f0c2ed080cdfff6b55fb5a8 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Feb 2026 15:22:33 +0700 Subject: [PATCH 2/5] fix --- .../NodeFactory/AnonymousFunctionFactory.php | 19 +++++++++++++++++++ src/PhpParser/Parser/InlineCodeParser.php | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/rules/Php72/NodeFactory/AnonymousFunctionFactory.php b/rules/Php72/NodeFactory/AnonymousFunctionFactory.php index be6c9ba511b..e2f9381ef8c 100644 --- a/rules/Php72/NodeFactory/AnonymousFunctionFactory.php +++ b/rules/Php72/NodeFactory/AnonymousFunctionFactory.php @@ -82,6 +82,7 @@ public function create( public function createAnonymousFunctionFromExpr(Expr $expr): ?Closure { $stringValue = $this->inlineCodeParser->stringify($expr); + $stringValue = $this->normalizeHexBackreferenceExpression($stringValue); $phpCode = 'simplePhpParser->parseString($phpCode); @@ -125,6 +126,24 @@ public function createAnonymousFunctionFromExpr(Expr $expr): ?Closure return $anonymousFunction; } + private function normalizeHexBackreferenceExpression(string $stringValue): string + { + // Only rewrite when the expression has no quotes, to avoid mutating string literals. + if (str_contains($stringValue, "'") || str_contains($stringValue, '"')) { + return $stringValue; + } + + return Strings::replace( + $stringValue, + '#0x(?\\\d+|\$\d+)#', + static function (array $match): string { + $backreference = $match['backreference']; + $number = ltrim($backreference, '\\$'); + return 'hexdec($matches[' . $number . '])'; + } + ); + } + /** * @param Param[] $params * @return string[] diff --git a/src/PhpParser/Parser/InlineCodeParser.php b/src/PhpParser/Parser/InlineCodeParser.php index 6bc47e76d49..0a5d0661d65 100644 --- a/src/PhpParser/Parser/InlineCodeParser.php +++ b/src/PhpParser/Parser/InlineCodeParser.php @@ -52,6 +52,11 @@ */ private const string BACKREFERENCE_NO_DOUBLE_QUOTE_START_REGEX = '#(?\$\d+)#'; + /** + * @see https://regex101.com/r/8Dk7Qn/1 + */ + private const string HEX_BACKREFERENCE_REGEX = '#0x(?\\\d+|\$\d+)#'; + public function __construct( private BetterStandardPrinter $betterStandardPrinter, private SimplePhpParser $simplePhpParser, @@ -81,6 +86,20 @@ public function parseString(string $fileContent): array public function stringify(Expr $expr): string { if ($expr instanceof String_) { + if (! str_contains($expr->value, "'") && ! str_contains($expr->value, '"') && StringUtils::isMatch( + $expr->value, + self::HEX_BACKREFERENCE_REGEX + )) { + return Strings::replace( + $expr->value, + self::HEX_BACKREFERENCE_REGEX, + static function (array $match): string { + $number = ltrim((string) $match['backreference'], '\\$'); + return 'hexdec($matches[' . $number . '])'; + } + ); + } + if (! StringUtils::isMatch($expr->value, self::BACKREFERENCE_NO_QUOTE_REGEX)) { return Strings::replace( $expr->value, From 70c9343e926bf929cb8c72987c2bfee7f60cac52 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Feb 2026 15:28:24 +0700 Subject: [PATCH 3/5] clean up regex --- .../NodeFactory/AnonymousFunctionFactory.php | 19 ------------------- src/PhpParser/Parser/InlineCodeParser.php | 4 ++-- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/rules/Php72/NodeFactory/AnonymousFunctionFactory.php b/rules/Php72/NodeFactory/AnonymousFunctionFactory.php index e2f9381ef8c..be6c9ba511b 100644 --- a/rules/Php72/NodeFactory/AnonymousFunctionFactory.php +++ b/rules/Php72/NodeFactory/AnonymousFunctionFactory.php @@ -82,7 +82,6 @@ public function create( public function createAnonymousFunctionFromExpr(Expr $expr): ?Closure { $stringValue = $this->inlineCodeParser->stringify($expr); - $stringValue = $this->normalizeHexBackreferenceExpression($stringValue); $phpCode = 'simplePhpParser->parseString($phpCode); @@ -126,24 +125,6 @@ public function createAnonymousFunctionFromExpr(Expr $expr): ?Closure return $anonymousFunction; } - private function normalizeHexBackreferenceExpression(string $stringValue): string - { - // Only rewrite when the expression has no quotes, to avoid mutating string literals. - if (str_contains($stringValue, "'") || str_contains($stringValue, '"')) { - return $stringValue; - } - - return Strings::replace( - $stringValue, - '#0x(?\\\d+|\$\d+)#', - static function (array $match): string { - $backreference = $match['backreference']; - $number = ltrim($backreference, '\\$'); - return 'hexdec($matches[' . $number . '])'; - } - ); - } - /** * @param Param[] $params * @return string[] diff --git a/src/PhpParser/Parser/InlineCodeParser.php b/src/PhpParser/Parser/InlineCodeParser.php index 0a5d0661d65..ca64905f5e5 100644 --- a/src/PhpParser/Parser/InlineCodeParser.php +++ b/src/PhpParser/Parser/InlineCodeParser.php @@ -53,9 +53,9 @@ private const string BACKREFERENCE_NO_DOUBLE_QUOTE_START_REGEX = '#(?\$\d+)#'; /** - * @see https://regex101.com/r/8Dk7Qn/1 + * @see https://regex101.com/r/13mVVg/1 */ - private const string HEX_BACKREFERENCE_REGEX = '#0x(?\\\d+|\$\d+)#'; + private const string HEX_BACKREFERENCE_REGEX = '#0x(?\$\d+)#'; public function __construct( private BetterStandardPrinter $betterStandardPrinter, From 1f6b343b5ebbc87b574182a7fa1b3f9e3ad71b3d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Feb 2026 15:31:41 +0700 Subject: [PATCH 4/5] clean up regex --- .../call_function_variable_numeric.php.inc | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc diff --git a/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc b/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc deleted file mode 100644 index 92b5e966e87..00000000000 --- a/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_numeric.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - From 31e237eacc80598ad44e40c94d5bcbb8fe55a1fa Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Feb 2026 15:31:49 +0700 Subject: [PATCH 5/5] rename fixture --- ...nction_variable_hex_back_reference.php.inc | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_hex_back_reference.php.inc diff --git a/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_hex_back_reference.php.inc b/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_hex_back_reference.php.inc new file mode 100644 index 00000000000..ae1e255f260 --- /dev/null +++ b/rules-tests/Php55/Rector/FuncCall/PregReplaceEModifierRector/Fixture/call_function_variable_hex_back_reference.php.inc @@ -0,0 +1,29 @@ + +----- +