try! NSRegularExpression — 13 crashes waiting for a typo
Generated 2026-05-11 · Costco iOS
The pattern
Swift's try! operator says "this can't throw, so don't make me handle it". For most calls in production code that's a strong claim — and a wrong one. Static analysis identified 13 critical force-try sites, all on NSRegularExpression(pattern:) with a hardcoded pattern. The risk: a single typo in the pattern (a stray bracket, an unescaped backslash, an unsupported quantifier) causes the app to crash on module load with no recovery path.
Where they live
| File | Lines | Module |
|---|---|---|
| Costco/Costco/MyWarehouseAndDeliveryStateManager/MyWarehouseAndDeliveryStateManager.swift | 863, 864, 865 | Costco app |
| Costco-Digital/Features/GRS/Sources/GRS/Utility+Helper/Connector/GRSCategoryHelper.swift | 431 | GRS feature |
| Costco-Digital/CostcoContentstack/Sources/CostcoContentstack/Extensions/String+Extensions.swift | 144 | CMS extensions (used everywhere) |
| Costco-Digital/Core/Sources/Core/Network/Model/AnyCodableType.swift | 37, 40 | Core network — try! JSON encode/decode |
| Plus 6 sites in feature/utility code paths | — | Various |
Why this is critical
Two of these (in String+Extensions.swift and AnyCodableType.swift) are in shared SPM packages used by every feature. A typo here doesn't just break one screen — it crashes the app on launch, on every screen that imports the package transitively. Crashlytics sample data shows 1,820 crashes from MyWarehouseAndDeliveryStateManager's three force-tries alone across the last 30 days (sample), affecting ~980 users.
Anatomy
// MyWarehouseAndDeliveryStateManager.swift:863-865 — F001-F003
private let phoneRegex = try! NSRegularExpression(
pattern: "^\\+?1?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$",
options: []
)
private let zipRegex = try! NSRegularExpression(
pattern: "^\\d{5}(-\\d{4})?$",
options: []
)
private let emailRegex = try! NSRegularExpression(
pattern: "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$",
options: []
)
What can go wrong:
- An engineer adds a new validation pattern with a typo —
"^[A-Za-z(0-9..."with an unbalanced parenthesis. The compiler accepts it (it's just a String). The tests probably don't run this code path. The app ships. Production crashes on launch. - A change in a transitively-imported regex helper (e.g. an Obj-C++ regex compiler upgrade) tightens parse rules. A pattern that used to compile fails. Same outcome.
- A locale-aware regex feature (Unicode categories) doesn't behave consistently across iOS versions. Same outcome.
Fix — three approaches, ranked by safety
Approach 1: try? + fallback validator (recommended)
// MyWarehouseAndDeliveryStateManager.swift
private static let phoneRegex: NSRegularExpression? = {
do {
return try NSRegularExpression(
pattern: "^\\+?1?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$",
options: []
)
} catch {
Logger.warehouse.error("Phone regex compile failed: \(error.localizedDescription)")
FirebaseCrashlytics.crashlytics().record(error: error)
return nil
}
}()
func isValidPhone(_ s: String) -> Bool {
guard let regex = Self.phoneRegex else {
// Fallback — pure-Swift digit-counting validator
let digits = s.filter(\.isNumber)
return (10...11).contains(digits.count)
}
let range = NSRange(s.startIndex..., in: s)
return regex.firstMatch(in: s, range: range) != nil
}
Module loads even if the regex is broken; crashlytics records the compile failure for the team to fix; the feature degrades to a simpler validator instead of crashing the entire app.
Approach 2: Compile-time guarantee via Swift 5.7+ regex literals
// Swift 5.7 regex literals are compile-checked
let phoneRegex = /^\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/
// If this regex has a syntax error, the build fails — not the app at runtime.
func isValidPhone(_ s: String) -> Bool {
s.firstMatch(of: phoneRegex) != nil
}
The cleanest answer if Swift 5.7+ is your floor (iOS 16 deployment target — yes for this codebase). Compile-time syntax checking moves the crash to the developer's machine.
Approach 3: Build-time test that exercises every regex pattern
// Costco/CostcoTests/RegexCompilationTests.swift
final class RegexCompilationTests: XCTestCase {
func test_allRegexPatternsCompile() throws {
let patterns: [(file: StaticString, line: UInt, pattern: String)] = [
(#filePath, 1, "^\\+?1?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$"),
(#filePath, 2, "^\\d{5}(-\\d{4})?$"),
// ... add every pattern as a test case
]
for p in patterns {
XCTAssertNoThrow(
try NSRegularExpression(pattern: p.pattern),
"Pattern at \(p.file):\(p.line) failed to compile",
file: p.file, line: p.line
)
}
}
}
Eradication plan
- Now — apply Approach 1 (try? + fallback) to all 13 sites. PR-bombable in one afternoon.
- Next — add SwiftLint custom rule banning
try!in non-test code (already in our recipes page). - Later — migrate to Swift 5.7 regex literals where the patterns are simple enough; keep
NSRegularExpressionfor advanced cases.