18

In a UIViewController (rolePageController) I configure another UIViewController (drawerController) and pass it 2 UIViews from the role page that will be part of the drawerController's configuration. As soon as the drawerController tries to access the IBOutlet views from the rolePageController, it crashes with EXC_BAD_ACCESS (code=EXC_I386_GPFLT).

In the 1st VC (rolePageController), here are the IBOutlets:

@IBOutlet var rolePageDrawerView: UIView!
@IBOutlet var rolePageContentView: UIView!

In rolePageController.viewDidLoad() I make a call to the drawerController.configureDrawer(...):

override func viewDidLoad() {
    super.viewDidLoad()

    //other stuff happens here

    let drawerController = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateViewController(withIdentifier: "drawerController") as! DrawerViewController
    drawerController.configureDrawer(drawerContainerView: self.rolePageDrawerView, overlaidView: self.rolePageContentView)

    //other stuff here
}

The DrawerViewController protocol is defined as:

protocol DrawerViewController where Self: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

Here is the code for the configureDrawer(...) func:

private var drawerParentView: UIView!
private var overlaidByDrawerView: UIView!


func configureDrawer(drawerContainerView: UIView, overlaidView: UIView) {
    self.drawerParentView = drawerContainerView
    self.overlaidByDrawerView = overlaidView
}

Noticed in the debugger that the drawerController instance that is called does not match the self instance that receives the call. Here is the address of the instance that will be called:

enter image description here

Here is the address of the instance when I step into the call:

enter image description here

The address of drawerController before the call is not the address of self when I step into the call. That should never happen.

I have created a simplified project that reproduces the crash at https://github.com/ksoftllc/DynamicStackBufferOverflow.

Solution Solution turned out to be to remove the where clause from the DrawerViewController protocol.

