Skip to content

Commit 189f815

Browse files
authored
Merge pull request #41077 from rjmccall/defer-isolation-5.6
[5.6] Look through defer bodies in actor isolation checking
2 parents 97f8ef6 + 169167e commit 189f815

File tree

4 files changed

+208
-5
lines changed

4 files changed

+208
-5
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
66
Swift 5.6
77
---------
88

9+
* Actor isolation checking now understands that `defer` bodies share the isolation of their enclosing function.
10+
11+
```swift
12+
// Works on global actors
13+
@MainActor
14+
func runAnimation(controller: MyViewController) async {
15+
controller.hasActiveAnimation = true
16+
defer { controller.hasActiveAnimation = false }
17+
18+
// do the animation here...
19+
}
20+
21+
// Works on actor instances
22+
actor OperationCounter {
23+
var activeOperationCount = 0
24+
25+
func operate() async {
26+
activeOperationCount += 1
27+
defer { activeOperationCount -= 1 }
28+
29+
// do work here...
30+
}
31+
}
32+
```
33+
934
* [SE-0335][]:
1035

1136
Swift now allows existential types to be explicitly written with the `any`

lib/SILGen/SILGenProlog.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,29 @@ void SILGenFunction::emitProlog(CaptureInfo captureInfo,
552552
return false;
553553
};
554554

555-
// Initialize ExpectedExecutor if the function is an async
556-
// function or closure.
555+
auto isDefer = [](DeclContext *dc) {
556+
if (auto fn = dyn_cast<FuncDecl>(dc))
557+
return fn->isDeferBody();
558+
return false;
559+
};
560+
561+
// Initialize ExpectedExecutor if:
562+
// - this function is async or
563+
// - this function is sync and isolated to an actor, and we want to
564+
// dynamically check that we're on the right executor.
565+
//
566+
// Actor destructors are isolated in the sense that we now have a
567+
// unique reference to the actor, but we probably aren't running on
568+
// the actor's executor, so we cannot safely do this check.
569+
//
570+
// Defer bodies are always called synchronously within their enclosing
571+
// function, so the check is unnecessary; in addition, we cannot
572+
// necessarily perform the check because the defer may not have
573+
// captured the isolated parameter of the enclosing function.
557574
bool wantDataRaceChecks = getOptions().EnableActorDataRaceChecks &&
558575
!F.isAsync() &&
559-
!isInActorDestructor(FunctionDC);
576+
!isInActorDestructor(FunctionDC) &&
577+
!isDefer(FunctionDC);
560578

561579
// Local function to load the expected executor from a local actor
562580
auto loadExpectedExecutorForLocalVar = [&](VarDecl *var) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,12 @@ namespace {
17281728
}
17291729
}
17301730

