Firebase-ios-sdk: FIRAScreenViewReporter incorrectly forces loads of parent view controller's views

Created on 10 Oct 2017  路  21Comments  路  Source: firebase/firebase-ios-sdk

Environment

  • Xcode version: 9.0/9.1b1
  • iOS version: 11.0.1
  • Firebase SDK version: 4.0.3
  • Firebase Component: Analytics

Description

Firebase hooks into UIKit to know when a UIViewController gets a -viewWillAppear: call, but this calls FIRACurrentViewControllerInStack, which seems to be accessing the parent view controller's view. In our situation, this happens when the parent view controller is in -loadView, which results in an infinite loop. It may even be the cause for https://github.com/firebase/quickstart-ios/issues/231. I noticed that the debug navigator in Xcode doesn't show the Firebase calls in the stack, but manually running backtrace in lldb does.
We've currently disabled the screen reporting, but would love to see this fixed!

Steps to reproduce:

This is difficult to do, because this may be reliant on UIPageViewController behaviour. The stack trace below should be informative enough. I can confirm, though, that this only occurs when screen reporting is enabled.


Stacktrace

    frame #3713: 0x00000001089529cc PSPDFKitUI`-[PSPDFPageCurlViewController viewDidLoad](self=0x000000010a84b200, _cmd="viewDidLoad") at PSPDFPageCurlViewController.m:65
    frame #3714: 0x000000018dc6fbfc UIKit`-[UIViewController loadViewIfRequired] + 1040
    frame #3715: 0x000000018dc6f7d4 UIKit`-[UIViewController view] + 28
    frame #3716: 0x0000000108925fc4 PSPDFKitUI`::-[PSPDFDocumentView setupScrollViewForPageViewControllerBasedLayout](self=0x0000000109f18450, _cmd="setupScrollViewForPageViewControllerBasedLayout") at PSPDFDocumentView.mm:203
    frame #3717: 0x00000001089258e4 PSPDFKitUI`::-[PSPDFDocumentView setupScrollView](self=0x0000000109f18450, _cmd="setupScrollView") at PSPDFDocumentView.mm:156
    frame #3718: 0x0000000108925350 PSPDFKitUI`::-[PSPDFDocumentView initWithFrame:layout:](self=0x0000000109f18450, _cmd="initWithFrame:layout:", frame=(origin = (x = 0, y = 0), size = (width = 414, height = 736)), layout=0x000000010a1e0ff0) at PSPDFDocumentView.mm:103
    frame #3719: 0x00000001089c4c58 PSPDFKitUI`::-[PSPDFDocumentViewController loadView](self=0x000000010a1e0c20, _cmd="loadView") at PSPDFDocumentViewController.mm:108
    frame #3720: 0x000000018dc6f8a4 UIKit`-[UIViewController loadViewIfRequired] + 184
    frame #3721: 0x000000018dc6f7d4 UIKit`-[UIViewController view] + 28
    frame #3722: 0x0000000103079604 Viewer`FIRACurrentViewControllerInStack + 1008
    frame #3723: 0x000000010307ab48 Viewer`-[FIRAScreenViewReporter viewControllerDidAppear:] + 516
    frame #3724: 0x000000010307c0a4 Viewer`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 84
    frame #3725: 0x000000018dc8869c UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 852
    frame #3726: 0x000000018dc88c08 UIKit`-[UIViewController _endAppearanceTransition:] + 228
    frame #3727: 0x000000018e5d8d30 UIKit`-[UIPageViewController _childEndAppearanceTransition:] + 64
    frame #3728: 0x000000018e5dd1dc UIKit`-[UIPageViewController _setViewControllers:withCurlOfType:fromLocation:direction:animated:notifyDelegate:completion:] + 1880
    frame #3729: 0x000000018e5debc4 UIKit`-[UIPageViewController setViewControllers:direction:animated:completion:] + 172
    frame #3730: 0x0000000108953810 PSPDFKitUI`-[PSPDFPageCurlViewController updateControllersForSpreadIndex:animated:](self=0x000000010b08a200, _cmd="updateControllersForSpreadIndex:animated:", spreadIndex=0, animated=NO) at PSPDFPageCurlViewController.m:141
    frame #3731: 0x00000001089529cc PSPDFKitUI`-[PSPDFPageCurlViewController viewDidLoad](self=0x000000010b08a200, _cmd="viewDidLoad") at PSPDFPageCurlViewController.m:65
    frame #3732: 0x000000018dc6fbfc UIKit`-[UIViewController loadViewIfRequired] + 1040
    frame #3733: 0x000000018dc6f7d4 UIKit`-[UIViewController view] + 28
    frame #3734: 0x0000000108925fc4 PSPDFKitUI`::-[PSPDFDocumentView setupScrollViewForPageViewControllerBasedLayout](self=0x000000010a1e6f80, _cmd="setupScrollViewForPageViewControllerBasedLayout") at PSPDFDocumentView.mm:203
    frame #3735: 0x00000001089258e4 PSPDFKitUI`::-[PSPDFDocumentView setupScrollView](self=0x000000010a1e6f80, _cmd="setupScrollView") at PSPDFDocumentView.mm:156
    frame #3736: 0x0000000108925350 PSPDFKitUI`::-[PSPDFDocumentView initWithFrame:layout:](self=0x000000010a1e6f80, _cmd="initWithFrame:layout:", frame=(origin = (x = 0, y = 0), size = (width = 414, height = 736)), layout=0x000000010a1e0ff0) at PSPDFDocumentView.mm:103
    frame #3737: 0x00000001089c4c58 PSPDFKitUI`::-[PSPDFDocumentViewController loadView](self=0x000000010a1e0c20, _cmd="loadView") at PSPDFDocumentViewController.mm:108
    frame #3738: 0x000000018dc6f8a4 UIKit`-[UIViewController loadViewIfRequired] + 184
    frame #3739: 0x000000018dc6f7d4 UIKit`-[UIViewController view] + 28
    frame #3740: 0x0000000103079604 Viewer`FIRACurrentViewControllerInStack + 1008
    frame #3741: 0x000000010307ab48 Viewer`-[FIRAScreenViewReporter viewControllerDidAppear:] + 516
    frame #3742: 0x000000010307c0a4 Viewer`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 84
    frame #3743: 0x000000018dc8869c UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 852
    frame #3744: 0x000000018dc88c08 UIKit`-[UIViewController _endAppearanceTransition:] + 228
    frame #3745: 0x000000018e5d8d30 UIKit`-[UIPageViewController _childEndAppearanceTransition:] + 64
    frame #3746: 0x000000018e5dd1dc UIKit`-[UIPageViewController _setViewControllers:withCurlOfType:fromLocation:direction:animated:notifyDelegate:completion:] + 1880
    frame #3747: 0x000000018e5debc4 UIKit`-[UIPageViewController setViewControllers:direction:animated:completion:] + 172
    frame #3766: 0x0000000108953810 PSPDFKitUI`-[PSPDFPageCurlViewController updateControllersForSpreadIndex:animated:](self=0x000000010a8e6e00, _cmd="updateControllersForSpreadIndex:animated:", spreadIndex=0, animated=NO) at PSPDFPageCurlViewController.m:141
    frame #3767: 0x00000001089529cc PSPDFKitUI`-[PSPDFPageCurlViewController viewDidLoad](self=0x000000010a8e6e00, _cmd="viewDidLoad") at PSPDFPageCurlViewController.m:65
    frame #3768: 0x000000018dc6fbfc UIKit`-[UIViewController loadViewIfRequired] + 1040
    frame #3769: 0x000000018dc6f7d4 UIKit`-[UIViewController view] + 28
    frame #3770: 0x0000000108925fc4 PSPDFKitUI`::-[PSPDFDocumentView setupScrollViewForPageViewControllerBasedLayout](self=0x000000010a1e1370, _cmd="setupScrollViewForPageViewControllerBasedLayout") at PSPDFDocumentView.mm:203
    frame #3771: 0x00000001089258e4 PSPDFKitUI`::-[PSPDFDocumentView setupScrollView](self=0x000000010a1e1370, _cmd="setupScrollView") at PSPDFDocumentView.mm:156
    frame #3772: 0x0000000108925350 PSPDFKitUI`::-[PSPDFDocumentView initWithFrame:layout:](self=0x000000010a1e1370, _cmd="initWithFrame:layout:", frame=(origin = (x = 0, y = 0), size = (width = 414, height = 736)), layout=0x000000010a1e0ff0) at PSPDFDocumentView.mm:103
  * frame #3773: 0x00000001089c4c58 PSPDFKitUI`::-[PSPDFDocumentViewController loadView](self=0x000000010a1e0c20, _cmd="loadView") at PSPDFDocumentViewController.mm:108

