Skip to content

Commit d82b88c

Browse files
authored
Merge pull request #23907 from slavapestov/noescape-parameter-call-restriction
Re-implement "no-escape parameter call restriction" as SIL pass
2 parents 5577ec9 + e2cb057 commit d82b88c

File tree

7 files changed

+115
-145
lines changed

7 files changed

+115
-145
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ NOTE(value_captured_here,none,"captured here", ())
137137
NOTE(value_captured_transitively,none,
138138
"captured indirectly by this call", ())
139139

140+
ERROR(err_noescape_param_call,none,
141+
"passing a %select{|closure which captures a }1non-escaping function "
142+
"parameter %0 to a call to a non-escaping function parameter can allow "
143+
"re-entrant modification of a variable",
144+
(DeclName, unsigned))
145+
140146
// Definite initialization diagnostics.
141147
NOTE(variable_defined_here,none,
142148
"%select{variable|constant}0 defined here", (bool))

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,6 @@ ERROR(incorrect_explicit_closure_result,none,
289289
"declared closure result %0 is incompatible with contextual type %1",
290290
(Type, Type))
291291

292-
ERROR(err_noescape_param_call,none,
293-
"passing a %select{|closure which captures a }1non-escaping function "
294-
"parameter %0 to a call to a non-escaping function parameter can allow "
295-
"re-entrant modification of a variable",
296-
(DeclName, unsigned))
297292
ERROR(cannot_call_function_value,none,
298293
"cannot invoke value of function type with argument list '%0'",
299294
(StringRef))

lib/SILOptimizer/Mandatory/DiagnoseInvalidEscapingCaptures.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,72 @@ static void checkPartialApply(ASTContext &Context, DeclContext *DC,
329329
}
330330
}
331331

332+
static void checkApply(ASTContext &Context, FullApplySite site) {
333+
auto isNoEscapeParam = [&](SILValue value) -> const ParamDecl * {
334+
// If the value is an escaping, do not enforce any restrictions.
335+
if (!value->getType().getASTType()->isNoEscape())
336+
return nullptr;
337+
338+
// If the value is not a function parameter, do not enforce any restrictions.
339+
return getParamDeclFromOperand(value);
340+
};
341+
342+
// If the callee is not a no-escape parameter, there is nothing to check.
343+
auto callee = site.getCalleeOrigin();
344+
if (!isNoEscapeParam(callee))
345+
return;
346+
347+
// See if any of our arguments are noescape parameters, or closures capturing
348+
// noescape parameters.
349+
SmallVector<std::pair<SILValue, bool>, 4> args;
350+
llvm::SmallDenseSet<SILValue, 4> visited;
351+
auto arglistInsert = [&](SILValue arg, bool capture) {
352+
if (visited.insert(arg).second)
353+
args.emplace_back(arg, capture);
354+
};
355+
356+
for (auto arg : site.getArguments())
357+
arglistInsert(arg, /*capture=*/false);
358+
359+
while (!args.empty()) {
360+
auto pair = args.pop_back_val();
361+
auto arg = pair.first;
362+
bool capture = pair.second;
363+
364+
if (auto *CI = dyn_cast<ConversionInst>(arg)) {
365+
arglistInsert(CI->getConverted(), /*capture=*/false);
366+
continue;
367+
}
368+
369+
// If one of our call arguments is a noescape parameter, diagnose the
370+
// violation.
371+
if (auto *param = isNoEscapeParam(arg)) {
372+
diagnose(Context, site.getLoc(), diag::err_noescape_param_call,
373+
param->getName(), capture);
374+
return;
375+
}
376+
377+
// If one of our call arguments is a closure, recursively visit all of
378+
// the closure's captures.
379+
if (auto *PAI = dyn_cast<PartialApplyInst>(arg)) {
380+
ApplySite site(PAI);
381+
for (auto arg : site.getArguments())
382+
arglistInsert(arg, /*capture=*/true);
383+
continue;
384+
}
385+
}
386+
}
387+
332388
static void checkForViolationsAtInstruction(ASTContext &Context,
333389
DeclContext *DC,
334390
SILInstruction *I) {
335391
if (auto *PAI = dyn_cast<PartialApplyInst>(I))
336392
checkPartialApply(Context, DC, PAI);
393+
394+
if (isa<ApplyInst>(I) || isa<TryApplyInst>(I)) {
395+
FullApplySite site(I);
396+
checkApply(Context, site);
397+
}
337398
}
338399

