diff --git a/Sources/_StringProcessing/ByteCodeGen.swift b/Sources/_StringProcessing/ByteCodeGen.swift index e8c92f2b5..8d7224710 100644 --- a/Sources/_StringProcessing/ByteCodeGen.swift +++ b/Sources/_StringProcessing/ByteCodeGen.swift @@ -109,7 +109,7 @@ fileprivate extension Compiler.ByteCodeGen { } // Fast path for eliding boundary checks for an all ascii quoted literal - if optimizationsEnabled && s.allSatisfy(\.isASCII) { + if optimizationsEnabled && s.allSatisfy(\.isASCII) && !s.isEmpty { let lastIdx = s.unicodeScalars.indices.last! for idx in s.unicodeScalars.indices { let boundaryCheck = idx == lastIdx @@ -1061,6 +1061,8 @@ extension DSLTree.Node { case .atom(let atom): switch atom { case .changeMatchingOptions, .assertion: return false + // Captures may be nil so backreferences may be zero length matches + case .backreference: return false default: return true } case .trivia, .empty: diff --git a/Tests/RegexTests/MatchTests.swift b/Tests/RegexTests/MatchTests.swift index 8e01582a9..f2a8f9e82 100644 --- a/Tests/RegexTests/MatchTests.swift +++ b/Tests/RegexTests/MatchTests.swift @@ -2465,6 +2465,9 @@ extension RegexTests { // case insensitive tests firstMatchTest(#"(?i)abc\u{301}d"#, input: "AbC\u{301}d", match: "AbC\u{301}d", semanticLevel: .unicodeScalar) + + // check that we don't crash on empty strings + firstMatchTest(#"\Q\E"#, input: "", match: "") } func testCase() { @@ -2534,4 +2537,8 @@ extension RegexTests { expectCompletion(regex: #"(a{,4})*"#, in: "aa") expectCompletion(regex: #"((|)+)*"#, in: "aa") } + + func testFuzzerArtifacts() throws { + expectCompletion(regex: #"(b?)\1*"#, in: "a") + } }