analytics bug

Most helpful comment

FYI I forgot to tell the Analytics team to make a release this cycle, so it'll be in the next minor point release.

Sorry all :(

All 21 comments

(PSPDFKit lead here)
I realize 4.3.0 is the current version - did this code change in any meaningful way, should we try to update?

With a strategically isViewLoaded in FIRACurrentViewControllerInStack this should be easy to be worked around. We can reproduce this locally in a branch of https://pdfviewer.io so we can also test custom binaries if that would help.

Alternative Fix:
It would be possible to work around this with setting flags to break on re-entry, but this seems like a band-aid and not the right thing to do. We could try to change the load-order until after loadView which could work, but again, if we hit that, other people will hit that too (and they might just disable or remove Firebase, instead of reporting this here as an issue)

We have a PR up internally that addresses this (171700365). I'll let you know when it's merged and what release it's slated for.

Thanks for flagging!

@steipete or @caughtinflux can either of you share an app that reproduces this? The fix PR should be complete pending some small tweaks, but I'd like to test to make sure it solves this particular issue.

@morganchen12 We could potentially share this under NDA... it's a rather large project and not open source. It's probably easier if you email me binaries and we test them on our side. Feel free to reach out to peter at pspdfkit.com for that.

Good thing is that it's very easy to reproduce on our side. With some effort, we could probably also build a custom PSPDFKit-sample app using the just released 7.0.1.

Understandable. Can you describe the VC hierarchy in the app that causes this issue? I'll see about getting you a binary to test. :)

At a high level, here's what's going on:

PSPDFDocumentViewController
|__ PSPDFPageCurlViewController
    |__UIPageViewController
        |__[PSPDFPageViewController]

in PSPDFDocumentViewController's -loadView, the view controller (and view) hierarchy is set up. This triggers -viewDidLoad on PSPDFPageCurlViewController (expected), which sets the view controllers on the UIPageViewController.
UIPageViewController takes this to mean "make these view controllers appear immediately", even though we're still in its parent's -viewDidLoad. When -viewWillAppear: is called on one of the PSPDFPageViewControllers, FIRAScreenViewReporter jumps in and tries to load all the parent views. This kicks off an infinite recursion, as seen in the stack trace.

We were able to reproduce this locally, and the patch does fix the issue. Will let you know when it's merged and which release it's slated for.

Awesome, thanks!

This has been merged, and should be out in the next point release. If this regresses past that point, please reopen or file a new issue. I'll close this issue following the new release.

Thanks all!

Thanks for the fast turnaround! 鉂わ笍

FYI I forgot to tell the Analytics team to make a release this cycle, so it'll be in the next minor point release.

Sorry all :(

This has been released. Thanks for waiting!

Hi. I have same problem. And last release don't fix this

Can you post the stack trace along with your pod versions?

Installing Firebase 4.6.0 (was 3.7.1)
Installing FirebaseAnalytics 4.0.5 (was 3.4.4)
Installing FirebaseCore 4.0.11 (was 3.4.3)
Installing FirebaseCrash 2.0.2 (was 1.0.7)
Installing FirebaseInstanceID 2.0.6 (was 1.0.9)

frame #174487: 0x000000010cfffc2e App`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 33
frame #174488: 0x000000010cfffc2e App`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 33
frame #174489: 0x000000010cfffc2e App`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 33
frame #174490: 0x000000010cfffc2e App`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 33
frame #174491: 0x0000000110ba2405 SWRevealViewController`-[SWRevealViewController viewDidAppear:](self=0x00007fec9880e000, _cmd="viewDidAppear:", animated=NO) at SWRevealViewController.m:721
frame #174492: 0x00000001129a769b UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 699
frame #174493: 0x00000001129aa545 UIKit`__64-[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:]_block_invoke + 42
frame #174494: 0x00000001129a8839 UIKit`-[UIViewController _executeAfterAppearanceBlock] + 86
frame #174495: 0x0000000112808761 UIKit`_runAfterCACommitDeferredBlocks + 634
frame #174496: 0x00000001127f6bc5 UIKit`_cleanUpAfterCAFlushAndRunDeferredBlocks + 280
frame #174497: 0x0000000112817d48 UIKit`__34-[UIApplication _firstCommitBlock]_block_invoke_2 + 155
frame #174498: 0x000000011213e18c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
frame #174499: 0x000000011212296b CoreFoundation`__CFRunLoopDoBlocks + 203
frame #174500: 0x0000000112122144 CoreFoundation`__CFRunLoopRun + 1300
frame #174501: 0x00000001121219b9 CoreFoundation`CFRunLoopRunSpecific + 409
frame #174502: 0x00000001178129c6 GraphicsServices`GSEventRunModal + 62
frame #174503: 0x00000001127fc5e8 UIKit`UIApplicationMain + 159
frame #174504: 0x000000010cf0eed5 App`main at main.swift:23
frame #174505: 0x0000000116690d81 libdyld.dylib`start + 1

This is a slightly different crash, since -[UIViewController view] isn't in the stack trace.

Can you file a new issue and (if possible) share the implementation of -[SWRevealViewController viewDidAppear:]?

This way we can avoid spamming the folks that aren't interested in this new crash.

Thanks!

Hi, I'm having similar issues with screen tracking.

Environment

  • Xcode version: 9.2
  • iOS version: 10
  • Firebase SDK version: Firebase (4.10.1) / FirebaseAnalytics (4.1.0) / FirebaseCore (4.0.17)

Description

FIRAScreenViewReporter is calling viewDidLoad() of a ViewController that has been initialized using instantiateViewController(withIdentifier:) .

Scenario

I have two ViewControllers: _MyFirstViewController_ and _MySecondViewController_ that was both initialized using instantiateViewController(withIdentifier:) like so:


first = storyboard.instantiateViewController(withIdentifier: "MyFirstViewController")

second = storyboard.instantiateViewController(withIdentifier: "MySecondViewController")

They have a same _ParentViewController_. The _First_ one is presented

presentViewController(first, animated: true, completion: nil)

and hence start its lifecycle and of its parent. After that, somehow the _Second_ calls its viewDidLoad(). Since it wasn't my code that did so (I didn't presented the _MySecondViewController_), I checked the stacktrace (sorry, is not pretty printed) and the last thing I could identify was a FIRACurrentViewControllerInStack call. To check if the Firebase Screen Tracking was causing this behavior I created a flag FirebaseScreenReportingEnabled on my _Info.plist_ and set it to NO. Now MySecondViewController's viewDidLoad() wasn't being called by Firebase anymore.

Why is this an issue?

Because sometimes you have code on your viewDidLoad() that is not suppose to execute without your awareness.

frame #0: 0x000000010869ff9c my-app`MySecondViewController.viewDidLoad(self=0x00007f88b685a110) at MySecondViewController.swift:17
frame #1: 0x00000001086a12f4 my-app`@objc MySecondViewController.viewDidLoad() at MySecondViewController.swift:0
frame #2: 0x000000010a776931 UIKit`-[UIViewController loadViewIfRequired] + 1344
frame #3: 0x000000010a776c7d UIKit`-[UIViewController view] + 27
frame #4: 0x000000010875599a my-app`FIRACurrentViewControllerInStack + 1217
frame #5: 0x0000000108757141 my-app`-[FIRAScreenViewReporter viewControllerDidAppear:] + 312
frame #6: 0x0000000108758810 my-app`-[UIViewController(FIRAScreenClassName) fira_viewDidAppear:] + 84
frame #7: 0x000000010a7b46af UIKit`-[UINavigationController viewDidAppear:] + 85
frame #8: 0x000000010a77c31d UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 830
frame #9: 0x000000010a77c726 UIKit`__52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke + 166
frame #10: 0x000000010d19cd7d CoreFoundation`__53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 77
frame #11: 0x000000010d19cc4f CoreFoundation`-[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 207
frame #12: 0x000000010a77c505 UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 1318
frame #13: 0x000000010a7b4716 UIKit`-[UINavigationController viewDidAppear:] + 188
frame #14: 0x000000010a77c31d UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 830
frame #15: 0x000000010a77f0fa UIKit`__64-[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:]_block_invoke + 42
frame #16: 0x000000010a77d3a6 UIKit`-[UIViewController _executeAfterAppearanceBlock] + 86
frame #17: 0x000000010a5f75fa UIKit`_runAfterCACommitDeferredBlocks + 633
frame #18: 0x000000010a6097ee UIKit`_cleanUpAfterCAFlushAndRunDeferredBlocks + 95
frame #19: 0x000000010d18708c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
frame #20: 0x000000010d17cd15 CoreFoundation`__CFRunLoopDoBlocks + 341
frame #21: 0x000000010d17c472 CoreFoundation`__CFRunLoopRun + 850
frame #22: 0x000000010d17be98 CoreFoundation`CFRunLoopRunSpecific + 488
frame #23: 0x0000000110686ad2 GraphicsServices`GSEventRunModal + 161
frame #24: 0x000000010a5eb676 UIKit`UIApplicationMain + 171
frame #25: 0x0000000108478f4f my-app`main(argc=3, argv=0x00007ffee77ab448) at main.m:13
frame #26: 0x000000010e47e92d libdyld.dylib`start + 1
frame #27: 0x000000010e47e92d libdyld.dylib`start + 1

This is a similar issue but doesn't seem to affect the page view controller case mentioned originally.

I'll take a look.

This is fixed and will be out in the next Analytics release.

So fast! Thank you

The fix will go out with the next minor release of Firebase. Follow the milestone tag for more details.

Was this page helpful?
0 / 5 - 0 ratings