Skip to content

Commit b92e77a

Browse files
committed
[wip] emit error on implicit destruction of self in discard context
As part of SE-390, you're required to write either: - `consume self` - pass self as a `consuming` parameter to a function - `discard self` before the function ends in a context that contains a `discard self` somewhere. This prevents people from accidentally invoking the deinit due to implicit destruction of `self` before exiting the function. rdar://106099027
1 parent 381f292 commit b92e77a

File tree

5 files changed

+65
-1
lines changed

5 files changed

+65
-1
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,11 @@ ERROR(sil_movechecking_notconsumable_but_assignable_was_consumed, none,
777777
ERROR(sil_movechecking_cannot_destructure_has_deinit, none,
778778
"cannot partially consume '%0' when it has a deinitializer",
779779
(StringRef))
780+
ERROR(sil_movechecking_discard_missing_consume_self, none,
781+
"must 'consume self' before exiting method that discards self", ())
780782

783+
NOTE(sil_movechecking_discard_self_here, none,
784+
"discarded self here", ())
781785
NOTE(sil_movechecking_partial_consume_here, none,
782786
"partially consumed here", ())
783787
NOTE(sil_movechecking_consuming_use_here, none,

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,9 @@ struct UseState {
691691
/// [assign] that are reinits that we will convert to inits and true reinits.
692692
llvm::SmallMapVector<SILInstruction *, TypeTreeLeafTypeRange, 4> reinitInsts;
693693

694+
/// The set of drop_deinits of this mark_must_check
695+
SmallSetVector<SILInstruction *, 2> dropDeinitInsts;
696+
694697
/// A "inout terminator use" is an implicit liveness use of the entire value
695698
/// placed on a terminator. We use this both so we add liveness for the
696699
/// terminator user and so that we can use the set to quickly identify later
@@ -721,6 +724,7 @@ struct UseState {
721724
takeInsts.clear();
722725
initInsts.clear();
723726
reinitInsts.clear();
727+
dropDeinitInsts.clear();
724728
inoutTermUsers.clear();
725729
debugValue = nullptr;
726730
}
@@ -755,6 +759,10 @@ struct UseState {
755759
for (auto pair : reinitInsts) {
756760
llvm::dbgs() << *pair.first;
757761
}
762+
llvm::dbgs() << "DropDeinits:\n";
763+
for (auto *inst : dropDeinitInsts) {
764+
llvm::dbgs() << *inst;
765+
}
758766
llvm::dbgs() << "InOut Term Users:\n";
759767
for (auto *inst : inoutTermUsers) {
760768
llvm::dbgs() << *inst;
@@ -1737,6 +1745,12 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
17371745
LLVM_DEBUG(llvm::dbgs() << "Running copy propagation!\n");
17381746
moveChecker.changed |= moveChecker.canonicalizer.canonicalize();
17391747

1748+
// Export the drop_deinit's discovered by the ObjectChecker into the
1749+
// AddressChecker to preserve it for later use. We need to do this since
1750+
// the ObjectChecker's state gets cleared after running on this LoadInst.
1751+
for (auto *dropDeinit : moveChecker.canonicalizer.getDropDeinitUses())
1752+
moveChecker.addressUseState.dropDeinitInsts.insert(dropDeinit);
1753+
17401754
// If we are asked to perform no_consume_or_assign checking or
17411755
// assignable_but_not_consumable checking, if we found any consumes of our
17421756
// load, then we need to emit an error.
@@ -2458,10 +2472,26 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses(
24582472
FieldSensitiveMultiDefPrunedLiveRange &liveness,
24592473
const FieldSensitivePrunedLivenessBoundary &boundary) {
24602474
LLVM_DEBUG(llvm::dbgs() << "MoveOnlyAddressChecker Rewrite Uses!\n");
2475+
2476+
/// whether the marked value appeared in a discard statement.
2477+
const bool isDiscardingContext = !addressUseState.dropDeinitInsts.empty();
2478+
24612479
// First remove all destroy_addr that have not been claimed.
24622480
for (auto destroyPair : addressUseState.destroys) {
24632481
if (!consumes.claimConsume(destroyPair.first, destroyPair.second)) {
24642482
destroyPair.first->eraseFromParent();
2483+
continue;
2484+
}
2485+
2486+
// Otherwise, if we're in a discarding context and checking 'self', flag
2487+
// the claimed destroy_addr as a point where we're missing an explicit
2488+
// `consume self`. The reasoning here is that if we leave behind one of
2489+
// these destroys, then the 'self' didn't get explicitly consumed or
2490+
// discarded along this path.
2491+
if (isDiscardingContext) {
2492+
auto *dropDeinit = addressUseState.dropDeinitInsts.front();
2493+
diagnosticEmitter.emitMissingConsumeInDiscardingContext(destroyPair.first,
2494+
dropDeinit);
24652495
}
24662496
}
24672497

@@ -2657,7 +2687,6 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
26572687
liveness.computeBoundary(boundary);
26582688
insertDestroysOnBoundary(markedAddress, liveness, boundary);
26592689
rewriteUses(markedAddress, liveness, boundary);
2660-
26612690
return true;
26622691
}
26632692

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,17 @@ void DiagnosticEmitter::emitCheckedMissedCopyError(SILInstruction *copyInst) {
190190
diag::sil_movechecking_bug_missed_copy);
191191
}
192192

193+
void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
194+
SILInstruction *leftoverDestroy,
195+
SILInstruction *discard) {
196+
assert(isa<DropDeinitInst>(discard));
197+
198+
diagnose(leftoverDestroy->getFunction()->getASTContext(), leftoverDestroy,
199+
diag::sil_movechecking_discard_missing_consume_self);
200+
diagnose(discard->getFunction()->getASTContext(), discard,
201+
diag::sil_movechecking_discard_self_here);
202+
}
203+
193204
//===----------------------------------------------------------------------===//
194205
// MARK: Object Diagnostics
195206
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ class DiagnosticEmitter {
119119
/// way to file a bug.
120120
void emitCheckedMissedCopyError(SILInstruction *copyInst);
121121

122+
/// Assuming the given instruction represents the implicit destruction of
123+
/// 'self', emits an error saying that you needed to explicitly 'consume self'
124+
/// here because you're in a discarding context.
125+
void emitMissingConsumeInDiscardingContext(SILInstruction *leftoverDestroy,
126+
SILInstruction *dropDeinit);
127+
122128
void emitCheckerDoesntUnderstandDiagnostic(MarkMustCheckInst *markedValue);
123129
void emitObjectGuaranteedDiagnostic(MarkMustCheckInst *markedValue);
124130
void emitObjectOwnedDiagnostic(MarkMustCheckInst *markedValue);

lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,20 @@ struct OSSACanonicalizer {
117117
return !isa<PartialApplyInst>(user);
118118
});
119119
}
120+
121+
struct DropDeinitFilter {
122+
bool operator()(SILInstruction *inst) const {
123+
return isa<DropDeinitInst>(inst);
124+
}
125+
};
126+
using DropDeinitIter =
127+
llvm::filter_iterator<SILInstruction *const *, DropDeinitFilter>;
128+
using DropDeinitRange = iterator_range<DropDeinitIter>;
129+
130+
/// Returns a range of final uses of the mark_must_check that are drop_deinit
131+
DropDeinitRange getDropDeinitUses() const {
132+
return llvm::make_filter_range(consumingBoundaryUsers, DropDeinitFilter());
133+
}
120134
};
121135

122136
/// Search for candidate object mark_must_checks. If we find one that does not

0 commit comments

Comments
 (0)