mattgallagher / CwlPreconditionTesting

A Mach exception handler that allows Swift precondition failures to be caught and tested.
ISC License
175 stars 46 forks source link

Occasional crashes when running tests #11

Closed pettermahlen closed 2 years ago

pettermahlen commented 6 years ago

Not entirely sure that this library is the cause, but it seems likely. Here's a partial crash log from one instance:

Process:               xctest [10244]
Path:                  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest
Identifier:            xctest
Version:               13764
Code Type:             X86-64 (Native)
Parent Process:        launchd_sim [10207]
Responsible:           xctest [10244]
User ID:               501

Date/Time:             2018-03-26 10:35:53.690 +0200
OS Version:            Mac OS X 10.13.3 (17D47)
Report Version:        12
Bridge OS Version:     3.0 (14Y661)
Anonymous UUID:        1ADB661F-7E9B-F1CE-C84B-B4201B79CF67

Sleep/Wake UUID:       824E7AA5-8794-4169-800A-3FEC2F0BEF0D

Time Awake Since Boot: 920000 seconds
Time Since Wake:       7700 seconds

System Integrity Protection: enabled

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x00007f7d4925e670
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Segmentation fault: 11
Termination Reason:    Namespace SIGNAL, Code 0xb
Terminating Process:   exc handler [0]

VM Regions Near 0x7f7d4925e670:
    Stack                  000070000bad5000-000070000bb57000 [  520K] rw-/rwx SM=COW  thread 3
-->
    MALLOC_TINY            00007f9d47400000-00007f9d47800000 [ 4096K] rw-/rwx SM=PRV

Application Specific Information:
CoreSimulator 494.33 - Device: iPhone 6 - Runtime: iOS 11.2 (15C107) - DeviceType: iPhone 6
Fatal error: cannot add connections when disposed: file /Users/petter/swift/Mobius.swift/MobiusCore/Sources/ConnectablePublisher.swift, line 43
Fatal error: cannot accept values when disposed: file /Users/petter/swift/Mobius.swift/MobiusCore/Sources/ConnectablePublisher.swift, line 34

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_malloc.dylib              0x00000001141dd0ab tiny_malloc_from_free_list + 148
1   libsystem_malloc.dylib              0x00000001141cbf13 szone_malloc_should_clear + 384
2   libsystem_malloc.dylib              0x00000001141d1fa9 malloc_zone_malloc + 103
3   libsystem_malloc.dylib              0x00000001141d421d malloc + 24
4   libxpc.dylib                        0x0000000114325308 _xpc_malloc + 47
5   libxpc.dylib                        0x0000000114318dd9 _xpc_dictionary_insert + 345
6   libxpc.dylib                        0x00000001143195b6 xpc_dictionary_set_uuid + 41
7   libsystem_trace.dylib               0x00000001142ddce0 _os_activity_stream_entry_encode + 980
8   libsystem_trace.dylib               0x00000001142dd736 _os_activity_stream_reflect + 310
9   libsystem_trace.dylib               0x00000001142e9c8a _os_log_impl_stream + 301
10  libsystem_trace.dylib               0x00000001142e9226 _os_log_impl_flatten_and_send + 4596
11  libsystem_trace.dylib               0x00000001142ea528 _os_log_with_args_impl + 449
12  libsystem_asl.dylib                 0x0000000114012eed asl_log + 258
13  libswiftCore.dylib                  0x0000000122f13960 swift_reportError + 64
14  libswiftCore.dylib                  0x0000000122f54060 _swift_stdlib_reportFatalErrorInFile + 224
15  libswiftCore.dylib                  0x0000000122c1b9bc closure #1 in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:) + 284
16  libswiftCore.dylib                  0x0000000122ef0301 partial apply for closure #1 in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:) + 97
17  libswiftCore.dylib                  0x0000000122f07269 closure #1 in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:)partial apply + 9
18  libswiftCore.dylib                  0x0000000122c1b1eb specialized StaticString.withUTF8Buffer<A>(_:) + 187
19  libswiftCore.dylib                  0x0000000122e573c4 specialized closure #1 in _assertionFailure(_:_:file:line:flags:) + 180
20  libswiftCore.dylib                  0x0000000122ea419f partial apply for closure #1 in _assertionFailure(_:_:file:line:flags:) + 127
21  libswiftCore.dylib                  0x0000000122c1b1eb specialized StaticString.withUTF8Buffer<A>(_:) + 187
22  libswiftCore.dylib                  0x0000000122dd7e70 specialized _assertionFailure(_:_:file:line:flags:) + 144
23  com.spotify.mobius.core             0x0000000122b8d00d MobiusController.connect<A>(_:) + 845
24  com.spotify.MobiusTests             0x000000011ff24e35 closure #1 in closure #1 in closure #2 in closure #2 in closure #1 in MobiusControllerTests.spec() + 405
25  com.spotify.MobiusTests             0x000000011ff2eb83 partial apply for closure #1 in closure #1 in closure #2 in closure #2 in closure #1 in MobiusControllerTests.spec() + 67
26  com.spotify.MobiusTests             0x000000011fefe48a thunk for @callee_owned () -> (@unowned ()?, @error @owned Error) + 26
27  com.spotify.MobiusTests             0x000000011ff2ec23 thunk for @callee_owned () -> (@unowned ()?, @error @owned Error)partial apply + 83
28  net.jeffhui.Nimble                  0x0000000122a3df98 closure #1 in memoizedClosure<A>(_:) + 280 (Expression.swift:9)
29  net.jeffhui.Nimble                  0x0000000122a3ec11 partial apply for closure #1 in memoizedClosure<A>(_:) + 97
30  net.jeffhui.Nimble                  0x0000000122a47fad specialized closure #1 in closure #1 in throwAssertion() + 61 (ThrowAssertion.swift:29)
31  net.jeffhui.Nimble                  0x0000000122a47f45 partial apply for closure #1 in closure #1 in throwAssertion() + 101
32  net.jeffhui.Nimble                  0x0000000122a319d0 thunk for @callee_owned () -> () + 32
33  net.jeffhui.Nimble                  0x00000001229d4651 catchExceptionOfKind + 31
34  net.jeffhui.Nimble                  0x0000000122a36986 specialized catchReturnTypeConverter<A>(_:block:) + 118 (CwlCatchException.swift:29)
35  net.jeffhui.Nimble                  0x0000000122a368ee static NSException.catchException(in:) + 30 (CwlCatchException.swift:33)
36  net.jeffhui.Nimble                  0x0000000122a1f12b specialized catchBadInstruction(in:) + 379 (CwlCatchBadInstruction.swift:185)
37  net.jeffhui.Nimble                  0x0000000122a47e5d specialized closure #1 in throwAssertion() + 285 (ThrowAssertion.swift:34)

