Skip to content

Commit 58dbf60

Browse files
authored
Merge pull request #36441 from slavapestov/ban-defer-forward-reference
Sema: Ban forward references of captured values from 'defer' body
2 parents 97ed11f + b6381d4 commit 58dbf60

File tree

7 files changed

+200
-181
lines changed

7 files changed

+200
-181
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
@@ -352,6 +352,34 @@ static bool isMemberChainTail(Expr *expr, Expr *parent) {
352352
return parent == nullptr || !isMemberChainMember(parent);
353353
}
354354

355+
static bool isValidForwardReference(ValueDecl *D, DeclContext *DC,
356+
ValueDecl **localDeclAfterUse) {
357+
*localDeclAfterUse = nullptr;
358+
359+
// References to variables injected by lldb are always valid.
360+
if (isa<VarDecl>(D) && cast<VarDecl>(D)->isDebuggerVar())
361+
return true;
362+
363+
// If we find something in the current context, it must be a forward
364+
// reference, because otherwise if it was in scope, it would have
365+
// been returned by the call to ASTScope::lookupLocalDecls() above.
366+
if (D->getDeclContext()->isLocalContext()) {
367+
do {
368+
if (D->getDeclContext() == DC) {
369+
*localDeclAfterUse = D;
370+
return false;
371+
}
372+
373+
// If we're inside of a 'defer' context, walk up to the parent
374+
// and check again. We don't want 'defer' bodies to forward
375+
// reference bindings in the immediate outer scope.
376+
} while (isa<FuncDecl>(DC) &&
377+
cast<FuncDecl>(DC)->isDeferBody() &&
378+
(DC = DC->getParent()));
379+
}
380+
return true;
381+
}
382+
355383
/// Bind an UnresolvedDeclRefExpr by performing name lookup and
356384
/// returning the resultant expression. Context is the DeclContext used
357385
/// for the lookup.
@@ -422,24 +450,12 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
422450
Lookup = TypeChecker::lookupUnqualified(DC, LookupName, Loc, lookupOptions);
423451

424452
ValueDecl *localDeclAfterUse = nullptr;
425-
auto isValid = [&](ValueDecl *D) {
426-
// References to variables injected by lldb are always valid.
427-
if (isa<VarDecl>(D) && cast<VarDecl>(D)->isDebuggerVar())
428-
return true;
429-
430-
// If we find something in the current context, it must be a forward
431-
// reference, because otherwise if it was in scope, it would have
432-
// been returned by the call to ASTScope::lookupLocalDecls() above.
433-
if (D->getDeclContext()->isLocalContext() &&
434-
D->getDeclContext() == DC) {
435-
localDeclAfterUse = D;
436-
return false;
437-
}
438-
return true;
439-
};
440453
AllDeclRefs =
441454
findNonMembers(Lookup.innerResults(), UDRE->getRefKind(),
442-
/*breakOnMember=*/true, ResultValues, isValid);
455+
/*breakOnMember=*/true, ResultValues,
456+
[&](ValueDecl *D) {
457+
return isValidForwardReference(D, DC, &localDeclAfterUse);
458+
});
443459

