Skip to content

Commit b163fed

Browse files
authored
Merge pull request #75887 from gottesmm/release/6.0-rdar133798044
[6.0][region-isolation] Improve the error we emit for closure literals captured as a sending parameter.
2 parents 7abaf9e + bfb1f2e commit b163fed

11 files changed

+551
-28
lines changed

include/swift/AST/DiagnosticsSIL.def

+17
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,23 @@ NOTE(regionbasedisolation_named_isolated_closure_yields_race, none,
10411041
"%0%1 is captured by a %2 closure. %2 uses in closure may race against later %3 uses",
10421042
(StringRef, Identifier, ActorIsolation, ActorIsolation))
10431043

1044+
ERROR(regionbasedisolation_typed_tns_passed_sending_closure, none,
1045+
"passing closure as a 'sending' parameter risks causing data races between %0 and concurrent execution of the closure",
1046+
(StringRef))
1047+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value, none,
1048+
"closure captures %0 %1",
1049+
(StringRef, DeclName))
1050+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated, none,
1051+
"closure captures %0 which is accessible to code in the current task",
1052+
(DeclName))
1053+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_region, none,
1054+
"closure captures %1 which is accessible to %0 code",
1055+
(StringRef, DeclName))
1056+
1057+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_multiple_value, none,
1058+
"closure captures non-Sendable %0",
1059+
(DeclName))
1060+
10441061
NOTE(regionbasedisolation_named_transfer_nt_asynclet_capture, none,
10451062
"sending %1 %0 into async let risks causing data races between nonisolated and %1 uses",
10461063
(Identifier, StringRef))

include/swift/SILOptimizer/Utils/PartitionUtils.h

+6
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ class RepresentativeValue {
141141
return value.get<SILInstruction *>();
142142
}
143143

144+
bool operator==(SILValue other) const {
145+
if (hasRegionIntroducingInst())
146+
return false;
147+
return getValue() == other;
148+
}
149+
144150
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
145151

146152
private:

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

+241-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ using Region = PartitionPrimitives::Region;
5858
// MARK: Utilities
5959
//===----------------------------------------------------------------------===//
6060

