Rxswift: Programmatically triggering UIBarButtonItem action in tests causes a crash

Created on 15 Jun 2017  路  3Comments  路  Source: ReactiveX/RxSwift

Short description of the issue:

In some unit tests, I use code like the following to simulate tapping on a UIBarButtonItem through its target-action pair. If that action is wired up using Rx, I get a crash in my tests related to memory.

    func testBarButtonItem_actionExecution() {
        let button = UIBarButtonItem()
        weak var tapExpectation = expectation(description: "tap")
        button.rx.tap.subscribe(onNext: {
            tapExpectation?.fulfill()
        }).disposed(by: disposeBag)
        _ = button.target?.perform(button.action) // crashes here
        waitForExpectations(timeout: 1, handler: nil)
    }

Expected outcome:

Triggering the action fires the Rx observable and everything is happy. This was actually working until recently, when I switched they way I was integrating RxSwift from Carthage-built to directly incorporated into my project's workspace via a Carthage-managed git submodule.

What actually happens:

Crash in the execution of BarButtonItemTarget.action, swift_unknownRetain on the lines with the arrow:

RxCocoa`@objc BarButtonItemTarget.action(AnyObject) -> ():
    0x115984f10 <+0>:  pushq  %rbp
    0x115984f11 <+1>:  movq   %rsp, %rbp
    0x115984f14 <+4>:  pushq  %r15
    0x115984f16 <+6>:  pushq  %r14
    0x115984f18 <+8>:  pushq  %r12
    0x115984f1a <+10>: pushq  %rbx
    0x115984f1b <+11>: movq   %rdx, %r14
    0x115984f1e <+14>: movq   %rdi, %rbx
    0x115984f21 <+17>: incq   0x51058(%rip)             ; __profc__TZFC7RxCocoa22RxWebViewDelegateProxy20createProxyForObjectfPs9AnyObject_PS1__ + 5504
    0x115984f28 <+24>: movq   0x4eba9(%rip), %r15       ; direct field offset for RxCocoa.BarButtonItemTarget.callback : Swift.ImplicitlyUnwrappedOptional<() -> ()>
    0x115984f2f <+31>: movq   (%rbx,%r15), %r12
    0x115984f33 <+35>: movq   %r14, %rdi
    0x115984f36 <+38>: callq  0x11599f708               ; symbol stub for: swift_unknownRetain
->  0x115984f3b <+43>: movq   %rbx, %rdi
    0x115984f3e <+46>: callq  0x11599f510               ; symbol stub for: objc_retain
    0x115984f43 <+51>: testq  %r12, %r12
    0x115984f46 <+54>: je     0x115984f73               ; <+99> [inlined] RxCocoa.BarButtonItemTarget.action (Swift.AnyObject) -> ()
    0x115984f48 <+56>: movq   0x8(%rbx,%r15), %r15
    0x115984f4d <+61>: movq   %r15, %rdi
    0x115984f50 <+64>: callq  0x1159429b0               ; swift_rt_swift_retain
    0x115984f55 <+69>: movq   %r15, %rdi
    0x115984f58 <+72>: callq  *%r12
    0x115984f5b <+75>: movq   %rbx, %rdi
    0x115984f5e <+78>: callq  0x11599f50a               ; symbol stub for: objc_release
    0x115984f63 <+83>: movq   %r14, %rdi
    0x115984f66 <+86>: popq   %rbx
    0x115984f67 <+87>: popq   %r12
    0x115984f69 <+89>: popq   %r14
    0x115984f6b <+91>: popq   %r15
    0x115984f6d <+93>: popq   %rbp
    0x115984f6e <+94>: jmp    0x11599f6fc               ; symbol stub for: swift_unknownRelease
    0x115984f73 <+99>: ud2    
libswiftCore.dylib`swift_unknownRetain:
    0x115d8c6d0 <+0>:  testq  %rdi, %rdi
    0x115d8c6d3 <+3>:  je     0x115d8c6ff               ; <+47>
    0x115d8c6d5 <+5>:  movabsq $-0x7fffffffffffffff, %rax ; imm = 0x8000000000000001 
    0x115d8c6df <+15>: andq   %rdi, %rax
    0x115d8c6e2 <+18>: jne    0x115d8c6ff               ; <+47>
    0x115d8c6e4 <+20>: movq   0x8a97d(%rip), %rax       ; swift_isaMask
    0x115d8c6eb <+27>: andq   (%rdi), %rax
->  0x115d8c6ee <+30>: testb  $0x1, 0x20(%rax)
    0x115d8c6f2 <+34>: je     0x115d8c6fa               ; <+42>
    0x115d8c6f4 <+36>: testb  $0x2, 0x28(%rax)
    0x115d8c6f8 <+40>: jne    0x115d8c700               ; <+48>
    0x115d8c6fa <+42>: jmp    0x115d987cc               ; symbol stub for: objc_retain
    0x115d8c6ff <+47>: retq   
    0x115d8c700 <+48>: jmp    0x115d7a350               ; swift_retain
    0x115d8c705 <+53>: nopw   %cs:(%rax,%rax)

Self contained code example that reproduces the issue:

See test added in this branch:

https://github.com/ReactiveX/RxSwift/compare/develop...lyricsboy:lyricsboy/UIBarButton-tap-tests

Platform/Environment

  • [x] iOS
  • [ ] macOS
  • [ ] tvOS
  • [ ] watchOS
  • [ ] playgrounds

How easy is to reproduce? (chances of successful reproduce after running the self contained code)

  • [x] easy, 100% repro
  • [ ] sometimes, 10%-100%
  • [ ] hard, 2% - 10%
  • [ ] extremely hard, %0 - 2%

Xcode version:

 Xcode 8.3.2
Build version 8E2002

Installation method:

  • [ ] CocoaPods
  • [ ] Carthage
  • [x] Git submodules

I have multiple versions of Xcode installed:
(so we can know if this is a potential cause of your issue)

  • [x] yes (9 beta)
  • [ ] no

Level of RxSwift knowledge:
(this is so we can understand your level of knowledge
and formulate the response in an appropriate manner)

  • [ ] just starting
  • [x] I have a small code base
  • [ ] I have a significant code base
help wanted

Most helpful comment

Could you try with button.sendActions(for: .touchUpInside)?

All 3 comments

Not sure if this is a bug, button.target?.perform(button.action) makes an incorrect assumption that button.action doesn't take any arguments, but in fact the action function BarButtonItemTarget.action(_:) takes one argument. Changing it to button.target?.perform(button.action, with: nil) can avoid this crash.

Could you try with button.sendActions(for: .touchUpInside)?

@crazytonyli we have a winner! That was totally it. Good catch. I have no idea why it didn't crash in a different build context (release vs. debug maybe?), but that's a question best left to the thinkers.

@devxoul unfortunately not. UIBarButtonItem is not a UIControl, for reasons that are lost to time.

I'll submit a PR for the test I added, since it does test something that wasn't being tested before.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Z-JaDe picture Z-JaDe  路  3Comments

trant picture trant  路  3Comments

acecilia picture acecilia  路  3Comments

angerman picture angerman  路  3Comments

dmial picture dmial  路  3Comments