Skip to content

Commit a34b52c

Browse files
authored
Merge pull request #36117 from DougGregor/explicit-capture-concurrency-checks
2 parents eaece0c + c4ea7bd commit a34b52c

File tree

5 files changed

+124
-85
lines changed

5 files changed

+124
-85
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 107 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,8 @@ ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
530530
case DeclKind::Func:
531531
case DeclKind::Subscript: {
532532
// Local captures are checked separately.
533-
if (cast<ValueDecl>(decl)->isLocalCapture()) {
534-
return forLocalCapture(decl->getDeclContext());
535-
}
533+
if (cast<ValueDecl>(decl)->isLocalCapture())
534+
return forUnrestricted();
536535

537536
// 'let' declarations are immutable, so they can be accessed across
538537
// actors.
@@ -886,6 +885,11 @@ namespace {
886885
SmallVector<const DeclContext *, 4> contextStack;
887886
SmallVector<ApplyExpr*, 4> applyStack;
888887

888+
/// Keeps track of the capture context of variables that have been
889+
/// explicitly captured in closures.
890+
llvm::SmallDenseMap<VarDecl *, TinyPtrVector<const DeclContext *>>
891+
captureContexts;
892+
889893
using MutableVarSource = llvm::PointerUnion<DeclRefExpr *, InOutExpr *>;
890894
using MutableVarParent = llvm::PointerUnion<InOutExpr *, LoadExpr *>;
891895

@@ -1134,6 +1138,13 @@ namespace {
11341138
if (isa<ObjCSelectorExpr>(expr))
11351139
return { false, expr };
11361140

1141+
// Track the capture contexts for variables.
1142+
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
1143+
for (const auto &entry : captureList->getCaptureList()) {
1144+
captureContexts[entry.Var].push_back(captureList->getClosureBody());
1145+
}
1146+
}
1147+
11371148
return { true, expr };
11381149
}
11391150

@@ -1156,6 +1167,17 @@ namespace {
11561167
mutableLocalVarParent.erase(inoutExpr);
11571168
}
11581169

1170+
// Remove the tracked capture contexts.
1171+
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
1172+
for (const auto &entry : captureList->getCaptureList()) {
1173+
auto &contexts = captureContexts[entry.Var];
1174+
assert(contexts.back() == captureList->getClosureBody());
1175+
contexts.pop_back();
1176+
if (contexts.empty())
1177+
captureContexts.erase(entry.Var);
1178+
}
1179+
}
1180+
11591181
return expr;
11601182
}
11611183