61+
static SILValue stripFunctionConversions(SILValue val) {
62+
while (true) {
63+
if (auto ti = dyn_cast<ThinToThickFunctionInst>(val)) {
64+
val = ti->getOperand();
65+
continue;
66+
}
67+
68+
if (auto cfi = dyn_cast<ConvertFunctionInst>(val)) {
69+
val = cfi->getOperand();
70+
continue;
71+
}
72+
73+
if (auto cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(val)) {
74+
val = cvt->getOperand();
75+
continue;
76+
}
77+
78+
break;
79+
}
80+
81+
return val;
82+
}
83+
6184
static std::optional<DiagnosticBehavior>
6285
getDiagnosticBehaviorLimitForValue(SILValue value) {
6386
auto *nom = value->getType().getNominalOrBoundGenericNominal();
@@ -72,6 +95,38 @@ getDiagnosticBehaviorLimitForValue(SILValue value) {
7295
return getConcurrencyDiagnosticBehaviorLimit(nom, fromDC);
7396
}
7497

98+
static std::optional<DiagnosticBehavior>
99+
getDiagnosticBehaviorLimitForCapturedValue(CapturedValue value) {
100+
ValueDecl *decl = value.getDecl();
101+
auto *nom = decl->getInterfaceType()->getNominalOrBoundGenericNominal();
102+
if (!nom)
103+
return {};
104+
105+
auto *fromDC = decl->getInnermostDeclContext();
106+
return getConcurrencyDiagnosticBehaviorLimit(nom, fromDC);
107+
}
108+
109+
/// Find the most conservative diagnostic behavior by taking the max over all
110+
/// DiagnosticBehavior for the captured values.
111+
static std::optional<DiagnosticBehavior>
112+
getDiagnosticBehaviorLimitForCapturedValues(
113+
ArrayRef<CapturedValue> capturedValues) {
114+
using UnderlyingType = std::underlying_type<DiagnosticBehavior>::type;
115+
116+
std::optional<DiagnosticBehavior> diagnosticBehavior;
117+
for (auto value : capturedValues) {
118+
auto lhs = UnderlyingType(
119+
diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified));
120+
auto rhs = UnderlyingType(
121+
getDiagnosticBehaviorLimitForCapturedValue(value).value_or(
122+
DiagnosticBehavior::Unspecified));
123+
auto result = DiagnosticBehavior(std::max(lhs, rhs));
124+
if (result != DiagnosticBehavior::Unspecified)
125+
diagnosticBehavior = result;
126+
}
127+
return diagnosticBehavior;
128+
}
129+
75130
static std::optional<SILDeclRef> getDeclRefForCallee(SILInstruction *inst) {
76131
auto fas = FullApplySite::isa(inst);
77132
if (!fas)
@@ -1318,6 +1373,98 @@ class TransferNonTransferrableDiagnosticEmitter {
13181373
.limitBehaviorIf(getBehaviorLimit());
13191374
}
13201375

1376+
/// Only use if we were able to find the actual isolated value.
1377+
void emitTypedSendingNeverSendableToSendingClosureParamDirectlyIsolated(
1378+
SILLocation loc, CapturedValue capturedValue) {
1379+
SmallString<64> descriptiveKindStr;
1380+
{
1381+
llvm::raw_svector_ostream os(descriptiveKindStr);
1382+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1383+
os << "code in the current task";
1384+
} else {
1385+
getIsolationRegionInfo().printForDiagnostics(os);
1386+
os << " code";
1387+
}
1388+
}
1389+
1390+
diagnoseError(loc,
1391+
diag::regionbasedisolation_typed_tns_passed_sending_closure,
1392+
descriptiveKindStr)
1393+
.highlight(loc.getSourceRange())
1394+
.limitBehaviorIf(
1395+
getDiagnosticBehaviorLimitForCapturedValue(capturedValue));
1396+
1397+
auto capturedLoc = RegularLocation(capturedValue.getLoc());
1398+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1399+
auto diag = diag::
1400+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
1401+
diagnoseNote(capturedLoc, diag, capturedValue.getDecl()->getName());
1402+
return;
1403+
}
1404+
1405+
descriptiveKindStr.clear();
1406+
{
1407+
llvm::raw_svector_ostream os(descriptiveKindStr);
1408+
getIsolationRegionInfo().printForDiagnostics(os);
1409+
}
1410+
1411+
auto diag = diag::
1412+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value;
1413+
diagnoseNote(capturedLoc, diag, descriptiveKindStr,
1414+
capturedValue.getDecl()->getName());
1415+
}
1416+
1417+
void emitTypedSendingNeverSendableToSendingClosureParam(
1418+
SILLocation loc, ArrayRef<CapturedValue> capturedValues) {
1419+
SmallString<64> descriptiveKindStr;
1420+
{
1421+
llvm::raw_svector_ostream os(descriptiveKindStr);
1422+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1423+
os << "code in the current task";
1424+
} else {
1425+
getIsolationRegionInfo().printForDiagnostics(os);
1426+
os << " code";
1427+
}
1428+
}
1429+
1430+
auto behaviorLimit =
1431+
getDiagnosticBehaviorLimitForCapturedValues(capturedValues);
1432+
diagnoseError(loc,
1433+
diag::regionbasedisolation_typed_tns_passed_sending_closure,
1434+
descriptiveKindStr)
1435+
.highlight(loc.getSourceRange())
1436+
.limitBehaviorIf(behaviorLimit);
1437+
1438+
if (capturedValues.size() == 1) {
1439+
auto captured = capturedValues.front();
1440+
auto capturedLoc = RegularLocation(captured.getLoc());
1441+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1442+
auto diag = diag::
1443+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
1444+
diagnoseNote(capturedLoc, diag, captured.getDecl()->getName());
1445+
return;
1446+
}
1447+
1448+
descriptiveKindStr.clear();
1449+
{
1450+
llvm::raw_svector_ostream os(descriptiveKindStr);
1451+
getIsolationRegionInfo().printForDiagnostics(os);
1452+
}
1453+
auto diag = diag::
1454+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_region;
1455+
diagnoseNote(capturedLoc, diag, descriptiveKindStr,
1456+
captured.getDecl()->getName());
1457+
return;
1458+
}
1459+
1460+
for (auto captured : capturedValues) {
1461+
auto capturedLoc = RegularLocation(captured.getLoc());
1462+
auto diag = diag::
1463+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_multiple_value;
1464+
diagnoseNote(capturedLoc, diag, captured.getDecl()->getName());
1465+
}
1466+
}
1467+
13211468
void emitNamedOnlyError(SILLocation loc, Identifier name) {
13221469
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
13231470
name)
@@ -1455,12 +1602,13 @@ class TransferNonTransferrableDiagnosticEmitter {
14551602
class TransferNonTransferrableDiagnosticInferrer {
14561603
struct AutoClosureWalker;
14571604

1605+
RegionAnalysisValueMap &valueMap;
14581606
TransferNonTransferrableDiagnosticEmitter diagnosticEmitter;
14591607

14601608
public:
14611609
TransferNonTransferrableDiagnosticInferrer(
1462-
TransferredNonTransferrableInfo info)
1463-
: diagnosticEmitter(info) {}
1610+
RegionAnalysisValueMap &valueMap, TransferredNonTransferrableInfo info)
1611+
: valueMap(valueMap), diagnosticEmitter(info) {}
14641612

14651613
/// Gathers diagnostics. Returns false if we emitted a "I don't understand
14661614
/// error". If we emit such an error, we should bail without emitting any
@@ -1474,10 +1622,94 @@ class TransferNonTransferrableDiagnosticInferrer {
14741622
bool initForIsolatedPartialApply(
14751623
Operand *op, AbstractClosureExpr *ace,
14761624
std::optional<ActorIsolation> actualCallerIsolation = {});
1625+
1626+
bool initForSendingPartialApply(FullApplySite fas, Operand *pai);
1627+
1628+
std::optional<unsigned>
1629+
getIsolatedValuePartialApplyIndex(PartialApplyInst *pai,
1630+
SILValue isolatedValue) {
1631+
for (auto &paiOp : ApplySite(pai).getArgumentOperands()) {
1632+
if (valueMap.getTrackableValue(paiOp.get()).getRepresentative() ==
1633+
isolatedValue) {
1634+
// isolated_any causes all partial apply parameters to be shifted by 1
1635+
// due to the implicit isolated any parameter.
1636+
unsigned isIsolatedAny = pai->getFunctionType()->getIsolation() ==
1637+
SILFunctionTypeIsolation::Erased;
1638+
return ApplySite(pai).getAppliedArgIndex(paiOp) - isIsolatedAny;
1639+
}
1640+
}
1641+
1642+
return {};
1643+
}
14771644
};
14781645

14791646
} // namespace
14801647

