Skip to content

Commit e3f2ca0

Browse files
authored
Merge pull request #2911 from ahoppen/fix-recovery-stack-overflow
Fix a stack overflow when using `canRecoveryTo` with too many open delimiters
2 parents 0bf5b6c + 020f763 commit e3f2ca0

File tree

2 files changed

+18
-7
lines changed

2 files changed

+18
-7
lines changed

Sources/SwiftParser/Recovery.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,19 @@ struct RecoveryConsumptionHandle {
6161
extension 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)

Tests/SwiftParserTest/ParserTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ class ParserTests: ParserTestCase {
125125
/// Swift compiler. This requires the Swift compiler to have been checked
126126
/// out into the "swift" directory alongside swift-syntax.
127127
func testSwiftValidationTestsuite() throws {
128-
try XCTSkipIf(true, "Crashing with signal 10 in CI")
129128
try XCTSkipIf(longTestsDisabled)
130129
let testDir =
131130
packageDir

0 commit comments

Comments
 (0)