mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
From the perspective of the IR, we are changing SILIsolationInfo such that
inferring an actor instance means looking at equivalence classes of values where
we consider operands to look through instructions to be equivalent to their dest
value. The result is that cases where the IR maybe puts in a copy_value or the
like, we consider the copy_value to have the same isolation info as using the
actor directly. This prevents a class of crashes due to merge failings. Example:
```swift
actor MyActor {
init() async {
init(ns: NonSendableKlass) async {
self.k = NonSendableKlass()
self.helper(ns)
}
func helper(_ newK: NonSendableKlass) {}
}
```
Incidently, we already had a failing test case from this behavior rather than
the one that was the original genesis. Specifically:
1. If a function's SILIsolationInfo is the same as the isolation info of a
SILValue, we assume that no transfer actually occurs.
2. Since we were taking too static of a view of actor instances when comparing,
we would think that a SILIsolationInfo of a #isolation parameter to as an
argument would be different than the ambient's function isolation which is also
that same one. So we would emit a transfer non transferrable error if we pass in
any parameters of the ambient function into another isolated function. Example:
```swift
actor Test {
@TaskLocal static var local: Int?
func withTaskLocal(isolation: isolated (any Actor)? = #isolation,
_ body: (consuming NonSendableValue, isolated (any Actor)?) -> Void) async {
Self.$local.withValue(12) {
// We used to get these errors here since we thought that body's isolation
// was different than the body's isolation.
//
// warning: sending 'body' risks causing data races
// note: actor-isolated 'body' is captured by a actor-isolated closure...
body(NonSendableValue(), isolation)
}
}
}
```
rdar://129400019
178 lines
7.1 KiB
Swift
178 lines
7.1 KiB
Swift
// RUN: %target-swift-frontend -emit-sil -strict-concurrency=complete -disable-availability-checking -swift-version 6 -verify %s -o /dev/null
|
|
|
|
// REQUIRES: concurrency
|
|
// REQUIRES: asserts
|
|
|
|
// This test validates the behavior of transfernonsendable around initializers.
|
|
|
|
////////////////////////
|
|
// MARK: Declarations //
|
|
////////////////////////
|
|
|
|
class NonSendableKlass {}
|
|
@MainActor func transferToMain<T>(_ t: T) async {}
|
|
|
|
actor CustomActorInstance {}
|
|
|
|
@globalActor
|
|
struct CustomActor {
|
|
static let shared = CustomActorInstance()
|
|
}
|
|
|
|
/////////////////
|
|
// MARK: Tests //
|
|
/////////////////
|
|
|
|
actor ActorWithSynchronousNonIsolatedInit {
|
|
let k: NonSendableKlass
|
|
|
|
init(_ newK: NonSendableKlass) {
|
|
k = NonSendableKlass()
|
|
|
|
helper(newK)
|
|
|
|
// TODO: This should say actor isolated.
|
|
let _ = { @MainActor in
|
|
print(newK) // expected-error {{sending 'newK' risks causing data races}}
|
|
// expected-note @-1 {{task-isolated 'newK' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
|
|
init(x newK: NonSendableKlass) {
|
|
k = newK
|
|
|
|
helper(newK)
|
|
|
|
let _ = { @MainActor in
|
|
// TODO: Second part should say later 'self'-isolated uses
|
|
print(newK) // expected-error {{sending 'newK' risks causing data races}}
|
|
// expected-note @-1 {{'self'-isolated 'newK' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
|
|
init(ns: NonSendableKlass) async {
|
|
self.k = NonSendableKlass()
|
|
self.isolatedHelper(ns)
|
|
}
|
|
|
|
nonisolated func helper(_ newK: NonSendableKlass) {}
|
|
func isolatedHelper(_ newK: NonSendableKlass) {}
|
|
}
|
|
|
|
func initActorWithSyncNonIsolatedInit() {
|
|
let k = NonSendableKlass()
|
|
// TODO: This should say actor isolated.
|
|
_ = ActorWithSynchronousNonIsolatedInit(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{sending 'k' to actor-isolated initializer 'init(_:)' risks causing data races between actor-isolated and local nonisolated uses}}
|
|
let _ = { @MainActor in // expected-note {{access can happen concurrently}}
|
|
print(k)
|
|
}
|
|
}
|
|
|
|
func initActorWithSyncNonIsolatedInit2(_ k: NonSendableKlass) {
|
|
// TODO: This should say actor isolated.
|
|
_ = ActorWithSynchronousNonIsolatedInit(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{sending task-isolated 'k' to actor-isolated initializer 'init(_:)' risks causing data races between actor-isolated and task-isolated uses}}
|
|
let _ = { @MainActor in
|
|
print(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{task-isolated 'k' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
|
|
actor ActorWithAsyncIsolatedInit {
|
|
init(_ newK: NonSendableKlass) async {
|
|
// TODO: This should say actor isolated.
|
|
let _ = { @MainActor in
|
|
print(newK) // expected-error {{sending 'newK' risks causing data races}}
|
|
// expected-note @-1 {{'self'-isolated 'newK' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
}
|
|
|
|
func initActorWithAsyncIsolatedInit() async {
|
|
let k = NonSendableKlass()
|
|
// TODO: This should say actor isolated.
|
|
_ = await ActorWithAsyncIsolatedInit(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{sending 'k' to actor-isolated initializer 'init(_:)' risks causing data races between actor-isolated and local nonisolated uses}}
|
|
let _ = { @MainActor in // expected-note {{access can happen concurrently}}
|
|
print(k)
|
|
}
|
|
}
|
|
|
|
func initActorWithAsyncIsolatedInit2(_ k: NonSendableKlass) async {
|
|
// TODO: This should say actor isolated.
|
|
_ = await ActorWithAsyncIsolatedInit(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{sending task-isolated 'k' to actor-isolated initializer 'init(_:)' risks causing data races between actor-isolated and task-isolated uses}}
|
|
let _ = { @MainActor in
|
|
print(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{task-isolated 'k' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
// MARK: Actor Isolated Class //
|
|
////////////////////////////////
|
|
|
|
@CustomActor
|
|
class ClassWithSynchronousNonIsolatedInit {
|
|
nonisolated init(_ newK: NonSendableKlass) {
|
|
// We do not error on this since helper is nonisolated and newK is
|
|
// considered task isolated.
|
|
helper(newK)
|
|
|
|
let _ = { @MainActor in
|
|
print(newK) // expected-error {{sending 'newK' risks causing data races}}
|
|
// expected-note @-1 {{task-isolated 'newK' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
|
|
nonisolated func helper(_ newK: NonSendableKlass) {}
|
|
}
|
|
|
|
func initClassWithSyncNonIsolatedInit() {
|
|
let k = NonSendableKlass()
|
|
_ = ClassWithSynchronousNonIsolatedInit(k)
|
|
let _ = { @MainActor in
|
|
print(k)
|
|
}
|
|
}
|
|
|
|
func initClassWithSyncNonIsolatedInit2(_ k: NonSendableKlass) {
|
|
_ = ClassWithSynchronousNonIsolatedInit(k)
|
|
let _ = { @MainActor in
|
|
print(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{task-isolated 'k' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
|
|
@CustomActor
|
|
class ClassWithAsyncIsolatedInit {
|
|
init(_ newK: NonSendableKlass) async {
|
|
let _ = { @MainActor in
|
|
print(newK) // expected-error {{sending 'newK' risks causing data races}}
|
|
// expected-note @-1 {{global actor 'CustomActor'-isolated 'newK' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|
|
}
|
|
|
|
func initClassWithAsyncIsolatedInit() async {
|
|
let k = NonSendableKlass()
|
|
// TODO: Might make sense to emit a more specific error here since the closure
|
|
// is MainActor isolated. The actual capture is initially not isolated to
|
|
// MainActor.
|
|
_ = await ClassWithAsyncIsolatedInit(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{sending 'k' to global actor 'CustomActor'-isolated initializer 'init(_:)' risks causing data races between global actor 'CustomActor'-isolated and local nonisolated uses}}
|
|
let _ = { @MainActor in // expected-note {{access can happen concurrently}}
|
|
print(k)
|
|
}
|
|
}
|
|
|
|
func initClassWithAsyncIsolatedInit2(_ k: NonSendableKlass) async {
|
|
_ = await ClassWithAsyncIsolatedInit(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{sending task-isolated 'k' to global actor 'CustomActor'-isolated initializer 'init(_:)' risks causing data races between global actor 'CustomActor'-isolated and task-isolated uses}}
|
|
let _ = { @MainActor in
|
|
print(k) // expected-error {{sending 'k' risks causing data races}}
|
|
// expected-note @-1 {{task-isolated 'k' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
|
|
}
|
|
}
|