@@ -457,9 +457,9 @@ def _compile_info(code, pattern, flags):
457457_ASCII_WORD = frozenset (b'_' ) | frozenset (
458458 range (0x30 , 0x3a )) | frozenset (range (0x41 , 0x5b )) | frozenset (range (0x61 , 0x7b ))
459459_PROBE_LIMIT = 64 # cap on the size of a finite atom used as a witness set
460- _FOLLOW_LIMIT = 64 # cap on the size of a follower set (each empty branch
461- # alternative appends the continuation again, so unchecked
462- # growth is exponential, e.g. for (|)(|)(|)...)
460+ _FOLLOW_LIMIT = 64 # cap on a follower set: an empty branch alternative
461+ # re- appends the continuation, exponential for (|)(|)...
462+ _DEPTH_LIMIT = 100 # cap on the follower-scan recursion: one level per group
463463
464464def _tolower (c , flags ):
465465 if flags & SRE_FLAG_UNICODE :
@@ -689,9 +689,12 @@ def _leading_atom(data):
689689 lead = a
690690 return lead
691691
692- def _first_consumers (seq , i , flags , cont ):
692+ def _first_consumers (seq , i , flags , cont , depth = 0 ):
693693 # Atoms for every character that could be consumed at position i of seq;
694694 # cont is the same for what follows seq. None if it can't be analyzed.
695+ if depth >= _DEPTH_LIMIT :
696+ return None
697+ depth += 1
695698 acc = []
696699 n = len (seq )
697700 while i < n :
@@ -702,30 +705,30 @@ def _first_consumers(seq, i, flags, cont):
702705 if op is SUBPATTERN :
703706 if av [1 ] or av [2 ]:
704707 return None # flag-scoping group: atoms can't carry their flags
705- after = _first_consumers (seq , i + 1 , flags , cont )
708+ after = _first_consumers (seq , i + 1 , flags , cont , depth )
706709 if after is None :
707710 return None
708- inner = _first_consumers (av [3 ].data , 0 , flags , after )
711+ inner = _first_consumers (av [3 ].data , 0 , flags , after , depth )
709712 return None if inner is None else acc + inner
710713 if op is ATOMIC_GROUP :
711- after = _first_consumers (seq , i + 1 , flags , cont )
714+ after = _first_consumers (seq , i + 1 , flags , cont , depth )
712715 if after is None :
713716 return None
714- inner = _first_consumers (av .data , 0 , flags , after )
717+ inner = _first_consumers (av .data , 0 , flags , after , depth )
715718 return None if inner is None else acc + inner
716719 if op is BRANCH :
717- after = _first_consumers (seq , i + 1 , flags , cont )
720+ after = _first_consumers (seq , i + 1 , flags , cont , depth )
718721 if after is None :
719722 return None
720723 for alt in av [1 ]:
721- a = _first_consumers (alt .data , 0 , flags , after )
724+ a = _first_consumers (alt .data , 0 , flags , after , depth )
722725 if a is None or len (acc ) + len (a ) > _FOLLOW_LIMIT :
723726 return None
724727 acc += a
725728 return acc
726729 if op in _REPEAT_CODES :
727730 mn , mx , p = av
728- sub = _first_consumers (p .data , 0 , flags , None )
731+ sub = _first_consumers (p .data , 0 , flags , None , depth )
729732 if sub is None or len (acc ) + len (sub ) > _FOLLOW_LIMIT :
730733 return None
731734 acc += sub
@@ -851,5 +854,9 @@ def _walk(seq, flags, cont):
851854
852855def optimize (pattern , flags ):
853856 """Rewrite a parsed pattern in place and return it."""
854- _walk (pattern .data , flags , [])
857+ try :
858+ _walk (pattern .data , flags , [])
859+ except RecursionError :
860+ # A backstop for _DEPTH_LIMIT; the rewrites already applied are sound.
861+ pass
855862 return pattern
0 commit comments