Skip to content

Commit 1bedce0

Browse files
Merge pull request swiftlang#31554 from ravikandhadai/definite-init-failable-init-diag
[Definite Init] Improve diagnostics for delegating, failable initializers that do not initialize along all paths.
2 parents 70140a0 + 4e5389d commit 1bedce0

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
@@ -1233,6 +1233,67 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
12331233
}
12341234
}
12351235

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

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