diff --git a/CHANGELOG b/CHANGELOG index a3b240fff70..991e99dfca5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.29.0 (2026-XX-XX) + * Fix the `matches` operator silently treating PCRE execution errors as non-matches * Add the `HtmlExtension::htmlAttrValue()` method to resolve a single HTML attribute value the way the `html_attr` function renders it * Fix `html_attr` JSON encoding a `Stringable` value in a `data-*` attribute instead of using its string representation * Add documentation comments to attach metadata to nodes (experimental) diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index e14b8b6978b..8d811518cef 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -1188,7 +1188,7 @@ public static function compare($a, $b) } /** - * @throws RuntimeError When an invalid pattern is used + * @throws RuntimeError When the regular expression cannot be evaluated * * @internal */ @@ -1198,7 +1198,11 @@ public static function matches(string $regexp, ?string $str): int throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); }); try { - return preg_match($regexp, $str ?? ''); + if (false === $result = preg_match($regexp, $str ?? '')) { + throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" failed: %s.', $regexp, preg_last_error_msg())); + } + + return $result; } finally { restore_error_handler(); } diff --git a/tests/Fixtures/expressions/matches_error_execution.test b/tests/Fixtures/expressions/matches_error_execution.test new file mode 100644 index 00000000000..79e4014f0cc --- /dev/null +++ b/tests/Fixtures/expressions/matches_error_execution.test @@ -0,0 +1,8 @@ +--TEST-- +Twig reports PCRE execution failures for the "matches" operator +--TEMPLATE-- +{{ subject matches regexp }} +--DATA-- +return ['subject' => 'aX', 'regexp' => '~(*LIMIT_MATCH=1)^(a+)+$~']; +--EXCEPTION-- +Twig\Error\RuntimeError: Regexp "~(*LIMIT_MATCH=1)^(a+)+$~" passed to "matches" failed: Backtrack limit exhausted in "index.twig" at line 2.