Repository: apple/swift-syntax (swift)

Issue:
Swift parser crashes on invalid multi-line string literal
The following crashes the new parser:

```swift
#sourceLocation """\("
```

```
SwiftParser/StringLiterals.swift:346: Fatal error: String segment produced by the lexer should not have unexpected text or trivia because we would drop it during post-processing
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.      Program arguments: /Users/hamish/src/swift-dev/build/Fuzz/swift-macosx-arm64/bin/swift-frontend -typecheck -sdk /Users/hamish/src/MacOSX.sdk /Users/hamish/src/swift-test-arena/main.swift
1.      Swift version 6.2-dev (LLVM 4e0a8255b12089a, Swift 1398b316fee6062)
2.      Compiling with effective version 5.10
3.      While evaluating request ParseTopLevelDeclsRequest(source_file "/Users/hamish/src/swift-test-arena/main.swift")
4.      While evaluating request ParseSourceFileRequest(source_file "/Users/hamish/src/swift-test-arena/main.swift")
5.      With parser at source location: /Users/hamish/src/swift-test-arena/main.swift:1236:17
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend                0x000000010c2b719c llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend                0x000000010c2b58ec llvm::sys::RunSignalHandlers() + 112
2  swift-frontend                0x000000010c2b77e4 SignalHandler(int, __siginfo*, void*) + 296
3  libsystem_platform.dylib      0x0000000182672744 _sigtramp + 56
4  libswiftCore.dylib            0x0000000195d809ac $ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_SSAHSus6UInt32VtF + 176
5  lib_CompilerSwiftParser.dylib 0x0000000119801fbc $s20_CompilerSwiftParser0C0V33postProcessMultilineStringLiteral33_78149DB072C20084E7D780D86E26C3AFLL03rawG15DelimitersToken9openQuote8segments05closeU0Say01_aB6Syntax03RawsX0VG023unexpectedBeforeOpeningU0_AL07openingU0SayAJ0ygh11SegmentListX0V7ElementOGAhM0z13BeforeClosingU0AL07closingU0tALSg_AltLtF + 3080
6  lib_CompilerSwiftParser.dylib 0x00000001197fef84 $s20_CompilerSwiftParser0C0V17parseSimpleString01_aB6Syntax03Rawef11LiteralExprG0VyF + 3908
7  lib_CompilerSwiftParser.dylib 0x000000011972dce8 $s20_CompilerSwiftParser0C0V33parsePoundSourceLocationDirective01_aB6Syntax03RawefgI0VyF + 2968
8  lib_CompilerSwiftParser.dylib 0x0000000119809d74 $s20_CompilerSwiftParser0C0V9parseItem33_008C1B0E0A90478841CED0DF9A7053A1LL12isAtTopLevel13allowInitDecl01_aB6Syntax012RawCodeBlockeU0V0E0OSb_SbtF + 132
9  lib_CompilerSwiftParser.dylib 0x00000001198095e8 $s20_CompilerSwiftParser0C0V18parseCodeBlockItem12isAtTopLevel13allowInitDecl01_aB6Syntax03RawefgO0VSgSb_SbtF + 840
10 lib_CompilerSwiftParser.dylib 0x0000000119806b30 $s20_CompilerSwiftParser0C0V22parseCodeBlockItemList12isAtTopLevel13allowInitDecl5until01_aB6Syntax03RawefghQ0VSb_S2bACzXEtF05$s20_ab17Parser0C0V27parseklef25Items01_aB6Syntax03Rawgh8gH15J0VyFSbACzXEfU_Tf1nncn_n + 100
11 lib_CompilerSwiftParser.dylib 0x000000011987024c $s20_CompilerSwiftSyntax010SourceFileC0V01_aB6ParserE5parse4fromAcD0F0Vz_tFZ + 132
12 swift-frontend                0x00000001078997a0 $s11swiftASTGen15parseSourceFile6buffer10moduleName8filename14declContextPtr4kindSVSo16BridgedStringRefV_A2ISvSgSo0n9GenerateddE4KindVtF + 1188
```

Pull request description:
The only place where we were dropping unexpected tokens or trivia around a string segment was when inferring the indentation of the closing quote. Instead of having a precondition, add the conditions that the last segment doesn’t have any of these here and fall back to the same logic that is used when the indentaiton could not be inferred.

Fixes #3097

