Skip to content

Commit bd253c6

Browse files
committed
prevent reinitialization of self after discard
The value `self` is mutable (i.e., var-bound) in a `consuming` method. Since you're allowed to reinitialize a var after consuming, that means you were also naturally allowed to reinitialize self after `discard self`. But that capability was not intended; after you discard self you shouldn't be reinitializing it, as that's probably a mistake. This change makes reinitialization of `self` reachable from a `discard self` statement an error. rdar://106098163
1 parent 4ef135c commit bd253c6

File tree

5 files changed

+205
-0
lines changed

5 files changed

+205
-0
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,8 @@ ERROR(sil_movechecking_cannot_destructure_has_deinit, none,
778778
(StringRef))
779779
ERROR(sil_movechecking_discard_missing_consume_self, none,
780780
"must consume 'self' before exiting method that discards self", ())
781+
ERROR(sil_movechecking_reinit_after_discard, none,
782+
"cannot reinitialize 'self' after 'discard self'", ())
781783

782784
NOTE(sil_movechecking_discard_self_here, none,
783785
"discarded self here", ())

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,10 @@ struct MoveOnlyAddressCheckerPImpl {
13001300
FieldSensitiveMultiDefPrunedLiveRange &liveness,
13011301
const FieldSensitivePrunedLivenessBoundary &boundary);
13021302

1303+
/// Identifies and diagnoses reinitializations that are reachable from a
1304+
/// discard statement.
1305+
void checkForReinitAfterDiscard();
1306+
13031307
void handleSingleBlockDestroy(SILInstruction *destroy, bool isReinit);
13041308
};
13051309

@@ -2629,6 +2633,80 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses(
26292633
#endif
26302634
}
26312635

2636+
void MoveOnlyAddressCheckerPImpl::checkForReinitAfterDiscard() {
2637+
auto const &dropDeinits = addressUseState.dropDeinitInsts;
2638+
auto const &reinits = addressUseState.reinitInsts;
2639+
2640+
if (dropDeinits.empty() || reinits.empty())
2641+
return;
2642+
2643+
using BasicBlockMap = llvm::DenseMap<SILBasicBlock *,
2644+
llvm::SmallPtrSet<SILInstruction *, 2>>;
2645+
BasicBlockMap blocksWithReinit;
2646+
for (auto const &info : reinits) {
2647+
auto *reinit = info.first;
2648+
blocksWithReinit[reinit->getParent()].insert(reinit);
2649+
}
2650+
2651+
// Starting from each drop_deinit instruction, can we reach a reinit of self?
2652+
for (auto *dropInst : dropDeinits) {
2653+
auto *dropBB = dropInst->getParent();
2654+
2655+
// First, if the block containing this drop_deinit also contains a reinit,
2656+
// check if that reinit happens after this drop_deinit.
2657+
auto result = blocksWithReinit.find(dropBB);
2658+
if (result != blocksWithReinit.end()) {
2659+
auto &blockReinits = result->second;
2660+
for (auto ii = std::next(dropInst->getIterator()); ii != dropBB->end();
2661+
++ii) {
2662+
SILInstruction *current = &*ii;
2663+
if (blockReinits.contains(current)) {
2664+
// Then the drop_deinit can reach a reinit immediately after it in the
2665+
// same block.
2666+
diagnosticEmitter.emitReinitAfterDiscardError(current, dropInst);
2667+
return;
2668+
}
2669+
}
2670+
}
2671+
2672+
BasicBlockWorklist worklist(fn);
2673+
2674+
// Seed the search with the successors of the drop_init block, so that if we
2675+
// visit the drop_deinit block again, we'll know the reinits _before_ the
2676+
// drop_deinit are reachable via some back-edge / cycle.
2677+
for (auto *succ : dropBB->getSuccessorBlocks())
2678+
worklist.pushIfNotVisited(succ);
2679+
2680+
// Determine reachability across blocks.
2681+
while (auto *bb = worklist.pop()) {
2682+
// Set-up next iteration.
2683+
for (auto *succ : bb->getSuccessorBlocks())
2684+
worklist.pushIfNotVisited(succ);
2685+
2686+
auto result = blocksWithReinit.find(bb);
2687+
if (result == blocksWithReinit.end())
2688+
continue;
2689+
2690+
// We found a reachable reinit! Identify the earliest reinit in this block
2691+
// for diagnosis.
2692+
auto &blockReinits = result->second;
2693+
SILInstruction *firstBadReinit = nullptr;
2694+
for (auto &inst : *bb) {
2695+
if (blockReinits.contains(&inst)) {
2696+
firstBadReinit = &inst;
2697+
break;
2698+
}
2699+
}
2700+
2701+
if (!firstBadReinit)
2702+
llvm_unreachable("bug");
2703+
2704+
diagnosticEmitter.emitReinitAfterDiscardError(firstBadReinit, dropInst);
2705+
return;
2706+
}
2707+
}
2708+
}
2709+
26322710
bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
26332711
MarkMustCheckInst *markedAddress) {
26342712
SWIFT_DEFER { diagnosticEmitter.clearUsesWithDiagnostic(); };
@@ -2724,6 +2802,7 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
27242802
FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements());
27252803
liveness.computeBoundary(boundary);
27262804
insertDestroysOnBoundary(markedAddress, liveness, boundary);
2805+
checkForReinitAfterDiscard();
27272806
rewriteUses(markedAddress, liveness, boundary);
27282807

27292808
return true;

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

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

