Browse Source
gh-93671: Avoid exponential backtracking in deeply nested sequence patterns in match statements (GH-93680)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
pull/93699/head
Pablo Galindo Salgado
4 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with
36 additions and
3 deletions
-
Grammar/python.gram
-
Lib/test/test_patma.py
-
Misc/NEWS.d/next/Core and Builtins/2022-06-10-12-03-17.gh-issue-93671.idkQqG.rst
-
Parser/parser.c
|
|
|
@ -471,7 +471,7 @@ or_pattern[pattern_ty]: |
|
|
|
| patterns[asdl_pattern_seq*]='|'.closed_pattern+ { |
|
|
|
asdl_seq_LEN(patterns) == 1 ? asdl_seq_GET(patterns, 0) : _PyAST_MatchOr(patterns, EXTRA) } |
|
|
|
|
|
|
|
closed_pattern[pattern_ty]: |
|
|
|
closed_pattern[pattern_ty] (memo): |
|
|
|
| literal_pattern |
|
|
|
| capture_pattern |
|
|
|
| wildcard_pattern |
|
|
|
@ -558,7 +558,7 @@ maybe_star_pattern[pattern_ty]: |
|
|
|
| star_pattern |
|
|
|
| pattern |
|
|
|
|
|
|
|
star_pattern[pattern_ty]: |
|
|
|
star_pattern[pattern_ty] (memo): |
|
|
|
| '*' target=pattern_capture_target { |
|
|
|
_PyAST_MatchStar(target->v.Name.id, EXTRA) } |
|
|
|
| '*' wildcard_pattern { |
|
|
|
|
|
|
|
@ -3151,6 +3151,27 @@ class TestTracing(unittest.TestCase): |
|
|
|
self.assertListEqual(self._trace(f, "go x"), [1, 2, 3]) |
|
|
|
self.assertListEqual(self._trace(f, "spam"), [1, 2, 3]) |
|
|
|
|
|
|
|
def test_parser_deeply_nested_patterns(self): |
|
|
|
# Deeply nested patterns can cause exponential backtracking when parsing. |
|
|
|
# See gh-93671 for more information. |
|
|
|
|
|
|
|
levels = 100 |
|
|
|
|
|
|
|
patterns = [ |
|
|
|
"A" + "(" * levels + ")" * levels, |
|
|
|
"{1:" * levels + "1" + "}" * levels, |
|
|
|
"[" * levels + "1" + "]" * levels, |
|
|
|
] |
|
|
|
|
|
|
|
for pattern in patterns: |
|
|
|
with self.subTest(pattern): |
|
|
|
code = inspect.cleandoc(""" |
|
|
|
match None: |
|
|
|
case {}: |
|
|
|
pass |
|
|
|
""".format(pattern)) |
|
|
|
compile(code, "<string>", "exec") |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
""" |
|
|
|
|
|
|
|
@ -0,0 +1,2 @@ |
|
|
|
Fix some exponential backtrace case happening with deeply nested sequence |
|
|
|
patterns in match statements. Patch by Pablo Galindo |
|
|
|
@ -7945,6 +7945,10 @@ closed_pattern_rule(Parser *p) |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
pattern_ty _res = NULL; |
|
|
|
if (_PyPegen_is_memoized(p, closed_pattern_type, &_res)) { |
|
|
|
p->level--; |
|
|
|
return _res; |
|
|
|
} |
|
|
|
int _mark = p->mark; |
|
|
|
{ // literal_pattern |
|
|
|
if (p->error_indicator) { |
|
|
|
@ -8100,6 +8104,7 @@ closed_pattern_rule(Parser *p) |
|
|
|
} |
|
|
|
_res = NULL; |
|
|
|
done: |
|
|
|
_PyPegen_insert_memo(p, _mark, closed_pattern_type, _res); |
|
|
|
p->level--; |
|
|
|
return _res; |
|
|
|
} |
|
|
|
@ -9623,6 +9628,10 @@ star_pattern_rule(Parser *p) |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
pattern_ty _res = NULL; |
|
|
|
if (_PyPegen_is_memoized(p, star_pattern_type, &_res)) { |
|
|
|
p->level--; |
|
|
|
return _res; |
|
|
|
} |
|
|
|
int _mark = p->mark; |
|
|
|
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) { |
|
|
|
p->error_indicator = 1; |
|
|
|
@ -9707,6 +9716,7 @@ star_pattern_rule(Parser *p) |
|
|
|
} |
|
|
|
_res = NULL; |
|
|
|
done: |
|
|
|
_PyPegen_insert_memo(p, _mark, star_pattern_type, _res); |
|
|
|
p->level--; |
|
|
|
return _res; |
|
|
|
} |
|
|
|
|