Skip to content

Commit f6fd0bc

Browse files
committed
[DI] InitAccessors: Enforce that @out parameters are fully initialized before every terminator
This closes a hole where an early return could leave some properties from `initializes(...)` list uninitialized. For example: ```swift init(initialValue) initializes(_a, _b) { _a = initialValue.0 if _a > 0 { return } _b = initialValue.1 } ``` Here `_b` is not initialized when `_a > 0`.
1 parent db024d9 commit f6fd0bc

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,14 +1154,38 @@ void LifetimeChecker::doIt() {
11541154

11551155
// All of the indirect results marked as "out" have to be fully initialized
11561156
// before their lifetime ends.
1157-
if (TheMemory.isOut() && Uses.empty()) {
1158-
auto loc = TheMemory.getLoc();
1159-
1160-
std::string propertyName;
1161-
auto *property = TheMemory.getPathStringToElement(0, propertyName);
1162-
diagnose(Module, F.getLocation(),
1163-
diag::ivar_not_initialized_by_init_accessor, property->getName());
1164-
EmittedErrorLocs.push_back(loc);
1157+
if (TheMemory.isOut()) {
1158+
auto diagnoseMissingInit = [&]() {
1159+
std::string propertyName;
1160+
auto *property = TheMemory.getPathStringToElement(0, propertyName);
1161+
diagnose(Module, F.getLocation(),
1162+
diag::ivar_not_initialized_by_init_accessor,
1163+
property->getName());
1164+
EmittedErrorLocs.push_back(TheMemory.getLoc());
1165+
};
1166+
1167+
// No uses means that there was no initialization.
1168+
if (Uses.empty()) {
1169+
diagnoseMissingInit();
1170+
return;
1171+
}
1172+
1173+
// Go over every return block and check whether member is fully initialized
1174+
// because it's possible that there is branch that doesn't have any use of
1175+
// the memory and nothing else is going to diagnose that. This is different
1176+
// from `self`, for example, because it would always have either `copy_addr`
1177+
// or `load` before return.
1178+
1179+
auto returnBB = F.findReturnBB();
1180+
1181+
while (returnBB != F.end()) {
1182+
auto *terminator = returnBB->getTerminator();
1183+
1184+
if (!isInitializedAtUse(DIMemoryUse(terminator, DIUseKind::Load, 0, 1)))
1185+
diagnoseMissingInit();
1186+
1187+
++returnBB;
1188+
}
11651189
}
11661190

11671191
// If the memory object has nontrivial type, then any destroy/release of the

test/SILOptimizer/init_accessor_definite_init_diagnostics.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,43 @@ struct TestAccessBeforeInit {
139139
self.y = y
140140
}
141141
}
142+
143+
class TestInitWithGuard {
144+
var _a: Int
145+
var _b: Int
146+
147+
var pair1: (Int, Int) {
148+
init(initialValue) initializes(_a, _b) { // expected-error {{property '_b' not initialized by init accessor}}
149+
_a = initialValue.0
150+
151+
if _a > 0 {
152+
return
153+
}
154+
155+
_b = initialValue.1
156+
}
157+
158+
get { (_a, _b) }
159+
set { }
160+
}
161+
162+
var pair2: (Int, Int) {
163+
init(initialValue) initializes(_a, _b) { // Ok
164+
_a = initialValue.0
165+
166+
if _a > 0 {
167+
_b = 0
168+
return
169+
}
170+
171+
_b = initialValue.1
172+
}
173+
174+
get { (_a, _b) }
175+
set { }
176+
}
177+
178+
init(a: Int, b: Int) {
179+
self.pair2 = (a, b)
180+
}
181+
}

0 commit comments

Comments
 (0)