and another thread:

Thread 3:
0   libsystem_kernel.dylib              0x00000001143d37c2 mach_msg_trap + 10
1   libsystem_kernel.dylib              0x00000001143d2cdc mach_msg + 60
2   net.jeffhui.Nimble                  0x0000000122a1ea92 machMessageHandler(_:) + 178 (CwlCatchBadInstruction.swift:102)
3   net.jeffhui.Nimble                  0x0000000122a1efa9 @objc machMessageHandler(_:) + 9
4   libsystem_pthread.dylib             0x00000001144136c1 _pthread_body + 340
5   libsystem_pthread.dylib             0x000000011441356d _pthread_start + 377
6   libsystem_pthread.dylib             0x0000000114412c5d thread_start + 13

It feels like malloc shouldn't be crashe-able from a pure Swift application like the one that's being tested. And there are two different threads running CwlCatchBadInstruction code - is it possible that there's a threading issue somewhere? The code being tested uses a custom concurrent DispatchQueue.

mattgallagher commented 6 years ago

You're seeing heap corruption. The heap corruption does not appear to be from the current call to catchExceptionOfKind shown on the stack or the current machMessageHandler on Thread 3. It is most likely due to a previous call.

My guess – given that the log file includes multiple "Fatal error:" messages – is that you've hit a previous Mach Exception that was successfully caught by CwlPreconditionTesting and this has unwound the stack over your code (i.e. a "catch") but this unwinding has left memory in an inconsistent state.

This might not be a fixable problem. You would need to understand which throw is leaving memory inconsistent and reorder operations around it so it is resilient to an exception being thrown through it.

pettermahlen commented 6 years ago

Hmm... Not sure I quite understand - are you talking about reordering Swift operations in the test code, in the production code, or in the CwlPreconditionTesting library? It doesn't look to me like I'm doing something that should potentially be harmful - this is apparently caused by multiple tests that check for exceptions, and that should be normal.

Is the problem potentially related to fatalError returning Never, thus AFAIU permitting the compiler to optimise-away any cleanup happening after it? Would love to dig a little deeper into how CwlPreconditionTesting works if you have a quick pointer to the relevant code to look at.

mattgallagher commented 6 years ago

CwlPreconditionTesting throws an Objective-C exception which unwinds the stack (ignores everything and abruptly jumps to the catch location). Code needs to be "exception safe" for this to work and Swift's automatic reference counting is not exception safe (you can look up problems with exception safe code in Objective-C, Swift or C++, the issues involved are fairly similar). With CwlPreconditionTesting, you will almost always get memory leaks (as the release to balance any retained memory is skipped) but it's double free problems (retains skipped and memory getting freed while still in use) that tends to cause memory corruption. Without careful memory analysis though, I can't precisely guess what's causing your problems.

mattgallagher commented 2 years ago

It's been a while on this and it doesn't look like this is a widespread issue. I'm going to close the issue. It is certainly possible to trigger memory corruption across exception frames. That's why Swift doesn't officially support exceptions. It's just something you need to recode to avoid.