Skip to content

Commit 5b0d9c4

Browse files
authored
Merge pull request #71536 from hborla/isolated-closure-capture
[Concurrency] Don't allow non-`Sendable`, isolated closures to capture non-`Sendable` values that may be isolated to a different actor.
2 parents 30185fe + b88e74b commit 5b0d9c4

File tree

3 files changed

+135
-3
lines changed

3 files changed

+135
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5436,6 +5436,10 @@ ERROR(non_sendable_capture,none,
54365436
"capture of %1 with non-sendable type %0 in a `@Sendable` "
54375437
"%select{local function|closure}2",
54385438
(Type, DeclName, bool))
5439+
ERROR(non_sendable_isolated_capture,none,
5440+
"capture of %1 with non-sendable type %0 in an isolated "
5441+
"%select{local function|closure}2",
5442+
(Type, DeclName, bool))
54395443
ERROR(implicit_async_let_non_sendable_capture,none,
54405444
"capture of %1 with non-sendable type %0 in 'async let' binding",
54415445
(Type, DeclName))

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2460,6 +2460,7 @@ namespace {
24602460
// which the declaration occurred, it's okay.
24612461
auto decl = capture.getDecl();
24622462
auto *context = localFunc.getAsDeclContext();
2463+
auto fnType = localFunc.getType()->getAs<AnyFunctionType>();
24632464
if (!mayExecuteConcurrentlyWith(context, decl->getDeclContext()))
24642465
continue;
24652466

@@ -2492,11 +2493,19 @@ namespace {
24922493
diag::implicit_non_sendable_capture,
24932494
decl->getName());
24942495
}
2496+
} else if (fnType->isSendable()) {
2497+
diagnoseNonSendableTypes(type, getDeclContext(),
2498+
/*inDerivedConformance*/Type(),
2499+
capture.getLoc(),
2500+
diag::non_sendable_capture,
2501+
decl->getName(),
2502+
/*closure=*/closure != nullptr);
24952503
} else {
24962504
diagnoseNonSendableTypes(type, getDeclContext(),
24972505
/*inDerivedConformance*/Type(),
24982506
capture.getLoc(),
2499-
diag::non_sendable_capture, decl->getName(),
2507+
diag::non_sendable_isolated_capture,
2508+
decl->getName(),
25002509
/*closure=*/closure != nullptr);
25012510
}
25022511
}
@@ -4026,15 +4035,30 @@ bool ActorIsolationChecker::mayExecuteConcurrentlyWith(
40264035
if (useContext == defContext)
40274036
return false;
40284037

4029-
// If both contexts are isolated to the same actor, then they will not
4030-
// execute concurrently.
4038+
bool isolatedStateMayEscape = false;
4039+
40314040
auto useIsolation = getActorIsolationOfContext(
40324041
const_cast<DeclContext *>(useContext), getClosureActorIsolation);
40334042
if (useIsolation.isActorIsolated()) {
40344043
auto defIsolation = getActorIsolationOfContext(
40354044
const_cast<DeclContext *>(defContext), getClosureActorIsolation);
4045+
// If both contexts are isolated to the same actor, then they will not
4046+
// execute concurrently.
40364047
if (useIsolation == defIsolation)
40374048
return false;
4049+
4050+
// If the local function is not Sendable, its isolation differs
4051+
// from that of the context, and both contexts are actor isolated,
4052+
// then capturing non-Sendable values allows the closure to stash
4053+
// those values into actor isolated state. The original context
4054+
// may also stash those values into isolated state, enabling concurrent
4055+
// access later on.
4056+
auto &ctx = useContext->getASTContext();
4057+
bool regionIsolationEnabled =
4058+
ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation);
4059+
isolatedStateMayEscape =
4060+
(!regionIsolationEnabled &&
4061+
useIsolation.isActorIsolated() && defIsolation.isActorIsolated());
40384062
}
40394063

