Skip to content

Commit b6381d4

Browse files
committed
Sema: Ban forward references of captured values from 'defer' body
While 'defer' is implemented as a local function, it doesn't behave as one. In particular, since SILGen runs it after destroying all local bindings that appear after the 'defer' definition, the body of a 'defer' cannot forward reference captured bindings the way that local functions can. Note that I had to remove a SILGen test case for an older, related issue. The new diagnostic in Sema catches these cases earlier. Fixes rdar://problem/75088379.
1 parent 4c7f0fd commit b6381d4

File tree

6 files changed

+69
-51
lines changed

6 files changed

+69
-51
lines changed

include/swift/AST/AnyFunctionRef.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,6 @@ class AnyFunctionRef {
179179
return TheFunction.dyn_cast<AbstractClosureExpr*>();
180180
}
181181

182-
bool isDeferBody() const {
183-
if (auto *fd = dyn_cast_or_null<FuncDecl>(getAbstractFunctionDecl()))
184-
return fd->isDeferBody();
185-
return false;
186-
}
187-
188182
/// Return true if this closure is passed as an argument to a function and is
189183
/// known not to escape from that function. In this case, captures can be
190184
/// more efficient.

include/swift/AST/DiagnosticsSIL.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ ERROR(objc_selector_malformed,none,"the type ObjectiveC.Selector is malformed",
103103
// Capture before declaration diagnostics.
104104
ERROR(capture_before_declaration,none,
105105
"closure captures %0 before it is declared", (Identifier))
106-
ERROR(capture_before_declaration_defer,none,
107-
"'defer' block captures %0 before it is declared", (Identifier))
108106
NOTE(captured_value_declared_here,none,
109107
"captured value declared here", ())
110108

lib/SILGen/SILGenFunction.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,22 +245,16 @@ void SILGenFunction::emitCaptures(SILLocation loc,
245245
auto &Diags = getASTContext().Diags;
246246

247247
SourceLoc loc;
248-
bool isDeferBody;
249248
if (closure.kind == SILDeclRef::Kind::DefaultArgGenerator) {
250249
auto *param = getParameterAt(closure.getDecl(),
251250
closure.defaultArgIndex);
252251
loc = param->getLoc();
253-
isDeferBody = false;
254252
} else {
255253
auto f = *closure.getAnyFunctionRef();
256254
loc = f.getLoc();
257-
isDeferBody = f.isDeferBody();
258255
}
259256

260-
Diags.diagnose(loc,
261-
isDeferBody
262-
? diag::capture_before_declaration_defer
263-
: diag::capture_before_declaration,
257+
Diags.diagnose(loc, diag::capture_before_declaration,
264258
vd->getBaseIdentifier());
265259
Diags.diagnose(vd->getLoc(), diag::captured_value_declared_here);
266260
Diags.diagnose(capture.getLoc(), diag::value_captured_here);

lib/Sema/PreCheckExpr.cpp

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,34 @@ static bool isMemberChainTail(Expr *expr, Expr *parent) {
328328
return parent == nullptr || !isMemberChainMember(parent);
329329
}
330330

331+
static bool isValidForwardReference(ValueDecl *D, DeclContext *DC,
332+
ValueDecl **localDeclAfterUse) {
333+
*localDeclAfterUse = nullptr;
334+
335+
// References to variables injected by lldb are always valid.
336+
if (isa<VarDecl>(D) && cast<VarDecl>(D)->isDebuggerVar())
337+
return true;
338+
339+
// If we find something in the current context, it must be a forward
340+
// reference, because otherwise if it was in scope, it would have
341+
// been returned by the call to ASTScope::lookupLocalDecls() above.
342+
if (D->getDeclContext()->isLocalContext()) {
343+
do {
344+
if (D->getDeclContext() == DC) {
345+
*localDeclAfterUse = D;
346+
return false;
347+
}
348+
349+
// If we're inside of a 'defer' context, walk up to the parent
350+
// and check again. We don't want 'defer' bodies to forward
351+
// reference bindings in the immediate outer scope.
352+
} while (isa<FuncDecl>(DC) &&
353+
cast<FuncDecl>(DC)->isDeferBody() &&
354+
(DC = DC->getParent()));
355+
}
356+
return true;
357+
}
358+
331359
/// Bind an UnresolvedDeclRefExpr by performing name lookup and
332360
/// returning the resultant expression. Context is the DeclContext used
333361
/// for the lookup.
@@ -398,24 +426,12 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
398426
Lookup = TypeChecker::lookupUnqualified(DC, LookupName, Loc, lookupOptions);
399427

400428
ValueDecl *localDeclAfterUse = nullptr;
401-
auto isValid = [&](ValueDecl *D) {
402-
// References to variables injected by lldb are always valid.
403-
if (isa<VarDecl>(D) && cast<VarDecl>(D)->isDebuggerVar())
404-
return true;
405-
406-
// If we find something in the current context, it must be a forward
407-
// reference, because otherwise if it was in scope, it would have
408-
// been returned by the call to ASTScope::lookupLocalDecls() above.
409-
if (D->getDeclContext()->isLocalContext() &&
410-
D->getDeclContext() == DC) {
411-
localDeclAfterUse = D;
412-
return false;
413-
}
414-
return true;
415-
};
416429
AllDeclRefs =
417430
findNonMembers(Lookup.innerResults(), UDRE->getRefKind(),
418-
/*breakOnMember=*/true, ResultValues, isValid);
431+
/*breakOnMember=*/true, ResultValues,
432+
[&](ValueDecl *D) {
433+
return isValidForwardReference(D, DC, &localDeclAfterUse);
434+
});
419435

420436
// If local declaration after use is found, check outer results for
421437
// better matching candidates.
@@ -435,7 +451,10 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
435451
localDeclAfterUse = nullptr;
436452
AllDeclRefs =
437453
findNonMembers(Lookup.innerResults(), UDRE->getRefKind(),
438-
/*breakOnMember=*/true, ResultValues, isValid);
454+
/*breakOnMember=*/true, ResultValues,
455+
[&](ValueDecl *D) {
456+
return isValidForwardReference(D, DC, &localDeclAfterUse);
457+
});
439458
}
440459
}
441460
}