protocol DrawerViewController where Self: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
Chuck Krutsinger
  • 2,830
  • 4
  • 28
  • 50
  • 2
    Try moving the stuff from viewDidLoad to viewDidAppear. Sounds like it might not be drawn on the screen when it's called. Also, if something's got a `!` after it, it's gotta be there, so it shouldn't be weak. Not sure why Xcode behaves that way out of the box, but don't make implicitly unwrapped optionals weak references. – Adrian Dec 07 '18 at 03:04
  • Just because something's not `nil` doesn't mean it's a valid piece of memory. You can still get a bad access from non-`nil` memory locations if they are on pages of memory your process doesn't have permission to access. – user1118321 Dec 07 '18 at 03:49
  • The memory is valid when I access it in the 1st VC, but not in the 2nd. I already mentioned that I tried making the references strong and still get the access violation. – Chuck Krutsinger Dec 07 '18 at 05:32
  • I have updated the question to include more information and more source code. I have also changed the variables to strong and the crash is exactly the same. – Chuck Krutsinger Dec 07 '18 at 15:20
  • @Adrian IB uses weak references to avoid a retain cycle since the view has a reference to its controller. Regardless, I made them strong and it still has the same problem. In addition, the issue is not that the address is nil or deallocated, its that it is giving a General Page Fault. The screen shots and the debugger messages in my post show that the memory is valid in the calling VC, but invalid in the called VC even though the same address is shown. – Chuck Krutsinger Dec 07 '18 at 19:49
  • @user1118321 Any thoughts as to why valid memory in the calling VC would be invalid when accessed in the called VC? They are both in the same process and same thread and same software module. – Chuck Krutsinger Dec 07 '18 at 19:50
  • My first guess would be that some object is a zombie and that object is trying to access a pointer that it contains, but which has been overwritten with invalid data. You could run with Zombies enabled or use the Zombies instrument to rule out that possibility. – user1118321 Dec 08 '18 at 02:13
  • I'd also run with address sanitizer turned on to see if it turns up anything. – user1118321 Dec 08 '18 at 02:19
  • 1
    @ChuckKrutsinger Would you like to share a sample project with the issue? Did you try restarting Xcode, deleting app and installing again? – Kamran Dec 09 '18 at 20:50
  • 1
    Yes a sample project would help. – J. Doe Dec 10 '18 at 10:06
  • 1
    Isn't here some invalid initial value for the properties of `drawerController`? e.g. some old storyboard connection? – Sulthan Dec 10 '18 at 18:07
  • `@IBOutle var rolePageDrawerView: UIView! ` does not compile, because `@IBOutle` is not a valid attribute. Are you sure you copied and pasted your code accurately? Even minor typos can make it impossible to diagnose a problem. – rob mayoff Dec 10 '18 at 18:25
  • 1
    Unwrapping an implicitly-unwrapped optional (like `rolePageDrawerView`) does not trigger `EXC_BAD_ACCESS`. In a release build, it triggers `EXC_BAD_INSTRUCTION`, and in a debug build it first stops in `_swift_runtime_on_report` and then (if you continue execution) triggers `EXC_BAD_INSTRUCTION`. So the problem is probably **not** due to an unexpected nil. – rob mayoff Dec 10 '18 at 18:26
  • Thanks @robmayoff for catching that typo. I have fixed it. The code did not contain the typo and is still crashing. – Chuck Krutsinger Dec 10 '18 at 18:30
  • If even `self` is not valid that indicates that the drawerController instance is somehow immediately discarded or not properly initialized. Also I noticed that it's a custom view controller (`FanConcertCalendarViewController`, I hope that wasn't supposed to be confidential, it's in your screenshot). I can also see that you're trying to get its view right after configure, and though that's never reached it makes me think it's not a simple design you have overall (not saying that's bad!). This makes it hard for us to understand the issue. – Gero Dec 10 '18 at 18:31
  • @ChuckKrutsinger sometimes debugger is not working well, better use `print` function and check output in console – Maciej Gad Dec 10 '18 at 18:45
  • Thanks @Gero. Yes, it is a complex UI with views inside of views inside of views. The issue, though, should be diagnosable using the info we have or by collecting more. The issue is that the 2nd VC instance that gets called is somehow not a valid instance (see update 4). The value returned from HasDrawer.drawerController is problematic, but I don't know why. It does not deinit and is valid before being returned. – Chuck Krutsinger Dec 10 '18 at 18:51
  • 1
    I would take a good stare at the storyboard, "StoryboardName". Good chance there's a stray/obsolete connection in there that's corrupting things on when the VC is instantiated, and that's messing up the later code. – Graham Perks Dec 10 '18 at 22:42
  • How would anything in the storyboard corrupt the stack? @GrahamPerks – Chuck Krutsinger Dec 10 '18 at 22:56
  • 1
    I've seen issues when e.g. a XIB references an outlet with incorrect type information. Or the Custom Class of an object is incorrect. I don't know that would blow the stack, but does cause head-scratching. Alternately, is there another thread doing stuff here? – Graham Perks Dec 10 '18 at 23:07
  • Seems likely corruption is occurring earlier. Try eliminating code paths to see if anything makes the issue go away. The code marked `//other stuff happens here` is immediately suspect :) – Graham Perks Dec 10 '18 at 23:11
  • @GrahamPerks How can it be that "corruption is occurring earlier" if the instance is called in the very next line after it is instantiated? – Chuck Krutsinger Dec 11 '18 at 01:30
  • Well it sure isn’t getting corrupted later! Narrow down the cause by eliminating other code. Eventually you’ll skip something, and this crashing code will suddenly work. – Graham Perks Dec 11 '18 at 04:59
  • I think Graham's right, it has to be either something weird in the storyboard (which perhaps could also explain why the compiler isn't complaining, the way outlets are handled prevents some things it does I guess) or maybe even something threading related. Maybe the initializer of the `FanConcertCalendarViewController` itself somehow triggers it, or an observer on a property? I've seen mistakes like dispatching something to another queue in an observer and kicking out a reference under the main thread's feet... (not in Swift yet, though) – Gero Dec 11 '18 at 07:48
  • Can you just upload the smallest project with reproducible issue? – Timur Bernikovich Dec 11 '18 at 10:13
  • @TimurBernikowich I have uploaded a simple project that reproduces the issue. – Chuck Krutsinger Dec 12 '18 at 14:24

6 Answers6

9

Found the offending code, but I don't know why this would cause the errors I was seeing. The drawerController conforms to DrawerViewController protocol, defined as:

protocol DrawerViewController where Self: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

When I remove the Where condition, it no longer crashes.

protocol DrawerViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

The where clause was not actually necessary for correct function of the program, so I will proceed without it.

UPDATE I filed a bug with swift.org and received a response. Adding a where clause to a protocol is not supported in Swift 4.2, but will be supported in Swift 5.0. In addition, @J Doe posted below a way to accomplish this with an update to Xcode toolkit.

Chuck Krutsinger
  • 2,830
  • 4
  • 28
  • 50
  • 1
    This could be a Swift compiler bug. If you can reduce it to a small test case, you should file a bug at https://bugs.swift.org/ – rob mayoff Dec 11 '18 at 20:04
  • 2
    Also found this answer on stackoverflow that indicates there are known issues with using the where clause with a protocol and even suggests a workaround if the intent is to require a certain superclass as part of the protocol. https://stackoverflow.com/a/50647762/1256015 – Chuck Krutsinger Dec 11 '18 at 21:44
  • 2
    Just fyi, I tried out your sample project and it slightly differs from what you described in the original question. It didn't crash *inside* the `configureDrawer` method but right before it. It seems that's related to the fact the `DrawerView` extension wasn't actually implementing the required method (that was already done in `DrawerViewController` directly). Moving the method implementation into the actual extension of the protocol resulted in it crashing on the second line, similarly to your scenario (why not on the 1st I don't know). Removing `where...` then worked again. – Gero Dec 12 '18 at 10:43
  • 2
    More weirdness/hints: Calling `configureDrawer` directly on the `vc` variable, without a temporary one cast to `DrawerView` also avoids the crash (regardless of whether the method is implemented in the extension or directly in the class). This seems to indicate it has to do with some internal casting. I will eventually comment on the bug report itself as well – Gero Dec 12 '18 at 10:50
