@@ -61,17 +61,19 @@ struct RecoveryConsumptionHandle {
6161extension Parser . Lookahead {
6262 /// See `canRecoverTo` that takes 3 specs.
6363 mutating func canRecoverTo(
64- _ spec: TokenSpec
64+ _ spec: TokenSpec ,
65+ recursionDepth: Int = 1
6566 ) -> RecoveryConsumptionHandle ? {
66- return canRecoverTo ( spec, spec, spec)
67+ return canRecoverTo ( spec, spec, spec, recursionDepth : recursionDepth )
6768 }
6869
6970 /// See `canRecoverTo` that takes 3 specs.
7071 mutating func canRecoverTo(
7172 _ spec1: TokenSpec ,
72- _ spec2: TokenSpec
73+ _ spec2: TokenSpec ,
74+ recursionDepth: Int = 1
7375 ) -> RecoveryConsumptionHandle ? {
74- return canRecoverTo ( spec1, spec2, spec1)
76+ return canRecoverTo ( spec1, spec2, spec1, recursionDepth : recursionDepth )
7577 }
7678
7779 /// Tries eating tokens until it finds a token that matches `spec1`, `spec2` or `spec3`
@@ -84,8 +86,18 @@ extension Parser.Lookahead {
8486 mutating func canRecoverTo(
8587 _ spec1: TokenSpec ,
8688 _ spec2: TokenSpec ,
87- _ spec3: TokenSpec
89+ _ spec3: TokenSpec ,
90+ recursionDepth: Int = 1
8891 ) -> RecoveryConsumptionHandle ? {
92+ if recursionDepth > 10 {
93+ // `canRecoverTo` calls itself recursively if it finds a nested opening token, eg. when calling `canRecoverTo` on
94+ // `{{{`. To avoid stack overflowing, limit the number of nested `canRecoverTo` calls we make. Since returning a
95+ // recovery handle from this function only improves error recovery but is not necessary for correctness, bailing
96+ // from recovery is safe.
97+ // The value 10 was chosen fairly arbitrarily. It seems unlikely that we get useful recovery if we find more than
98+ // 10 nested open and closing delimiters.
99+ return nil
100+ }
89101 #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
90102 if shouldRecordAlternativeTokenChoices {
91103 recordAlternativeTokenChoice ( for: self . currentToken, choices: [ spec1, spec2, spec3] )
@@ -144,7 +156,7 @@ extension Parser.Lookahead {
144156 continue
145157 }
146158 self . consumeAnyToken ( )
147- guard self . canRecoverTo ( closingDelimiterSpec) != nil else {
159+ guard self . canRecoverTo ( closingDelimiterSpec, recursionDepth : recursionDepth + 1 ) != nil else {
148160 continue
149161 }
150162 self . eat ( closingDelimiterSpec)
0 commit comments