Crash with macros and Swift Testing

I'm experiencing a crash in Swift Testing. I think it has to do with my own macros and @testable import conflicting.

I have a package that defines a protocol (FooProtocol). I then have a series of macros that takes a struct and expands it out to work with these protocols. For example, the following:

@Foo
struct MyFoo {
    @FooProperty var value1: String
    @SecureFooProperty(key: "12345") var value2: String
    @FooProperty var value3: String
}

Expands out to this:

struct MyFoo {
    var value1: String
    var value2: String
    private let value2Key: [UInt8] = [0x00, 0x01, 0x02]
    var value3: String

    init() {
        // ...
    }
}

extension Foo: FooProtocol { }

protocol AnyMyFoo {
    var value1: String { get set }
    var value2: String { get set }
    var value3: String { get set }
}

struct MockMyFoo: AnyMyFoo {
    var value1: String = ""
    var value2: String = ""
    var value3: String = ""
}

This all works, and I can use SwiftTesting inside of the Swift Package.

If I then import this package in to my main Xcode project, recreate the same MyFoo struct, and try to test it, I get a crash.

import FooPackage
@testable import MyApp

struct MyFooTest {

    func initTest() {
        let foo = MyFoo()
        #expect(foo.value1 == "Something")
        #expect(foo.value2 == "Something Else")
        #expect(foo.value3 == "Another Thing")
    }
}

I'll see a couple of odd behaviors here. Assuming that the MyFoo initializer sets the string values to non-empty strings:

  1. The #expect test will fail, complaining that foo.value2 is actually an empty string.
  2. If I breakpoint in this test and print out via lldb, foo.value2 will actually be correct.
  3. A crash will occur. Typically it happens on the value after the first "secure" property. value3 here.

The crash: Swift/UnsafePointer.swift:1104: Fatal error: UnsafeMutablePointer.initialize overlapping range.

Stack trace:

#0	0x00000001977713f4 in _swift_runtime_on_report ()
#1	0x00000001978341b8 in _swift_stdlib_reportFatalErrorInFile ()
#2	0x000000019785c5b0 in _assertionFailure ()
#3	0x000000019785c928 in _fatalErrorMessage ()
#4	0x00000001978424c4 in merged generic specialization <Swift.UInt8> of Swift.UnsafeMutablePointer.initialize(from: Swift.UnsafePointer<τ_0_0>, count: Swift.Int) -> () ()
#5	0x00000001979c76fc in append ()
#6	0x000000010d0e0800 in protocol witness for Testing.CustomTestStringConvertible.testDescription.getter : Swift.String in conformance Swift.String : Testing.CustomTestStringConvertible in Testing ()
#7	0x000000010d0df964 in __allocating_init ()
#8	0x000000010d0e28a0 in __allocating_init ()
#9	0x000000010d0e3bb4 in __allocating_init ()
#10	0x000000010d0e2740 in __allocating_init ()
#11	0x000000010d06aa94 in generic specialization <Pack{}, Swift.String> of Testing.__Expression.capturingRuntimeValues<each τ_0_0, τ_0_1>(Swift.Optional<τ_0_1>, repeat Swift.Optional<τ_0_0>) -> Testing.__Expression ()
#12	0x000000010d06a1a8 in generic specialization <Pack{Swift.String}, Swift.String> of Testing.__Expression.capturingRuntimeValues<each τ_0_0, τ_0_1>(Swift.Optional<τ_0_1>, repeat Swift.Optional<τ_0_0>) -> Testing.__Expression ()
#13	0x000000010d079fac in __checkBinaryOperation ()
#14	0x000000010d553330 in freestanding macro expansion #2 of expect in module MyApp_Tests file FooTests.swift line 114 column 9 [inlined] at /var/folders/0h/kybjbzf516d2dcknt3fw1hzm0000gn/T/swift-generated-sources/@__swiftmacro_11MyApp_Tests0025FooTestsswift_jqFBgfMX113_8_6expectfMf0_.swift:1

I'm assuming here that @testable import is bringing in the memory layout incorrectly for the struct, but I can't be certain. Using the struct in the app works fine without issue. Testing a similar struct in the Swift package (even using @testable import) works fine. It's just in this case of the Xcode project using Swift Testing to the app's generated code that this occurs.

Additionally, creating the test as an XCTest and using XCTAssert in the same manner as #expect also works.

This appears to be a compiler bug. @hamishknight is this a duplicate of rdar://122011759?

At first glance I don't think so, rdar://122011759 is when you have an intermediate call to something that takes an UnsafePointer argument, e.g:

    let file = try #require(fopen("/file/path", "wb"))

Since the macro expansion inserts additional expressions between the String argument and UnsafePointer parameter which could lead to a use-after-free if the type-checker chooses incorrect bindings for the generic parameters involved. I don't see any such calls to functions taking UnsafePointers here.

@hamishknight @grynspan Should this be reported elsewhere then?

I’m actually able to see this happening in XCTest-based test as well, so it does look like it’s a bug beyond testing frameworks.

I made a change to my macro to put all of the “hidden” key values together at the bottom of the property list of the struct, and that actually resolved the issue as well.

There does to seem to be a memory layout issue here, but the workaround is to put “hidden” values at the end of the struct.