Skip to content

Commit 1598a21

Browse files
committed
DI: Warn on non-delegating cross-module struct initializers
...as detected by initializing an individual field without having initialized the whole object (via `self = value`). This only applies in pre-Swift-5 mode because the next commit will treat all cross-module struct initializers as delegating in Swift 5.
1 parent 1d1d489 commit 1598a21

File tree

18 files changed

+398
-95
lines changed

18 files changed

+398
-95
lines changed

docs/SIL.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,6 +2308,7 @@ mark_uninitialized
23082308
sil-instruction ::= 'mark_uninitialized' '[' mu_kind ']' sil-operand
23092309
mu_kind ::= 'var'
23102310
mu_kind ::= 'rootself'
2311+
mu_kind ::= 'crossmodulerootself'
23112312
mu_kind ::= 'derivedself'
23122313
mu_kind ::= 'derivedselfonly'
23132314
mu_kind ::= 'delegatingself'
@@ -2325,6 +2326,9 @@ the mark_uninitialized instruction refers to:
23252326

23262327
- ``var``: designates the start of a normal variable live range
23272328
- ``rootself``: designates ``self`` in a struct, enum, or root class
2329+
- ``crossmodulerootself``: same as ``rootself``, but in a case where it's not
2330+
really safe to treat ``self`` as a root because the original module might add
2331+
more stored properties. This is only used for Swift 4 compatibility.
23282332
- ``derivedself``: designates ``self`` in a derived (non-root) class
23292333
- ``derivedselfonly``: designates ``self`` in a derived (non-root) class whose stored properties have already been initialized
23302334
- ``delegatingself``: designates ``self`` on a struct, enum, or class in a delegating constructor (one that calls self.init)

include/swift/AST/DiagnosticsSIL.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ ERROR(mutating_protocol_witness_method_on_let_constant,none,
215215
"cannot perform mutating operation: '%0' is a 'let' constant",
216216
(StringRef))
217217

218+
WARNING(designated_init_in_cross_module_extension,none,
219+
"initializer for struct %0 must use \"self.init(...)\" or \"self = ...\""
220+
"%select{| on all paths}1 because it is not in module %2",
221+
(Type, bool, Identifier))
222+
218223

219224
// Control flow diagnostics.
220225
ERROR(missing_return,none,

include/swift/SIL/SILInstruction.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3164,6 +3164,14 @@ class MarkUninitializedInst
31643164
/// RootSelf designates "self" in a struct, enum, or root class.
31653165
RootSelf,
31663166

3167+
/// CrossModuleRootSelf is the same as "RootSelf", but in a case where
3168+
/// it's not really safe to treat 'self' as root because the original
3169+
/// module might add more stored properties.
3170+
///
3171+
/// This is only used for Swift 4 compatibility. In Swift 5, cross-module
3172+
/// initializers are always DelegatingSelf.
3173+
CrossModuleRootSelf,
3174+
31673175
/// DerivedSelf designates "self" in a derived (non-root) class.
31683176
DerivedSelf,
31693177

@@ -3190,6 +3198,9 @@ class MarkUninitializedInst
31903198
bool isRootSelf() const {
31913199
return ThisKind == RootSelf;
31923200
}
3201+
bool isCrossModuleRootSelf() const {
3202+
return ThisKind == CrossModuleRootSelf;
3203+
}
31933204
bool isDerivedClassSelf() const {
31943205
return ThisKind == DerivedSelf;
31953206
}

lib/ParseSIL/ParseSIL.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,6 +3048,8 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
30483048
Kind = MarkUninitializedInst::Var;
30493049
else if (KindId.str() == "rootself")
30503050
Kind = MarkUninitializedInst::RootSelf;
3051+
else if (KindId.str() == "crossmodulerootself")
3052+
Kind = MarkUninitializedInst::CrossModuleRootSelf;
30513053
else if (KindId.str() == "derivedself")
30523054
Kind = MarkUninitializedInst::DerivedSelf;
30533055
else if (KindId.str() == "derivedselfonly")
@@ -3056,8 +3058,8 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
30563058
Kind = MarkUninitializedInst::DelegatingSelf;
30573059
else {
30583060
P.diagnose(KindLoc, diag::expected_tok_in_sil_instr,
3059-
"var, rootself, derivedself, derivedselfonly, "
3060-
"or delegatingself");
3061+
"var, rootself, crossmodulerootself, derivedself, "
3062+
"derivedselfonly, or delegatingself");
30613063
return true;
30623064
}
30633065

