Skip to content

[DefiniteInitialization] Check whether globals captured by top-level defer statements are initialized. #17542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ ERROR(variable_inout_before_initialized,none,
ERROR(variable_closure_use_uninit,none,
"%select{variable|constant}1 '%0' captured by a closure before being"
" initialized", (StringRef, bool))
ERROR(variable_defer_use_uninit,none,
"%select{variable|constant}1 '%0' used in defer before being"
" initialized", (StringRef, bool))
ERROR(self_closure_use_uninit,none,
"'self' captured by a closure before all members were initialized", ())

Expand Down
15 changes: 11 additions & 4 deletions lib/SILGen/SILGenStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,15 +467,22 @@ namespace {

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

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




void StmtEmitter::visitIfStmt(IfStmt *S) {
Scope condBufferScope(SGF.Cleanups, S);

Expand Down
25 changes: 22 additions & 3 deletions lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Stmt.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/SILArgument.h"
Expand Down Expand Up @@ -1239,14 +1240,32 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
noteUninitializedMembers(Use);
return;
}


// Extract the reason why this escape-use instruction exists and present
// diagnostics. While an escape-use instruction generally corresponds to a
// capture by a closure, there are the following special cases to consider:
//
// (a) A MarkFunctionEscapeInst with an operand say %var. This is introduced
// by the SILGen phase when %var is the address of a global variable that
// escapes because it is used by a closure or a defer statement or a function
// definition appearing at the top-level. The specific reason why %var escapes
// is recorded in MarkFunctionEscapeInst by making its SIL Location refer to
// the AST of the construct that uses the global variable (namely, a closure
// or a defer statement or a function definition). So, if %var is
// uninitialized at MarkFunctionEscapeInst, extract and report the reason
// why the variable escapes in the error message.
//
// (b) An UncheckedTakeEnumDataAddrInst takes the address of the data of
// an optional and is introduced as an intermediate step in optional chaining.
Diag<StringRef, bool> DiagMessage;
if (isa<MarkFunctionEscapeInst>(Inst)) {
if (Inst->getLoc().isASTNode<AbstractClosureExpr>())
if (Inst->getLoc().isASTNode<AbstractClosureExpr>()) {
DiagMessage = diag::variable_closure_use_uninit;
else
} else if (Inst->getLoc().isASTNode<DeferStmt>()) {
DiagMessage = diag::variable_defer_use_uninit;
} else {
DiagMessage = diag::variable_function_use_uninit;
}
} else if (isa<UncheckedTakeEnumDataAddrInst>(Inst)) {
DiagMessage = diag::variable_used_before_initialized;
} else {
Expand Down
11 changes: 11 additions & 0 deletions test/SILGen/toplevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ fooUsesUninitializedValue()
NotInitializedInteger = 10
fooUsesUninitializedValue()

// Test initialization of variables captured by top-level defer.

// CHECK: alloc_global @$S8toplevel9uninitVarSiv
// CHECK-NEXT: [[UNINIADDR:%[0-9]+]] = global_addr @$S8toplevel9uninitVarSiv
// CHECK-NEXT: [[UNINIMUI:%[0-9]+]] = mark_uninitialized [var] [[UNINIADDR]] : $*Int
// CHECK-NEXT: mark_function_escape [[UNINIMUI]] : $*Int
var uninitVar: Int
defer {
print(uninitVar)
}




Expand Down
86 changes: 86 additions & 0 deletions test/SILOptimizer/definite_init_diagnostics_globals.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// RUN: %target-swift-frontend -emit-sil -enable-sil-ownership -primary-file %s -o /dev/null -verify

import Swift

// Tests for definite initialization of globals.

var x: Int // expected-note {{variable defined here}}
// expected-note@-1 {{variable defined here}}
// expected-note@-2 {{variable defined here}}
// expected-note@-3 {{variable defined here}}

let y: Int // expected-note {{constant defined here}}
// expected-note@-1 {{constant defined here}}
// expected-note@-2 {{constant defined here}}

// Test top-level defer.
defer { print(x) } // expected-error {{variable 'x' used in defer before being initialized}}

defer { print(y) } // expected-error {{constant 'y' used in defer before being initialized}}

// Test top-level functions.

func testFunc() { // expected-error {{variable 'x' used by function definition before being initialized}}
defer { print(x) }
}

// Test top-level closures.

let clo: () -> Void = { print(y) } // expected-error {{constant 'y' captured by a closure before being initialized}}
clo()

({ () -> Void in print(x) })() // expected-error {{variable 'x' captured by a closure before being initialized}}

let clo2: () -> Void = { [x] in print(x) } // expected-error {{variable 'x' used before being initialized}}
clo2()

class C {
var f = 0
}

var c: C // expected-note {{variable defined here}}

let clo3: () -> Void = { [weak c] in // expected-error {{variable 'c' used before being initialized}}
guard let cref = c else{ return }
print(cref)
}
clo3()

// Test inner functions.

func testFunc2() { // expected-error {{constant 'y' used by function definition before being initialized}}
func innerFunc() {
print(y)
}
}

// Test class initialization and methods.

var w: String // expected-note {{variable defined here}}
// expected-note@-1 {{variable defined here}}
// expected-note@-2 {{variable defined here}}

// FIXME: the error should blame the class definition: <rdar://41490541>.
class TestClass1 { // expected-error {{variable 'w' used by function definition before being initialized}}
let fld = w
}

class TestClass2 {
init() { // expected-error {{variable 'w' used by function definition before being initialized}}
print(w)
}

func bar() { // expected-error {{variable 'w' used by function definition before being initialized}}
print(w)
}
}

// Test initialization of global variables of protocol type.

protocol P {
var f: Int { get }
}

var p: P // expected-note {{variable defined here}}

defer { print(p.f) } // expected-error {{variable 'p' used in defer before being initialized}}