From af39c764c6e85e3a68e4d0ce82f2b290cbf86aa1 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Wed, 8 Feb 2023 09:28:46 -0800 Subject: [PATCH 1/2] Stop at end of search string in TwoWaySearcher (#631) When searching for a substring that doesn't exist, it was possible for TwoWaySearcher to advance beyond the end of the search string, causing a crash. This change adds a `limitedBy:` parameter to that index movement, avoiding the invalid movement. Fixes rdar://105154010 --- .../Algorithms/Searchers/TwoWaySearcher.swift | 5 ++++- Tests/RegexTests/AlgorithmsTests.swift | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/_StringProcessing/Algorithms/Searchers/TwoWaySearcher.swift b/Sources/_StringProcessing/Algorithms/Searchers/TwoWaySearcher.swift index c3537d415..2428f89cd 100644 --- a/Sources/_StringProcessing/Algorithms/Searchers/TwoWaySearcher.swift +++ b/Sources/_StringProcessing/Algorithms/Searchers/TwoWaySearcher.swift @@ -133,7 +133,10 @@ extension TwoWaySearcher: CollectionSearcher { searched.formIndex(before: &lIndex) if pattern[i] != searched[lIndex] { - searched.formIndex(&state.criticalIndex, offsetBy: period) + _ = searched.formIndex( + &state.criticalIndex, + offsetBy: period, + limitedBy: searched.endIndex) if periodIsExact { state.memory = (pattern.count - period, end) } return nil } diff --git a/Tests/RegexTests/AlgorithmsTests.swift b/Tests/RegexTests/AlgorithmsTests.swift index 71eff7c6d..8ff3ad607 100644 --- a/Tests/RegexTests/AlgorithmsTests.swift +++ b/Tests/RegexTests/AlgorithmsTests.swift @@ -174,6 +174,16 @@ class AlgorithmTests: XCTestCase { expectRanges("ababacabababa", "abababa", [6..<13]) expectRanges("ababacabababa", "aba", [0..<3, 6..<9, 10..<13]) } + + // rdar://105154010 + func testFirstRangeMissingCrash() { + let str = "%2$@ %#@AROUND_TIME@" + let target = "%@" + XCTAssertNil(str.firstRange(of: target)) + XCTAssertNil(str.dropFirst().dropLast().firstRange(of: target)) + XCTAssertNil(str.dropFirst().dropLast().firstRange(of: target[...])) + XCTAssertNil(str.firstRange(of: target[...])) + } func testRegexSplit() { func expectSplit( From 3706999356947d6ab422bd29301b5efebd709531 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Wed, 1 Feb 2023 14:37:18 -0600 Subject: [PATCH 2/2] Merge pull request #628 from apple/result_builder_changes_workaround Add type annotations in RegexBuilder tests --- .../_StringProcessing/MatchingOptions.swift | 12 +++++++++--- Tests/RegexBuilderTests/CustomTests.swift | 18 +++++++++--------- Tests/RegexBuilderTests/MotivationTests.swift | 12 ++++++------ Tests/RegexBuilderTests/RegexDSLTests.swift | 10 +++++----- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Sources/_StringProcessing/MatchingOptions.swift b/Sources/_StringProcessing/MatchingOptions.swift index d511c9f7c..60cc7d0de 100644 --- a/Sources/_StringProcessing/MatchingOptions.swift +++ b/Sources/_StringProcessing/MatchingOptions.swift @@ -14,7 +14,9 @@ /// A type that represents the current state of regex matching options, with /// stack-based scoping. struct MatchingOptions { - fileprivate var stack: [Representation] + // FIXME: Workaround for rdar://104923020 + // fileprivate + var stack: [Representation] fileprivate func _invariantCheck() { assert(!stack.isEmpty, "Unbalanced call to endScope") @@ -125,7 +127,9 @@ extension MatchingOptions { // MARK: - Implementation extension MatchingOptions { /// An option that changes the behavior of a regular expression. - fileprivate enum Option: Int { + // FIXME: Workaround for rdar://104923020 + // fileprivate + enum Option: Int { // PCRE options case caseInsensitive case allowDuplicateGroupNames @@ -212,7 +216,9 @@ extension MatchingOptions { extension MatchingOptions { /// A set of matching options. - fileprivate struct Representation: OptionSet, RawRepresentable { + // FIXME: Workaround for rdar://104923020 + // fileprivate + struct Representation: OptionSet, RawRepresentable { var rawValue: UInt32 /// Returns `true` if the option denoted by `kind` is a member of this set. diff --git a/Tests/RegexBuilderTests/CustomTests.swift b/Tests/RegexBuilderTests/CustomTests.swift index bc71f2363..d34b5689f 100644 --- a/Tests/RegexBuilderTests/CustomTests.swift +++ b/Tests/RegexBuilderTests/CustomTests.swift @@ -183,7 +183,7 @@ class CustomRegexComponentTests: XCTestCase { // tests. func testCustomRegexComponents() throws { customTest( - Regex { + Regex { Numbler() Asciibbler() }, @@ -194,7 +194,7 @@ class CustomRegexComponentTests: XCTestCase { ("t4", .match, nil)) customTest( - Regex { + Regex { OneOrMore { Numbler() } }, ("ab123c", .firstMatch, "123"), @@ -425,7 +425,7 @@ class CustomRegexComponentTests: XCTestCase { ) customTest( - Regex { + Regex { CurrencyParser() }, ("USD", .usd, nil), @@ -437,7 +437,7 @@ class CustomRegexComponentTests: XCTestCase { // No capture, two errors customTest( - Regex { + Regex { IntParser() " " IntParser() @@ -449,7 +449,7 @@ class CustomRegexComponentTests: XCTestCase { ) customTest( - Regex { + Regex { CurrencyParser() IntParser() }, @@ -462,7 +462,7 @@ class CustomRegexComponentTests: XCTestCase { // One capture, two errors: One error is thrown from inside a capture, // while the other one is thrown from outside customTest( - Regex { + Regex<(Substring, CurrencyParser.Currency)> { Capture { CurrencyParser() } IntParser() }, @@ -473,7 +473,7 @@ class CustomRegexComponentTests: XCTestCase { ) customTest( - Regex { + Regex<(Substring, Int)> { CurrencyParser() Capture { IntParser() } }, @@ -485,7 +485,7 @@ class CustomRegexComponentTests: XCTestCase { // One capture, two errors: Both errors are thrown from inside the capture customTest( - Regex { + Regex<(Substring, Substring)> { Capture { CurrencyParser() IntParser() @@ -499,7 +499,7 @@ class CustomRegexComponentTests: XCTestCase { // Two captures, two errors: Different erros are thrown from inside captures customTest( - Regex { + Regex<(Substring, CurrencyParser.Currency, Int)> { Capture(CurrencyParser()) Capture(IntParser()) }, diff --git a/Tests/RegexBuilderTests/MotivationTests.swift b/Tests/RegexBuilderTests/MotivationTests.swift index 7dd4c77e4..f72486610 100644 --- a/Tests/RegexBuilderTests/MotivationTests.swift +++ b/Tests/RegexBuilderTests/MotivationTests.swift @@ -309,8 +309,8 @@ extension RegexDSLTests { "CREDIT" "DEBIT" } - } transform: { - TransactionKind(rawValue: String($0)) + } transform: { (s: Substring) in + TransactionKind(rawValue: String(s)) } OneOrMore(.whitespace) @@ -322,8 +322,8 @@ extension RegexDSLTests { Repeat(.digit, count: 2) Repeat(.digit, count: 2) Repeat(.digit, count: 4) - } transform: { - Date(mmddyyyy: String($0)) + } transform: { (s: Substring) in + Date(mmddyyyy: String(s)) } OneOrMore(.whitespace) @@ -345,8 +345,8 @@ extension RegexDSLTests { OneOrMore(.digit) "." Repeat(.digit, count: 2) - } transform: { - Double($0) + } transform: { (s: Substring) in + Double(s) } } diff --git a/Tests/RegexBuilderTests/RegexDSLTests.swift b/Tests/RegexBuilderTests/RegexDSLTests.swift index e25f2df05..8b7611536 100644 --- a/Tests/RegexBuilderTests/RegexDSLTests.swift +++ b/Tests/RegexBuilderTests/RegexDSLTests.swift @@ -1253,8 +1253,8 @@ class RegexDSLTests: XCTestCase { TryCapture(as: b) { "#" OneOrMore(.digit) - } transform: { - Int($0.dropFirst()) + } transform: { (s: Substring) in + Int(s.dropFirst()) } } a @@ -1271,14 +1271,14 @@ class RegexDSLTests: XCTestCase { do { let a = Reference(Substring.self) let b = Reference(Int.self) - let regex = Regex { + let regex = Regex<(Substring, Substring, Int?, Int?, Substring?)> { Capture("abc", as: a) ZeroOrMore { TryCapture(as: b) { "#" OneOrMore(.digit) - } transform: { - Int($0.dropFirst()) + } transform: { (s: Substring) -> Int? in + Int(s.dropFirst()) } } a