Skip to content

Commit 388fd1f

Browse files
committed
[Concurrency] Fix checking of captures in concurrent closures.
inout expressions can occur as children of "load" expressions, so ensure that we (1) treat them as loads, semantically, and (2) don't visit them multiple times.
1 parent d1c7890 commit 388fd1f

File tree

2 files changed

+91
-22
lines changed

2 files changed

+91
-22
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,11 +1001,13 @@ namespace {
10011001

10021002
ConcurrentExecutionChecker concurrentExecutionChecker;
10031003

1004+
using MutableVarSource = llvm::PointerUnion<DeclRefExpr *, InOutExpr *>;
10041005
using MutableVarParent = llvm::PointerUnion<InOutExpr *, LoadExpr *>;
10051006

1006-
/// Mapping from mutable local variables to the parent expression, when
1007-
/// that parent is either a load or a inout expression.
1008-
llvm::SmallDenseMap<DeclRefExpr *, MutableVarParent, 4> mutableLocalVarParent;
1007+
/// Mapping from mutable local variables or inout expressions to the
1008+
/// parent expression, when that parent is either a load or a inout expression.
1009+
llvm::SmallDenseMap<MutableVarSource, MutableVarParent, 4>
1010+
mutableLocalVarParent;
10091011

10101012
const DeclContext *getDeclContext() const {
10111013
return contextStack.back();
@@ -1022,28 +1024,66 @@ namespace {
10221024
/// If the subexpression is a reference to a mutable local variable from a
10231025
/// different context, record its parent. We'll query this as part of
10241026
/// capture semantics in concurrent functions.
1025-
void recordMutableVarParent(MutableVarParent parent, Expr *subExpr) {
1026-
auto declRef = dyn_cast<DeclRefExpr>(subExpr);
1027-
if (!declRef)
1028-
return;
1027+
///
1028+
/// \returns true if we recorded anything, false otherwise.
1029+
bool recordMutableVarParent(MutableVarParent parent, Expr *subExpr) {
1030+
subExpr = subExpr->getValueProvidingExpr();
1031+
1032+
if (auto declRef = dyn_cast<DeclRefExpr>(subExpr)) {
1033+
auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
1034+
if (!var)
1035+
return false;
10291036

1030-
auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
1031-
if (!var)
1032-
return;
1037+
// Only mutable variables matter.
1038+
if (!var->supportsMutation())
1039+
return false;
10331040

1034-
// Only mutable variables matter.
1035-
if (!var->supportsMutation())
1036-
return;
1041+
// Only mutable variables outside of the current context. This is an
1042+
// optimization, because the parent map won't be queried in this case, and
1043+
// it is the most common case for variables to be referenced in their
1044+
// own context.
1045+
if (var->getDeclContext() == getDeclContext())
1046+
return false;
10371047

1038-
// Only mutable variables outside of the current context. This is an
1039-
// optimization, because the parent map won't be queried in this case, and
1040-
// it is the most common case for variables to be referenced in their
1041-
// own context.
1042-
if (var->getDeclContext() == getDeclContext())
1043-
return;
1048+
assert(mutableLocalVarParent[declRef].isNull());
1049+
mutableLocalVarParent[declRef] = parent;
1050+
return true;
1051+
}
1052+
1053+
// For a member reference, try to record a parent for the base
1054+
// expression.
1055+
if (auto memberRef = dyn_cast<MemberRefExpr>(subExpr)) {
1056+
return recordMutableVarParent(parent, memberRef->getBase());
1057+
}
1058+
1059+
// For a subscript, try to record a parent for the base expression.
1060+
if (auto subscript = dyn_cast<SubscriptExpr>(subExpr)) {
1061+
return recordMutableVarParent(parent, subscript->getBase());
1062+
}
10441063

1045-
assert(mutableLocalVarParent[declRef].isNull());
1046-
mutableLocalVarParent[declRef] = parent;
1064+
// Look through postfix '!'.
1065+
if (auto force = dyn_cast<ForceValueExpr>(subExpr)) {
1066+
return recordMutableVarParent(parent, force->getSubExpr());
1067+
}
1068+
1069+
// Look through postfix '?'.
1070+
if (auto bindOpt = dyn_cast<BindOptionalExpr>(subExpr)) {
1071+
return recordMutableVarParent(parent, bindOpt->getSubExpr());
1072+
}
1073+
1074+
if (auto optEval = dyn_cast<OptionalEvaluationExpr>(subExpr)) {
1075+
return recordMutableVarParent(parent, optEval->getSubExpr());
1076+
}
1077+
1078+
// & expressions can be embedded for references to mutable variables
1079+
// or subscribes inside a struct/enum.
1080+
if (auto inout = dyn_cast<InOutExpr>(subExpr)) {
1081+
// Record the parent of the inout so we don't look at it again later.
1082+
mutableLocalVarParent[inout] = parent;
1083+
return recordMutableVarParent(parent, inout->getSubExpr());
1084+
}
1085+
1086+
return false;
10471087
}
10481088

10491089
public:
@@ -1127,7 +1167,8 @@ namespace {
11271167
if (!applyStack.empty())
11281168
diagnoseInOutArg(applyStack.back(), inout, false);
11291169

1130-
recordMutableVarParent(inout, inout->getSubExpr());
1170+
if (mutableLocalVarParent.count(inout) == 0)
1171+
recordMutableVarParent(inout, inout->getSubExpr());
11311172
}
11321173

11331174
if (auto load = dyn_cast<LoadExpr>(expr)) {
@@ -1225,6 +1266,9 @@ namespace {
12251266
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
12261267
mutableLocalVarParent.erase(declRefExpr);
12271268
}
1269+
if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
1270+
mutableLocalVarParent.erase(inoutExpr);
1271+
}
12281272

12291273
return expr;
12301274
}

test/attr/attr_concurrent.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,28 @@ func mutationOfLocal() {
9393

9494
localInt = 20
9595
}
96+
97+
struct NonTrivialValueType {
98+
var int: Int = 0
99+
var array: [Int] = []
100+
var optArray: [Int]? = nil
101+
}
102+
103+
func testCaseNonTrivialValue() {
104+
var i = NonTrivialValueType()
105+
var j = 0
106+
acceptsConcurrent { value in
107+
print(i.int)
108+
print(i.array[0])
109+
print(i.array[j])
110+
print(i.optArray?[j] ?? 0)
111+
print(i.optArray![j])
112+
113+
i.int = 5 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
114+
i.array[0] = 5 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
115+
116+
return value
117+
}
118+
119+
j = 17
120+
}

0 commit comments

Comments
 (0)