444460
// If local declaration after use is found, check outer results for
445461
// better matching candidates.
@@ -459,7 +475,10 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
459475
localDeclAfterUse = nullptr;
460476
AllDeclRefs =
461477
findNonMembers(Lookup.innerResults(), UDRE->getRefKind(),
462-
/*breakOnMember=*/true, ResultValues, isValid);
478+
/*breakOnMember=*/true, ResultValues,
479+
[&](ValueDecl *D) {
480+
return isValidForwardReference(D, DC, &localDeclAfterUse);
481+
});
463482
}
464483
}
465484
}

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: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
func voidReturn1() {}
4+
func breakContinue(_: Int) -> Int {}
5+
6+
func testDefer(_ a : Int) {
7+
8+
defer { voidReturn1() }
9+
defer { breakContinue(1)+42 } // expected-warning {{result of operator '+' is unused}}
10+
11+
// Ok:
12+
defer { while false { break } }
13+
14+
// Not ok.
15+
while false { defer { break } } // expected-error {{'break' cannot transfer control out of a defer statement}}
16+
// expected-warning@-1 {{'defer' statement at end of scope always executes immediately}}{{17-22=do}}
17+
defer { return } // expected-error {{'return' cannot transfer control out of a defer statement}}
18+
// expected-warning@-1 {{'defer' statement at end of scope always executes immediately}}{{3-8=do}}
19+
}
20+
21+
class SomeTestClass {
22+
var x = 42
23+
24+
func method() {
25+
defer { x = 97 } // self. not required here!
26+
// expected-warning@-1 {{'defer' statement at end of scope always executes immediately}}{{5-10=do}}
27+
}
28+
}
29+
30+
enum DeferThrowError: Error {
31+
case someError
32+
}
33+
34+
func throwInDefer() {
35+
defer { throw DeferThrowError.someError } // expected-error {{errors cannot be thrown out of a defer body}}
36+
print("Foo")
37+
}
38+
39+
func throwInDeferOK1() {
40+
defer {
41+
do {
42+
throw DeferThrowError.someError
43+
} catch {}
44+
}
45+
print("Bar")
46+
}
47+
48+
func throwInDeferOK2() throws {
49+
defer {
50+
do {
51+
throw DeferThrowError.someError
52+
} catch {}
53+
}
54+
print("Bar")
55+
}
56+
57+
func throwingFuncInDefer1() throws {
58+
defer { try throwingFunctionCalledInDefer() } // expected-error {{errors cannot be thrown out of a defer body}}
59+
print("Bar")
60+
}
61+
62+
func throwingFuncInDefer1a() throws {
63+
defer {
64+
do {
65+
try throwingFunctionCalledInDefer()
66+
} catch {}
67+
}
68+
print("Bar")
69+
}
70+
71+
func throwingFuncInDefer2() throws {
72+
defer { throwingFunctionCalledInDefer() } // expected-error {{errors cannot be thrown out of a defer body}}
73+
print("Bar")
74+
}
75+
76+
func throwingFuncInDefer2a() throws {
77+
defer {
78+
do {
79+
throwingFunctionCalledInDefer()
80+
// expected-error@-1 {{call can throw but is not marked with 'try'}}
81+
// expected-note@-2 {{did you mean to use 'try'?}}
82+
// expected-note@-3 {{did you mean to handle error as optional value?}}
83+
// expected-note@-4 {{did you mean to disable error propagation?}}
84+
} catch {}
85+
}
86+
print("Bar")
87+
}
88+
89+
func throwingFuncInDefer3() {
90+
defer { try throwingFunctionCalledInDefer() } // expected-error {{errors cannot be thrown out of a defer body}}
91+
print("Bar")
92+
}
93+
94+
func throwingFuncInDefer3a() {
95+
defer {
96+
do {
97+
try throwingFunctionCalledInDefer()
98+
} catch {}
99+
}
100+
print("Bar")
101+
}
102+
103+
func throwingFuncInDefer4() {
104+
defer { throwingFunctionCalledInDefer() } // expected-error {{errors cannot be thrown out of a defer body}}
105+
print("Bar")
106+
}
107+
108+
func throwingFuncInDefer4a() {
109+
defer {
110+
do {
111+
throwingFunctionCalledInDefer()
112+
// expected-error@-1 {{call can throw but is not marked with 'try'}}
113+
// expected-note@-2 {{did you mean to use 'try'?}}
114+
// expected-note@-3 {{did you mean to handle error as optional value?}}
115+
// expected-note@-4 {{did you mean to disable error propagation?}}
116+
} catch {}
117+
}
118+
print("Bar")
119+
}
120+
121+
func throwingFunctionCalledInDefer() throws {
122+
throw DeferThrowError.someError
123+
}
124+
125+
class SomeDerivedClass: SomeTestClass {
126+
override init() {
127+
defer {
128+
super.init() // expected-error {{initializer chaining ('super.init') cannot be nested in another expression}}
129+
}
130+
}
131+
}
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)