Skip to content

Fix crash when loading test content records from the type metadata section. #1015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 22 additions & 20 deletions Sources/Testing/Test+Discovery+Legacy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,37 @@
#if !SWT_NO_LEGACY_TEST_DISCOVERY
@_spi(Experimental) @_spi(ForToolsIntegrationOnly) internal import _TestDiscovery

/// A shadow declaration of `_TestDiscovery.TestContentRecordContainer` that
/// allows us to add public conformances to it without causing the
/// `_TestDiscovery` module to appear in `Testing.private.swiftinterface`.
///
/// This protocol is not part of the public interface of the testing library.
@_alwaysEmitConformanceMetadata
protocol TestContentRecordContainer: _TestDiscovery.TestContentRecordContainer {}

/// An abstract base class describing a type that contains tests.
/// A protocol base class describing a type that contains tests.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo here fixed on main already.

///
/// - Warning: This class is used to implement the `@Test` macro. Do not use it
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo here fixed on main already.

/// directly.
open class __TestContentRecordContainer: TestContentRecordContainer {
/// The corresponding test content record.
@_alwaysEmitConformanceMetadata
public protocol __TestContentRecordContainer {
/// The test content record associated with this container.
///
/// - Warning: This property is used to implement the `@Test` macro. Do not
/// use it directly.
open nonisolated class var __testContentRecord: __TestContentRecord {
(0, 0, nil, 0, 0)
}
nonisolated static var __testContentRecord: __TestContentRecord { get }
}

extension DiscoverableAsTestContent where Self: ~Copyable {
/// Get all test content of this type known to Swift and found in the current
/// process using the legacy discovery mechanism.
///
/// - Returns: A sequence of instances of ``TestContentRecord``. Only test
/// content records matching this ``TestContent`` type's requirements are
/// included in the sequence.
static func allTypeMetadataBasedTestContentRecords() -> AnySequence<TestContentRecord<Self>> {
return allTypeMetadataBasedTestContentRecords { type, buffer in
guard let type = type as? any __TestContentRecordContainer.Type else {
return false
}

static func storeTestContentRecord(to outTestContentRecord: UnsafeMutableRawPointer) -> Bool {
outTestContentRecord.withMemoryRebound(to: __TestContentRecord.self, capacity: 1) { outTestContentRecord in
outTestContentRecord.initialize(to: __testContentRecord)
buffer.withMemoryRebound(to: __TestContentRecord.self) { buffer in
buffer.baseAddress!.initialize(to: type.__testContentRecord)
}
return true
}
}
}

@available(*, unavailable)
extension __TestContentRecordContainer: Sendable {}
#endif
6 changes: 3 additions & 3 deletions Sources/TestingMacros/ConditionMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,10 @@ extension ExitTestConditionMacro {
// Create another local type for legacy test discovery.
var recordDecl: DeclSyntax?
#if !SWT_NO_LEGACY_TEST_DISCOVERY
let className = context.makeUniqueName("__🟡$")
let legacyEnumName = context.makeUniqueName("__🟡$")
recordDecl = """
private final class \(className): Testing.__TestContentRecordContainer {
override nonisolated class var __testContentRecord: Testing.__TestContentRecord {
enum \(legacyEnumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(enumName).testContentRecord
}
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/TestingMacros/SuiteDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,12 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable {

#if !SWT_NO_LEGACY_TEST_DISCOVERY
// Emit a type that contains a reference to the test content record.
let className = context.makeUniqueName("__🟡$")
let enumName = context.makeUniqueName("__🟡$")
result.append(
"""
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
final class \(className): Testing.__TestContentRecordContainer {
override nonisolated class var __testContentRecord: Testing.__TestContentRecord {
enum \(enumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(testContentRecordName)
}
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/TestingMacros/TestDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -491,12 +491,12 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {

#if !SWT_NO_LEGACY_TEST_DISCOVERY
// Emit a type that contains a reference to the test content record.
let className = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$")
let enumName = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$")
result.append(
"""
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
final class \(className): Testing.__TestContentRecordContainer {
override nonisolated class var __testContentRecord: Testing.__TestContentRecord {
enum \(enumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(testContentRecordName)
}
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/_TestDiscovery/DiscoverableAsTestContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,17 @@ public protocol DiscoverableAsTestContent: Sendable, ~Copyable {
/// By default, this type equals `Never`, indicating that this type of test
/// content does not support hinting during discovery.
associatedtype TestContentAccessorHint = Never

#if !SWT_NO_LEGACY_TEST_DISCOVERY
/// A string present in the names of types containing test content records
/// associated with this type.
@available(swift, deprecated: 100000.0, message: "Do not adopt this functionality in new code. It will be removed in a future release.")
static var _testContentTypeNameHint: String { get }
#endif
}

extension DiscoverableAsTestContent where Self: ~Copyable {
public static var _testContentTypeNameHint: String {
"__🟡$"
}
}
100 changes: 26 additions & 74 deletions Sources/_TestDiscovery/TestContentRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,80 +249,17 @@ extension DiscoverableAsTestContent where Self: ~Copyable {

private import _TestingInternals

/// A protocol describing a type, emitted at compile time or macro expansion
/// time, that represents a single test content record.
///
/// Use this protocol to make discoverable any test content records contained in
/// the type metadata section (the "legacy discovery mechanism"). For example,
/// if you have creasted a test content record named `myRecord` and your test
/// content record typealias is named `MyRecordType`:
///
/// ```swift
/// private enum MyRecordContainer: TestContentRecordContainer {
/// nonisolated static func storeTestContentRecord(to outTestContentRecord: UnsafeMutableRawPointer) -> Bool {
/// outTestContentRecord.initializeMemory(as: MyRecordType.self, to: myRecord)
/// return true
/// }
/// }
/// ```
///
/// Then, at discovery time, call ``DiscoverableAsTestContent/allTypeMetadataBasedTestContentRecords()``
/// to look up `myRecord`.
///
/// Types that represent test content and that should be discoverable at runtime
/// should not conform to this protocol. Instead, they should conform to
/// ``DiscoverableAsTestContent``.
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
@_alwaysEmitConformanceMetadata
@available(swift, deprecated: 100000.0, message: "Do not adopt this functionality in new code. It will be removed in a future release.")
public protocol TestContentRecordContainer {
/// Store this container's corresponding test content record to memory.
///
/// - Parameters:
/// - outTestContentRecord: A pointer to uninitialized memory large enough
/// to hold a test content record. The memory is untyped so that client
/// code can use a custom definition of the test content record tuple
/// type.
///
/// - Returns: Whether or not `outTestContentRecord` was initialized. If this
/// function returns `true`, the caller is responsible for deinitializing
/// said memory after it is done using it.
nonisolated static func storeTestContentRecord(to outTestContentRecord: UnsafeMutableRawPointer) -> Bool
}

extension DiscoverableAsTestContent where Self: ~Copyable {
/// Make a test content record of this type from the given test content record
/// container type if it matches this type's requirements.
///
/// - Parameters:
/// - containerType: The test content record container type.
/// - sb: The section bounds containing `containerType` and, thus, the test
/// content record.
///
/// - Returns: A new test content record value, or `nil` if `containerType`
/// failed to store a record or if the record's kind did not match this
/// type's ``testContentKind`` property.
private static func _makeTestContentRecord(from containerType: (some TestContentRecordContainer).Type, in sb: SectionBounds) -> TestContentRecord<Self>? {
withUnsafeTemporaryAllocation(of: _TestContentRecord.self, capacity: 1) { buffer in
// Load the record from the container type.
guard containerType.storeTestContentRecord(to: buffer.baseAddress!) else {
return nil
}
let record = buffer.baseAddress!.move()

// Make sure that the record's kind matches.
guard record.kind == Self.testContentKind else {
return nil
}

// Construct the TestContentRecord instance from the record.
return TestContentRecord(imageAddress: sb.imageAddress, record: record)
}
}

/// Get all test content of this type known to Swift and found in the current
/// process using the legacy discovery mechanism.
///
/// - Parameters:
/// - baseType: The type which all discovered container types must
/// conform to or subclass.
/// - loader: A function that is called once per type conforming to or
/// subclassing `baseType`. This function should load the corresponding
/// test content record into the buffer passed to it.
///
/// - Returns: A sequence of instances of ``TestContentRecord``. Only test
/// content records matching this ``TestContent`` type's requirements are
/// included in the sequence.
Expand All @@ -332,15 +269,30 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
/// opaque type due to a compiler crash. ([143080508](rdar://143080508))
/// }
@available(swift, deprecated: 100000.0, message: "Do not adopt this functionality in new code. It will be removed in a future release.")
public static func allTypeMetadataBasedTestContentRecords() -> AnySequence<TestContentRecord<Self>> {
public static func allTypeMetadataBasedTestContentRecords(
loadingWith loader: @escaping @Sendable (Any.Type, UnsafeMutableRawBufferPointer) -> Bool
) -> AnySequence<TestContentRecord<Self>> {
validateMemoryLayout()

let typeNameHint = _testContentTypeNameHint
let kind = testContentKind
let loader: @Sendable (Any.Type) -> _TestContentRecord? = { type in
withUnsafeTemporaryAllocation(of: _TestContentRecord.self, capacity: 1) { buffer in
// Load the record from the container type.
guard loader(type, .init(buffer)) else {
return nil
}
return buffer.baseAddress!.move()
}
}

let result = SectionBounds.all(.typeMetadata).lazy.flatMap { sb in
stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: "__🟡$") }
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: typeNameHint) }
.map { unsafeBitCast($0, to: Any.Type.self) }
.compactMap { $0 as? any TestContentRecordContainer.Type }
.compactMap { _makeTestContentRecord(from: $0, in: sb) }
.compactMap(loader)
.filter { $0.kind == kind }
.map { TestContentRecord<Self>(imageAddress: sb.imageAddress, record: $0) }
}
return AnySequence(result)
}
Expand Down