Patch:
diff --git a/Sources/SwiftParser/StringLiterals.swift b/Sources/SwiftParser/StringLiterals.swift
index b03c8877def..d3b9c8adc4d 100644
--- a/Sources/SwiftParser/StringLiterals.swift
+++ b/Sources/SwiftParser/StringLiterals.swift
@@ -340,22 +340,6 @@ extension Parser {
     unexpectedBeforeClosingQuote: [RawTokenSyntax],
     closingQuote: RawTokenSyntax
   ) {
-    // -------------------------------------------------------------------------
-    // Precondition
-
-    precondition(
-      allSegments.allSatisfy {
-        if case .stringSegment(let segment) = $0 {
-          return segment.unexpectedBeforeContent == nil
-            && segment.unexpectedAfterContent == nil
-            && segment.content.leadingTriviaByteLength == 0
-        } else {
-          return true
-        }
-      },
-      "String segment produced by the lexer should not have unexpected text or trivia because we would drop it during post-processing"
-    )
-
     // -------------------------------------------------------------------------
     // Variables
 
@@ -395,6 +379,9 @@ extension Parser {
     // Parse indentation of the closing quote
 
     if let lastSegment,
+      lastSegment.unexpectedBeforeContent == nil,
+      lastSegment.unexpectedAfterContent == nil,
+      lastSegment.content.leadingTriviaByteLength == 0,
       let parsedTrivia = parseIndentationTrivia(text: lastSegment.content.tokenText)
     {
       indentationTrivia = parsedTrivia
@@ -409,10 +396,9 @@ extension Parser {
         arena: self.arena
       )
     } else {
-      if let lastSegment = lastSegment {
-        indentationTrivia = TriviaParser.parseTrivia(lastSegment.content.tokenText, position: .leading).prefix(while: {
-          $0.isIndentationWhitespace
-        })
+      if let lastSegment {
+        indentationTrivia = TriviaParser.parseTrivia(lastSegment.content.tokenText, position: .leading)
+          .prefix(while: \.isIndentationWhitespace)
         let indentationByteLength = indentationTrivia.reduce(0, { $0 + $1.byteLength })
         indentation = SyntaxText(rebasing: lastSegment.content.tokenText[0..<indentationByteLength])
         middleSegments.append(.stringSegment(lastSegment))
@@ -703,10 +689,7 @@ extension Parser {
       }
     }
 
-    let (unexpectedBetweenSegmentAndCloseQuote, closeQuote) = self.expect(
-      anyIn: SimpleStringLiteralExprSyntax.ClosingQuoteOptions.self,
-      default: openQuote.closeTokenKind
-    )
+    let (unexpectedBetweenSegmentAndCloseQuote, closeQuote) = self.expect(openQuote.closeTokenKind.spec)
     let closeDelimiter = self.consume(if: .rawStringPoundDelimiter)
 
     if openQuote.tokenKind == .multilineStringQuote, !openQuote.isMissing, !closeQuote.isMissing {


Test patch:
diff --git a/Tests/SwiftParserTest/translated/StringLiteralEofTests.swift b/Tests/SwiftParserTest/translated/StringLiteralEofTests.swift
index 00dc1127bb8..ae17fe465fd 100644
--- a/Tests/SwiftParserTest/translated/StringLiteralEofTests.swift
+++ b/Tests/SwiftParserTest/translated/StringLiteralEofTests.swift
@@ -243,4 +243,37 @@ final class StringLiteralEofTests: ParserTestCase {
         """##
     )
   }
+
+  func testSimpleMultilineStringLiteralWith() {
+    assertParse(
+      #"""
+      #sourceLocation1️⃣(file: 2️⃣"""3️⃣\(4️⃣"5️⃣
+      """#,
+      diagnostics: [
+        DiagnosticSpec(locationMarker: "3️⃣", message: "argument cannot be an interpolated string literal"),
+        DiagnosticSpec(
+          locationMarker: "4️⃣",
+          message: #"expected '"""' to end simple string literal"#,
+          notes: [NoteSpec(locationMarker: "2️⃣", message: #"to match this opening '"""'"#)],
+          fixIts: [#"insert '"""'"#]
+        ),
+        DiagnosticSpec(
+          locationMarker: "4️⃣",
+          message: "expected ', line:' and line number in '#sourceLocation' arguments",
+          fixIts: ["insert ', line:' and line number"]
+        ),
+        DiagnosticSpec(
+          locationMarker: "4️⃣",
+          message: "expected ')' in '#sourceLocation' directive",
+          notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '('")],
+          fixIts: ["insert ')'"]
+        ),
+        DiagnosticSpec(locationMarker: "4️⃣", message: "extra tokens following the #sourceLocation directive"),
+      ],
+      fixedSource: #"""
+        #sourceLocation(file: """\(
+        """, line: <#integer literal#>)"
+        """#
+    )
+  }
 }