1731+
// Look through defers.
1732+
// FIXME: should this be covered automatically by the logic below?
1733+
if (auto func = dyn_cast<FuncDecl>(dc))
1734+
if (func->isDeferBody())
1735+
continue;
1736+
17311737
if (auto func = dyn_cast<AbstractFunctionDecl>(dc)) {
17321738
// @Sendable functions are nonisolated.
17331739
if (func->isSendable())
@@ -1779,12 +1785,26 @@ namespace {
17791785
return nullptr;
17801786
}
17811787

1788+
static FuncDecl *findAnnotatableFunction(DeclContext *dc) {
1789+
auto fn = dyn_cast<FuncDecl>(dc);
1790+
if (!fn) return nullptr;
1791+
if (fn->isDeferBody())
1792+
return findAnnotatableFunction(fn->getDeclContext());
1793+
return fn;
1794+
}
1795+
17821796
/// Note when the enclosing context could be put on a global actor.
17831797
void noteGlobalActorOnContext(DeclContext *dc, Type globalActor) {
17841798
// If we are in a synchronous function on the global actor,
17851799
// suggest annotating with the global actor itself.
1786-
if (auto fn = dyn_cast<FuncDecl>(dc)) {
1787-
if (!isa<AccessorDecl>(fn) && !fn->isAsyncContext()) {
1800+
if (auto fn = findAnnotatableFunction(dc)) {
1801+
// Suppress this for accesssors because you can't change the
1802+
// actor isolation of an individual accessor. Arguably we could
1803+
// add this to the entire storage declaration, though.
1804+
// Suppress this for async functions out of caution; but don't
1805+
// suppress it if we looked through a defer.
1806+
if (!isa<AccessorDecl>(fn) &&
1807+
(!fn->isAsyncContext() || fn != dc)) {
17881808
switch (getActorIsolation(fn)) {
17891809
case ActorIsolation::ActorInstance:
17901810
case ActorIsolation::DistributedActorInstance:
@@ -3463,6 +3483,14 @@ ActorIsolation ActorIsolationRequest::evaluate(
34633483
// If this is a local function, inherit the actor isolation from its
34643484
// context if it global or was captured.
34653485
if (auto func = dyn_cast<FuncDecl>(value)) {
3486+
// If this is a defer body, inherit unconditionally; we don't
3487+
// care if the enclosing function captures the isolated parameter.
3488+
if (func->isDeferBody()) {
3489+
auto enclosingIsolation =
3490+
getActorIsolationOfContext(func->getDeclContext());
3491+
return inferredIsolation(enclosingIsolation);
3492+
}
3493+
34663494
if (func->isLocalCapture() && !func->isSendable()) {
34673495
switch (auto enclosingIsolation =
34683496
getActorIsolationOfContext(func->getDeclContext())) {

test/Concurrency/actor_defer.swift

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// RUN: %target-swift-frontend -parse-as-library -emit-sil -DNEGATIVES -verify %s
2+
// RUN: %target-swift-frontend -parse-as-library -emit-sil -enable-actor-data-race-checks -o - %s | %FileCheck %s
3+
4+
// REQUIRES: concurrency
5+
6+
func doSomething() {}
7+
8+
// expected-note @+1 4 {{calls to global function 'requiresMainActor()' from outside of its actor context are implicitly asynchronous}}
9+
@MainActor func requiresMainActor() {}
10+
11+
@MainActor func testNonDefer_positive() {
12+
requiresMainActor()
13+
}
14+
15+
#if NEGATIVES
16+
// expected-note @+1 {{add '@MainActor' to make global function 'testNonDefer_negative()' part of global actor 'MainActor'}}
17+
func testNonDefer_negative() {
18+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}}
19+
requiresMainActor()
20+
}
21+
#endif
22+
23+
@MainActor func testGlobalActor_positive() {
24+
defer {
25+
requiresMainActor()
26+
}
27+
doSomething()
28+
}
29+
// Don't include a data race check at the start of the defer
30+
// CHECK-LABEL: sil private @$s11actor_defer24testGlobalActor_positiveyyF6$deferL_yyF
31+
// CHECK-NEXT: bb0:
32+
// CHECK-NEXT: // function_ref
33+
// CHECK-NEXT: function_ref
34+
// CHECK-NEXT: apply
35+
36+
#if NEGATIVES
37+
// expected-note @+1 {{add '@MainActor' to make global function 'testGlobalActor_negative()' part of global actor 'MainActor'}}
38+
func testGlobalActor_negative() {
39+
defer {
40+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}}
41+
requiresMainActor()
42+
}
43+
doSomething()
44+
}
45+
#endif
46+
47+
@available(SwiftStdlib 5.1, *)
48+
@MainActor func testGlobalActorAsync_positive() async {
49+
defer {
50+
requiresMainActor()
51+
}
52+
doSomething()
53+
}
54+
55+
#if NEGATIVES
56+
// expected-note @+2 {{add '@MainActor' to make global function 'testGlobalActorAsync_negative()' part of global actor 'MainActor'}}
57+
@available(SwiftStdlib 5.1, *)
58+
func testGlobalActorAsync_negative() async {
59+
defer {
60+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}}
61+
requiresMainActor()
62+
}
63+
doSomething()
64+
}
65+
#endif
66+
67+
@available(SwiftStdlib 5.1, *)
68+
actor Actor {
69+
// expected-note @+1 3 {{mutation of this property is only permitted within the actor}}
70+
var actorProperty = 0
71+
72+
func testActor_positive() {
73+
defer {
74+
actorProperty += 1
75+
}
76+
doSomething()
77+
}
78+
79+
#if NEGATIVES
80+
nonisolated func testActor_negative() {
81+
defer {
82+
// expected-error @+1 {{actor-isolated property 'actorProperty' can not be mutated from a non-isolated context}}
83+
actorProperty += 1
84+
}
85+
doSomething()
86+
}
87+
@MainActor func testActor_negative_globalActor() {
88+
defer {
89+
// expected-error @+1 {{actor-isolated property 'actorProperty' can not be mutated from the main actor}}
90+
actorProperty += 1
91+
}
92+
doSomething()
93+
}
94+
#endif
95+
96+
@MainActor func testGlobalActor_positive() {
97+
defer {
98+
requiresMainActor()
99+
}
100+
doSomething()
101+
}
102+
103+
#if NEGATIVES
104+
func testGlobalActor_negative() {
105+
defer {
106+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous actor-isolated context}}
107+
requiresMainActor()
108+
}
109+
doSomething()
110+
}
111+
#endif
112+
}
113+
114+
@available(SwiftStdlib 5.1, *)
115+
func testIsolatedActor_positive(actor: isolated Actor) {
116+
actor.actorProperty += 1
117+
defer {
118+
actor.actorProperty += 1
119+
}
120+
doSomething()
121+
}
122+
123+
#if NEGATIVES
124+
@available(SwiftStdlib 5.1, *)
125+
func testIsolatedActor_negative(actor: Actor) {
126+
defer {
127+
// expected-error @+1 {{actor-isolated property 'actorProperty' can not be mutated from a non-isolated context}}
128+
actor.actorProperty += 1
129+
}
130+
doSomething()
131+
}
132+
#endif

0 commit comments

Comments
 (0)