Skip to content

Commit 8f36c73

Browse files
authored
[3.10] gh-93671: Avoid exponential backtracking in deeply nested sequence patterns in match statements (GH-93680) (#93690)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>. (cherry picked from commit 53a8b17) Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
1 parent 9041b00 commit 8f36c73

File tree

4 files changed

+37
-2
lines changed

4 files changed

+37
-2
lines changed

Grammar/python.gram

+4-2
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ as_pattern[pattern_ty]:
248248
or_pattern[pattern_ty]:
249249
| patterns[asdl_pattern_seq*]='|'.closed_pattern+ {
250250
asdl_seq_LEN(patterns) == 1 ? asdl_seq_GET(patterns, 0) : _PyAST_MatchOr(patterns, EXTRA) }
251-
closed_pattern[pattern_ty]:
251+
252+
closed_pattern[pattern_ty] (memo):
252253
| literal_pattern
253254
| capture_pattern
254255
| wildcard_pattern
@@ -329,7 +330,8 @@ maybe_sequence_pattern[asdl_seq*]:
329330
maybe_star_pattern[pattern_ty]:
330331
| star_pattern
331332
| pattern
332-
star_pattern[pattern_ty]:
333+
334+
star_pattern[pattern_ty] (memo):
333335
| '*' target=pattern_capture_target {
334336
_PyAST_MatchStar(target->v.Name.id, EXTRA) }
335337
| '*' wildcard_pattern {

Lib/test/test_patma.py

+21
Original file line numberDiff line numberDiff line change
@@ -3138,6 +3138,27 @@ def f(command): # 0
31383138
self.assertListEqual(self._trace(f, "go x"), [1, 2, 3])
31393139
self.assertListEqual(self._trace(f, "spam"), [1, 2, 3])
31403140

3141+
def test_parser_deeply_nested_patterns(self):
3142+
# Deeply nested patterns can cause exponential backtracking when parsing.
3143+
# See gh-93671 for more information.
3144+
3145+
levels = 100
3146+
3147+
patterns = [
3148+
"A" + "(" * levels + ")" * levels,
3149+
"{1:" * levels + "1" + "}" * levels,
3150+
"[" * levels + "1" + "]" * levels,
3151+
]
3152+
3153+
for pattern in patterns:
3154+
with self.subTest(pattern):
3155+
code = inspect.cleandoc("""
3156+
match None:
3157+
case {}:
3158+
pass
3159+
""".format(pattern))
3160+
compile(code, "<string>", "exec")
3161+
31413162

31423163
if __name__ == "__main__":
31433164
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix some exponential backtrace case happening with deeply nested sequence
2+
patterns in match statements. Patch by Pablo Galindo

Parser/parser.c

+10
Original file line numberDiff line numberDiff line change
@@ -5920,6 +5920,10 @@ closed_pattern_rule(Parser *p)
59205920
return NULL;
59215921
}
59225922
pattern_ty _res = NULL;
5923+
if (_PyPegen_is_memoized(p, closed_pattern_type, &_res)) {
5924+
p->level--;
5925+
return _res;
5926+
}
59235927
int _mark = p->mark;
59245928
{ // literal_pattern
59255929
if (p->error_indicator) {
@@ -6075,6 +6079,7 @@ closed_pattern_rule(Parser *p)
60756079
}
60766080
_res = NULL;
60776081
done:
6082+
_PyPegen_insert_memo(p, _mark, closed_pattern_type, _res);
60786083
p->level--;
60796084
return _res;
60806085
}
@@ -7598,6 +7603,10 @@ star_pattern_rule(Parser *p)
75987603
return NULL;
75997604
}
76007605
pattern_ty _res = NULL;
7606+
if (_PyPegen_is_memoized(p, star_pattern_type, &_res)) {
7607+
p->level--;
7608+
return _res;
7609+
}
76017610
int _mark = p->mark;
76027611
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
76037612
p->error_indicator = 1;
@@ -7682,6 +7691,7 @@ star_pattern_rule(Parser *p)
76827691
}
76837692
_res = NULL;
76847693
done:
7694+
_PyPegen_insert_memo(p, _mark, star_pattern_type, _res);
76857695
p->level--;
76867696
return _res;
76877697
}

0 commit comments

Comments
 (0)