mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Explanation: There were some scenarios where we could call an unsafe function without marking the expression as unsafe. These affect mostly cases where the function's result is passed to another function or returned. This PR makes sure we always flag functions with unsafe return types, even if their result is not stored anywhere for later use. Issues: rdar://157237301 Original PRs: #83520 Risk: Low, worst case scenario the user has to add redundant unsafe keywords in strict memory safe mode. Testing: Added a compiler test. Reviewers: @DougGregor
439 lines
17 KiB
Swift
439 lines
17 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2021 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 the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Swift
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_silgen_name("swift_continuation_logFailedCheck")
|
|
internal func logFailedCheck(_ message: UnsafeRawPointer)
|
|
|
|
/// Implementation class that holds the `UnsafeContinuation` instance for
|
|
/// a `CheckedContinuation`.
|
|
@available(SwiftStdlib 5.1, *)
|
|
internal final class CheckedContinuationCanary: @unchecked Sendable {
|
|
// The instance state is stored in tail-allocated raw memory, so that
|
|
// we can atomically check the continuation state.
|
|
|
|
private init() { fatalError("must use create") }
|
|
|
|
private static func _create(continuation: UnsafeRawPointer, function: String)
|
|
-> CheckedContinuationCanary {
|
|
let instance = unsafe Builtin.allocWithTailElems_1(CheckedContinuationCanary.self,
|
|
1._builtinWordValue,
|
|
(UnsafeRawPointer?, String).self)
|
|
|
|
unsafe instance._continuationPtr.initialize(to: continuation)
|
|
unsafe instance._functionPtr.initialize(to: function)
|
|
return instance
|
|
}
|
|
|
|
private var _continuationPtr: UnsafeMutablePointer<UnsafeRawPointer?> {
|
|
return unsafe UnsafeMutablePointer<UnsafeRawPointer?>(
|
|
Builtin.projectTailElems(self, (UnsafeRawPointer?, String).self))
|
|
}
|
|
private var _functionPtr: UnsafeMutablePointer<String> {
|
|
let tailPtr = unsafe UnsafeMutableRawPointer(
|
|
Builtin.projectTailElems(self, (UnsafeRawPointer?, String).self))
|
|
|
|
let functionPtr = unsafe tailPtr
|
|
+ MemoryLayout<(UnsafeRawPointer?, String)>.offset(of: \(UnsafeRawPointer?, String).1)!
|
|
|
|
return unsafe functionPtr.assumingMemoryBound(to: String.self)
|
|
}
|
|
|
|
internal static func create<T, E>(continuation: UnsafeContinuation<T, E>,
|
|
function: String) -> CheckedContinuationCanary {
|
|
return unsafe _create(
|
|
continuation: unsafeBitCast(continuation, to: UnsafeRawPointer.self),
|
|
function: function)
|
|
}
|
|
|
|
internal var function: String {
|
|
return unsafe _functionPtr.pointee
|
|
}
|
|
|
|
// Take the continuation away from the container, or return nil if it's
|
|
// already been taken.
|
|
internal func takeContinuation<T, E>() -> UnsafeContinuation<T, E>? {
|
|
// Atomically exchange the current continuation value with a null pointer.
|
|
let rawContinuationPtr = unsafe unsafeBitCast(_continuationPtr,
|
|
to: Builtin.RawPointer.self)
|
|
let rawOld = Builtin.atomicrmw_xchg_seqcst_Word(rawContinuationPtr,
|
|
0._builtinWordValue)
|
|
|
|
return unsafe unsafeBitCast(rawOld, to: UnsafeContinuation<T, E>?.self)
|
|
}
|
|
|
|
deinit {
|
|
unsafe _functionPtr.deinitialize(count: 1)
|
|
// Log if the continuation was never consumed before the instance was
|
|
// destructed.
|
|
if unsafe _continuationPtr.pointee != nil {
|
|
#if !$Embedded
|
|
unsafe logFailedCheck("SWIFT TASK CONTINUATION MISUSE: \(function) leaked its continuation without resuming it. This may cause tasks waiting on it to remain suspended forever.\n")
|
|
#else
|
|
fatalError("SWIFT TASK CONTINUATION MISUSE")
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A mechanism to interface
|
|
/// between synchronous and asynchronous code,
|
|
/// logging correctness violations.
|
|
///
|
|
/// A *continuation* is an opaque representation of program state.
|
|
/// To create a continuation in asynchronous code,
|
|
/// call the `withUnsafeContinuation(function:_:)` or
|
|
/// `withUnsafeThrowingContinuation(function:_:)` function.
|
|
/// To resume the asynchronous task,
|
|
/// call the `resume(returning:)`,
|
|
/// `resume(throwing:)`,
|
|
/// `resume(with:)`,
|
|
/// or `resume()` method.
|
|
///
|
|
/// - Important: You must call a resume method exactly once
|
|
/// on every execution path throughout the program.
|
|
///
|
|
/// Resuming from a continuation more than once is undefined behavior.
|
|
/// Never resuming leaves the task in a suspended state indefinitely,
|
|
/// and leaks any associated resources.
|
|
/// `CheckedContinuation` logs a message
|
|
/// if either of these invariants is violated.
|
|
///
|
|
/// `CheckedContinuation` performs runtime checks
|
|
/// for missing or multiple resume operations.
|
|
/// `UnsafeContinuation` avoids enforcing these invariants at runtime
|
|
/// because it aims to be a low-overhead mechanism
|
|
/// for interfacing Swift tasks with
|
|
/// event loops, delegate methods, callbacks,
|
|
/// and other non-`async` scheduling mechanisms.
|
|
/// However, during development, the ability to verify that the
|
|
/// invariants are being upheld in testing is important.
|
|
/// Because both types have the same interface,
|
|
/// you can replace one with the other in most circumstances,
|
|
/// without making other changes.
|
|
@available(SwiftStdlib 5.1, *)
|
|
public struct CheckedContinuation<T, E: Error>: Sendable {
|
|
private let canary: CheckedContinuationCanary
|
|
|
|
/// Creates a checked continuation from an unsafe continuation.
|
|
///
|
|
/// Instead of calling this initializer,
|
|
/// most code calls the `withCheckedContinuation(function:_:)` or
|
|
/// `withCheckedThrowingContinuation(function:_:)` function instead.
|
|
/// You only need to initialize
|
|
/// your own `CheckedContinuation<T, E>` if you already have an
|
|
/// `UnsafeContinuation` you want to impose checking on.
|
|
///
|
|
/// - Parameters:
|
|
/// - continuation: An instance of `UnsafeContinuation`
|
|
/// that hasn't yet been resumed.
|
|
/// After passing the unsafe continuation to this initializer,
|
|
/// don't use it outside of this object.
|
|
/// - function: A string identifying the declaration that is the notional
|
|
/// source for the continuation, used to identify the continuation in
|
|
/// runtime diagnostics related to misuse of this continuation.
|
|
public init(continuation: UnsafeContinuation<T, E>, function: String = #function) {
|
|
canary = unsafe CheckedContinuationCanary.create(
|
|
continuation: continuation,
|
|
function: function)
|
|
}
|
|
|
|
/// Resume the task awaiting the continuation by having it return normally
|
|
/// from its suspension point.
|
|
///
|
|
/// - Parameter value: The value to return from the continuation.
|
|
///
|
|
/// A continuation must be resumed exactly once. If the continuation has
|
|
/// already been resumed through this object, then the attempt to resume
|
|
/// the continuation will trap.
|
|
///
|
|
/// After `resume` enqueues the task, control immediately returns to
|
|
/// the caller. The task continues executing when its executor is
|
|
/// able to reschedule it.
|
|
public func resume(returning value: sending T) {
|
|
if let c: UnsafeContinuation<T, E> = unsafe canary.takeContinuation() {
|
|
unsafe c.resume(returning: value)
|
|
} else {
|
|
#if !$Embedded
|
|
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(value)!\n")
|
|
#else
|
|
fatalError("SWIFT TASK CONTINUATION MISUSE")
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// Resume the task awaiting the continuation by having it throw an error
|
|
/// from its suspension point.
|
|
///
|
|
/// - Parameter error: The error to throw from the continuation.
|
|
///
|
|
/// A continuation must be resumed exactly once. If the continuation has
|
|
/// already been resumed through this object, then the attempt to resume
|
|
/// the continuation will trap.
|
|
///
|
|
/// After `resume` enqueues the task, control immediately returns to
|
|
/// the caller. The task continues executing when its executor is
|
|
/// able to reschedule it.
|
|
public func resume(throwing error: __owned E) {
|
|
if let c: UnsafeContinuation<T, E> = unsafe canary.takeContinuation() {
|
|
unsafe c.resume(throwing: error)
|
|
} else {
|
|
#if !$Embedded
|
|
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, throwing \(error)!\n")
|
|
#else
|
|
fatalError("SWIFT TASK CONTINUATION MISUSE")
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
extension CheckedContinuation {
|
|
/// Resume the task awaiting the continuation by having it either
|
|
/// return normally or throw an error based on the state of the given
|
|
/// `Result` value.
|
|
///
|
|
/// - Parameter result: A value to either return or throw from the
|
|
/// continuation.
|
|
///
|
|
/// A continuation must be resumed exactly once. If the continuation has
|
|
/// already been resumed through this object, then the attempt to resume
|
|
/// the continuation will trap.
|
|
///
|
|
/// After `resume` enqueues the task, control immediately returns to
|
|
/// the caller. The task continues executing when its executor is
|
|
/// able to reschedule it.
|
|
@_alwaysEmitIntoClient
|
|
public func resume<Er: Error>(with result: __shared sending Result<T, Er>) where E == Error {
|
|
switch result {
|
|
case .success(let val):
|
|
self.resume(returning: val)
|
|
case .failure(let err):
|
|
self.resume(throwing: err)
|
|
}
|
|
}
|
|
|
|
/// Resume the task awaiting the continuation by having it either
|
|
/// return normally or throw an error based on the state of the given
|
|
/// `Result` value.
|
|
///
|
|
/// - Parameter result: A value to either return or throw from the
|
|
/// continuation.
|
|
///
|
|
/// A continuation must be resumed exactly once. If the continuation has
|
|
/// already been resumed through this object, then the attempt to resume
|
|
/// the continuation will trap.
|
|
///
|
|
/// After `resume` enqueues the task, control immediately returns to
|
|
/// the caller. The task continues executing when its executor is
|
|
/// able to reschedule it.
|
|
@_alwaysEmitIntoClient
|
|
public func resume(with result: __shared sending Result<T, E>) {
|
|
switch result {
|
|
case .success(let val):
|
|
self.resume(returning: val)
|
|
case .failure(let err):
|
|
self.resume(throwing: err)
|
|
}
|
|
}
|
|
|
|
/// Resume the task awaiting the continuation by having it return normally
|
|
/// from its suspension point.
|
|
///
|
|
/// A continuation must be resumed exactly once. If the continuation has
|
|
/// already been resumed through this object, then the attempt to resume
|
|
/// the continuation will trap.
|
|
///
|
|
/// After `resume` enqueues the task, control immediately returns to
|
|
/// the caller. The task continues executing when its executor is
|
|
/// able to reschedule it.
|
|
@_alwaysEmitIntoClient
|
|
public func resume() where T == Void {
|
|
self.resume(returning: ())
|
|
}
|
|
}
|
|
|
|
/// Invokes the passed in closure with a checked continuation for the current task.
|
|
///
|
|
/// The body of the closure executes synchronously on the calling task, and once it returns
|
|
/// the calling task is suspended. It is possible to immediately resume the task, or escape the
|
|
/// continuation in order to complete it afterwards, which will then resume the suspended task.
|
|
///
|
|
/// You must invoke the continuation's `resume` method exactly once.
|
|
///
|
|
/// Missing to invoke it (eventually) will cause the calling task to remain suspended
|
|
/// indefinitely which will result in the task "hanging" as well as being leaked with
|
|
/// no possibility to destroy it.
|
|
///
|
|
/// The checked continuation offers detection of misuse, and dropping the last reference
|
|
/// to it, without having resumed it will trigger a warning. Resuming a continuation twice
|
|
/// is also diagnosed and will cause a crash.
|
|
///
|
|
/// - Parameters:
|
|
/// - function: A string identifying the declaration that is the notional
|
|
/// source for the continuation, used to identify the continuation in
|
|
/// runtime diagnostics related to misuse of this continuation.
|
|
/// - body: A closure that takes a `CheckedContinuation` parameter.
|
|
/// - Returns: The value continuation is resumed with.
|
|
///
|
|
/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)`
|
|
/// - SeeAlso: `withUnsafeContinuation(function:_:)`
|
|
/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)`
|
|
@inlinable
|
|
@available(SwiftStdlib 5.1, *)
|
|
#if !$Embedded
|
|
@backDeployed(before: SwiftStdlib 6.0)
|
|
#endif
|
|
public func withCheckedContinuation<T>(
|
|
isolation: isolated (any Actor)? = #isolation,
|
|
function: String = #function,
|
|
_ body: (CheckedContinuation<T, Never>) -> Void
|
|
) async -> sending T {
|
|
return await Builtin.withUnsafeContinuation {
|
|
let unsafeContinuation = unsafe UnsafeContinuation<T, Never>($0)
|
|
return body(unsafe CheckedContinuation(continuation: unsafeContinuation,
|
|
function: function))
|
|
}
|
|
}
|
|
|
|
// Note: hack to stage out @_unsafeInheritExecutor forms of various functions
|
|
// in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful
|
|
// to the type checker.
|
|
//
|
|
// This function also doubles as an ABI-compatibility shim predating the
|
|
// introduction of #isolation.
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
|
|
@_silgen_name("$ss23withCheckedContinuation8function_xSS_yScCyxs5NeverOGXEtYalF")
|
|
public func _unsafeInheritExecutor_withCheckedContinuation<T>(
|
|
function: String = #function,
|
|
_ body: (CheckedContinuation<T, Never>) -> Void
|
|
) async -> T {
|
|
return await unsafe withUnsafeContinuation {
|
|
body(unsafe CheckedContinuation(continuation: $0, function: function))
|
|
}
|
|
}
|
|
|
|
|
|
/// Invokes the passed in closure with a checked continuation for the current task.
|
|
///
|
|
/// The body of the closure executes synchronously on the calling task, and once it returns
|
|
/// the calling task is suspended. It is possible to immediately resume the task, or escape the
|
|
/// continuation in order to complete it afterwards, which will then resume the suspended task.
|
|
///
|
|
/// If `resume(throwing:)` is called on the continuation, this function throws that error.
|
|
///
|
|
/// You must invoke the continuation's `resume` method exactly once.
|
|
///
|
|
/// Missing to invoke it (eventually) will cause the calling task to remain suspended
|
|
/// indefinitely which will result in the task "hanging" as well as being leaked with
|
|
/// no possibility to destroy it.
|
|
///
|
|
/// The checked continuation offers detection of misuse, and dropping the last reference
|
|
/// to it, without having resumed it will trigger a warning. Resuming a continuation twice
|
|
/// is also diagnosed and will cause a crash.
|
|
///
|
|
/// - Parameters:
|
|
/// - function: A string identifying the declaration that is the notional
|
|
/// source for the continuation, used to identify the continuation in
|
|
/// runtime diagnostics related to misuse of this continuation.
|
|
/// - body: A closure that takes a `CheckedContinuation` parameter.
|
|
/// - Returns: The value continuation is resumed with.
|
|
///
|
|
/// - SeeAlso: `withCheckedContinuation(function:_:)`
|
|
/// - SeeAlso: `withUnsafeContinuation(function:_:)`
|
|
/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)`
|
|
@inlinable
|
|
@available(SwiftStdlib 5.1, *)
|
|
#if !$Embedded
|
|
@backDeployed(before: SwiftStdlib 6.0)
|
|
#endif
|
|
public func withCheckedThrowingContinuation<T>(
|
|
isolation: isolated (any Actor)? = #isolation,
|
|
function: String = #function,
|
|
_ body: (CheckedContinuation<T, Error>) -> Void
|
|
) async throws -> sending T {
|
|
return try await Builtin.withUnsafeThrowingContinuation {
|
|
let unsafeContinuation = unsafe UnsafeContinuation<T, Error>($0)
|
|
return body(unsafe CheckedContinuation(continuation: unsafeContinuation,
|
|
function: function))
|
|
}
|
|
}
|
|
|
|
// Note: hack to stage out @_unsafeInheritExecutor forms of various functions
|
|
// in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful
|
|
// to the type checker.
|
|
//
|
|
// This function also doubles as an ABI-compatibility shim predating the
|
|
// introduction of #isolation.
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
|
|
@_silgen_name("$ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF")
|
|
public func _unsafeInheritExecutor_withCheckedThrowingContinuation<T>(
|
|
function: String = #function,
|
|
_ body: (CheckedContinuation<T, Error>) -> Void
|
|
) async throws -> T {
|
|
return try await unsafe withUnsafeThrowingContinuation {
|
|
body(unsafe CheckedContinuation(continuation: $0, function: function))
|
|
}
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
|
|
// Intrinsics used by SILGen to create, resume, or fail checked continuations.
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
internal func _createCheckedContinuation<T>(
|
|
_ continuation: __owned UnsafeContinuation<T, Never>
|
|
) -> CheckedContinuation<T, Never> {
|
|
return unsafe CheckedContinuation(continuation: continuation)
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
internal func _createCheckedThrowingContinuation<T>(
|
|
_ continuation: __owned UnsafeContinuation<T, Error>
|
|
) -> CheckedContinuation<T, Error> {
|
|
return unsafe CheckedContinuation(continuation: continuation)
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
internal func _resumeCheckedContinuation<T>(
|
|
_ continuation: CheckedContinuation<T, Never>,
|
|
_ value: sending T
|
|
) {
|
|
continuation.resume(returning: value)
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
internal func _resumeCheckedThrowingContinuation<T>(
|
|
_ continuation: CheckedContinuation<T, Error>,
|
|
_ value: sending T
|
|
) {
|
|
continuation.resume(returning: value)
|
|
}
|
|
|
|
@available(SwiftStdlib 5.1, *)
|
|
@_alwaysEmitIntoClient
|
|
internal func _resumeCheckedThrowingContinuationWithError<T>(
|
|
_ continuation: CheckedContinuation<T, Error>,
|
|
_ error: consuming Error
|
|
) {
|
|
continuation.resume(throwing: error)
|
|
}
|
|
|
|
#endif
|