|
2 | 2 | import CwlPreconditionTesting
|
3 | 3 | #elseif canImport(CwlPosixPreconditionTesting)
|
4 | 4 | import CwlPosixPreconditionTesting
|
| 5 | +#elseif canImport(Glibc) |
| 6 | +// swiftlint:disable all |
| 7 | +import Glibc |
| 8 | + |
| 9 | +// This function is called from the signal handler to shut down the thread and return 1 (indicating a SIGILL was received). |
| 10 | +private func callThreadExit() { |
| 11 | + pthread_exit(UnsafeMutableRawPointer(bitPattern: 1)) |
| 12 | +} |
| 13 | + |
| 14 | +// When called, this signal handler simulates a function call to `callThreadExit` |
| 15 | +private func sigIllHandler(code: Int32, info: UnsafeMutablePointer<siginfo_t>?, uap: UnsafeMutableRawPointer?) -> Void { |
| 16 | + guard let context = uap?.assumingMemoryBound(to: ucontext_t.self) else { return } |
| 17 | + |
| 18 | + // 1. Decrement the stack pointer |
| 19 | + context.pointee.uc_mcontext.gregs.15 /* REG_RSP */ -= Int64(MemoryLayout<Int>.size) |
| 20 | + |
| 21 | + // 2. Save the old Instruction Pointer to the stack. |
| 22 | + let rsp = context.pointee.uc_mcontext.gregs.15 /* REG_RSP */ |
| 23 | + if let ump = UnsafeMutablePointer<Int64>(bitPattern: Int(rsp)) { |
| 24 | + ump.pointee = rsp |
| 25 | + } |
| 26 | + |
| 27 | + // 3. Set the Instruction Pointer to the new function's address |
| 28 | + var f: @convention(c) () -> Void = callThreadExit |
| 29 | + withUnsafePointer(to: &f) { $0.withMemoryRebound(to: Int64.self, capacity: 1) { ptr in |
| 30 | + context.pointee.uc_mcontext.gregs.16 /* REG_RIP */ = ptr.pointee |
| 31 | + } } |
| 32 | +} |
| 33 | + |
| 34 | +/// Without Mach exceptions or the Objective-C runtime, there's nothing to put in the exception object. It's really just a boolean – either a SIGILL was caught or not. |
| 35 | +public class BadInstructionException { |
| 36 | +} |
| 37 | + |
| 38 | +/// Run the provided block. If a POSIX SIGILL is received, handle it and return a BadInstructionException (which is just an empty object in this POSIX signal version). Otherwise return nil. |
| 39 | +/// NOTE: This function is only intended for use in test harnesses – use in a distributed build is almost certainly a bad choice. If a SIGILL is received, the block will be interrupted using a C `longjmp`. The risks associated with abrupt jumps apply here: most Swift functions are *not* interrupt-safe. Memory may be leaked and the program will not necessarily be left in a safe state. |
| 40 | +/// - parameter block: a function without parameters that will be run |
| 41 | +/// - returns: if an SIGILL is raised during the execution of `block` then a BadInstructionException will be returned, otherwise `nil`. |
| 42 | +public func catchBadInstruction(block: @escaping () -> Void) -> BadInstructionException? { |
| 43 | + // Construct the signal action |
| 44 | + var sigActionPrev = sigaction() |
| 45 | + var sigActionNew = sigaction() |
| 46 | + sigemptyset(&sigActionNew.sa_mask) |
| 47 | + sigActionNew.sa_flags = SA_SIGINFO |
| 48 | + sigActionNew.__sigaction_handler = .init(sa_sigaction: sigIllHandler) |
| 49 | + |
| 50 | + // Install the signal action |
| 51 | + if sigaction(SIGILL, &sigActionNew, &sigActionPrev) != 0 { |
| 52 | + fatalError("Sigaction error: \(errno)") |
| 53 | + } |
| 54 | + |
| 55 | + defer { |
| 56 | + // Restore the previous signal action |
| 57 | + if sigaction(SIGILL, &sigActionPrev, nil) != 0 { |
| 58 | + fatalError("Sigaction error: \(errno)") |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + var b = block |
| 63 | + let caught: Bool = withUnsafeMutablePointer(to: &b) { blockPtr in |
| 64 | + // Run the block on its own thread |
| 65 | + var handlerThread: pthread_t = 0 |
| 66 | + let e = pthread_create(&handlerThread, nil, { arg in |
| 67 | + guard let arg = arg else { return nil } |
| 68 | + (arg.assumingMemoryBound(to: (() -> Void).self).pointee)() |
| 69 | + return nil |
| 70 | + }, blockPtr) |
| 71 | + precondition(e == 0, "Unable to create thread") |
| 72 | + |
| 73 | + // Wait for completion and get the result. It will be either `nil` or bitPattern 1 |
| 74 | + var rawResult: UnsafeMutableRawPointer? = nil |
| 75 | + let e2 = pthread_join(handlerThread, &rawResult) |
| 76 | + precondition(e2 == 0, "Thread join failed") |
| 77 | + return Int(bitPattern: rawResult) != 0 |
| 78 | + } |
| 79 | + |
| 80 | + return caught ? BadInstructionException() : nil |
| 81 | +} |
| 82 | +// swiftlint:enable all |
5 | 83 | #endif
|
6 | 84 |
|
7 | 85 | public func throwAssertion<Out>() -> Predicate<Out> {
|
8 | 86 | return Predicate { actualExpression in
|
9 |
| - #if arch(x86_64) && canImport(Darwin) |
| 87 | + #if arch(x86_64) && (canImport(Darwin) || canImport(Glibc)) |
10 | 88 | let message = ExpectationMessage.expectedTo("throw an assertion")
|
11 | 89 |
|
12 | 90 | var actualError: Error?
|
@@ -43,9 +121,11 @@ public func throwAssertion<Out>() -> Predicate<Out> {
|
43 | 121 | return PredicateResult(bool: caughtException != nil, message: message)
|
44 | 122 | }
|
45 | 123 | #else
|
46 |
| - fatalError("The throwAssertion Nimble matcher can only run on x86_64 platforms with " + |
47 |
| - "Objective-C (e.g. macOS, iPhone 5s or later simulators). You can silence this error " + |
48 |
| - "by placing the test case inside an #if arch(x86_64) or canImport(Darwin) conditional statement") |
| 124 | + let message = """ |
| 125 | + The throwAssertion Nimble matcher can only run on x86_64 platforms. |
| 126 | + You can silence this error by placing the test case inside an #if arch(x86_64) conditional statement. |
| 127 | + """ |
| 128 | + fatalError(message) |
49 | 129 | #endif
|
50 | 130 | }
|
51 | 131 | }
|
0 commit comments