Overview › try! NSRegularExpression — 13 crashes waiting for a typo

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

FileLinesModule
Costco/Costco/MyWarehouseAndDeliveryStateManager/MyWarehouseAndDeliveryStateManager.swift863, 864, 865Costco app
Costco-Digital/Features/GRS/Sources/GRS/Utility+Helper/Connector/GRSCategoryHelper.swift431GRS feature
Costco-Digital/CostcoContentstack/Sources/CostcoContentstack/Extensions/String+Extensions.swift144CMS extensions (used everywhere)
Costco-Digital/Core/Sources/Core/Network/Model/AnyCodableType.swift37, 40Core network — try! JSON encode/decode
Plus 6 sites in feature/utility code pathsVarious

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:

  1. 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.
  2. 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.
  3. 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

  1. Now — apply Approach 1 (try? + fallback) to all 13 sites. PR-bombable in one afternoon.
  2. Next — add SwiftLint custom rule banning try! in non-test code (already in our recipes page).
  3. Later — migrate to Swift 5.7 regex literals where the patterns are simple enough; keep NSRegularExpression for advanced cases.
Costco iOS · Code Review Report · Generated 2026-05-11 · 88 machine-curated findings