Skip to content

[Firestore]: 'Document path cannot be empty.' Error results in an app crash. #14605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MichaelVerdon opened this issue Mar 20, 2025 · 6 comments

Comments

@MichaelVerdon
Copy link

Description

Hi there guys, I am coming over here from FlutterFire after we received this report: firebase/flutterfire#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:

  1. Create a Firebase Application with Firestore.
  2. 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")
            }
        }
    }
  1. Assign this function to a button.
  2. Launch the app and click it. (Receiving an error is intentional)
  3. 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!
@dconeybe
Copy link
Contributor

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:

ThrowInvalidArgument("Document path cannot be empty.");

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 firebase/flutterfire#17197.

@MichaelVerdon
Copy link
Author

MichaelVerdon commented Mar 21, 2025

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.

@dconeybe
Copy link
Contributor

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.

@google-oss-bot
Copy link

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!

@MichaelVerdon
Copy link
Author

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?

@dconeybe
Copy link
Contributor

dconeybe commented Apr 1, 2025

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:

  1. The string is non-empty.
  2. The string contains an odd number of / characters for documents, or an even number of / characters for collections.
  3. 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?

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

No branches or pull requests

4 participants