Skip to content

Commit b0dc18d

Browse files
committed
Fix an assert in exclusivity diagnostics when inouts escape.
An error found in DiagnoseInvalidEscapingCaptures can indicate invalid SIL, which will cause DiagnoseStaticExclusivity to assert during SIL verification. When the source-level closure captures an inout argument, it appears in SIL to be a non-escaping closure. The SIL verification then fails because the "nonescaping" closure actually escapes. Ensure that capture diagnostics run on closures before exclusivity enforcement runs on the parent function. Bypass the SIL verification assert if a diagnostic error was found. Fixes rdar://75364904 (Crash with assertion `noescape partial_apply has unexpected use`)
1 parent 617a027 commit b0dc18d

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

lib/SILOptimizer/Analysis/AccessSummaryAnalysis.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,22 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
143143
/// used by directly calling it or passing it as argument, but not using it as a
144144
/// partial_apply callee.
145145
///
146-
/// FIXME: This needs to be checked in the SILVerifier.
146+
/// An error found in DiagnoseInvalidEscapingCaptures can indicate invalid SIL
147+
/// that is detected here but not in normal SIL verification. When the
148+
/// source-level closure captures an inout argument, it appears in SIL to be a
149+
/// non-escaping closure. The following verification then fails because the
150+
/// "nonescaping" closure actually escapes.
151+
///
152+
/// FIXME: This should be checked in the SILVerifier, with consideration for the
153+
/// caveat above where an inout has been captured be an escaping closure.
147154
static bool hasExpectedUsesOfNoEscapePartialApply(Operand *partialApplyUse) {
148155
SILInstruction *user = partialApplyUse->getUser();
149156

157+
// Bypass this verification when a diagnostic error is present. See comments
158+
// on DiagnoseInvalidEscapingCaptures above.
159+
if (user->getModule().getASTContext().hadError())
160+
return true;
161+
150162
// It is fine to call the partial apply
151163
switch (user->getKind()) {
152164
case SILInstructionKind::ApplyInst:

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,25 +1054,39 @@ static void checkAccessedAddress(Operand *memOper, StorageMap &Accesses) {
10541054

10551055
namespace {
10561056

1057-
class DiagnoseStaticExclusivity : public SILFunctionTransform {
1057+
/// TODO: This is currently a module transform to ensure that source-level
1058+
/// diagnostics, like DiagnoseInvalidCaptures run on closures (in addition to
1059+
/// other callees) before this pass processes their parent functions. Otherwise,
1060+
/// AccessSummaryAnalysis may crash on invalid SIL. Fix the pass manager to
1061+
/// ensure that closures are always diagnosed before their parent. Then add an
1062+
/// SCC transform to ensure that the previous diagnostic pass runs on all
1063+
/// functions in the SCC before the next diagnostic pass. This will handle
1064+
/// closures that call back into their parent. Then this can be converted to an
1065+
/// SCC transform.
1066+
class DiagnoseStaticExclusivity : public SILModuleTransform {
10581067
public:
10591068
DiagnoseStaticExclusivity() {}
10601069

10611070
private:
10621071
void run() override {
1063-
// Don't rerun diagnostics on deserialized functions.
1064-
if (getFunction()->wasDeserializedCanonical())
1065-
return;
1072+
for (auto &function : *getModule()) {
1073+
if (!function.isDefinition())
1074+
continue;
10661075

1067-
SILFunction *Fn = getFunction();
1068-
// This is a staging flag. Eventually the ability to turn off static
1069-
// enforcement will be removed.
1070-
if (!Fn->getModule().getOptions().EnforceExclusivityStatic)
1071-
return;
1076+
// Don't rerun diagnostics on deserialized functions.
1077+
if (function.wasDeserializedCanonical())
1078+
continue;
10721079

1073-
PostOrderFunctionInfo *PO = getAnalysis<PostOrderAnalysis>()->get(Fn);
1074-
auto *ASA = getAnalysis<AccessSummaryAnalysis>();
1075-
checkStaticExclusivity(*Fn, PO, ASA);
1080+
// This is a staging flag. Eventually the ability to turn off static
1081+
// enforcement will be removed.
1082+
if (!function.getModule().getOptions().EnforceExclusivityStatic)
1083+
continue;
1084+
1085+
PostOrderFunctionInfo *PO =
1086+
getAnalysis<PostOrderAnalysis>()->get(&function);
1087+
auto *ASA = getAnalysis<AccessSummaryAnalysis>();
1088+
checkStaticExclusivity(function, PO, ASA);
1089+
}
10761090
}
10771091
};
10781092

test/SILOptimizer/invalid_escaping_captures.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,23 @@ func badNoEscapeCaptureThroughVar(_ fn: () -> ()) {
255255
takesEscaping { // expected-error {{escaping closure captures non-escaping value}}
256256
myFunc()
257257
}
258-
}
258+
}
259+
260+
@inline(never)
261+
func takeNoEscapeReturnGetter(f: ()->()->Int64) -> ()->Int64 { return f() }
262+
263+
// Test that invalid escaping capture diagnostics are run on nested
264+
// closures before exclusivity diagnostics are run. Exclusivity
265+
// diagnostics need to inspect all referenced closures that capture
266+
// inouts. Also ensure that exclusivity diagnostics does not crash
267+
// when verifying that a closure that captures inout does not escape
268+
// as long as a previous diagnostic error is present.
269+
struct TestInoutEscapeInClosure {
270+
var someValue: Int64 = 0
271+
mutating func testInoutEscapeInClosure() -> () -> Int64 {
272+
return takeNoEscapeReturnGetter {
273+
return { return someValue } // expected-error {{escaping closure captures mutating 'self' parameter}}
274+
// expected-note@-1 {{captured here}}
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)