Skip to content

Acknowledge unsafe API usages in code expanded from testing library macros #1134

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 7 commits into from
Jun 2, 2025
27 changes: 27 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,25 @@ let package = Package(
"Testing",
"_Testing_CoreGraphics",
"_Testing_Foundation",
"MemorySafeTestingTests",
],
swiftSettings: .packageSettings
),

// Use a plain `.target` instead of a `.testTarget` to avoid the unnecessary
// overhead of having a separate test target for this module. Conceptually,
// the content in this module is no different than content which would
// typically be placed in the `TestingTests` target, except this content
// needs the (module-wide) strict memory safety feature to be enabled.
.target(
name: "MemorySafeTestingTests",
dependencies: [
"Testing",
],
path: "Tests/_MemorySafeTestingTests",
swiftSettings: .packageSettings + .strictMemorySafety
),

.macro(
name: "TestingMacros",
dependencies: [
Expand Down Expand Up @@ -355,6 +370,18 @@ extension Array where Element == PackageDescription.SwiftSetting {

return result
}

/// Settings necessary to enable Strict Memory Safety, introduced in
/// [SE-0458: Opt-in Strict Memory Safety Checking](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md#swiftpm-integration).
static var strictMemorySafety: Self {
#if compiler(>=6.2)
// FIXME: Adopt official `.strictMemorySafety()` condition once the minimum
// supported toolchain is 6.2.
[.unsafeFlags(["-strict-memory-safety"])]
#else
[]
#endif
}
}

extension Array where Element == PackageDescription.CXXSetting {
Expand Down
3 changes: 3 additions & 0 deletions Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ extension ExitTest {
///
/// - Warning: This function is used to implement the
/// `#expect(processExitsWith:)` macro. Do not use it directly.
#if compiler(>=6.2)
@safe
#endif
public static func __store<each T>(
_ id: (UInt64, UInt64, UInt64, UInt64),
_ body: @escaping @Sendable (repeat each T) async throws -> Void,
Expand Down
3 changes: 3 additions & 0 deletions Sources/Testing/Test+Discovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ extension Test {
///
/// - Warning: This function is used to implement the `@Test` macro. Do not
/// use it directly.
#if compiler(>=6.2)
@safe
#endif
public static func __store(
_ generator: @escaping @Sendable () async -> Test,
into outValue: UnsafeMutableRawPointer,
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/ConditionMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,11 @@ extension ExitTestConditionMacro {
var recordDecl: DeclSyntax?
#if !SWT_NO_LEGACY_TEST_DISCOVERY
let legacyEnumName = context.makeUniqueName("__🟡$")
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
recordDecl = """
enum \(legacyEnumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(enumName).testContentRecord
\(unsafeKeyword)\(enumName).testContentRecord
}
}
"""
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/SuiteDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,13 @@ 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 enumName = context.makeUniqueName("__🟡$")
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
result.append(
"""
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
enum \(enumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(testContentRecordName)
\(unsafeKeyword)\(testContentRecordName)
}
}
"""
Expand Down
28 changes: 15 additions & 13 deletions Sources/TestingMacros/Support/EffectfulExpressionHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ extension BidirectionalCollection<Syntax> {

// MARK: - Inserting effect keywords/thunks

/// Whether or not the `unsafe` expression keyword is supported.
var isUnsafeKeywordSupported: Bool {
// The 'unsafe' keyword was introduced in 6.2 as part of SE-0458. Older
// toolchains are not aware of it.
#if compiler(>=6.2)
true
#else
false
#endif
}

/// Make a function call expression to an effectful thunk function provided by
/// the testing library.
///
Expand Down Expand Up @@ -127,12 +138,7 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
let needAwait = effectfulKeywords.contains(.await) && !expr.is(AwaitExprSyntax.self)
let needTry = effectfulKeywords.contains(.try) && !expr.is(TryExprSyntax.self)

// The 'unsafe' keyword was introduced in 6.2 as part of SE-0458. Older
// toolchains are not aware of it, so avoid emitting expressions involving
// that keyword when the macro has been built using an older toolchain.
#if compiler(>=6.2)
let needUnsafe = effectfulKeywords.contains(.unsafe) && !expr.is(UnsafeExprSyntax.self)
#endif
let needUnsafe = isUnsafeKeywordSupported && effectfulKeywords.contains(.unsafe) && !expr.is(UnsafeExprSyntax.self)

// First, add thunk function calls.
if needAwait {
Expand All @@ -141,41 +147,37 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
if needTry {
expr = _makeCallToEffectfulThunk(.identifier("__requiringTry"), passing: expr)
}
#if compiler(>=6.2)
if needUnsafe {
expr = _makeCallToEffectfulThunk(.identifier("__requiringUnsafe"), passing: expr)
}
#endif

// Then add keyword expressions. (We do this separately so we end up writing
// `try await __r(__r(self))` instead of `try __r(await __r(self))` which is
// less accepted by the compiler.)
if needAwait {
expr = ExprSyntax(
AwaitExprSyntax(
awaitKeyword: .keyword(.await).with(\.trailingTrivia, .space),
awaitKeyword: .keyword(.await, trailingTrivia: .space),
expression: expr
)
)
}
if needTry {
expr = ExprSyntax(
TryExprSyntax(
tryKeyword: .keyword(.try).with(\.trailingTrivia, .space),
tryKeyword: .keyword(.try, trailingTrivia: .space),
expression: expr
)
)
}
#if compiler(>=6.2)
if needUnsafe {
expr = ExprSyntax(
UnsafeExprSyntax(
unsafeKeyword: .keyword(.unsafe).with(\.trailingTrivia, .space),
unsafeKeyword: .keyword(.unsafe, trailingTrivia: .space),
expression: expr
)
)
}
#endif

expr.leadingTrivia = originalExpr.leadingTrivia
expr.trailingTrivia = originalExpr.trailingTrivia
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/Support/TestContentGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax?
IntegerLiteralExprSyntax(context, radix: .binary)
}

let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
var result: DeclSyntax = """
@available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.")
private nonisolated \(staticKeyword(for: typeName)) let \(name): Testing.__TestContentRecord = (
\(kindExpr), \(kind.commentRepresentation)
0,
\(accessorName),
\(unsafeKeyword)\(accessorName),
\(contextExpr),
0
)
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/TestDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -494,12 +494,13 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {
#if !SWT_NO_LEGACY_TEST_DISCOVERY
// Emit a type that contains a reference to the test content record.
let enumName = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$")
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
result.append(
"""
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
enum \(enumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(testContentRecordName)
\(unsafeKeyword)\(testContentRecordName)
}
}
"""
Expand Down
31 changes: 31 additions & 0 deletions Tests/_MemorySafeTestingTests/MemorySafeTestDecls.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if compiler(>=6.2)

@testable import Testing

#if !hasFeature(StrictMemorySafety)
#error("This file requires strict memory safety to be enabled")
#endif

@Test(.hidden)
func exampleTestFunction() {}

@Suite(.hidden)
struct ExampleSuite {
@Test func example() {}
}

func exampleExitTest() async {
await #expect(processExitsWith: .success) {}
}

#endif