5

dynamic-stack-buffer-overflow doesn't have anything to do with recursion. It means an alloca buffer was overrun. Check the asan runtime source code.

Suppose the stack is laid out so that you have an alloca buffer followed by an object pointer—maybe even one of the object pointers passed as an argument.

Suppose the alloca buffer gets overrun. In an asan build, this can trigger a dynamic-stack-buffer-overflow error. But in a non-asan build, it just writes over the bytes of that object pointer. Suppose it writes bytes that form an address that's not mapped in your process's page table.

If the program tries to read that object pointer and store it elsewhere (say, in an instance variable), it has to increment the reference count of the object. But that means dereferencing the pointer—and the pointer points to an unmapped address. Perhaps that's leading to a general protection fault, which Mach calls an EXC_I386_GPFLT.

It would be helpful if you posted the stack trace of the asan dynamic-stack-buffer-overflow error, and the disassembly of the code leading up to the error.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thank you for the insights about the dynamic-stack-buffer-overflow. That is helpful. I'll add the stacktrace shortly. – Chuck Krutsinger Dec 10 '18 at 19:39
  • 1
    Your stack trace needs to be indented four spaces for better formatting. Anyway, the stack trace from `print(self)` is probably not useful, because by then the memory corruption has already happened. You need to run with asan, and look at the stack trace when asan reports an error. – rob mayoff Dec 10 '18 at 20:03
  • Interestingly, the print(self) is where memory was already corrupt but just before ASAN reports it. Nonetheless, I removed the print(self). – Chuck Krutsinger Dec 10 '18 at 21:32
  • Rob, please take a look at the stack trace. I can't figure this out and it is a show stopper. – Chuck Krutsinger Dec 11 '18 at 02:49
  • 1
    You should try to figure out what buffer is being overrun. Walk up the stack one frame at a time, and use the lldb command `frame var -L -D1` to see what is around the address reported by asan. – rob mayoff Dec 11 '18 at 04:15
  • Rob @rob mayoff, the ASAN says "Address x is located in stack of thread T0" where x is the address of self.drawerParentView. What does that indicate? How can I resolve the issue knowing this? Also, if I go up to the caller's frame, drawerParentView is nil, but in the drawerController frame it has the same address as the tableView ivar and both should be nil. – Chuck Krutsinger Dec 11 '18 at 18:26
  • Rob, please see the latest update. How is the address changing? – Chuck Krutsinger Dec 11 '18 at 19:34
3

It really looks like Swift compiler bug. I simplified your code for clarification:

func foo(_ wow: TestProtocol) {
    wow.foo()
}

protocol TestProtocol where Self: NSObject {
    func foo()
}

class TestClass: NSObject, TestProtocol {

    func foo() {
        print("Much wow")
    }

}

foo(TestClass())

You can report this as bug. To resolve this issue I propose you not use where statement or pass object with it's type func foo(_ wow: TestClass {.

Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58
0

To fix your issue, run it on the development trunk toolchain snapshot. You can download it here:

https://swift.org/download/

Go to Snapshots -> Trunk Development (master) XCode (so not Swift 5.0) and download the snapshot as of 15 December (I got the one from 30 November, but I am sure 15 December will work as well.)

After you installed the toolchain, in XCode go to: File -> Preferences -> Components and select the newest toolchain. It now runs without any crash.

Also, the where Self: UIViewController can be shorten to :UIViewcontroller (This only works on the newest toolchains):

protocol DrawerViewController: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114
0

Had a similar issue today. For some reason, it was only occurring on the latest macOS.

The reason was that I hadn't specified a correct subclass for one of my IBOutlet in Storyboard.

The funny thing is that exc_bad_access occurred on another IBOutlet, albeit a subview of the one which wasn't appropriately specified. So, all in all, someone may find it helpful to check that all subclasses of IBOutlets are correctly specified in Storyboard :)

Andriy Gordiychuk
  • 6,163
  • 1
  • 24
  • 59
-2

Move this Function Call from viewDidLoad to viewWillAppear drawerController.configureDrawer(drawerContainerView: self.rolePageDrawerView, overlaidView: self.rolePageContentView)

Shreyas Shetty
  • 27
  • 1
  • 2
  • 7