194+
void DiagnosticEmitter::emitReinitAfterDiscardError(SILInstruction *badReinit,
195+
SILInstruction *discard) {
196+
assert(isa<DropDeinitInst>(discard));
197+
assert(badReinit->getLoc() && "missing loc!");
198+
assert(discard->getLoc() && "missing loc!");
199+
200+
diagnose(badReinit->getFunction()->getASTContext(),
201+
badReinit,
202+
diag::sil_movechecking_reinit_after_discard);
203+
204+
diagnose(discard->getFunction()->getASTContext(), discard,
205+
diag::sil_movechecking_discard_self_here);
206+
}
207+
194208
void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
195209
SILInstruction *leftoverDestroy,
196210
SILInstruction *discard) {

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

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

122+
/// Given a drop_deinit of self and an instruction reinitializing self,
123+
/// emits an error saying that you cannot reinitialize self after a discard.
124+
void emitReinitAfterDiscardError(SILInstruction *badReinit,
125+
SILInstruction *dropDeinit);
126+
122127
/// Assuming the given instruction represents the implicit destruction of
123128
/// 'self', emits an error saying that you needed to explicitly 'consume self'
124129
/// here because you're in a discarding context.

test/SILOptimizer/discard_checking.swift

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ enum E: Error {
1010
}
1111

1212
func globalConsumingFn(_ b: consuming Basics) {}
13+
func globalThrowingFn() throws {}
1314

1415
struct Basics: ~Copyable {
1516
consuming func test1(_ b: Bool) {
@@ -441,6 +442,110 @@ struct Basics: ~Copyable {
441442
return
442443
}
443444

445+
446+
////////////////////////////////////////
447+
/// Checking for reinit-after-discard
448+
////////////////////////////////////////
449+
consuming func reinitAfterDiscard1(_ c: Color) throws {
450+
discard self // expected-note {{discarded self here}}
451+
self = Basics() // expected-error {{cannot reinitialize 'self' after 'discard self'}}
452+
_ = consume self
453+
}
454+
455+
consuming func reinitAfterDiscard2_bad(_ c: Color) throws {
456+
discard self // expected-note {{discarded self here}}
457+
458+
if case .red = c {
459+
throw E.someError
460+
}
461+
462+
self = Basics() // expected-error {{cannot reinitialize 'self' after 'discard self'}}
463+
_ = consume self
464+
}
465+
466+
consuming func reinitAfterDiscard2_ok(_ c: Color) throws {
467+
if case .red = c {
468+
discard self
469+
throw E.someError
470+
}
471+
472+
self = Basics()
473+
_ = consume self
474+
}
475+
476+
consuming func reinitAfterDiscard3_bad(_ c: Color) throws {
477+
// expected-error@-1 {{must consume 'self' before exiting method that discards self}}
478+
// FIXME: ^ this error is related to rdar://110239087
479+
480+
repeat {
481+
self = Basics() // expected-error {{cannot reinitialize 'self' after 'discard self'}}
482+
discard self // expected-note 2{{discarded self here}}
483+
} while false
484+
}
485+
486+
consuming func reinitAfterDiscard3_ok(_ c: Color) throws {
487+
self = Basics()
488+
discard self
489+
}
490+
491+
consuming func reinitAfterDiscard4_bad(_ c: Color) throws {
492+
do {
493+
discard self // expected-note {{discarded self here}}
494+
if case .red = c {
495+
try globalThrowingFn()
496+
}
497+
} catch {
498+
self = Basics() // expected-error {{cannot reinitialize 'self' after 'discard self'}}
499+
_ = consume self
500+
}
501+
}
502+
503+
consuming func reinitAfterDiscard4_ok(_ c: Color) throws {
504+
do {
505+
if case .red = c {
506+
try globalThrowingFn()
507+
}
508+
discard self
509+
} catch {
510+
self = Basics()
511+
_ = consume self
512+
}
513+
}
514+
515+
consuming func reinitAfterDiscard4_ok_v2(_ c: Color) throws {
516+
do {
517+
if case .red = c {
518+
try? globalThrowingFn()
519+
}
520+
discard self
521+
} catch { // expected-warning {{'catch' block is unreachable because no errors are thrown in 'do' block}}
522+
self = Basics()
523+
_ = consume self
524+
}
525+
}
526+
527+
// FIXME move checker is treating the defer like a closure capture (rdar://100468597)
528+
// There is a different expected error here, where this reinit in a defer
529+
// should be illegal because it's a reinit after discard.
530+
consuming func reinitAfterDiscard5(_ c: Color) throws {
531+
// expected-error@-1 {{'self' used after consume}}
532+
533+
defer { self = Basics() } // expected-note {{used here}}
534+
discard self // expected-note {{consumed here}}
535+
}
536+
537+
consuming func reinitAfterDiscard6(_ c: Color) throws {
538+
self = Basics()
539+
discard self // expected-note 3{{discarded self here}}
540+
switch c {
541+
case .red, .blue:
542+
self = Basics() // expected-error {{must consume 'self' before exiting method that discards self}}
543+
default:
544+
self = Basics() // expected-error {{must consume 'self' before exiting method that discards self}}
545+
// expected-error@-1 {{cannot reinitialize 'self' after 'discard self'}}
546+
}
547+
}
548+
444549
mutating func mutator() { self = Basics() }
445550
borrowing func borrower() {}
446551
borrowing func thrower() throws {}

0 commit comments

Comments
 (0)