lib/SIL/SILPrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,9 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
12831283
switch (MU->getKind()) {
12841284
case MarkUninitializedInst::Var: *this << "[var] "; break;
12851285
case MarkUninitializedInst::RootSelf: *this << "[rootself] "; break;
1286+
case MarkUninitializedInst::CrossModuleRootSelf:
1287+
*this << "[crossmodulerootself] ";
1288+
break;
12861289
case MarkUninitializedInst::DerivedSelf: *this << "[derivedself] "; break;
12871290
case MarkUninitializedInst::DerivedSelfOnly:
12881291
*this << "[derivedselfonly] ";

lib/SILGen/SILGenConstructor.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,25 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
200200
assert(!selfTy.getClassOrBoundGenericClass()
201201
&& "can't emit a class ctor here");
202202

203+
// Decide if we need to do extra work to warn on unsafe behavior in pre-Swift-5
204+
// modes.
205+
MarkUninitializedInst::Kind MUIKind;
206+
if (isDelegating) {
207+
MUIKind = MarkUninitializedInst::DelegatingSelf;
208+
} else if (getASTContext().isSwiftVersionAtLeast(5)) {
209+
MUIKind = MarkUninitializedInst::RootSelf;
210+
} else {
211+
auto *dc = ctor->getParent();
212+
if (isa<ExtensionDecl>(dc) &&
213+
dc->getAsStructOrStructExtensionContext()->getParentModule() !=
214+
dc->getParentModule()) {
215+
MUIKind = MarkUninitializedInst::CrossModuleRootSelf;
216+
} else {
217+
MUIKind = MarkUninitializedInst::RootSelf;
218+
}
219+
}
220+
203221
// Allocate the local variable for 'self'.
204-
auto MUIKind = isDelegating ? MarkUninitializedInst::DelegatingSelf
205-
: MarkUninitializedInst::RootSelf;
206222
emitLocalVariableWithCleanup(selfDecl, MUIKind)->finishInitialization(*this);
207223
SILValue selfLV = VarLocs[selfDecl].value;
208224

lib/SILOptimizer/Mandatory/DIMemoryUseCollectorOwnership.h

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,25 @@ class DIMemoryObjectInfo {
108108
/// True if the memory object is the 'self' argument of a struct initializer.
109109
bool isStructInitSelf() const {
110110
if (auto *MUI = dyn_cast<MarkUninitializedInst>(MemoryInst))
111-
if (MUI->isRootSelf())
111+
if (MUI->isRootSelf() || MUI->isCrossModuleRootSelf())
112112
if (auto decl = getType()->getAnyNominal())
113113
if (isa<StructDecl>(decl))
114114
return true;
115115
return false;
116116
}
117117

118+
/// True if the memory object is the 'self' argument of a non-delegating
119+
/// cross-module struct initializer.
120+
bool isCrossModuleStructInitSelf() const {
121+
if (auto *MUI = dyn_cast<MarkUninitializedInst>(MemoryInst)) {
122+
if (MUI->isCrossModuleRootSelf()) {
123+
assert(isStructInitSelf());
124+
return true;
125+
}
126+
}
127+
return false;
128+
}
129+
118130
/// True if the memory object is the 'self' argument of a class initializer.
119131
bool isClassInitSelf() const {
120132
if (auto *MUI = dyn_cast<MarkUninitializedInst>(MemoryInst))
@@ -125,17 +137,15 @@ class DIMemoryObjectInfo {
125137
return false;
126138
}
127139

128-
/// isDerivedClassSelf - Return true if this memory object is the 'self' of
129-
/// a derived class init method.
140+
/// True if this memory object is the 'self' of a derived class initializer.
130141
bool isDerivedClassSelf() const {
131142
if (auto MUI = dyn_cast<MarkUninitializedInst>(MemoryInst))
132143
return MUI->isDerivedClassSelf();
133144
return false;
134145
}
135146

136-
/// isDerivedClassSelfOnly - Return true if this memory object is the 'self'
137-
/// of a derived class init method for which we can assume that all ivars
138-
/// have been initialized.
147+
/// True if this memory object is the 'self' of a derived class initializer for
148+
/// which we can assume that all ivars have been initialized.
139149
bool isDerivedClassSelfOnly() const {
140150
if (auto MUI = dyn_cast<MarkUninitializedInst>(MemoryInst))
141151
return MUI->isDerivedClassSelfOnly();
@@ -171,9 +181,17 @@ class DIMemoryObjectInfo {
171181
/// stored properties.
172182
bool isNonDelegatingInit() const {
173183
if (auto *MUI = dyn_cast<MarkUninitializedInst>(MemoryInst)) {
174-
if (MUI->isDerivedClassSelf() || MUI->isDerivedClassSelfOnly() ||
175-
MUI->isRootSelf())
184+
switch (MUI->getKind()) {
185+
case MarkUninitializedInst::Var:
186+
return false;
187+
case MarkUninitializedInst::RootSelf:
188+
case MarkUninitializedInst::CrossModuleRootSelf:
189+
case MarkUninitializedInst::DerivedSelf:
190+
case MarkUninitializedInst::DerivedSelfOnly:
176191
return true;
192+
case MarkUninitializedInst::DelegatingSelf:
193+
return false;
194+
}
177195
}
178196
return false;
179197
}

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,12 @@ namespace {
506506
/// This is true when there is a destroy on a path where the self value may
507507
/// have been consumed, in which case there is nothing to do.
508508
bool HasConditionalSelfInitialized = false;
509+
510+
/// This is true when the object being checked is a 'self' parameter for a
511+
/// struct in a non-delegating cross-module initializer. In this case, the
512+
/// initializer is not allowed to be fieldwise in Swift 5, so we produce a
513+
/// warning in Swift 4 and earlier.
514+
bool WantsCrossModuleStructInitializerDiagnostic = false;
509515

510516
// Keep track of whether we've emitted an error. We only emit one error per
511517
// location as a policy decision.
@@ -631,6 +637,11 @@ LifetimeChecker::LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
631637
// locally inferred by the loop above. Mark any unset elements as not
632638
// available.
633639
MemBBInfo.setUnknownToNotAvailable();
640+
641+
// Finally, check if we need to emit compatibility diagnostics for cross-module
642+
// non-delegating struct initializers.
643+
if (TheMemory.isCrossModuleStructInitSelf())
644+
WantsCrossModuleStructInitializerDiagnostic = true;
634645
}
635646

636647
/// Determine whether the specified block is reachable from the entry of the
@@ -995,6 +1006,50 @@ void LifetimeChecker::handleStoreUse(unsigned UseID) {
9951006
}
9961007
}
9971008

1009+
// Check if we're in a struct initializer that uses CrossModuleRootSelf rather
1010+
// than DelegatingSelf for Swift 4 compatibility. We look for a problem case by
1011+
// seeing if there are any assignments to individual fields that might be
1012+
// initializations; that is, that they're not dominated by `self = other`.
1013+
1014+
auto isFullValueAssignment = [this](const SILInstruction *inst) -> bool {
1015+
SILValue addr;
1016+
if (auto *copyAddr = dyn_cast<CopyAddrInst>(inst))
1017+
addr = copyAddr->getDest();
1018+
else if (auto *assign = dyn_cast<AssignInst>(inst))
1019+
addr = assign->getDest();
1020+
else
1021+
return false;
1022+
1023+
if (auto *access = dyn_cast<BeginAccessInst>(addr))
1024+
addr = access->getSource();
1025+
if (auto *projection = dyn_cast<ProjectBoxInst>(addr))
1026+
addr = projection->getOperand();
1027+
1028+
return addr == TheMemory.getAddress();
1029+
};
1030+
1031+
if (!isFullyInitialized && WantsCrossModuleStructInitializerDiagnostic &&
1032+
!isFullValueAssignment(Use.Inst)) {
1033+
// Deliberately don't check shouldEmitError here; we're using DI to approximate
1034+
// whether this would be a valid delegating initializer, but the error when it
1035+
// /is/ a delegating initializer won't be path-sensitive.
1036+
1037+
Type selfTy;
1038+
SILLocation fnLoc = TheMemory.getFunction().getLocation();
1039+
if (auto *ctor = fnLoc.getAsASTNode<ConstructorDecl>())
1040+
selfTy = ctor->getImplicitSelfDecl()->getType()->getInOutObjectType();
1041+
else
1042+
selfTy = TheMemory.getType();
1043+
1044+
diagnose(Module, Use.Inst->getLoc(),
1045+
diag::designated_init_in_cross_module_extension,
1046+
selfTy, !isFullyUninitialized,
1047+
selfTy->getAnyNominal()->getParentModule()->getName());
1048+
1049+
// Don't emit more than one of these diagnostics per initializer.
1050+
WantsCrossModuleStructInitializerDiagnostic = false;
1051+
}
1052+
9981053
// If this is an initialization or a normal assignment, upgrade the store to
9991054
// an initialization or assign in the uses list so that clients know about it.
10001055
if (isFullyUninitialized) {

0 commit comments

Comments
 (0)