@@ -1257,7 +1279,6 @@ namespace {
12571279
auto isolation = ActorIsolationRestriction::forDeclaration(decl);
12581280
switch (isolation) {
12591281
case ActorIsolationRestriction::Unrestricted:
1260-
case ActorIsolationRestriction::LocalCapture:
12611282
case ActorIsolationRestriction::Unsafe:
12621283
break;
12631284
case ActorIsolationRestriction::CrossGlobalActor:
@@ -1526,13 +1547,95 @@ namespace {
15261547
llvm_unreachable("unhandled actor isolation kind!");
15271548
}
15281549

1550+
/// Find the innermost context in which this declaration was explicitly
1551+
/// captured.
1552+
const DeclContext *findCapturedDeclContext(ValueDecl *value) {
1553+
assert(value->isLocalCapture());
1554+
auto var = dyn_cast<VarDecl>(value);
1555+
if (!var)
1556+
return value->getDeclContext();
1557+
1558+
auto knownContexts = captureContexts.find(var);
1559+
if (knownContexts == captureContexts.end())
1560+
return value->getDeclContext();
1561+
1562+
return knownContexts->second.back();
1563+
}
1564+
1565+
/// Check a reference to a local capture.
1566+
bool checkLocalCapture(
1567+
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {
1568+
auto value = valueRef.getDecl();
1569+
1570+
// Check whether we are in a context that will not execute concurrently
1571+
// with the context of 'self'. If not, it's safe.
1572+
if (!mayExecuteConcurrentlyWith(
1573+
getDeclContext(), findCapturedDeclContext(value)))
1574+
return false;
1575+
1576+
// Check whether this is a local variable, in which case we can
1577+
// determine whether it was safe to access concurrently.
1578+
if (auto var = dyn_cast<VarDecl>(value)) {
1579+
auto parent = mutableLocalVarParent[declRefExpr];
1580+
1581+
// If the variable is immutable, it's fine so long as it involves
1582+
// ConcurrentValue types.
1583+
//
1584+
// When flow-sensitive concurrent captures are enabled, we also
1585+
// allow reads, depending on a SIL diagnostic pass to identify the
1586+
// remaining race conditions.
1587+
if (!var->supportsMutation() ||
1588+
(ctx.LangOpts.EnableExperimentalFlowSensitiveConcurrentCaptures &&
1589+
parent.dyn_cast<LoadExpr *>())) {
1590+
return diagnoseNonConcurrentTypesInReference(
1591+
valueRef, getDeclContext(), loc,
1592+
ConcurrentReferenceKind::LocalCapture);
1593+
}
1594+
1595+
// Otherwise, we have concurrent access. Complain.
1596+
ctx.Diags.diagnose(
1597+
loc, diag::concurrent_access_of_local_capture,
1598+
parent.dyn_cast<LoadExpr *>(),
1599+
var->getDescriptiveKind(), var->getName());
1600+
return true;
1601+
}
1602+
1603+
if (auto func = dyn_cast<FuncDecl>(value)) {
1604+
if (func->isConcurrent())
1605+
return false;
1606+
1607+
func->diagnose(
1608+
diag::local_function_executed_concurrently,
1609+
func->getDescriptiveKind(), func->getName())
1610+
.fixItInsert(func->getAttributeInsertionLoc(false), "@concurrent ");
1611+
1612+
// Add the @concurrent attribute implicitly, so we don't diagnose
1613+
// again.
1614+
const_cast<FuncDecl *>(func)->getAttrs().add(
1615+
new (ctx) ConcurrentAttr(true));
1616+
return true;
1617+
}
1618+
1619+
// Concurrent access to some other local.
1620+
ctx.Diags.diagnose(
1621+
loc, diag::concurrent_access_local,
1622+
value->getDescriptiveKind(), value->getName());
1623+
value->diagnose(
1624+
diag::kind_declared_here, value->getDescriptiveKind());
1625+
return true;
1626+
}
1627+
15291628
/// Check a reference to a local or global.
15301629
bool checkNonMemberReference(
15311630
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {
15321631
if (!valueRef)
15331632
return false;
15341633

15351634
auto value = valueRef.getDecl();
1635+
1636+
if (value->isLocalCapture())
1637+
return checkLocalCapture(valueRef, loc, declRefExpr);
1638+
15361639
switch (auto isolation =
15371640
ActorIsolationRestriction::forDeclaration(valueRef)) {
15381641
case ActorIsolationRestriction::Unrestricted:
@@ -1548,64 +1651,6 @@ namespace {
15481651
valueRef, loc, isolation.getGlobalActor(),
15491652
isolation == ActorIsolationRestriction::CrossGlobalActor);
15501653

1551-
case ActorIsolationRestriction::LocalCapture:
1552-
// Check whether we are in a context that will not execute concurrently
1553-
// with the context of 'self'. If not, it's safe.
1554-
if (!mayExecuteConcurrentlyWith(
1555-
getDeclContext(), isolation.getLocalContext()))
1556-
return false;
1557-
1558-
// Check whether this is a local variable, in which case we can
1559-
// determine whether it was safe to access concurrently.
1560-
if (auto var = dyn_cast<VarDecl>(value)) {
1561-
auto parent = mutableLocalVarParent[declRefExpr];
1562-
1563-
// If the variable is immutable, it's fine so long as it involves
1564-
// ConcurrentValue types.
1565-
//
1566-
// When flow-sensitive concurrent captures are enabled, we also
1567-
// allow reads, depending on a SIL diagnostic pass to identify the
1568-
// remaining race conditions.
1569-
if (!var->supportsMutation() ||
1570-
(ctx.LangOpts.EnableExperimentalFlowSensitiveConcurrentCaptures &&
1571-
parent.dyn_cast<LoadExpr *>())) {
1572-
return diagnoseNonConcurrentTypesInReference(
1573-
valueRef, getDeclContext(), loc,
1574-
ConcurrentReferenceKind::LocalCapture);
1575-
}
1576-
1577-
// Otherwise, we have concurrent access. Complain.
1578-
ctx.Diags.diagnose(
1579-
loc, diag::concurrent_access_of_local_capture,
1580-
parent.dyn_cast<LoadExpr *>(),
1581-
var->getDescriptiveKind(), var->getName());
1582-
return true;
1583-
}
1584-
1585-
if (auto func = dyn_cast<FuncDecl>(value)) {
1586-
if (func->isConcurrent())
1587-
return false;
1588-
1589-
func->diagnose(
1590-
diag::local_function_executed_concurrently,
1591-
func->getDescriptiveKind(), func->getName())
1592-
.fixItInsert(func->getAttributeInsertionLoc(false), "@concurrent ");
1593-
1594-
// Add the @concurrent attribute implicitly, so we don't diagnose
1595-
// again.
1596-
const_cast<FuncDecl *>(func)->getAttrs().add(
1597-
new (ctx) ConcurrentAttr(true));
1598-
return true;
1599-
}
1600-
1601-
// Concurrent access to some other local.
1602-
ctx.Diags.diagnose(
1603-
loc, diag::concurrent_access_local,
1604-
value->getDescriptiveKind(), value->getName());
1605-
value->diagnose(
1606-
diag::kind_declared_here, value->getDescriptiveKind());
1607-
return true;
1608-
16091654
case ActorIsolationRestriction::Unsafe:
16101655
return diagnoseReferenceToUnsafeGlobal(value, loc);
16111656
}
@@ -1756,9 +1801,6 @@ namespace {
17561801
memberRef, memberLoc, isolation.getGlobalActor(),
17571802
isolation == ActorIsolationRestriction::CrossGlobalActor);
17581803

1759-
case ActorIsolationRestriction::LocalCapture:
1760-
llvm_unreachable("Locals cannot be referenced with member syntax");
1761-
17621804
case ActorIsolationRestriction::Unsafe:
17631805
// This case is hit when passing actor state inout to functions in some
17641806
// cases. The error is emitted by diagnoseInOutArg.

lib/Sema/TypeCheckConcurrency.h

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ class ActorIsolationRestriction {
7777
/// Access to the declaration is unsafe in a concurrent context.
7878
Unsafe,
7979

80-
/// The declaration is a local entity whose capture could introduce
81-
/// data races. The context in which the local was defined is provided.
82-
LocalCapture,
83-
8480
/// References to this entity are allowed from anywhere, but doing so
8581
/// may cross an actor boundary if it is not on \c self.
8682
CrossActorSelf,
@@ -117,12 +113,6 @@ class ActorIsolationRestriction {
117113
public:
118114
Kind getKind() const { return kind; }
119115

120-
/// Retrieve the declaration context in which a local was defined.
121-
DeclContext *getLocalContext() const {
122-
assert(kind == LocalCapture);
123-
return data.localContext;
124-
}
125-
126116
/// Retrieve the actor class that the declaration is within.
127117
ClassDecl *getActorClass() const {
128118
assert(kind == ActorSelf || kind == CrossActorSelf);
@@ -154,13 +144,6 @@ class ActorIsolationRestriction {
154144
return result;
155145
}
156146

157-
/// Access is restricted to code running within the given local context.
158-
static ActorIsolationRestriction forLocalCapture(DeclContext *dc) {
159-
ActorIsolationRestriction result(LocalCapture);
160-
result.data.localContext = dc;
161-
return result;
162-
}
163-
164147
/// Accesses to the given declaration can only be made via this particular
165148
/// global actor or is a cross-actor access.
166149
static ActorIsolationRestriction forGlobalActor(

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ static bool checkObjCActorIsolation(const ValueDecl *VD,
413413
// FIXME: Consider whether to limit @objc on global-actor-qualified
414414
// declarations.
415415
case ActorIsolationRestriction::Unrestricted:
416-
case ActorIsolationRestriction::LocalCapture:
417416
case ActorIsolationRestriction::Unsafe:
418417
return false;
419418
}

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2755,7 +2755,6 @@ bool ConformanceChecker::checkActorIsolation(
27552755
}
27562756

27572757
case ActorIsolationRestriction::Unsafe:
2758-
case ActorIsolationRestriction::LocalCapture:
27592758
break;
27602759

27612760
case ActorIsolationRestriction::Unrestricted:

test/Concurrency/actor_isolation.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,28 @@ extension MyActor {
139139
}
140140

141141
// Concurrent closures might run... concurrently.
142-
acceptConcurrentClosure {
142+
var otherLocalVar = 12
143+
acceptConcurrentClosure { [otherLocalVar] in
144+
defer {
145+
_ = otherLocalVar
146+
}
147+
143148
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent closure}}
144149
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
145150
_ = localVar // expected-error{{reference to captured var 'localVar' in concurrently-executing code}}
146151
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
147152
_ = localConstant
153+
154+
_ = otherLocalVar
155+
}
156+
otherLocalVar = 17
157+
158+
acceptConcurrentClosure { [weak self, otherLocalVar] in
159+
defer {
160+
_ = self?.actorIndependentVar
161+
}
162+
163+
_ = otherLocalVar
148164
}
149165

150166
// Escaping closures might run concurrently.

0 commit comments

Comments
 (0)