test/SILGen/capture_order.swift

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -119,25 +119,6 @@ func captureInClosure() {
119119

120120
/// Regression tests
121121

122-
func sr3210_crash() {
123-
defer { // expected-error {{'defer' block captures 'b' before it is declared}}
124-
print("\(b)") // expected-note {{captured here}}
125-
}
126-
127-
return
128-
129-
let b = 2 // expected-note {{captured value declared here}}
130-
// expected-warning@-1 {{code after 'return' will never be executed}}
131-
}
132-
133-
func sr3210() {
134-
defer {
135-
print("\(b)")
136-
}
137-
138-
let b = 2
139-
}
140-
141122
class SR4812 {
142123
public func foo() {
143124
let bar = { [weak self] in

test/stmt/defer.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,35 @@ class SomeDerivedClass: SomeTestClass {
129129
}
130130
}
131131
}
132+
133+
// rdar://75088379 -- 'defer' should not be able to forward-reference captures
134+
func badForwardReference() {
135+
defer {
136+
_ = x2 // expected-error {{use of local variable 'x2' before its declaration}}
137+
138+
let x1 = 0
139+
let y1 = 0
140+
141+
defer {
142+
_ = x1
143+
_ = x2 // expected-error {{use of local variable 'x2' before its declaration}}
144+
_ = y1
145+
_ = y2 // expected-error {{use of local variable 'y2' before its declaration}}
146+
}
147+
148+
let y2 = 0 // expected-note {{'y2' declared here}}
149+
}
150+
151+
let x2 = 0 // expected-note 2{{'x2' declared here}}
152+
153+
func localFunc() {
154+
defer {
155+
_ = z1 // expected-error {{use of local variable 'z1' before its declaration}}
156+
_ = z2
157+
}
158+
159+
let z1 = 0 // expected-note {{'z1' declared here}}
160+
}
161+
162+
let z2 = 0
163+
}

0 commit comments

Comments
 (0)