1648+
bool TransferNonTransferrableDiagnosticInferrer::initForSendingPartialApply(
1649+
FullApplySite fas, Operand *paiOp) {
1650+
auto *pai =
1651+
dyn_cast<PartialApplyInst>(stripFunctionConversions(paiOp->get()));
1652+
if (!pai)
1653+
return false;
1654+
1655+
// For now we want this to be really narrow and to only apply to closure
1656+
// literals.
1657+
auto *ce = pai->getLoc().getAsASTNode<ClosureExpr>();
1658+
if (!ce)
1659+
return false;
1660+
1661+
// Ok, we now know we have a partial apply and it is a closure literal. Lets
1662+
// see if we can find the exact thing that caused the closure literal to be
1663+
// actor isolated.
1664+
auto isolationInfo = diagnosticEmitter.getIsolationRegionInfo();
1665+
if (isolationInfo->hasIsolatedValue()) {
1666+
// Now that we have the value, see if that value is one of our captured
1667+
// values.
1668+
auto isolatedValue = isolationInfo->getIsolatedValue();
1669+
auto matchingElt = getIsolatedValuePartialApplyIndex(pai, isolatedValue);
1670+
if (matchingElt) {
1671+
// Ok, we found the matching element. Lets emit our diagnostic!
1672+
auto capture = ce->getCaptureInfo().getCaptures()[*matchingElt];
1673+
diagnosticEmitter
1674+
.emitTypedSendingNeverSendableToSendingClosureParamDirectlyIsolated(
1675+
ce, capture);
1676+
return true;
1677+
}
1678+
}
1679+
1680+
// Ok, we are not tracking an actual isolated value or we do not capture the
1681+
// isolated value directly... we need to be smarter here. First lets gather up
1682+
// all non-Sendable values captured by the closure.
1683+
SmallVector<CapturedValue, 8> nonSendableCaptures;
1684+
for (auto capture : ce->getCaptureInfo().getCaptures()) {
1685+
auto *decl = capture.getDecl();
1686+
auto type = decl->getInterfaceType()->getCanonicalType();
1687+
auto silType = SILType::getPrimitiveObjectType(type);
1688+
if (!SILIsolationInfo::isNonSendableType(silType, pai->getFunction()))
1689+
continue;
1690+
1691+
auto *fromDC = decl->getInnermostDeclContext();
1692+
auto *nom = silType.getNominalOrBoundGenericNominal();
1693+
if (nom && fromDC) {
1694+
if (auto diagnosticBehavior =
1695+
getConcurrencyDiagnosticBehaviorLimit(nom, fromDC)) {
1696+
if (*diagnosticBehavior == DiagnosticBehavior::Ignore)
1697+
continue;
1698+
}
1699+
}
1700+
nonSendableCaptures.push_back(capture);
1701+
}
1702+
1703+
// If we do not have any non-Sendable captures... bail.
1704+
if (nonSendableCaptures.empty())
1705+
return false;
1706+
1707+
// Otherwise, emit the diagnostic.
1708+
diagnosticEmitter.emitTypedSendingNeverSendableToSendingClosureParam(
1709+
ce, nonSendableCaptures);
1710+
return true;
1711+
}
1712+
14811713
bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
14821714
Operand *op, AbstractClosureExpr *ace,
14831715
std::optional<ActorIsolation> actualCallerIsolation) {
@@ -1585,6 +1817,11 @@ bool TransferNonTransferrableDiagnosticInferrer::run() {
15851817
if (auto fas = FullApplySite::isa(op->getUser())) {
15861818
if (fas.getArgumentParameterInfo(*op).hasOption(
15871819
SILParameterInfo::Sending)) {
1820+
// Before we do anything, lets see if we are passing a sendable closure
1821+
// literal. If we do, we want to emit a special error that states which
1822+
// captured value caused the actual error.
1823+
if (initForSendingPartialApply(fas, op))
1824+
return true;
15881825

15891826
// See if we can infer a name from the value.
15901827
SmallString<64> resultingName;
@@ -1724,7 +1961,8 @@ void TransferNonSendableImpl::emitTransferredNonTransferrableDiagnostics() {
17241961
llvm::dbgs() << "Emitting transfer non transferrable diagnostics.\n");
17251962

17261963
for (auto info : transferredNonTransferrableInfoList) {
1727-
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(info);
1964+
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(
1965+
regionInfo->getValueMap(), info);
17281966
diagnosticInferrer.run();
17291967
}
17301968
}

test/Concurrency/concurrent_value_checking.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -413,20 +413,20 @@ extension NotConcurrent {
413413
func f() { }
414414

415415
func test() {
416-
Task { // expected-tns-warning {{task-isolated value of type '() async -> ()' passed as a strongly transferred parameter}}
417-
f()
416+
Task { // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
417+
f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
418418
}
419419

420-
Task { // expected-tns-warning {{task-isolated value of type '() async -> ()' passed as a strongly transferred parameter}}
421-
self.f()
420+
Task { // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
421+
self.f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
422422
}
423423

424-
Task { [self] in // expected-tns-warning {{task-isolated value of type '() async -> ()' passed as a strongly transferred parameter}}
425-
f()
424+
Task { [self] in // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
425+
f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
426426
}
427427

428-
Task { [self] in // expected-tns-warning {{task-isolated value of type '() async -> ()' passed as a strongly transferred parameter}}
429-
self.f()
428+
Task { [self] in // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
429+
self.f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
430430
}
431431
}
432432
}

test/Concurrency/isolated_parameters.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,9 @@ func testNonSendableCaptures(ns: NotSendable, a: isolated MyActor) {
501501

502502
// FIXME: The `a` in the capture list and `isolated a` are the same,
503503
// but the actor isolation checker doesn't know that.
504-
Task { [a] in // expected-tns-warning {{'a'-isolated value of type '() async -> ()' passed as a strongly transferred parameter}}
504+
Task { [a] in // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between 'a'-isolated code and concurrent execution of the closure}}
505505
_ = a
506-
_ = ns
506+
_ = ns // expected-tns-note {{closure captures 'a'-isolated 'ns'}}
507507
}
508508
}
509509

test/Concurrency/sendable_preconcurrency.swift

+6-3
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ struct MyType3 {
3131
}
3232

3333
func testA(ns: NS, mt: MyType, mt2: MyType2, mt3: MyType3, sc: StrictClass, nsc: NonStrictClass) async {
34-
// This is task isolated since we are capturing function arguments.
35-
Task { // expected-tns-warning {{task-isolated value of type '() async -> ()' passed as a strongly transferred parameter}}
34+
// This is task isolated since we are capturing function arguments... but
35+
// since we are merging NonStrictClass from a preconcurrency module, the whole
36+
// error is squelched since we allow for preconcurrency to apply to the entire
37+
// capture list.
38+
Task {
3639
print(ns)
37-
print(mt) // no warning: MyType is Sendable because we suppressed NonStrictClass's warning
40+
print(mt)
3841
print(mt2)
3942
print(mt3)
4043
print(sc)

0 commit comments

Comments
 (0)