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
					
					3 years ago
						Pablo Galindo Salgado
					
					3 years ago
					
						
							committed by
							
								 GitHub
								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 { | 
			
		
	
	
		
			
				
					|  |  | @ -1312,4 +1312,4 @@ invalid_kvpair: | 
			
		
	
		
			
				
					|  |  |  |     | a=expression !(':') { | 
			
		
	
		
			
				
					|  |  |  |         RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->end_col_offset - 1, a->end_lineno, -1, "':' expected after dictionary key") } | 
			
		
	
		
			
				
					|  |  |  |     | expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "cannot use a starred expression in a dictionary value") } | 
			
		
	
		
			
				
					|  |  |  |     | expression a=':' {RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") } | 
			
		
	
		
			
				
					|  |  |  |     | expression a=':' {RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") } | 
			
		
	
										
									
								
							
						 
					 
				 
			
		
			
				
					
					
						
							
								
									
										
											
	
		
			
				
					|  |  | @ -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; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | 
 |