Skip to content

Commit a2ff89b

Browse files
authored
Merge pull request #31483 from atrick/5.3-fix-exclusivity-coroutine
[5.3] Diagnose exclusivity in the presence of coroutines.
2 parents a6b2750 + 4a909cf commit a2ff89b

File tree

7 files changed

+253
-45
lines changed

7 files changed

+253
-45
lines changed

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,46 @@ CHANGELOG
2727
Swift 5.3
2828
----------
2929

30+
* [SR-11700][]:
31+
32+
Exclusivity violations within code that computes the `default`
33+
argument during Dictionary access are now diagnosed.
34+
35+
```swift
36+
struct Container {
37+
static let defaultKey = 0
38+
39+
var dictionary = [defaultKey:0]
40+
41+
mutating func incrementValue(at key: Int) {
42+
dictionary[key, default: dictionary[Container.defaultKey]!] += 1
43+
}
44+
}
45+
// error: overlapping accesses to 'self.dictionary', but modification requires exclusive access; consider copying to a local variable
46+
// dictionary[key, default: dictionary[Container.defaultKey]!] += 1
47+
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48+
// note: conflicting access is here
49+
// dictionary[key, default: dictionary[Container.defaultKey]!] += 1
50+
// ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
51+
```
52+
53+
The exclusivity violation can be avoided by precomputing the `default`
54+
argument using a local variable.
55+
56+
```swift
57+
struct Container {
58+
static let defaultKey = 0
59+
60+
var dictionary = [defaultKey:0]
61+
62+
mutating func incrementValue(at key: Int) {
63+
let defaultValue = dictionary[Container.defaultKey]!
64+
dictionary[key, default: defaultValue] += 1
65+
}
66+
}
67+
// No error.
68+
```
69+
3070
* [SR-7083][]:
3171

