Skip to content

Commit 32b7e36

Browse files
authored
Merge pull request swiftlang#35099 from etcwilde/ewilde/disallow-passing-global-actor-state-inout
[Concurrency] Disallow passing global actor state inout
2 parents 020b02a + 91d1d8d commit 32b7e36

File tree

2 files changed

+69
-28
lines changed

2 files changed

+69
-28
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -904,45 +904,58 @@ namespace {
904904
return false;
905905

906906
Expr *subArg = arg->getSubExpr();
907+
ValueDecl *valueDecl = nullptr;
907908
if (LookupExpr *baseArg = dyn_cast<LookupExpr>(subArg)) {
908909
while (LookupExpr *nextLayer = dyn_cast<LookupExpr>(baseArg->getBase()))
909910
baseArg = nextLayer;
910911
// subArg: the actual property being passed inout
911912
// baseArg: the property in the actor who's property is being passed
912913
// inout
913914

914-
auto memberDecl = baseArg->getMember().getDecl();
915-
auto isolation = ActorIsolationRestriction::forDeclaration(memberDecl);
916-
switch (isolation) {
917-
case ActorIsolationRestriction::Unrestricted:
918-
case ActorIsolationRestriction::LocalCapture:
919-
case ActorIsolationRestriction::Unsafe:
920-
case ActorIsolationRestriction::GlobalActor: // TODO: handle global
921-
// actors
922-
break;
923-
case ActorIsolationRestriction::ActorSelf: {
924-
if (isPartialApply) {
925-
// The partially applied InoutArg is a property of actor. This can
926-
// really only happen when the property is a struct with a mutating
927-
// async method.
928-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
929-
ValueDecl *fnDecl =
930-
cast<DeclRefExpr>(partialApply->getFn())->getDecl();
931-
ctx.Diags.diagnose(
932-
call->getLoc(), diag::actor_isolated_mutating_func,
933-
fnDecl->getName(), memberDecl->getDescriptiveKind(),
934-
memberDecl->getName());
935-
return true;
936-
}
937-
} else {
915+
valueDecl = baseArg->getMember().getDecl();
916+
} else if (DeclRefExpr *declExpr = dyn_cast<DeclRefExpr>(subArg)) {
917+
valueDecl = declExpr->getDecl();
918+
} else {
919+
llvm_unreachable("Inout argument is neither a lookup nor decl.");
920+
}
921+
assert(valueDecl != nullptr && "valueDecl was never set!");
922+
auto isolation = ActorIsolationRestriction::forDeclaration(valueDecl);
923+
switch (isolation) {
924+
case ActorIsolationRestriction::Unrestricted:
925+
case ActorIsolationRestriction::LocalCapture:
926+
case ActorIsolationRestriction::Unsafe:
927+
break;
928+
case ActorIsolationRestriction::GlobalActor: {
929+
ctx.Diags.diagnose(call->getLoc(),
930+
diag::actor_isolated_inout_state,
931+
valueDecl->getDescriptiveKind(),
932+
valueDecl->getName(),
933+
call->implicitlyAsync());
934+
valueDecl->diagnose(diag::kind_declared_here,
935+
valueDecl->getDescriptiveKind());
936+
return true;
937+
}
938+
case ActorIsolationRestriction::ActorSelf: {
939+
if (isPartialApply) {
940+
// The partially applied InoutArg is a property of actor. This can
941+
// really only happen when the property is a struct with a mutating
942+
// async method.
943+
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
944+
ValueDecl *fnDecl =
945+
cast<DeclRefExpr>(partialApply->getFn())->getDecl();
938946
ctx.Diags.diagnose(
939-
subArg->getLoc(), diag::actor_isolated_inout_state,
940-
memberDecl->getDescriptiveKind(), memberDecl->getName(),
941-
call->implicitlyAsync());
947+
call->getLoc(), diag::actor_isolated_mutating_func,
948+
fnDecl->getName(), valueDecl->getDescriptiveKind(),
949+
valueDecl->getName());
942950
return true;
943951
}
952+
} else {
953+
ctx.Diags.diagnose(
954+
subArg->getLoc(), diag::actor_isolated_inout_state,
955+
valueDecl->getDescriptiveKind(), valueDecl->getName(), call->implicitlyAsync());
956+
return true;
944957
}
945-
}
958+
}
946959
}
947960
return false;
948961
}

test/Concurrency/actor_inout_isolation.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,31 @@ extension TestActor {
131131
await other.modify(&value2)
132132
}
133133
}
134+
135+
// Verify global actor protection
136+
137+
@globalActor
138+
struct MyGlobalActor {
139+
static let shared = TestActor()
140+
}
141+
142+
@MyGlobalActor var number: Int = 0
143+
// expected-note@-1{{var declared here}}
144+
// expected-note@-2{{var declared here}}
145+
// expected-note@-3{{mutable state is only available within the actor instance}}
146+
147+
// expected-error@+2{{actor-isolated var 'number' cannot be passed 'inout' to 'async' function call}}
148+
// expected-error@+1{{var 'number' isolated to global actor 'MyGlobalActor' can not be referenced from this context}}
149+
let _ = Task.runDetached { await { (_ foo: inout Int) async in foo += 1 }(&number) }
150+
151+
// attempt to pass global state owned by the global actor to another async function
152+
// expected-error@+1{{actor-isolated var 'number' cannot be passed 'inout' to 'async' function call}}
153+
@MyGlobalActor func sneaky() async { await modifyAsynchronously(&number) }
154+
155+
// It's okay to pass actor state inout to synchronous functions!
156+
157+
func globalSyncFunction(_ foo: inout Int) { }
158+
@MyGlobalActor func globalActorSyncFunction(_ foo: inout Int) { }
159+
@MyGlobalActor func globalActorAsyncOkay() async { globalActorSyncFunction(&number) }
160+
@MyGlobalActor func globalActorAsyncOkay2() async { globalSyncFunction(&number) }
161+
@MyGlobalActor func globalActorSyncOkay() { globalSyncFunction(&number) }

0 commit comments

Comments
 (0)