40404064
// Walk the context chain from the use to the definition.
@@ -4043,13 +4067,19 @@ bool ActorIsolationChecker::mayExecuteConcurrentlyWith(
40434067
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
40444068
if (isSendableClosure(closure, /*forActorIsolation=*/false))
40454069
return true;
4070+
4071+
if (isolatedStateMayEscape)
4072+
return true;
40464073
}
40474074

40484075
if (auto func = dyn_cast<FuncDecl>(useContext)) {
40494076
if (func->isLocalCapture()) {
40504077
// If the function is @Sendable... it can be run concurrently.
40514078
if (func->isSendable())
40524079
return true;
4080+
4081+
if (isolatedStateMayEscape)
4082+
return true;
40534083
}
40544084
}
40554085

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %target-swift-frontend -verify -disable-availability-checking -strict-concurrency=complete -verify-additional-prefix complete- -emit-sil -o /dev/null %s
2+
// RUN: %target-swift-frontend -verify -disable-availability-checking -strict-concurrency=complete -verify-additional-prefix region-isolation- -emit-sil -o /dev/null %s -enable-experimental-feature RegionBasedIsolation
3+
4+
// REQUIRES: concurrency
5+
// REQUIRES: asserts
6+
7+
@globalActor
8+
actor MyActor {
9+
static let shared = MyActor()
10+
@MyActor static var ns: NotSendable?
11+
@MyActor static func ohNo() { ns!.x += 1 }
12+
}
13+
14+
@globalActor
15+
actor YourActor {
16+
static let shared = YourActor()
17+
@YourActor static var ns: NotSendable?
18+
@YourActor static func ohNo() { ns!.x += 1 }
19+
}
20+
21+
// expected-complete-note@+1 3{{class 'NotSendable' does not conform to the 'Sendable' protocol}}
22+
class NotSendable {
23+
var x: Int = 0
24+
25+
@MyActor init() {
26+
MyActor.ns = self
27+
}
28+
29+
init(x: Int) {
30+
self.x = x
31+
}
32+
33+
@MyActor func stash() {
34+
MyActor.ns = self
35+
}
36+
}
37+
38+
@MyActor func exhibitRace1() async {
39+
let ns = NotSendable(x: 0)
40+
MyActor.ns = ns
41+
42+
// expected-region-isolation-warning@+1 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller; this is an error in Swift 6}}
43+
await { @YourActor in
44+
// expected-complete-warning@+1 {{capture of 'ns' with non-sendable type 'NotSendable' in an isolated closure; this is an error in Swift 6}}
45+
YourActor.ns = ns
46+
}()
47+
48+
await withTaskGroup(of: Void.self) {
49+
$0.addTask {
50+
await MyActor.ohNo()
51+
}
52+
53+
$0.addTask {
54+
await YourActor.ohNo()
55+
}
56+
}
57+
}
58+
59+
@MyActor func exhibitRace2() async {
60+
let ns = NotSendable(x: 0)
61+
ns.stash()
62+
63+
// FIXME: Region isolation should diagnose this (https://github.com/apple/swift/issues/71533)
64+
await { @YourActor in
65+
// expected-complete-warning@+1 {{capture of 'ns' with non-sendable type 'NotSendable' in an isolated closure; this is an error in Swift 6}}
66+
YourActor.ns = ns
67+
}()
68+
69+
await withTaskGroup(of: Void.self) {
70+
$0.addTask {
71+
await MyActor.ohNo()
72+
}
73+
74+
$0.addTask {
75+
await YourActor.ohNo()
76+
}
77+
}
78+
}
79+
80+
@MyActor func exhibitRace3() async {
81+
let ns = NotSendable()
82+
83+
// FIXME: Region isolation should diagnose this (https://github.com/apple/swift/issues/71533)
84+
await { @YourActor in
85+
// expected-complete-warning@+1 {{capture of 'ns' with non-sendable type 'NotSendable' in an isolated closure; this is an error in Swift 6}}
86+
YourActor.ns = ns
87+
}()
88+
89+
await withTaskGroup(of: Void.self) {
90+
$0.addTask {
91+
await MyActor.ohNo()
92+
}
93+
94+
$0.addTask {
95+
await YourActor.ohNo()
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)