3272
Property observers such as `willSet` and `didSet` are now supported on `lazy` properties:
@@ -8068,4 +8108,5 @@ Swift 1.0
80688108
[SR-9827]: <https://bugs.swift.org/browse/SR-9827>
80698109
[SR-11298]: <https://bugs.swift.org/browse/SR-11298>
80708110
[SR-11429]: <https://bugs.swift.org/browse/SR-11429>
8111+
[SR-11700]: <https://bugs.swift.org/browse/SR-11700>
80718112
[SR-11841]: <https://bugs.swift.org/browse/SR-11841>

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,12 @@ struct ImmutableAddressUseVerifier {
562562
if (isConsumingOrMutatingYieldUse(use))
563563
return true;
564564
break;
565+
case SILInstructionKind::BeginAccessInst:
566+
if (cast<BeginAccessInst>(inst)->getAccessKind() != SILAccessKind::Read)
567+
return true;
568+
break;
569+
case SILInstructionKind::EndAccessInst:
570+
break;
565571
case SILInstructionKind::CopyAddrInst:
566572
if (isConsumingOrMutatingCopyAddrUse(use))
567573
return true;

lib/SILOptimizer/Analysis/AccessSummaryAnalysis.cpp

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,29 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
6565
Operand *operand = worklist.pop_back_val();
6666
SILInstruction *user = operand->getUser();
6767

68+
// Handle all types of full applies without switching over them.
69+
// Ultimately, this analysis only considers calls with @inout_aliasable
70+
// arguments because other argument conventions require an access on the
71+
// caller side.
72+
if (auto apply = FullApplySite::isa(user)) {
73+
SILFunction *callee = apply.getCalleeFunction();
74+
// We can't apply a summary for function whose body we can't see. Since
75+
// user-provided closures are always in the same module as their callee
76+
// This likely indicates a missing begin_access before an open-coded
77+
// call.
78+
if (!callee || callee->empty()) {
79+
summary.mergeWith(SILAccessKind::Modify, apply.getLoc(),
80+
getSubPathTrieRoot());
81+
continue;
82+
}
83+
unsigned operandNumber = operand->getOperandNumber();
84+
assert(operandNumber > 0 && "Summarizing apply for non-argument?");
85+
86+
unsigned calleeArgumentIndex = operandNumber - 1;
87+
processCall(info, argumentIndex, callee, calleeArgumentIndex, order);
88+
continue;
89+
}
90+
6891
switch (user->getKind()) {
6992
case SILInstructionKind::BeginAccessInst: {
7093
auto *BAI = cast<BeginAccessInst>(user);
@@ -101,14 +124,6 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
101124
processPartialApply(info, argumentIndex, cast<PartialApplyInst>(user),
102125
operand, order);
103126
break;
104-
case SILInstructionKind::ApplyInst:
105-
processFullApply(info, argumentIndex, cast<ApplyInst>(user), operand,
106-
order);
107-
break;
108-
case SILInstructionKind::TryApplyInst:
109-
processFullApply(info, argumentIndex, cast<TryApplyInst>(user), operand,
110-
order);
111-
break;
112127
default:
113128
// FIXME: These likely represent scenarios in which we're not generating
114129
// begin access markers. Ignore these for now. But we really should
@@ -124,9 +139,9 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
124139
}
125140

126141
#ifndef NDEBUG
127-
/// Sanity check to make sure that a noescape partial apply is
128-
/// only ultimately used by an apply, a try_apply or as an argument (but not
129-
/// the called function) in a partial_apply.
142+
/// Sanity check to make sure that a noescape partial apply is only ultimately
143+
/// used by directly calling it or passing it as argument, but not using it as a
144+
/// partial_apply callee.
130145
///
131146
/// FIXME: This needs to be checked in the SILVerifier.
132147
static bool hasExpectedUsesOfNoEscapePartialApply(Operand *partialApplyUse) {
@@ -136,6 +151,9 @@ static bool hasExpectedUsesOfNoEscapePartialApply(Operand *partialApplyUse) {
136151
switch (user->getKind()) {
137152
case SILInstructionKind::ApplyInst:
138153
case SILInstructionKind::TryApplyInst:
154+
case SILInstructionKind::BeginApplyInst:
155+
// The partial_apply must be passed to a @noescape argument type, but that
156+
// is already checked by the SIL verifier.
139157
return true;
140158
// partial_apply [stack] is terminated by a dealloc_stack.
141159
case SILInstructionKind::DeallocStackInst:
@@ -150,7 +168,10 @@ static bool hasExpectedUsesOfNoEscapePartialApply(Operand *partialApplyUse) {
150168
hasExpectedUsesOfNoEscapePartialApply);
151169

152170
case SILInstructionKind::PartialApplyInst:
153-
return partialApplyUse->get() != cast<PartialApplyInst>(user)->getCallee();
171+
if (partialApplyUse->get() == cast<PartialApplyInst>(user)->getCallee())
172+
return false;
173+
return llvm::all_of(cast<PartialApplyInst>(user)->getUses(),
174+
hasExpectedUsesOfNoEscapePartialApply);
154175

155176
// Look through begin_borrow.
156177
case SILInstructionKind::BeginBorrowInst:
@@ -245,27 +266,6 @@ void AccessSummaryAnalysis::processPartialApply(FunctionInfo *callerInfo,
245266
calleeArgumentIndex, order);
246267
}
247268

248-
void AccessSummaryAnalysis::processFullApply(FunctionInfo *callerInfo,
249-
unsigned callerArgumentIndex,
250-
FullApplySite apply,
251-
Operand *argumentOperand,
252-
FunctionOrder &order) {
253-
unsigned operandNumber = argumentOperand->getOperandNumber();
254-
assert(operandNumber > 0 && "Summarizing apply for non-argument?");
255-
256-
unsigned calleeArgumentIndex = operandNumber - 1;
257-
SILFunction *callee = apply.getCalleeFunction();
258-
// We can't apply a summary for function whose body we can't see.
259-
// Since user-provided closures are always in the same module as their callee
260-
// This likely indicates a missing begin_access before an open-coded
261-
// call.
262-
if (!callee || callee->empty())
263-
return;
264-
265-
processCall(callerInfo, callerArgumentIndex, callee, calleeArgumentIndex,
266-
order);
267-
}
268-
269269
void AccessSummaryAnalysis::processCall(FunctionInfo *callerInfo,
270270
unsigned callerArgumentIndex,
271271
SILFunction *callee,

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -886,17 +886,15 @@ static void checkForViolationsAtInstruction(SILInstruction &I,
886886
});
887887
}
888888

889-
if (auto *AI = dyn_cast<ApplyInst>(&I)) {
890-
// Record calls to swap() for potential Fix-Its.
891-
if (isCallToStandardLibrarySwap(AI, I.getFunction()->getASTContext()))
892-
State.CallsToSwap.push_back(AI);
893-
else
894-
checkForViolationAtApply(AI, State);
895-
return;
896-
}
897-
898-
if (auto *TAI = dyn_cast<TryApplyInst>(&I)) {
899-
checkForViolationAtApply(TAI, State);
889+
if (auto apply = FullApplySite::isa(&I)) {
890+
if (auto *AI = dyn_cast<ApplyInst>(&I)) {
891+
// Record calls to swap() for potential Fix-Its.
892+
if (isCallToStandardLibrarySwap(AI, I.getFunction()->getASTContext())) {
893+
State.CallsToSwap.push_back(AI);
894+
return;
895+
}
896+
}
897+
checkForViolationAtApply(apply, State);
900898
return;
901899
}
902900

test/SILOptimizer/access_summary_analysis.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ bb0(%0 : $*(Int, Int)):
226226
sil @closureWithMissingBody : $@convention(thin) (@inout_aliasable Int, Int) -> ()
227227

228228
// CHECK-LABEL: @callClosureWithMissingBody
229-
// CHECK-NEXT: ([], [])
229+
// CHECK-NEXT: ([modify], [])
230230
sil private [ossa] @callClosureWithMissingBody : $@convention(thin) (@inout_aliasable Int, Int) -> () {
231231
bb0(%0 : $*Int, %1 : $Int):
232232
%2 = function_ref @closureWithMissingBody : $@convention(thin) (@inout_aliasable Int, Int) -> ()

test/SILOptimizer/exclusivity_static_diagnostics.sil

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,7 @@ struct TestDynamic {
14361436

14371437
@_hasStorage @_hasInitialValue var s: S { get set }
14381438

1439+
// CHECK-LABEL: sil hidden [ossa] @testCallDynamic : $@convention(method) (@inout TestDynamic) -> () {
14391440
sil hidden [ossa] @testCallDynamic : $@convention(method) (@inout TestDynamic) -> () {
14401441
bb0(%0 : $*TestDynamic):
14411442
%access = begin_access [modify] [unknown] %0 : $*TestDynamic // expected-error {{overlapping accesses, but modification requires exclusive access; consider copying to a local variable}}
@@ -1462,3 +1463,137 @@ bb0(%0 : $*Builtin.Int64, %1 : $*TestDynamic):
14621463
%19 = tuple ()
14631464
return %19 : $()
14641465
}
1466+
1467+
// Dictionary.subscript.modify
1468+
sil [serialized] [always_inline] @$sSD_7defaultq_x_q_yXKtciM : $@yield_once @convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @noescape @callee_guaranteed () -> @out τ_0_1, @inout Dictionary<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1
1469+
1470+
// -----------------------------------------------------------------------------
1471+
// testBeginApplyOfClosure: DiagnoseStaticExclusivity must consider
1472+
// begin_apply as a user of accessed variables.
1473+
//
1474+
// Test a static exclusivity conflict between an outgoing coroutine
1475+
// argument and the closure passed to the coroutine.
1476+
// %access = begin_access [modify] %0
1477+
// begin_apply %coroutine(%access, %closure) -- where the closure captures %0
1478+
1479+
// CHECK-LABEL: sil private [ossa] @closureForBeginApplyOfClosure : $@convention(thin) (@inout_aliasable Int) -> @out Int {
1480+
sil private [ossa] @closureForBeginApplyOfClosure : $@convention(thin) (@inout_aliasable Int) -> @out Int {
1481+
bb0(%0 : $*Int, %1 : $*Int):
1482+
%access = begin_access [read] [static] %1 : $*Int // expected-note {{conflicting access is here}}
1483+
%val = load [trivial] %access : $*Int
1484+
end_access %access : $*Int
1485+
store %val to [trivial] %0 : $*Int
1486+
%v = tuple ()
1487+
return %v : $()
1488+
}
1489+
1490+
// CHECK-LABEL: sil [ossa] @testBeginApplyOfClosure : $@convention(thin) (@inout Int, @inout Dictionary<Int, Int>) -> () {
1491+
sil [ossa] @testBeginApplyOfClosure : $@convention(thin) (@inout Int, @inout Dictionary<Int, Int>) -> () {
1492+
bb0(%0 : $*Int, %1 : $*Dictionary<Int, Int>):
1493+
%f = function_ref @closureForBeginApplyOfClosure : $@convention(thin) (@inout_aliasable Int) -> @out Int
1494+
%pa = partial_apply [callee_guaranteed] %f(%0) : $@convention(thin) (@inout_aliasable Int) -> @out Int
1495+
%cvt = convert_escape_to_noescape [not_guaranteed] %pa : $@callee_guaranteed () -> @out Int to $@noescape @callee_guaranteed () -> @out Int
1496+
// function_ref Dictionary.subscript.modify
1497+
%mod = function_ref @$sSD_7defaultq_x_q_yXKtciM : $@yield_once @convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @noescape @callee_guaranteed () -> @out τ_0_1, @inout Dictionary<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1
1498+
%access = begin_access [modify] [static] %0 : $*Int // expected-error {{overlapping accesses, but modification requires exclusive access; consider copying to a local variable}}
1499+
(%yield, %token) = begin_apply %mod<Int, Int>(%access, %cvt, %1) : $@yield_once @convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @noescape @callee_guaranteed () -> @out τ_0_1, @inout Dictionary<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1
1500+
end_apply %token
1501+
end_access %access : $*Int
1502+
destroy_value %pa : $@callee_guaranteed () -> @out Int
1503+
%v = tuple ()
1504+
return %v : $()
1505+
}
1506+
1507+
// -----------------------------------------------------------------------------
1508+
// testCoroutineWithClosureArg: AccessedSummaryAnalysis must consider
1509+
// begin_apply a valid user of partial_apply.
1510+
//
1511+
// Test that this does not assert in hasExpectedUsesOfNoEscapePartialApply.
1512+
//
1513+
// This test needs two closures, one to capture the variable, another
1514+
// to recapture the variable, so AccessSummary is forced to process
1515+
// the closure.
1516+
// CHECK-LABEL: sil hidden [ossa] @testCoroutineWithClosureArg : $@convention(thin) (Int, @inout Int, @inout Dictionary<Int, Int>) -> () {
1517+
sil hidden [ossa] @testCoroutineWithClosureArg : $@convention(thin) (Int, @inout Int, @inout Dictionary<Int, Int>) -> () {
1518+
bb0(%0 : $Int, %1 : $*Int, %2 : $*Dictionary<Int, Int>):
1519+
%6 = function_ref @closureForTestCoroutineWithClosureArg : $@convention(thin) (@inout_aliasable Dictionary<Int, Int>, Int, @inout_aliasable Int) -> ()
1520+
%7 = apply %6(%2, %0, %1) : $@convention(thin) (@inout_aliasable Dictionary<Int, Int>, Int, @inout_aliasable Int) -> ()
1521+
%8 = tuple ()
1522+
return %8 : $()
1523+
}
1524+
1525+
// thunk for @callee_guaranteed () -> (@unowned Int)
1526+
sil shared [transparent] [serializable] [reabstraction_thunk] [ossa] @$sSiIgd_SiIegr_TR : $@convention(thin) (@noescape @callee_guaranteed () -> Int) -> @out Int {
1527+
bb0(%0 : $*Int, %1 : $@noescape @callee_guaranteed () -> Int):
1528+
%2 = apply %1() : $@noescape @callee_guaranteed () -> Int
1529+
store %2 to [trivial] %0 : $*Int
1530+
%4 = tuple ()
1531+
return %4 : $()
1532+
}
1533+
1534+
// CHECK-LABEL: sil private [ossa] @closureForTestCoroutineWithClosureArg : $@convention(thin) (@inout_aliasable Dictionary<Int, Int>, Int, @inout_aliasable Int) -> () {
1535+
sil private [ossa] @closureForTestCoroutineWithClosureArg : $@convention(thin) (@inout_aliasable Dictionary<Int, Int>, Int, @inout_aliasable Int) -> () {
1536+
bb0(%0 : $*Dictionary<Int, Int>, %1 : $Int, %2 : $*Int):
1537+
%6 = function_ref @implicitClosureForTestCoroutineWithClosureArg : $@convention(thin) (@inout_aliasable Int) -> Int
1538+
%7 = partial_apply [callee_guaranteed] %6(%2) : $@convention(thin) (@inout_aliasable Int) -> Int
1539+
%8 = convert_escape_to_noescape [not_guaranteed] %7 : $@callee_guaranteed () -> Int to $@noescape @callee_guaranteed () -> Int
1540+
%13 = begin_access [modify] [unknown] %0 : $*Dictionary<Int, Int>
1541+
%14 = alloc_stack $Int
1542+
store %1 to [trivial] %14 : $*Int
1543+
// thunk for @callee_guaranteed () -> (@unowned Int)
1544+
%16 = function_ref @$sSiIgd_SiIegr_TR : $@convention(thin) (@noescape @callee_guaranteed () -> Int) -> @out Int
1545+
%17 = partial_apply [callee_guaranteed] %16(%8) : $@convention(thin) (@noescape @callee_guaranteed () -> Int) -> @out Int
1546+
%18 = convert_escape_to_noescape [not_guaranteed] %17 : $@callee_guaranteed () -> @out Int to $@noescape @callee_guaranteed () -> @out Int
1547+
destroy_value %17 : $@callee_guaranteed () -> @out Int
1548+
// Dictionary.subscript.modify
1549+
%20 = function_ref @$sSD_7defaultq_x_q_yXKtciM : $@yield_once @convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @noescape @callee_guaranteed () -> @out τ_0_1, @inout Dictionary<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1
1550+
(%21, %22) = begin_apply %20<Int, Int>(%14, %18, %13) : $@yield_once @convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @noescape @callee_guaranteed () -> @out τ_0_1, @inout Dictionary<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1
1551+
assign %1 to %21 : $*Int
1552+
end_apply %22
1553+
end_access %13 : $*Dictionary<Int, Int>
1554+
dealloc_stack %14 : $*Int
1555+
destroy_value %7 : $@callee_guaranteed () -> Int
1556+
%28 = tuple ()
1557+
return %28 : $()
1558+
}
1559+
1560+
// CHECK-LABEL: sil private [transparent] [ossa] @implicitClosureForTestCoroutineWithClosureArg : $@convention(thin) (@inout_aliasable Int) -> Int {
1561+
sil private [transparent] [ossa] @implicitClosureForTestCoroutineWithClosureArg : $@convention(thin) (@inout_aliasable Int) -> Int {
1562+
bb0(%0 : $*Int):
1563+
%2 = begin_access [read] [unknown] %0 : $*Int
1564+
%3 = load [trivial] %2 : $*Int
1565+
end_access %2 : $*Int
1566+
return %3 : $Int
1567+
}
1568+
1569+
// -----------------------------------------------------------------------------
1570+
// testExternalWithClosureArg: handle inout arguments to resilient functions
1571+
// conservatively.
1572+
1573+
sil [ossa] @externalWithInout : $@convention(thin) (@inout Int) -> ()
1574+
1575+
// CHECK-LABEL: sil private [ossa] @closureForTestExternalWithClosureArg : $@convention(thin) (@inout_aliasable Int) -> () {
1576+
sil private [ossa] @closureForTestExternalWithClosureArg : $@convention(thin) (@inout_aliasable Int) -> () {
1577+
bb0(%0 : $*Int):
1578+
%f = function_ref @externalWithInout : $@convention(thin) (@inout Int) -> ()
1579+
%call = apply %f(%0) : $@convention(thin) (@inout Int) -> () // expected-note {{conflicting access is here}}
1580+
%v = tuple ()
1581+
return %v : $()
1582+
}
1583+
1584+
sil [ossa] @calleeForTestExternalWithClosureArg : $@convention(thin) (@inout Int, @noescape @callee_guaranteed () -> ()) -> ()
1585+
1586+
// CHECK-LABEL: sil hidden [ossa] @testExternalWithClosureArg : $@convention(thin) (@inout Int) -> () {
1587+
sil hidden [ossa] @testExternalWithClosureArg : $@convention(thin) (@inout Int) -> () {
1588+
bb0(%0 : $*Int):
1589+
%2 = function_ref @closureForTestExternalWithClosureArg : $@convention(thin) (@inout_aliasable Int) -> ()
1590+
%3 = partial_apply [callee_guaranteed] %2(%0) : $@convention(thin) (@inout_aliasable Int) -> ()
1591+
%4 = convert_escape_to_noescape [not_guaranteed] %3 : $@callee_guaranteed () -> () to $@noescape @callee_guaranteed () -> ()
1592+
%5 = begin_access [modify] [unknown] %0 : $*Int // expected-error {{overlapping accesses, but modification requires exclusive access; consider copying to a local variable}}
1593+
%6 = function_ref @calleeForTestExternalWithClosureArg : $@convention(thin) (@inout Int, @noescape @callee_guaranteed () -> ()) -> ()
1594+
%7 = apply %6(%5, %4) : $@convention(thin) (@inout Int, @noescape @callee_guaranteed () -> ()) -> ()
1595+
end_access %5 : $*Int
1596+
destroy_value %3 : $@callee_guaranteed () -> ()
1597+
%10 = tuple ()
1598+
return %10 : $()
1599+
}

test/SILOptimizer/exclusivity_static_diagnostics.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,3 +649,31 @@ struct DisjointLet {
649649
}
650650
}
651651
}
652+
653+
// -----------------------------------------------------------------------------
654+
// coroutineWithClosureArg: AccessedSummaryAnalysis must consider
655+
// begin_apply a valid user of partial_apply.
656+
//
657+
// Test that this does not assert in hasExpectedUsesOfNoEscapePartialApply.
658+
//
659+
// This test needs two closures, one to capture the variable, another
660+
// to recapture the variable, so AccessSummary is forced to process
661+
// the closure.
662+
func coroutineWithClosureArg(i: Int, x: inout Int, d: inout Dictionary<Int, Int>) {
663+
{ d[i, default: x] = 0 }()
664+
}
665+
666+
// -----------------------------------------------------------------------------
667+
//
668+
struct TestConflictInCoroutineClosureArg {
669+
static let defaultKey = 0
670+
671+
var dictionary = [defaultKey:0]
672+
673+
mutating func incrementValue(at key: Int) {
674+
dictionary[key, default:
675+
dictionary[TestConflictInCoroutineClosureArg.defaultKey]!] += 1
676+
// expected-error@-2 {{overlapping accesses to 'self.dictionary', but modification requires exclusive access; consider copying to a local variable}}
677+
// expected-note@-2 {{conflicting access is here}}
678+
}
679+
}

0 commit comments

Comments
 (0)