jeffh / CwlPreconditionTesting

A Mach exception handler that allows Swift precondition failures to be caught and tested.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CwlPreconditionTesting

A Mach exception handler, written in Swift and Objective-C, that allows EXC_BAD_INSTRUCTION (as raised by Swift's assertionFailure/preconditionFailure/fatalError) to be caught and tested.

For an extended discussion of this code, please see the Cocoa with Love article:

Partial functions in Swift, Part 2: Catching precondition failures

Usage

The short version is:

  1. git clone https://github.com/mattgallagher/CwlPreconditionTesting.git
  2. drag the "CwlPreconditionTesting.xcodeproj" file into your project's file tree in Xcode
  3. go to your testing target's Build Phase settings and under "Target Dependencies" press the "+" button and select the relevant "CwlPreconditionTesting" target ("_iOS" or "_OSX", depending on your testing target's SDK)
  4. write import CwlPreconditionTesting at the top of any test file where you want to use catchBadInstruction (Swift should handle the linkage automatically when you do this)
  5. use the catchBadInstruction function as shown in the CwlCatchBadInstructionTests.swift tests file

Project details

The "CwlPreconditionTesting.xcodeproj" contains two targets:

  • CwlPreconditionTesting_OSX
  • CwlPreconditionTesting_iOS

both build a framework named "CwlPreconditionTesting.framework". If you're linking manually, be certain to select the "CwlPreconditionTesting.framework" from the appropriate target.

Remember: the iOS build is useful only in the simulator. All Mach exception handling code will be conditionally excluded in any device build.

Static inclusion

Due to the complications associated with needing to call into and out of Objective-C, static inclusion in other projects is not a single file nor a quick drag and drop. There's at least 7 files and you'll need to add some project settings.

All of the following files:

  • CwlCatchBadInstruction.swift
  • CwlCatchBadInstruction.h
  • CwlCatchBadInstruction.m
  • CwlCatchException.swift
  • CwlCatchException.h
  • CwlCatchException.m

and either:

  • $(SDKROOT)/usr/include/mach/mach_exc.defs
  • mach_excServer.c

need to be added to the testing target for OS X projects or iOS projects, respectively.

Your target will also need to have the following macros defined in the "Apple LLVM - Preprocessing" → "Preprocessor Macros" build setting:

PRODUCT_NAME=$(PRODUCT_NAME)

This lets the Objective-C file generate the include directive for the autogenerated Swift header so it can call back into Swift during the Mach exception handler callbacks. This macro should stay in sync if you change the target name but if you do anything else in your project that changes the name of the autogenerated Swift header independent of the target name (or you want to add spaces or other command-line complications to the target name), you'll want to update "CwlCatchBadInstruction.m" directly with the correct include directive.

Additionally, you'll need a standard Objective-C "Bridging header" for your testing target and it will need to include the following import statements:

#if defined(__x86_64__)
#import <CwlPreconditionTesting/CwlCatchBadInstruction.h>
#endif

#import <CwlPreconditionTesting/CwlCatchException.h>

Using POSIX signals and setjmp/longjmp

For comparison or for anyone running this code on a platform without Mach exceptions or the Objective-C runtime, I've added a proof-of-concept implementation of catchBadInstruction that uses a POSIX SIGILL sigaction and setjmp/longjmp to perform the throw.

In Xcode, you can simply select the CwlPreconditionTesting_POSIX target (instead of the OSX or iOS targets). If you're building without Xcode: all you need is the CwlCatchBadInstructionPOSIX.swift file (compared to the Mach exception handler, the code is tiny doesn't have any weird Objective-C/MiG file dependencies).

Warning No. 1: on OS X, this approach can't be used when lldb is attached since lldb's Mach exception handler blocks the SIGILL from ever occurring (I've disabled the "Debug Executable" setting for the tests in Xcode - re-enable it to witness the problem).

Warning No. 2: if you're switching between the CwlPreconditionTesting_OSX and CwlPreconditionTesting_POSIX targets, Xcode (as of Xcode 7.2.1) will not detect the change and will not remove the old framework correctly so you'll need to clean your project otherwise the old framework will hang around.

Additional problems in decreasing severity include:

  • the signal handler is whole process (rather than correctly scoped to the thread where the "catch" occurs)
  • the signal handler doesn't deal with re-entrancy whereas the mach exception handler remains deterministic in the face of multiple fatal errors
  • the signal handler overwrites the "red zone" which is technically frowned upon in signal handlers (although unlikely to cause problems here)

About

A Mach exception handler that allows Swift precondition failures to be caught and tested.


Languages

Language:C 42.4%Language:Swift 41.2%Language:Objective-C 16.4%