Skip to content

Commit f0791b8

Browse files
authored
Merge pull request swiftlang#35660 from DougGregor/mutating-concurrent-access-errors
2 parents 07d362d + 5d337f7 commit f0791b8

File tree

6 files changed

+174
-42
lines changed

6 files changed

+174
-42
lines changed

include/swift/AST/AnyFunctionRef.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ class AnyFunctionRef {
181181
return false;
182182
}
183183

184+
/// Whether this function is @concurrent.
185+
bool isConcurrent() const {
186+
if (!hasType())
187+
return false;
188+
189+
if (auto *fnType = getType()->getAs<AnyFunctionType>())
190+
return fnType->isConcurrent();
191+
192+
return false;
193+
}
194+
184195
bool isObjC() const {
185196
if (auto afd = TheFunction.dyn_cast<AbstractFunctionDecl *>()) {
186197
return afd->isObjC();

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4256,9 +4256,8 @@ ERROR(global_actor_from_nonactor_context,none,
42564256
ERROR(actor_isolated_partial_apply,none,
42574257
"actor-isolated %0 %1 can not be partially applied",
42584258
(DescriptiveDeclKind, DeclName))
4259-
WARNING(concurrent_access_local,none,
4260-
"local %0 %1 is unsafe to reference in code that may execute "
4261-
"concurrently",
4259+
ERROR(concurrent_access_local,none,
4260+
"use of local %0 %1 in concurrently-executing code",
42624261
(DescriptiveDeclKind, DeclName))
42634262
ERROR(actor_isolated_from_concurrent_closure,none,
42644263
"actor-isolated %0 %1 cannot be referenced from a concurrent closure",
@@ -4275,6 +4274,9 @@ ERROR(actor_isolated_from_escaping_closure,none,
42754274
ERROR(local_function_executed_concurrently,none,
42764275
"concurrently-executed %0 %1 must be marked as '@concurrent'",
42774276
(DescriptiveDeclKind, DeclName))
4277+
ERROR(concurrent_mutation_of_local_capture,none,
4278+
"mutation of captured %0 %1 in concurrently-executing code",
4279+
(DescriptiveDeclKind, DeclName))
42784280
NOTE(concurrent_access_here,none,
42794281
"access in concurrently-executed code here", ())
42804282
NOTE(actor_isolated_sync_func,none,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,12 @@ namespace {
697697

698698
ConcurrentExecutionChecker concurrentExecutionChecker;
699699

700+
using MutableVarParent = llvm::PointerUnion<InOutExpr *, LoadExpr *>;
701+
702+
/// Mapping from mutable local variables to the parent expression, when
703+
/// that parent is either a load or a inout expression.
704+
llvm::SmallDenseMap<DeclRefExpr *, MutableVarParent, 4> mutableLocalVarParent;
705+
700706
const DeclContext *getDeclContext() const {
701707
return contextStack.back();
702708
}
@@ -709,6 +715,33 @@ namespace {
709715
useContext, defContext);
710716
}
711717

718+
/// If the subexpression is a reference to a mutable local variable from a
719+
/// different context, record its parent. We'll query this as part of
720+
/// capture semantics in concurrent functions.
721+
void recordMutableVarParent(MutableVarParent parent, Expr *subExpr) {
722+
auto declRef = dyn_cast<DeclRefExpr>(subExpr);
723+
if (!declRef)
724+
return;
725+
726+
auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
727+
if (!var)
728+
return;
729+
730+
// Only mutable variables matter.
731+
if (!var->supportsMutation())
732+
return;
733+
734+
// Only mutable variables outside of the current context. This is an
735+
// optimization, because the parent map won't be queried in this case, and
736+
// it is the most common case for variables to be referenced in their
737+
// own context.
738+
if (var->getDeclContext() == getDeclContext())
739+
return;
740+
741+
assert(mutableLocalVarParent[declRef].isNull());
742+
mutableLocalVarParent[declRef] = parent;
743+
}
744+
712745
public:
713746
ActorIsolationChecker(const DeclContext *dc) : ctx(dc->getASTContext()) {
714747
contextStack.push_back(dc);
@@ -777,6 +810,12 @@ namespace {
777810
if (auto inout = dyn_cast<InOutExpr>(expr)) {
778811
if (!applyStack.empty())
779812
diagnoseInOutArg(applyStack.back(), inout, false);
813+
814+
recordMutableVarParent(inout, inout->getSubExpr());
815+
}
816+
817+
if (auto load = dyn_cast<LoadExpr>(expr)) {
818+
recordMutableVarParent(load, load->getSubExpr());
780819
}
781820

782821
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
@@ -786,7 +825,8 @@ namespace {
786825
}
787826

788827
if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
789-
checkNonMemberReference(declRef->getDeclRef(), declRef->getLoc());
828+
checkNonMemberReference(
829+
declRef->getDeclRef(), declRef->getLoc(), declRef);
790830
return { true, expr };
791831
}
792832

@@ -865,6 +905,11 @@ namespace {
865905
applyStack.pop_back();
866906
}
867907

908+
// Clear out the mutable local variable parent map on the way out.
909+
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
910+
mutableLocalVarParent.erase(declRefExpr);
911+
}
912+
868913
return expr;
869914
}
870915

@@ -1216,7 +1261,8 @@ namespace {
12161261
}
12171262

12181263
/// Check a reference to a local or global.
1219-
bool checkNonMemberReference(ConcreteDeclRef valueRef, SourceLoc loc) {
1264+
bool checkNonMemberReference(
1265+
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {
12201266
if (!valueRef)
12211267
return false;
12221268

@@ -1234,22 +1280,37 @@ namespace {
12341280
value, loc, isolation.getGlobalActor());
12351281

12361282
case ActorIsolationRestriction::LocalCapture:
1237-
if (!shouldDiagnoseExistingDataRaces(getDeclContext()))
1283+
// Check whether we are in a context that will not execute concurrently
1284+
// with the context of 'self'. If not, it's safe.
1285+
if (!mayExecuteConcurrentlyWith(
1286+
getDeclContext(), isolation.getLocalContext()))
12381287
return false;
12391288

1240-
// Check whether we are in a context that will not execute concurrently
1241-
// with the context of 'self'.
1242-
if (mayExecuteConcurrentlyWith(
1243-
getDeclContext(), isolation.getLocalContext())) {
1289+
// Check whether this is a local variable, in which case we can
1290+
// determine whether it was captured by value.
1291+
if (auto var = dyn_cast<VarDecl>(value)) {
1292+
auto parent = mutableLocalVarParent[declRefExpr];
1293+
1294+
// If we have an immediate load of this variable, the by-value
1295+
// capture in a concurrent function will guarantee that this is
1296+
// safe.
1297+
if (parent.dyn_cast<LoadExpr *>())
1298+
return false;
1299+
1300+
// Otherwise, we have concurrent mutation. Complain.
12441301
ctx.Diags.diagnose(
1245-
loc, diag::concurrent_access_local,
1246-
value->getDescriptiveKind(), value->getName());
1247-
value->diagnose(
1248-
diag::kind_declared_here, value->getDescriptiveKind());
1302+
loc, diag::concurrent_mutation_of_local_capture,
1303+
var->getDescriptiveKind(), var->getName());
12491304
return true;
12501305
}
12511306

1252-
return false;
1307+
// Concurrent access to some other local.
1308+
ctx.Diags.diagnose(
1309+
loc, diag::concurrent_access_local,
1310+
value->getDescriptiveKind(), value->getName());
1311+
value->diagnose(
1312+
diag::kind_declared_here, value->getDescriptiveKind());
1313+
return true;
12531314

12541315
case ActorIsolationRestriction::Unsafe:
12551316
return diagnoseReferenceToUnsafeGlobal(value, loc);
@@ -1536,6 +1597,10 @@ SourceLoc ConcurrentExecutionChecker::getConcurrentReferenceLoc(
15361597
enclosingBody = enclosingFunc->getBody();
15371598
else if (auto enclosingClosure = dyn_cast<ClosureExpr>(enclosingDC))
15381599
enclosingBody = enclosingClosure->getBody();
1600+
else if (auto enclosingTopLevelCode = dyn_cast<TopLevelCodeDecl>(enclosingDC))
1601+
enclosingBody = enclosingTopLevelCode->getBody();
1602+
else
1603+
return SourceLoc();
15391604

15401605
assert(enclosingBody && "Cannot have a local function here");
15411606
ConcurrentLocalRefWalker walker(*this, localFunc);
@@ -1549,10 +1614,8 @@ bool ConcurrentExecutionChecker::mayExecuteConcurrentlyWith(
15491614
// Walk the context chain from the use to the definition.
15501615
while (useContext != defContext) {
15511616
// If we find a concurrent closure... it can be run concurrently.
1552-
// NOTE: We also classify escaping closures this way, which detects more
1553-
// problematic cases.
15541617
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
1555-
if (isEscapingClosure(closure) || isConcurrentClosure(closure))
1618+
if (isConcurrentClosure(closure))
15561619
return true;
15571620
}
15581621

@@ -1963,18 +2026,21 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
19632026
static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc) {
19642027
while (!dc->isModuleScopeContext()) {
19652028
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
1966-
// Async closures use concurrency features.
1967-
if (closure->getType() && closure->isBodyAsync())
1968-
return true;
2029+
// Async and concurrent closures use concurrency features.
2030+
if (auto closureType = closure->getType()) {
2031+
if (auto fnType = closureType->getAs<AnyFunctionType>())
2032+
if (fnType->isAsync() || fnType->isConcurrent())
2033+
return true;
2034+
}
19692035
} else if (auto decl = dc->getAsDecl()) {
19702036
// If any isolation attributes are present, we're using concurrency
19712037
// features.
19722038
if (getIsolationFromAttributes(decl, /*shouldDiagnose=*/false))
19732039
return true;
19742040

19752041
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
1976-
// Async functions use concurrency features.
1977-
if (func->hasAsync())
2042+
// Async and concurrent functions use concurrency features.
2043+
if (func->hasAsync() || func->isConcurrent())
19782044
return true;
19792045

19802046
// If there is an explicit @asyncHandler, we're using concurrency

test/Concurrency/actor_isolation.swift

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// REQUIRES: concurrency
33

44
let immutableGlobal: String = "hello"
5-
var mutableGlobal: String = "can't touch this" // expected-note 3{{var declared here}}
5+
var mutableGlobal: String = "can't touch this" // expected-note 5{{var declared here}}
66

77
func globalFunc() { }
88
func acceptClosure<T>(_: () -> T) { }
@@ -128,7 +128,7 @@ extension MyActor {
128128

129129
// Closures.
130130
let localConstant = 17
131-
var localVar = 17 // expected-note 4{{var declared here}}
131+
var localVar = 17
132132

133133
// Non-escaping closures are okay.
134134
acceptClosure {
@@ -142,31 +142,34 @@ extension MyActor {
142142
acceptConcurrentClosure {
143143
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent closure}}
144144
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
145-
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
145+
_ = localVar // okay
146+
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
146147
_ = localConstant
147148
}
148149

149150
// Escaping closures might run concurrently.
150151
acceptEscapingClosure {
151152
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from an '@escaping' closure}}
152153
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from an '@escaping' closure}}
153-
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
154+
_ = localVar // okay, don't complain about escaping
154155
_ = localConstant
155156
}
156157

157158
// Local functions might run concurrently.
158159
@concurrent func localFn1() {
159160
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
160161
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
161-
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
162+
_ = localVar // okay
163+
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
162164
_ = localConstant
163165
}
164166

165167
@concurrent func localFn2() {
166168
acceptClosure {
167169
_ = text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
168170
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
169-
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
171+
_ = localVar // okay
172+
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
170173
_ = localConstant
171174
}
172175
}
@@ -321,29 +324,39 @@ func testGlobalRestrictions(actor: MyActor) async {
321324
// Global mutable state cannot be accessed.
322325
_ = mutableGlobal // expected-warning{{reference to var 'mutableGlobal' is not concurrency-safe because it involves shared mutable state}}
323326

324-
// Local mutable variables cannot be accessed from concurrently-executing
327+
// Local mutable variables cannot be modified from concurrently-executing
325328
// code.
326-
var i = 17 // expected-note{{var declared here}}
327-
acceptEscapingClosure {
328-
i = 42 // expected-warning{{local var 'i' is unsafe to reference in code that may execute concurrently}}
329+
var i = 17
330+
acceptConcurrentClosure {
331+
_ = i
332+
i = 42 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
329333
}
330334
print(i)
331335
}
332336

337+
func f() {
338+
acceptConcurrentClosure {
339+
_ = mutableGlobal // expected-warning{{reference to var 'mutableGlobal' is not concurrency-safe because it involves shared mutable state}}
340+
}
341+
342+
@concurrent func g() {
343+
_ = mutableGlobal // expected-warning{{reference to var 'mutableGlobal' is not concurrency-safe because it involves shared mutable state}}
344+
}
345+
}
346+
333347
// ----------------------------------------------------------------------
334348
// Local function isolation restrictions
335349
// ----------------------------------------------------------------------
336350
func checkLocalFunctions() async {
337351
var i = 0
338-
var j = 0 // expected-note{{var declared here}}
352+
var j = 0
339353

340354
func local1() {
341355
i = 17
342356
}
343357

344358
func local2() { // expected-error{{concurrently-executed local function 'local2()' must be marked as '@concurrent'}}{{3-3=@concurrent }}
345-
j = 42 // expected-warning{{local var 'j' is unsafe to reference in code that may execute concurrently}}
346-
// FIXME: the above should be an error as well
359+
j = 42 // expected-error{{mutation of captured var 'j' in concurrently-executing code}}
347360
}
348361

349362
// Okay to call locally.
@@ -357,22 +370,22 @@ func checkLocalFunctions() async {
357370
}
358371

359372
// Escaping closures can make the local function execute concurrently.
360-
acceptEscapingClosure {
373+
acceptConcurrentClosure {
361374
local2() // expected-note{{access in concurrently-executed code here}}
362375
}
363376

364377
print(i)
365378
print(j)
366379

367-
var k = 17 // expected-note{{var declared here}}
380+
var k = 17
368381
func local4() {
369-
acceptEscapingClosure {
382+
acceptConcurrentClosure {
370383
local3() // expected-note{{access in concurrently-executed code here}}
371384
}
372385
}
373386

374387
func local3() { // expected-error{{concurrently-executed local function 'local3()' must be marked as '@concurrent'}}
375-
k = 25 // expected-warning{{local var 'k' is unsafe to reference in code that may execute concurrently}}
388+
k = 25 // expected-error{{mutation of captured var 'k' in concurrently-executing code}}
376389
}
377390

378391
print(k)

test/Concurrency/async_let_isolation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ actor class MyActor {
1717
async let z = synchronous()
1818
// expected-error @-1{{actor-isolated instance method 'synchronous()' cannot be referenced from 'async let' initializer}}
1919

20-
var localText = text // expected-note{{var declared here}}
20+
var localText = text
2121
async let w = localText.removeLast()
22-
// expected-warning@-1{{local var 'localText' is unsafe to reference in code that may execute concurrently}}
22+
// expected-error@-1{{mutation of captured var 'localText' in concurrently-executing code}}
2323

2424
_ = await x
2525
_ = await y

0 commit comments

Comments
 (0)