@@ -61,17 +61,19 @@ struct RecoveryConsumptionHandle {
61
61
extension Parser . Lookahead {
62
62
/// See `canRecoverTo` that takes 3 specs.
63
63
mutating func canRecoverTo(
64
- _ spec: TokenSpec
64
+ _ spec: TokenSpec ,
65
+ recursionDepth: Int = 1
65
66
) -> RecoveryConsumptionHandle ? {
66
- return canRecoverTo ( spec, spec, spec)
67
+ return canRecoverTo ( spec, spec, spec, recursionDepth : recursionDepth )
67
68
}
68
69
69
70
/// See `canRecoverTo` that takes 3 specs.
70
71
mutating func canRecoverTo(
71
72
_ spec1: TokenSpec ,
72
- _ spec2: TokenSpec
73
+ _ spec2: TokenSpec ,
74
+ recursionDepth: Int = 1
73
75
) -> RecoveryConsumptionHandle ? {
74
- return canRecoverTo ( spec1, spec2, spec1)
76
+ return canRecoverTo ( spec1, spec2, spec1, recursionDepth : recursionDepth )
75
77
}
76
78
77
79
/// Tries eating tokens until it finds a token that matches `spec1`, `spec2` or `spec3`
@@ -84,8 +86,18 @@ extension Parser.Lookahead {
84
86
mutating func canRecoverTo(
85
87
_ spec1: TokenSpec ,
86
88
_ spec2: TokenSpec ,
87
- _ spec3: TokenSpec
89
+ _ spec3: TokenSpec ,
90
+ recursionDepth: Int = 1
88
91
) -> 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
+ }
89
101
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
90
102
if shouldRecordAlternativeTokenChoices {
91
103
recordAlternativeTokenChoice ( for: self . currentToken, choices: [ spec1, spec2, spec3] )
@@ -144,7 +156,7 @@ extension Parser.Lookahead {
144
156
continue
145
157
}
146
158
self . consumeAnyToken ( )
147
- guard self . canRecoverTo ( closingDelimiterSpec) != nil else {
159
+ guard self . canRecoverTo ( closingDelimiterSpec, recursionDepth : recursionDepth + 1 ) != nil else {
148
160
continue
149
161
}
150
162
self . eat ( closingDelimiterSpec)
0 commit comments