Skip to content

Commit 895c1f0

Browse files
committed
[DefiniteInitialization] Check whether globals captured by top-level
defer statements are initialized. <rdar://30720636>
1 parent 8fec025 commit 895c1f0

File tree

5 files changed

+133
-7
lines changed

5 files changed

+133
-7
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ ERROR(variable_inout_before_initialized,none,
137137
ERROR(variable_closure_use_uninit,none,
138138
"%select{variable|constant}1 '%0' captured by a closure before being"
139139
" initialized", (StringRef, bool))
140+
ERROR(variable_defer_use_uninit,none,
141+
"%select{variable|constant}1 '%0' used in defer before being"
142+
" initialized", (StringRef, bool))
140143
ERROR(self_closure_use_uninit,none,
141144
"'self' captured by a closure before all members were initialized", ())
142145

lib/SILGen/SILGenStmt.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,15 +467,22 @@ namespace {
467467

468468
void StmtEmitter::visitDeferStmt(DeferStmt *S) {
469469
// Emit the closure for the defer, along with its binding.
470-
SGF.visitFuncDecl(S->getTempDecl());
470+
// If the defer is at the top-level code, insert 'mark_escape_inst'
471+
// to the top-level code to check initialization of any captured globals.
472+
FuncDecl *deferDecl = S->getTempDecl();
473+
auto declCtxKind = deferDecl->getDeclContext()->getContextKind();
474+
auto &sgm = SGF.SGM;
475+
if (declCtxKind == DeclContextKind::TopLevelCodeDecl && sgm.TopLevelSGF &&
476+
sgm.TopLevelSGF->B.hasValidInsertionPoint()) {
477+
sgm.emitMarkFunctionEscapeForTopLevelCodeGlobals(
478+
S, deferDecl->getCaptureInfo());
479+
}
480+
SGF.visitFuncDecl(deferDecl);
471481

472482
// Register a cleanup to invoke the closure on any exit paths.
473483
SGF.Cleanups.pushCleanup<DeferCleanup>(S->getDeferLoc(), S->getCallExpr());
474484
}
475485

476-
477-
478-
479486
void StmtEmitter::visitIfStmt(IfStmt *S) {
480487
Scope condBufferScope(SGF.Cleanups, S);
481488

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "swift/AST/DiagnosticEngine.h"
1717
#include "swift/AST/DiagnosticsSIL.h"
1818
#include "swift/AST/Expr.h"
19+
#include "swift/AST/Stmt.h"
1920
#include "swift/ClangImporter/ClangModule.h"
2021
#include "swift/SIL/InstructionUtils.h"
2122
#include "swift/SIL/SILArgument.h"
@@ -1239,14 +1240,32 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
12391240
noteUninitializedMembers(Use);
12401241
return;
12411242
}
1242-
12431243

1244+
// Extract the reason why this escape-use instruction exists and present
1245+
// diagnostics. While an escape-use instruction generally corresponds to a
1246+
// capture by a closure, there are the following special cases to consider:
1247+
//
1248+
// (a) A MarkFunctionEscapeInst with an operand say %var. This is introduced
1249+
// by the SILGen phase when %var is the address of a global variable that
1250+
// escapes because it is used by a closure or a defer statement or a function
1251+
// definition appearing at the top-level. The specific reason why %var escapes
1252+
// is recorded in MarkFunctionEscapeInst by making its SIL Location refer to
1253+
// the AST of the construct that uses the global variable (namely, a closure
1254+
// or a defer statement or a function definition). So, if %var is
1255+
// uninitialized at MarkFunctionEscapeInst, extract and report the reason
1256+
// why the variable escapes in the error message.
1257+
//
1258+
// (b) An UncheckedTakeEnumDataAddrInst takes the address of the data of
1259+
// an optional and is introduced as an intermediate step in optional chaining.
12441260
Diag<StringRef, bool> DiagMessage;
12451261
if (isa<MarkFunctionEscapeInst>(Inst)) {
1246-
if (Inst->getLoc().isASTNode<AbstractClosureExpr>())
1262+
if (Inst->getLoc().isASTNode<AbstractClosureExpr>()) {
12471263
DiagMessage = diag::variable_closure_use_uninit;
1248-
else
1264+
} else if (Inst->getLoc().isASTNode<DeferStmt>()) {
1265+
DiagMessage = diag::variable_defer_use_uninit;
1266+
} else {
12491267
DiagMessage = diag::variable_function_use_uninit;
1268+
}
12501269
} else if (isa<UncheckedTakeEnumDataAddrInst>(Inst)) {
12511270
DiagMessage = diag::variable_used_before_initialized;
12521271
} else {

test/SILGen/toplevel.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ fooUsesUninitializedValue()
102102
NotInitializedInteger = 10
103103
fooUsesUninitializedValue()
104104

105+
// Test initialization of variables captured by top-level defer.
106+
107+
// CHECK: alloc_global @$S8toplevel9uninitVarSiv
108+
// CHECK-NEXT: [[UNINIADDR:%[0-9]+]] = global_addr @$S8toplevel9uninitVarSiv
109+
// CHECK-NEXT: [[UNINIMUI:%[0-9]+]] = mark_uninitialized [var] [[UNINIADDR]] : $*Int
110+
// CHECK-NEXT: mark_function_escape [[UNINIMUI]] : $*Int
111+
var uninitVar: Int
112+
defer {
113+
print(uninitVar)
114+
}
115+
105116

106117

107118

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// RUN: %target-swift-frontend -emit-sil -enable-sil-ownership -primary-file %s -o /dev/null -verify
2+
3+
import Swift
4+
5+
// Tests for definite initialization of globals.
6+
7+
var x: Int // expected-note {{variable defined here}}
8+
// expected-note@-1 {{variable defined here}}
9+
// expected-note@-2 {{variable defined here}}
10+
// expected-note@-3 {{variable defined here}}
11+
12+
let y: Int // expected-note {{constant defined here}}
13+
// expected-note@-1 {{constant defined here}}
14+
// expected-note@-2 {{constant defined here}}
15+
16+
// Test top-level defer.
17+
defer { print(x) } // expected-error {{variable 'x' used in defer before being initialized}}
18+
19+
defer { print(y) } // expected-error {{constant 'y' used in defer before being initialized}}
20+
21+
// Test top-level functions.
22+
23+
func testFunc() { // expected-error {{variable 'x' used by function definition before being initialized}}
24+
defer { print(x) }
25+
}
26+
27+
// Test top-level closures.
28+
29+
let clo: () -> Void = { print(y) } // expected-error {{constant 'y' captured by a closure before being initialized}}
30+
clo()
31+
32+
({ () -> Void in print(x) })() // expected-error {{variable 'x' captured by a closure before being initialized}}
33+
34+
let clo2: () -> Void = { [x] in print(x) } // expected-error {{variable 'x' used before being initialized}}
35+
clo2()
36+
37+
class C {
38+
var f = 0
39+
}
40+
41+
var c: C // expected-note {{variable defined here}}
42+
43+
let clo3: () -> Void = { [weak c] in // expected-error {{variable 'c' used before being initialized}}
44+
guard let cref = c else{ return }
45+
print(cref)
46+
}
47+
clo3()
48+
49+
// Test inner functions.
50+
51+
func testFunc2() { // expected-error {{constant 'y' used by function definition before being initialized}}
52+
func innerFunc() {
53+
print(y)
54+
}
55+
}
56+
57+
// Test class initialization and methods.
58+
59+
var w: String // expected-note {{variable defined here}}
60+
// expected-note@-1 {{variable defined here}}
61+
// expected-note@-2 {{variable defined here}}
62+
63+
// FIXME: the error should blame the class definition: <rdar://41490541>.
64+
class TestClass1 { // expected-error {{variable 'w' used by function definition before being initialized}}
65+
let fld = w
66+
}
67+
68+
class TestClass2 {
69+
init() { // expected-error {{variable 'w' used by function definition before being initialized}}
70+
print(w)
71+
}
72+
73+
func bar() { // expected-error {{variable 'w' used by function definition before being initialized}}
74+
print(w)
75+
}
76+
}
77+
78+
// Test initialization of global variables of protocol type.
79+
80+
protocol P {
81+
var f: Int { get }
82+
}
83+
84+
var p: P // expected-note {{variable defined here}}
85+
86+
defer { print(p.f) } // expected-error {{variable 'p' used in defer before being initialized}}

0 commit comments

Comments
 (0)