firebase-ios-sdk
firebase-ios-sdk copied to clipboard
[Firestore]: 'Document path cannot be empty.' Error results in an app crash.
Description
Hi there guys, I am coming over here from FlutterFire after we received this report: https://github.com/firebase/flutterfire/issues/17196 where firestore related errors such as 'Document path cannot be empty.' appear to be caught but crash the application and I don't believe it should be doing that. As well as being able to reproduce this only on iOS, I was able to reproduce this using the native-sdk here. I believe the fix might be simple as I made a fix FlutterFire side for reference here but we believe it would be better addressed here as it is only a temporary solution for FlutterFire.
Reproducing the issue
Steps for reproduction:
- Create a Firebase Application with Firestore.
- Implement this function somewhere in the swiftUI
func triggerFirestoreError() {
let invalidDocRef = db.collection("teams").document("") // Invalid document ID
invalidDocRef.updateData(["test": "test"]) { error in
if let error = error {
print("Firestore Update Error: \(error.localizedDescription)")
// Show an alert in SwiftUI (similar to ScaffoldMessenger in Flutter)
showAlert(title: "Firestore Error", message: error.localizedDescription)
} else {
print("Firestore update successful")
}
}
}
- Assign this function to a button.
- Launch the app and click it. (Receiving an error is intentional)
- The application should crash and see the logs.
Firebase SDK Version
11.10.0
Xcode Version
16.1
Installation Method
Swift Package Manager
Firebase Product(s)
Firestore
Targeted Platforms
iOS
Relevant Log Output
*** Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Document path cannot be empty.'
*** First throw call stack:
(
0 CoreFoundation 0x00000001804b757c __exceptionPreprocess + 172
1 libobjc.A.dylib 0x000000018008eda8 objc_exception_throw + 72
2 firebase-test-app-ios.debug.dylib 0x00000001078dc498 _ZN8firebase9firestore4util16ObjcThrowHandlerENS1_13ExceptionTypeEPKcS4_iRKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEE + 108
3 firebase-test-app-ios.debug.dylib 0x00000001078dbf94 _ZN8firebase9firestore4util5ThrowENS1_13ExceptionTypeEPKcS4_iRKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEE + 20
4 firebase-test-app-ios.debug.dylib 0x00000001078e1a7c _ZN8firebase9firestore4util20ThrowInvalidArgumentIJEEEvPKcDpRKT_ + 56
5 firebase-test-app-ios.debug.dylib 0x00000001078e73b8 -[FIRCollectionReference documentWithPath:] + 284
6 firebase-test-app-ios.debug.dylib 0x00000001074eb55c $s21firebase_test_app_ios11ContentViewV21triggerFirestoreErroryyF + 224
7 firebase-test-app-ios.debug.dylib 0x00000001074edeac $s21firebase_test_app_ios11ContentViewV4bodyQrvg7SwiftUI05TupleF0VyAE0F0PAEE11buttonStyleyQrqd__AE015PrimitiveButtonL0Rd__lFQOyAE0N0VyAE4TextVG_AE017BorderedProminentnL0VQo__AiEEAJyQrqd__AeKRd__l 8 SwiftUI 0x00000001d283e9a8 $s7SwiftUI17ContextMenuBridgeC07contextD11Interaction_027willPerformPreviewActionForD4With8animatorySo09UIContextdG0C_So0oD13ConfigurationCSo0odG15CommitAnimating_ptFyycfU0_yyXEfU_TA + 24
9 SwiftUI 0x00000001d2f3e274 $sScM14assumeIsolated_4file4linexxyKScMYcXE_s12StaticStringVSutKs8SendableRzlFZyt_Tg5 + 132
10 SwiftUI 0x00000001d2f0a058 $s7SwiftUI12ButtonActionO14callAsFunctionyyF + 308
11 SwiftUI 0x00000001d21595f8 $s7SwiftUI27PlatformItemListButtonStyleV8makeBody13configurationQrAA09PrimitivefG13ConfigurationV_tFyycAGcfu_yycfu0_TATm + 52
12 SwiftUI 0x00000001d27de784 $s7SwiftUI14ButtonBehaviorV5ended33_AEEDD090E917AC57C12008D974DC6805LLyyF + 176
13 SwiftUI 0x00000001d27e06f0 $s7SwiftUI14ButtonBehaviorV4bodyQrvgyyYbcACyxGYbcfu_yyYbcfu0_TA + 32
14 SwiftUI 0x00000001d2e772b8 $s7SwiftUI14_ButtonGestureV12internalBodyQrvgAA04_MapD0VyAA09PrimitivecD0VytGyXEfU_ySo7CGPointVSgcfU0_yyScMYcXEfU_TA + 28
15 SwiftUI 0x00000001d2f3e274 $sScM14assumeIsolated_4file4linexxyKScMYcXE_s12StaticStringVSutKs8SendableRzlFZyt_Tg5 + 132
16 SwiftUI 0x00000001d2e722c4 $s7SwiftUI14_ButtonGestureV12internalBodyQrvgAA04_MapD0VyAA09PrimitivecD0VytGyXEfU_ySo7CGPointVSgcfU0_ + 80
17 SwiftUI 0x00000001d2e76d94 $s7SwiftUI31PrimitiveButtonGestureCallbacks33_2218E1141B3D7C3A65B6697591AFB638LLV8dispatch5phase5stateyycSgAA0E5PhaseOyAA0cdE4CoreACLLV5ValueVG_AA0d5PressQ0OztFyycfU0_ + 88
18 SwiftUICore 0x00000001d335518c $sIeg_ytIegr_TR + 20
19 SwiftUICore 0x00000001d335518c $sIeg_ytIegr_TR + 20
20 SwiftUI 0x00000001d23a8800 $s7SwiftUI19SubmitTriggerSourcePAAE14dispatchUpdateyyyyXEFyyXEfU_ + 20
21 SwiftUI 0x00000001d266f004 $s7SwiftUI17DragAndDropBridgeC15dragInteraction_16sessionWillBeginySo06UIDragH0C_So0L7Session_ptFyycfU0_ + 56
22 SwiftUICore 0x00000001d334b9b4 $sSDySo21NSAttributedStringKeyaypGSo8_NSRangeVSpy10ObjectiveC8ObjCBoolVGIggyy_AceIIeggyy_TRTA + 20
23 SwiftUICore 0x00000001d367b964 $s7SwiftUI6UpdateO15dispatchActionsyyFZ + 1080
24 SwiftUICore 0x00000001d367af64 $s7SwiftUI6UpdateO3endyyFZ + 108
25 SwiftUI 0x00000001d2a897a8 $s7SwiftUI32UIKitResponderEventBindingBridgeC12flushActionsyyF + 120
26 SwiftUI 0x00000001d2a89810 $s7SwiftUI32UIKitResponderEventBindingBridgeC12flushActionsyyFTo + 24
27 UIKitCore 0x0000000185602970 -[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 76
28 UIKitCore 0x000000018560a024 _UIGestureRecognizerSendTargetActions + 88
29 UIKitCore 0x0000000185607920 _UIGestureRecognizerSendActions + 312
30 UIKitCore 0x0000000185607674 -[UIGestureRecognizer _updateGestureForActiveEvents] + 584
31 UIKitCore 0x00000001855fcefc _UIGestureEnvironmentUpdate + 2596
32 UIKitCore 0x00000001855fc1f4 -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 324
33 UIKitCore 0x00000001855fbf40 -[UIGestureEnvironment _updateForEvent:window:] + 156
34 UIKitCore 0x0000000185b12428 -[UIWindow sendEvent:] + 2824
35 UIKitCore 0x0000000185af20f8 -[UIApplication sendEvent:] + 376
36 UIKit 0x00000001cb18e3ac -[UIApplicationAccessibility sendEvent:] + 108
37 UIKitCore 0x0000000185b7c25c __dispatchPreprocessedEventFromEventQueue + 1156
38 UIKitCore 0x0000000185b7f1ec __processEventQueue + 5592
39 UIKitCore 0x0000000185b774fc updateCycleEntry + 156
40 UIKitCore 0x000000018505d28c _UIUpdateSequenceRun + 76
41 UIKitCore 0x0000000185a07670 schedulerStepScheduledMainSection + 168
42 UIKitCore 0x0000000185a06aa8 runloopSourceCallback + 80
43 CoreFoundation 0x000000018041b7c4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
44 CoreFoundation 0x000000018041b70c __CFRunLoopDoSource0 + 172
45 CoreFoundation 0x000000018041ae70 __CFRunLoopDoSources0 + 232
46 CoreFoundation 0x00000001804153b4 __CFRunLoopRun + 788
47 CoreFoundation 0x0000000180414c24 CFRunLoopRunSpecific + 552
48 GraphicsServices 0x000000019020ab10 GSEventRunModal + 160
49 UIKitCore 0x0000000185ad82fc -[UIApplication _run] + 796
50 UIKitCore 0x0000000185adc4f4 UIApplicationMain + 124
51 SwiftUI 0x00000001d290b41c $s7SwiftUI17KitRendererCommon33_ACC2C5639A7D76F611E170E831FCA491LLys5NeverOyXlXpFAESpySpys4Int8VGSgGXEfU_ + 164
52 SwiftUI 0x00000001d290b144 $s7SwiftUI6runAppys5NeverOxAA0D0RzlF + 84
53 SwiftUI 0x00000001d266bef4 $s7SwiftUI3AppPAAE4mainyyFZ + 148
54 firebase-test-app-ios.debug.dylib 0x00000001074f0b2c $s21firebase_test_app_ios11AppDelegateC04YourE0V5$mainyyFZ + 40
55 firebase-test-app-ios.debug.dylib 0x00000001074f10e8 __debug_main_executable_dylib_entry_point + 12
56 dyld 0x0000000104e4d410 start_sim + 20
57 ??? 0x00000001047c6154 0x0 + 4370227540
58 ??? 0x0651000000000000 0x0 + 455145037341130752
)
libc++abi: terminating due to uncaught exception of type NSException
If using Swift Package Manager, the project's Package.resolved
Expand Package.resolved snippet
Replace this line with the contents of your Package.resolved.
If using CocoaPods, the project's Podfile.lock
Expand Podfile.lock snippet
Replace this line with the contents of your Podfile.lock!
Thanks for the bug report and the thorough investigation.
@MichaelVerdon: Could you clarify your "expected behavior" when the Swift code snippet from the OP is executed? Namely, how would you like to see this "Document path cannot be empty." be reported by the Firestore SDK?
For reference, here is the line of code where the exception is being thrown: https://github.com/firebase/firebase-ios-sdk/blob/d8f2ed8c0ed791273f0a7371c300d82dfb5571e6/Firestore/Source/API/FIRFirestore.mm#L269
One immediate concern that I have is that Firestore uses its ThrowInvalidArgument function all over the place to report precondition violations. So even if we do something to fix this specific instance, there will be many other functions that also potentially throw exceptions and would similarly crash a calling Flutter application.
I almost want to say that if Objective-C exceptions crash a Flutter application, then fireflutter may have to wrap each call into the Firestore SDK inside a @try/@catch block to avoid the exceptions from propagating up the call stack. Or at least wrap each call where such an exception could be more reasonably handled, such as calling an error callback, like was done in https://github.com/firebase/flutterfire/pull/17197.
Hey there @dconeybe thank you for your response. This is what i was hoping in terms of behaviour. Expected:
- correct error called
- app does not crash Actual:
- Correct error called
- App crashes It did not only crash FlutterFire, but it also crashed here when I reproduce it using the native sdk here only in a swiftUI app and I suppose we never desire crashing. Let me know if you would like a repo to test it out to save some time.
Something to add, is that on the Android side of things. The errors are caught and do not result in an app crash which is exactly what we want and the crash only occurs on iOS telling me it is iOS specific.
I looked into Android's behavior and it's not exactly ideal either: it does allow you to create a DocumentReference with an empty path; however, if you try to use that DocumentReference to actually do anything (e.g. get a document snapshot) then the app crashes due to an exception bubbling off to top of Firestore's thread's call stack.
My recommendation is to add some checks to the fireflutter sdk before it calls down to the underlying Firestore SDK. Namely, any method that takes a document path should do some validity checks on it before calling down to the underling Firestore SDK, such as verifying that it is non-empty. A good list of checks to perform would be to grep through the Firestore subdirectory of this git repository for usages of ThrowInvalidArgument.
This situation is, admittedly, sub-optimal; however, I'm not even sure what an "optimal" solution would look like. Throwing an objective-c exception actually seems like a reasonable thing to do for an iOS SDK (as the firebase-ios-sdk is currently doing and causing an issue for you). IMO the Android SDK should probably throw an exception just like iOS does; however, I'm hesitant to make that change for fear of needlessly breaking existing apps.
Hey @MichaelVerdon. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.
If you have more information that will help us get to the bottom of this, just add a comment!
Hey there @dconeybe, thanks for your response, so I take it you recommend we solve it on FlutterFire instead? Can you please elaborate on what you mean as I am unsure I understand?
Hi @MichaelVerdon. My apologies for being unclear in my previous response. My suggestion is to perform validation of strings in the dart/flutter sdk ("fireflutter") before passing those strings down to the native Firestore SDK.
For example, for strings that represent document paths or collection paths, verify the following:
- The string is non-empty.
- The string contains an odd number of
/characters for documents, or an even number of/characters for collections. - The string does not contain two consecutive
/characters.
If one or more of these requirements are found to be invalid, then the flutter/dart sdk should report the error in an appropriate way (for example, throw a dart exception or call an error callback). Only if these requirements are validated should the string then be passed down to the native Firestore SDK.
Does this sound like something you could reasonably do in your dart/flutter sdk?