Skip to content

Commit 4e5389d

Browse files
committed
[Definite Initialization] Improve diagnostics for delegating, failable
initializers that do not initialize along all paths. <rdar://problem/62562254>
1 parent a1716fe commit 4e5389d

File tree

5 files changed

+143
-60
lines changed

5 files changed

+143
-60
lines changed

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,67 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
12321232
}
12331233
}
12341234

1235+
/// Failable enum initializer produce a CFG for the return that looks like this,
1236+
/// where the load is the use of 'self'. Detect this pattern so we can consider
1237+
/// it a 'return' use of self.
1238+
///
1239+
/// %3 = load %2 : $*Enum
1240+
/// %4 = enum $Optional<Enum>, #Optional.Some!enumelt, %3 : $Enum
1241+
/// br bb2(%4 : $Optional<Enum>) // id: %5
1242+
/// bb1:
1243+
/// %6 = enum $Optional<Enum>, #Optional.None!enumelt // user: %7
1244+
/// br bb2(%6 : $Optional<Enum>) // id: %7
1245+
/// bb2(%8 : $Optional<Enum>): // Preds: bb0 bb1
1246+
/// dealloc_stack %1 : $*Enum // id: %9
1247+
/// return %8 : $Optional<Enum> // id: %10
1248+
///
1249+
static bool isFailableInitReturnUseOfEnum(EnumInst *EI) {
1250+
// Only allow enums forming an optional.
1251+
if (!EI->getType().getOptionalObjectType())
1252+
return false;
1253+
1254+
if (!EI->hasOneUse())
1255+
return false;
1256+
auto *BI = dyn_cast<BranchInst>(EI->use_begin()->getUser());
1257+
if (!BI || BI->getNumArgs() != 1)
1258+
return false;
1259+
1260+
auto *TargetArg = BI->getDestBB()->getArgument(0);
1261+
if (!TargetArg->hasOneUse())
1262+
return false;
1263+
return isa<ReturnInst>(TargetArg->use_begin()->getUser());
1264+
}
1265+
1266+
/// Given a load instruction, return true iff the result of the load is used
1267+
/// in a return instruction directly or is lifted to an optional (i.e., wrapped
1268+
/// into .some) and returned. These conditions are used to detect whether the
1269+
/// given load instruction is autogenerated for a return from the initalizers:
1270+
/// `init` or `init?`, respectively. In such cases, the load should not be
1271+
/// considered as a use of the value but rather as a part of the return
1272+
/// instruction. We emit a specific diagnostic in this case.
1273+
static bool isLoadForReturn(SingleValueInstruction *loadInst) {
1274+
bool hasReturnUse = false, hasUnknownUses = false;
1275+
1276+
for (auto LoadUse : loadInst->getUses()) {
1277+
auto *User = LoadUse->getUser();
1278+
// Ignore retains of the struct/enum before the return.
1279+
if (isa<RetainValueInst>(User))
1280+
continue;
1281+
if (isa<ReturnInst>(User)) {
1282+
hasReturnUse = true;
1283+
continue;
1284+
}
1285+
if (auto *EI = dyn_cast<EnumInst>(User))
1286+
if (isFailableInitReturnUseOfEnum(EI)) {
1287+
hasReturnUse = true;
1288+
continue;
1289+
}
1290+
hasUnknownUses = true;
1291+
break;
1292+
}
1293+
return hasReturnUse && !hasUnknownUses;
1294+
}
1295+
12351296
void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
12361297
// The value must be fully initialized at all escape points. If not, diagnose
12371298
// the error.
@@ -1259,8 +1320,7 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
12591320
// If this is a load with a single user that is a return, then this is
12601321
// a return before self.init. Emit a specific diagnostic.
12611322
if (auto *LI = dyn_cast<LoadInst>(Inst))
1262-
if (LI->hasOneUse() &&
1263-
isa<ReturnInst>((*LI->use_begin())->getUser())) {
1323+
if (isLoadForReturn(LI)) {
12641324
diagnose(Module, Inst->getLoc(),
12651325
diag::superselfinit_not_called_before_return,
12661326
(unsigned)TheMemory.isDelegatingInit());
@@ -1358,35 +1418,6 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
13581418
diagnoseInitError(Use, DiagMessage);
13591419
}
13601420

1361-
1362-
/// Failable enum initializer produce a CFG for the return that looks like this,
1363-
/// where the load is the use of 'self'. Detect this pattern so we can consider
1364-
/// it a 'return' use of self.
1365-
///
1366-
/// %3 = load %2 : $*Enum
1367-
/// %4 = enum $Optional<Enum>, #Optional.Some!enumelt, %3 : $Enum
1368-
/// br bb2(%4 : $Optional<Enum>) // id: %5
1369-
/// bb1:
1370-
/// %6 = enum $Optional<Enum>, #Optional.None!enumelt // user: %7
1371-
/// br bb2(%6 : $Optional<Enum>) // id: %7
1372-
/// bb2(%8 : $Optional<Enum>): // Preds: bb0 bb1
1373-
/// dealloc_stack %1 : $*Enum // id: %9
1374-
/// return %8 : $Optional<Enum> // id: %10
1375-
///
1376-
static bool isFailableInitReturnUseOfEnum(EnumInst *EI) {
1377-
// Only allow enums forming an optional.
1378-
if (!EI->getType().getOptionalObjectType())
1379-
return false;
1380-
1381-
if (!EI->hasOneUse()) return false;
1382-
auto *BI = dyn_cast<BranchInst>(EI->use_begin()->getUser());
1383-
if (!BI || BI->getNumArgs() != 1) return false;
1384-
1385-
auto *TargetArg = BI->getDestBB()->getArgument(0);
1386-
if (!TargetArg->hasOneUse()) return false;
1387-
return isa<ReturnInst>(TargetArg->use_begin()->getUser());
1388-
}
1389-
13901421
enum BadSelfUseKind {
13911422
BeforeStoredPropertyInit,
13921423
BeforeSuperInit,
@@ -1680,31 +1711,8 @@ void LifetimeChecker::handleLoadUseFailure(const DIMemoryUse &Use,
16801711
// diagnostic.
16811712
if (isa<LoadInst>(Inst) || isa<LoadBorrowInst>(Inst)) {
16821713
auto *LI = Inst;
1683-
bool hasReturnUse = false, hasUnknownUses = false;
1684-
1685-
for (auto LoadUse : cast<SingleValueInstruction>(LI)->getUses()) {
1686-
auto *User = LoadUse->getUser();
1687-
1688-
// Ignore retains of the struct/enum before the return.
1689-
if (isa<RetainValueInst>(User))
1690-
continue;
1691-
if (isa<ReturnInst>(User)) {
1692-
hasReturnUse = true;
1693-
continue;
1694-
}
1695-
1696-
if (auto *EI = dyn_cast<EnumInst>(User))
1697-
if (isFailableInitReturnUseOfEnum(EI)) {
1698-
hasReturnUse = true;
1699-
continue;
1700-
}
1701-
1702-
hasUnknownUses = true;
1703-
break;
1704-
}
1705-
1706-
// Okay, this load is part of a return sequence, diagnose it specially.
1707-
if (hasReturnUse && !hasUnknownUses) {
1714+
// If this load is part of a return sequence, diagnose it specially.
1715+
if (isLoadForReturn(cast<SingleValueInstruction>(LI))) {
17081716
// The load is probably part of the common epilog for the function, try to
17091717
// find a more useful source location than the syntactic end of the
17101718
// function.

test/SILOptimizer/definite_init_cross_module.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ extension Point {
5050
self.x = xx // expected-error {{'self' used before 'self.init' call or assignment to 'self'}}
5151
self.y = 0 // expected-error {{'self' used before 'self.init' call or assignment to 'self'}}
5252
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
53+
54+
// Test failable initializer.
55+
init?(p: Point) {
56+
if p.x > 0 {
57+
self = p
58+
}
59+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
5360
}
5461

5562
extension GenericPoint {

test/SILOptimizer/definite_init_diagnostics.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ enum MyAwesomeEnum {
10031003

10041004
init?() {
10051005

1006-
} // expected-error {{'self' used before 'self.init' call or assignment to 'self'}}
1006+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
10071007
}
10081008

10091009
// <rdar://problem/20679379> DI crashes on initializers on protocol extensions
@@ -1204,15 +1204,15 @@ enum SR1469_Enum1 {
12041204
}
12051205
// many lines later
12061206
self = .A
1207-
} // expected-error {{'self' used before 'self.init' call or assignment to 'self'}}
1207+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
12081208
}
12091209

12101210
enum SR1469_Enum2 {
12111211
case A, B
12121212

12131213
init?() {
12141214
return
1215-
} // expected-error {{'self' used before 'self.init' call or assignment to 'self'}}
1215+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
12161216
}
12171217
enum SR1469_Enum3 {
12181218
case A, B
@@ -1222,7 +1222,7 @@ enum SR1469_Enum3 {
12221222
self = .A
12231223
return
12241224
}
1225-
} // expected-error {{'self' used before 'self.init' call or assignment to 'self'}}
1225+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
12261226
}
12271227

12281228
class BadFooSuper {
@@ -1589,3 +1589,20 @@ class DelegatingInitTest {
15891589
// expected-error@-1 {{'self' used before 'self.init' call or assignment to 'self'}}
15901590
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
15911591
}
1592+
1593+
class A {
1594+
var a: Int
1595+
1596+
init(x: Int) {
1597+
self.a = x
1598+
}
1599+
1600+
convenience init(i: Int) {
1601+
if i > 0 {
1602+
self.init(x: i)
1603+
}
1604+
if i > -100 {
1605+
self.init(x: i)
1606+
}
1607+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
1608+
}

test/SILOptimizer/definite_init_failable_initializers_diagnostics.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ class FailableInitThatFailsReallyHard {
1111
}
1212
}
1313

14+
// Failable initializers must produce correct diagnostics
15+
struct A {
16+
var x: Int // expected-note {{'self.x' not initialized}}
17+
init?(i: Int) {
18+
if i > 0 {
19+
self.x = i
20+
}
21+
} // expected-error {{return from initializer without initializing all stored properties}}
22+
}
23+
24+
// Delegating, failable initializers that doesn't initialize along all paths must produce correct diagnostics.
25+
extension Int {
26+
init?(i: Int) {
27+
if i > 0 {
28+
self.init(i)
29+
}
30+
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
31+
}
1432

1533
class BaseClass {}
1634
final class DerivedClass : BaseClass {

test/SILOptimizer/definite_init_markuninitialized_delegatingself.sil

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,36 @@ bb0(%1 : $@thick MyClass4.Type):
292292
dealloc_stack %2 : $*MyClass4
293293
return %7 : $MyClass4
294294
}
295+
296+
// A failable initializer: Int.init?(i:) that doesn't initialize along all paths.
297+
sil hidden [ossa] @intInitExtension : $@convention(method) (Int, Bool) -> Optional<Int> {
298+
bb0(%0 : $Int, %1 : $Bool):
299+
%2 = alloc_stack $Int, var, name "self"
300+
%3 = mark_uninitialized [delegatingself] %2 : $*Int
301+
%5 = metatype $@thin Int.Type
302+
%6 = struct_extract %1 : $Bool, #Bool._value
303+
cond_br %6, bb1, bb2
304+
305+
bb1:
306+
%14 = begin_access [modify] [static] %3 : $*Int
307+
assign %0 to %14 : $*Int
308+
end_access %14 : $*Int
309+
br bb3
310+
311+
bb2:
312+
br bb3
313+
314+
bb3:
315+
%19 = load [trivial] %3 : $*Int // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
316+
%20 = enum $Optional<Int>, #Optional.some!enumelt, %19 : $Int
317+
dealloc_stack %2 : $*Int
318+
br bb5(%20 : $Optional<Int>)
319+
320+
bb4:
321+
dealloc_stack %2 : $*Int
322+
%24 = enum $Optional<Int>, #Optional.none!enumelt
323+
br bb5(%24 : $Optional<Int>)
324+
325+
bb5(%26 : $Optional<Int>):
326+
return %26 : $Optional<Int>
327+
}

0 commit comments

Comments
 (0)