339400
static void checkEscapingCaptures(SILFunction *F) {

lib/Sema/MiscDiagnostics.cpp

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,6 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
179179

180180
ConcreteDeclRef callee;
181181
if (auto *calleeDRE = dyn_cast<DeclRefExpr>(base)) {
182-
// This only cares about declarations of noescape function type.
183-
auto AFT = calleeDRE->getType()->getAs<FunctionType>();
184-
if (AFT && AFT->isNoEscape())
185-
checkNoEscapeParameterCall(Call);
186182
checkForSuspiciousBitCasts(calleeDRE, Call);
187183
callee = calleeDRE->getDeclRef();
188184

@@ -416,104 +412,6 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
416412
TC.diagnose(E->getStartLoc(), diag::value_of_module_type);
417413
}
418414

419-
class NoEscapeArgument {
420-
llvm::PointerIntPair<ParamDecl*, 1, bool> ParamAndIsCapture;
421-
public:
422-
NoEscapeArgument() {}
423-
NoEscapeArgument(ParamDecl *param, bool isCapture)
424-
: ParamAndIsCapture(param, isCapture) {
425-
assert(param);
426-
}
427-
428-
explicit operator bool() const {
429-
return ParamAndIsCapture.getPointer() != nullptr;
430-
}
431-
432-
ParamDecl *getDecl() const { return ParamAndIsCapture.getPointer(); }
433-
bool isDeclACapture() const { return ParamAndIsCapture.getInt(); }
434-
435-
static NoEscapeArgument find(TypeChecker &tc, ValueDecl *decl,
436-
bool isCapture) {
437-
if (auto param = dyn_cast<ParamDecl>(decl)) {
438-
if (auto fnType =
439-
param->getInterfaceType()->getAs<AnyFunctionType>()) {
440-
if (fnType->isNoEscape())
441-
return { param, isCapture };
442-
}
443-
return {};
444-
}
445-
446-
if (auto fn = dyn_cast<AbstractFunctionDecl>(decl)) {
447-
if (fn->getDeclContext()->isLocalContext()) {
448-
return findInCaptures(tc, fn);
449-
}
450-
return {};
451-
}
452-
453-
// FIXME: captures of computed local vars? Can these be non-escaping?
454-
return {};
455-
}
456-
457-
static NoEscapeArgument findInCaptures(TypeChecker &tc,
458-
AnyFunctionRef fn) {
459-
// Ensure we have accurate capture information for the function.
460-
tc.computeCaptures(fn);
461-
462-
for (const auto &capture : fn.getCaptureInfo().getCaptures()) {
463-
if (capture.isDynamicSelfMetadata()) continue;
464-
if (auto param = find(tc, capture.getDecl(), true))
465-
return param;
466-
}
467-
return {};
468-
}
469-
};
470-
471-
/// Enforce the exclusivity rule against calling a non-escaping
472-
/// function parameter with another non-escaping function parameter
473-
/// as an argument.
474-
void checkNoEscapeParameterCall(ApplyExpr *apply) {
475-
NoEscapeArgument noescapeArgument;
476-
Expr *problematicArg = nullptr;
477-
478-
visitArguments(apply, [&](unsigned argIndex, Expr *arg) {
479-
// Just find the first problematic argument.
480-
if (noescapeArgument) return;
481-
482-
// Remember the expression which used the argument.
483-
problematicArg = arg;
484-
485-
// Look through the same set of nodes that we look through when
486-
// checking for no-escape functions.
487-
arg = lookThroughArgument(arg);
488-
489-
// If the argument isn't noescape, ignore it.
490-
auto fnType = arg->getType()->getAs<AnyFunctionType>();
491-
if (!fnType || !fnType->isNoEscape())
492-
return;
493-
494-
// Okay, it should be a closure or a decl ref.
495-
if (auto declRef = dyn_cast<DeclRefExpr>(arg)) {
496-
noescapeArgument =
497-
NoEscapeArgument::find(TC, declRef->getDecl(), false);
498-
} else if (auto closure = dyn_cast<AbstractClosureExpr>(arg)) {
499-
noescapeArgument =
500-
NoEscapeArgument::findInCaptures(TC, closure);
501-
} else {
502-
// This can happen with withoutActuallyEscaping.
503-
assert(isa<OpaqueValueExpr>(arg) &&
504-
"unexpected expression yielding noescape closure");
505-
}
506-
});
507-
508-
if (!noescapeArgument) return;
509-
510-
TC.diagnose(apply->getLoc(),
511-
diag::err_noescape_param_call,
512-
noescapeArgument.getDecl()->getName(),
513-
noescapeArgument.isDeclACapture())
514-
.highlight(problematicArg->getSourceRange());
515-
}
516-
517415
// Diagnose metatype values that don't appear as part of a property,
518416
// method, or constructor reference.
519417
void checkUseOfMetaTypeName(Expr *E) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-swift-frontend -emit-sil %s -verify
2+
3+
func test0(a: (() -> ()) -> (), b: () -> ()) {
4+
a(b) // expected-error {{passing a non-escaping function parameter 'b' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
5+
}
6+
7+
func test0(fn: (() -> ()) -> ()) {
8+
fn { fn {} } // expected-error {{passing a closure which captures a non-escaping function parameter 'fn' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
9+
}
10+
11+
func test1(fn: (() -> ()) -> ()) {
12+
func foo() {
13+
fn { fn {} } // expected-error {{passing a closure which captures a non-escaping function parameter 'fn' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
14+
}
15+
}
16+
17+
func test2(x: inout Int, fn: (() -> ()) -> ()) {
18+
func foo(myfn: () -> ()) {
19+
x += 1
20+
myfn()
21+
}
22+
23+
// Make sure we only complain about calls to noescape parameters.
24+
foo { fn {} }
25+
}
26+
27+
func test3(fn: (() -> ()) -> ()) {
28+
{ myfn in myfn { fn {} } }(fn) // expected-error {{passing a closure which captures a non-escaping function parameter 'fn' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
29+
}
30+
31+
func test4(fn: (() -> ()) -> ()) {
32+
func foo() {
33+
fn {}
34+
}
35+
36+
fn(foo) // expected-error {{passing a closure which captures a non-escaping function parameter 'fn' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
37+
}
38+
39+
// rdar://problem/34496304
40+
func test5(outer: (() throws -> Int) throws -> Int) throws -> Int {
41+
func descend(_ inner: (() throws -> Int) throws -> Int) throws -> Int {
42+
return try inner { // expected-error {{passing a closure which captures a non-escaping function parameter 'inner' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
43+
try descend(inner)
44+
}
45+
}
46+
47+
return try descend(outer)
48+
}

test/attr/attr_noescape.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,6 @@ func doThing4(_ completion: @escaping CompletionHandler) {
303303
func apply<T, U>(_ f: @noescape (T) -> U, g: @noescape (@noescape (T) -> U) -> U) -> U {
304304
// expected-error@-1 6{{unknown attribute 'noescape'}}
305305
return g(f)
306-
// expected-error@-1 {{passing a non-escaping function parameter 'f' to a call to a non-escaping function parameter can allow re-entrant modification of a variable}}
307306
}
308307

309308
// <rdar://problem/19997577> @noescape cannot be applied to locals, leading to duplication of code

test/expr/postfix/call/noescape-param-exclusivity.swift

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)