Skip to content

Crash in GlucoseStore.addGlucoseSamples due to nil timestamp on glucose sample, EXC_BREAKPOINT SIGTRAP #2506

Description

@LucasSHYC

Loop 3.15.2 (26) crashes with EXC_BREAKPOINT (SIGTRAP) in GlucoseStore.addGlucoseSamples(_:) when processing glucose samples containing an invalid timestamp from CGM plugin (CGMBLEKitG6Plugin).
Date._unconditionallyBridgeFromObjectiveC is called when bridging the Swift Date to NSDate for CoreData storage, triggering Swift trap and terminating the app during bulk glucose sample mapping on CoreData background queue.

Environment

  • Device: iPhone16,2
  • iOS: 18.7.8 (22H352)
  • Loop version: 3.15.2 (26), TestFlight beta
  • CGM: G6 via CGMBLEKitG6Plugin
  • Pump: Omnipod via OmnipodKitPlugin
  • Incident Identifier: D37C3980-F614-4D9C-925E-465EB979B349

Crash Stack

Thread 9 Crashed:
0   Foundation                    	0x000000018b350894 static Date._unconditionallyBridgeFromObjectiveC(_:) + 52 (Date.swift:358)
1   LoopKit                       	0x0000000100d4b610 closure #3 in closure #1 in GlucoseStore.addGlucoseSamples(_:) + 8 (GlucoseStore.swift:324)
2   LoopKit                       	0x0000000100d4b610 specialized Collection.map<A, B>(_:) + 116 (/<compiler-generated>:0)
3   LoopKit                       	0x0000000100d4b610 closure #1 in GlucoseStore.addGlucoseSamples(_:) + 1416 (GlucoseStore.swift:324)
4   LoopKit                       	0x0000000100d5367c partial apply for closure #1 in GlucoseStore.addGlucoseSamples(_:) + 16 (/<compiler-generated>:0)
5   CoreData                      	0x00000001947b8150 closure #1 in closure #1 in NSManagedObjectContext._rethrowsHelper_perform_enqueued<A>(_:rescue:) + 372 (NSManagedObjectContext.swift:180)

Root Cause

NewGlucoseSample.startDate is defined as non-optional Swift Date, but the underlying CGM plugin can produce invalid Date values (.isInfinite / .isNaN).
When this invalid Date is bridged to NSDate for CoreData storage in CachedGlucoseObject.create(from:), it triggers Date._unconditionallyBridgeFromObjectiveC trap.
A single malformed sample crashes the entire batch ingestion and kills Loop.

Note: This is not a nil Swift Date. Swift enforces non-optional, but invalid time intervals create a Date that cannot bridge to ObjC NSDate.

Expected Behavior

Loop should skip individual malformed glucose samples with invalid timestamps, log the bad sample details (syncIdentifier), and continue processing remaining valid samples instead of hard crashing.

Actual Behavior

App terminates immediately with EXC_BREAKPOINT (SIGTRAP) when receiving a glucose sample with invalid timestamp.

Suggested Fix

Add validation before processing the batch: filter out samples with infinite / NaN startDate at the entry of addGlucoseSamples, emit error log for bad entries, and only proceed with valid samples.
The filter runs before duplicate checking and CoreData object creation, preventing invalid values from ever reaching NSDate bridging.

Proposed Patch

public func addGlucoseSamples(_ samples: [NewGlucoseSample], completion: @escaping (_ result: Result<[StoredGlucoseSample], Error>) -> Void) {
    guard !samples.isEmpty else {
        completion(.success([]))
        return
    }
    queue.async {
        var storedSamples: [StoredGlucoseSample] = []
        var error: Error?
        self.cacheStore.managedObjectContext.performAndWait {
            do {
+                // Filter out samples with invalid startDate to prevent crash during NSDate bridging
+                let validSamples = samples.compactMap { sample -> NewGlucoseSample? in
+                    guard !sample.startDate.isInfinite, !sample.startDate.isNaN else {
+                        self.log.error("Dropping glucose sample due to invalid startDate. syncIdentifier: %{public}@", sample.syncIdentifier)
+                        return nil
+                    }
+                    return sample
+                }
+
+                guard !validSamples.isEmpty else {
+                    self.log.default("All glucose samples were invalid, nothing to save")
+                    return
+                }
+
                // Filter samples to ensure no duplicate sync identifiers nor existing sample with matching sync identifier for our provenance identifier
                var syncIdentifiers = Set<String>()
-                let samples: [NewGlucoseSample] = try samples.compactMap { sample in
+                let samples: [NewGlucoseSample] = try validSamples.compactMap { sample in

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions