Skip to content

Commit 973a82e

Browse files
committed
[ownership] Cleanup how we emit filecheck-compatible verification errors for the ownership verifier.
This is doing a few things with a simple change to use a builder: 1. It cleans up how we emit errors so we have a builder object that constructs errors. The errors then just become dumb POD data that the builder vends to callers that via the boolean values describe what errors were found. 2. Now that we have one place where we are actually emitting these errors, I cleaned up how we emit the errors by normalizing the output so function names are quoted the same. 3. I changed our error emission so that we emit a unique count of the errors as we emit them. This makes it so that our pattern matching is much more robust against weird pattern match errors that can be difficult to debug due to the errors having unrelated test cases/file check patterns bleed together. Before/end checks eliminate this problem. I updated all of the relevant test cases. The reason /why/ I am doing this though is that I am going to be adding support to the LinearLifetimeChecker for flagging objects that are outside of the lifetime that we are verifying (meaning either before or after). This is going to cause me to need to track /all/ non consuming uses when performing linear lifetime checks and thus most likely emit more errors. I was finding it to be difficult to update the current tests given the state of the world before this patch, so I was inspired to clean this up to satisfy practical as well as debt concerns.
1 parent a6fc9b3 commit 973a82e

File tree

7 files changed

+436
-322
lines changed

7 files changed

+436
-322
lines changed

include/swift/SIL/LinearLifetimeChecker.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class LinearLifetimeChecker {
4848
public:
4949
class Error;
5050
struct ErrorBehaviorKind;
51+
class ErrorBuilder;
5152

5253
private:
5354
friend class SILOwnershipVerifier;
@@ -97,16 +98,16 @@ class LinearLifetimeChecker {
9798
/// to leak. Can be used to insert missing destroys.
9899
Error checkValue(SILValue value, ArrayRef<Operand *> consumingUses,
99100
ArrayRef<Operand *> nonConsumingUses,
100-
ErrorBehaviorKind errorBehavior);
101+
ErrorBuilder &errorBuilder);
101102

102103
Error checkValue(SILValue value, ArrayRef<Operand *> consumingUses,
103104
ArrayRef<Operand *> nonConsumingUses,
104-
ErrorBehaviorKind errorBehavior,
105+
ErrorBuilder &errorBuilder,
105106
function_ref<void(SILBasicBlock *)> leakingBlockCallback);
106107

107108
Error checkValueImpl(
108109
SILValue value, ArrayRef<Operand *> consumingUses,
109-
ArrayRef<Operand *> nonConsumingUses, ErrorBehaviorKind errorBehavior,
110+
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder,
110111
Optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback);
111112
};
112113

lib/SIL/Verifier/LinearLifetimeChecker.cpp

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
using namespace swift;
3434

35+
unsigned LinearLifetimeChecker::ErrorBuilder::errorMessageCount = 0;
36+
3537
//===----------------------------------------------------------------------===//
3638
// Declarations
3739
//===----------------------------------------------------------------------===//
@@ -54,9 +56,12 @@ struct State {
5456
// defining block.
5557
SILInstruction *beginInst;
5658

57-
/// The result error object that use to signal either that no errors were
58-
/// found or if errors are found the specific type of error that was found.
59-
LinearLifetimeChecker::Error error;
59+
/// A builder object that we use to build a LinearLifetimeChecker::Error
60+
/// object that describes exhaustively the set of errors that we encountered.
61+
///
62+
/// It also handles any asserts/messages that need to be emitted if we are
63+
/// supposed to fail hard.
64+
LinearLifetimeChecker::ErrorBuilder errorBuilder;
6065

6166
/// The blocks that we have already visited.
6267
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks;
@@ -87,20 +92,20 @@ struct State {
8792
SmallSetVector<SILBasicBlock *, 8> successorBlocksThatMustBeVisited;
8893

8994
State(SILValue value, SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks,
90-
LinearLifetimeChecker::ErrorBehaviorKind errorBehavior,
95+
LinearLifetimeChecker::ErrorBuilder errorBuilder,
9196
Optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
9297
ArrayRef<Operand *> consumingUses, ArrayRef<Operand *> nonConsumingUses)
9398
: value(value), beginInst(value->getDefiningInsertionPoint()),
94-
error(errorBehavior), visitedBlocks(visitedBlocks),
99+
errorBuilder(errorBuilder), visitedBlocks(visitedBlocks),
95100
leakingBlockCallback(leakingBlockCallback),
96101
consumingUses(consumingUses), nonConsumingUses(nonConsumingUses) {}
97102

98103
State(SILBasicBlock *beginBlock,
99104
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks,
100-
LinearLifetimeChecker::ErrorBehaviorKind errorBehavior,
105+
LinearLifetimeChecker::ErrorBuilder &errorBuilder,
101106
Optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
102107
ArrayRef<Operand *> consumingUses, ArrayRef<Operand *> nonConsumingUses)
103-
: value(), beginInst(&*beginBlock->begin()), error(errorBehavior),
108+
: value(), beginInst(&*beginBlock->begin()), errorBuilder(errorBuilder),
104109
visitedBlocks(visitedBlocks),
105110
leakingBlockCallback(leakingBlockCallback),
106111
consumingUses(consumingUses), nonConsumingUses(nonConsumingUses) {}
@@ -176,10 +181,9 @@ void State::initializeAllNonConsumingUses(
176181
[&use](const SILInstruction &inst) -> bool {
177182
return use->getUser() == &inst;
178183
}) == userBlock->end()) {
179-
error.handleUseAfterFree([&] {
180-
llvm::errs() << "Function: '"
181-
<< getBeginBlock()->getParent()->getName() << "'\n"
182-
<< "Found use before def?!\n"
184+
185+
errorBuilder.handleUseAfterFree([&] {
186+
llvm::errs() << "Found use before def?!\n"
183187
<< "Value: ";
184188
if (auto v = value) {
185189
llvm::errs() << *v;
@@ -264,10 +268,8 @@ void State::initializeConsumingUse(Operand *consumingUse,
264268
if (blocksWithConsumingUses.insert(userBlock).second)
265269
return;
266270

267-
error.handleOverConsume([&] {
268-
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
269-
<< "'\n"
270-
<< "Found over consume?!\n";
271+
errorBuilder.handleOverConsume([&] {
272+
llvm::errs() << "Found over consume?!\n";
271273
if (auto v = value) {
272274
llvm::errs() << "Value: " << *v;
273275
} else {
@@ -298,10 +300,8 @@ void State::checkForSameBlockUseAfterFree(Operand *consumingUse,
298300
[&nonConsumingUse](const SILInstruction &i) -> bool {
299301
return nonConsumingUse->getUser() == &i;
300302
}) != userBlock->end()) {
301-
error.handleUseAfterFree([&] {
302-
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
303-
<< "'\n"
304-
<< "Found use after free?!\n"
303+
errorBuilder.handleUseAfterFree([&] {
304+
llvm::errs() << "Found use after free?!\n"
305305
<< "Value: ";
306306
if (auto v = value) {
307307
llvm::errs() << *v;
@@ -336,10 +336,8 @@ void State::checkPredsForDoubleConsume(Operand *consumingUse,
336336
}
337337
}
338338

339-
error.handleOverConsume([&] {
340-
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
341-
<< "'\n"
342-
<< "Found over consume?!\n"
339+
errorBuilder.handleOverConsume([&] {
340+
llvm::errs() << "Found over consume?!\n"
343341
<< "Value: ";
344342
if (auto v = value) {
345343
llvm::errs() << *v;
@@ -368,10 +366,8 @@ void State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
368366
}
369367
}
370368

371-
error.handleOverConsume([&] {
372-
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
373-
<< "'\n"
374-
<< "Found over consume?!\n"
369+
errorBuilder.handleOverConsume([&] {
370+
llvm::errs() << "Found over consume?!\n"
375371
<< "Value: ";
376372
if (auto v = value) {
377373
llvm::errs() << *v;
@@ -472,19 +468,17 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
472468
}
473469

474470
// If we are supposed to error on leaks, do so now.
475-
error.handleLeak([&] {
476-
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
477-
<< "'\n"
478-
<< "Error! Found a leak due to a consuming post-dominance "
471+
errorBuilder.handleLeak([&] {
472+
llvm::errs() << "Error! Found a leak due to a consuming post-dominance "
479473
"failure!\n";
480474
if (auto v = value) {
481475
llvm::errs() << "Value: " << *value;
482476
} else {
483477
llvm::errs() << "Value: N/A\n";
484478
}
485-
llvm::errs() << " Post Dominating Failure Blocks:\n";
479+
llvm::errs() << "Post Dominating Failure Blocks:\n";
486480
for (auto *succBlock : successorBlocksThatMustBeVisited) {
487-
llvm::errs() << " bb" << succBlock->getDebugID();
481+
llvm::errs() << "bb" << succBlock->getDebugID();
488482
}
489483
llvm::errs() << '\n';
490484
});
@@ -506,10 +500,8 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
506500
continue;
507501
}
508502

509-
error.handleUseAfterFree([&] {
510-
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
511-
<< "'\n"
512-
<< "Found use after free due to unvisited non lifetime "
503+
errorBuilder.handleUseAfterFree([&] {
504+
llvm::errs() << "Found use after free due to unvisited non lifetime "
513505
"ending uses?!\n"
514506
<< "Value: ";
515507
if (auto v = value) {
@@ -518,7 +510,7 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
518510
llvm::errs() << "N/A. \n";
519511
}
520512

521-
llvm::errs() << " Remaining Users:\n";
513+
llvm::errs() << "Remaining Users:\n";
522514
for (auto &pair : blocksWithNonConsumingUses) {
523515
llvm::errs() << "User:" << *pair.second->getUser() << "Block: bb"
524516
<< pair.first->getDebugID() << "\n";
@@ -534,12 +526,12 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
534526

535527
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
536528
SILValue value, ArrayRef<Operand *> consumingUses,
537-
ArrayRef<Operand *> nonConsumingUses, ErrorBehaviorKind errorBehavior,
529+
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder,
538530
Optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback) {
539531
assert((!consumingUses.empty() || !deadEndBlocks.empty()) &&
540532
"Must have at least one consuming user?!");
541533

542-
State state(value, visitedBlocks, errorBehavior, leakingBlockCallback,
534+
State state(value, visitedBlocks, errorBuilder, leakingBlockCallback,
543535
consumingUses, nonConsumingUses);
544536

545537
// First add our non-consuming uses and their blocks to the
@@ -575,20 +567,18 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
575567
return useParent != value->getParentBlock() &&
576568
!deadEndBlocks.isDeadEnd(useParent);
577569
})) {
578-
state.error.handleUseAfterFree([&] {
579-
llvm::errs() << "Function: '" << value->getFunction()->getName()
580-
<< "'\n"
581-
<< "Found use after free due to unvisited non lifetime "
570+
state.errorBuilder.handleUseAfterFree([&] {
571+
llvm::errs() << "Found use after free due to unvisited non lifetime "
582572
"ending uses?!\n"
583-
<< "Value: " << *value << " Remaining Users:\n";
573+
<< "Value: " << *value << "Remaining Users:\n";
584574
for (const auto &use : nonConsumingUses) {
585575
llvm::errs() << "User: " << *use->getUser();
586576
}
587577
llvm::errs() << "\n";
588578
});
589579
}
590580

591-
return state.error;
581+
return std::move(state.errorBuilder).getFinalError();
592582
}
593583

594584
// Ok, we may have multiple consuming uses. Add the user block of each of our
@@ -623,29 +613,31 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
623613
// ...and then check that the end state shows that we have a valid linear
624614
// typed value.
625615
state.checkDataflowEndState(deadEndBlocks);
626-
return state.error;
616+
return std::move(state.errorBuilder).getFinalError();
627617
}
628618

629619
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValue(
630620
SILValue value, ArrayRef<Operand *> consumingUses,
631-
ArrayRef<Operand *> nonConsumingUses, ErrorBehaviorKind errorBehavior) {
632-
return checkValueImpl(value, consumingUses, nonConsumingUses, errorBehavior,
621+
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder) {
622+
return checkValueImpl(value, consumingUses, nonConsumingUses, errorBuilder,
633623
None);
634624
}
635625

636626
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValue(
637627
SILValue value, ArrayRef<Operand *> consumingUses,
638-
ArrayRef<Operand *> nonConsumingUses, ErrorBehaviorKind errorBehavior,
628+
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder,
639629
function_ref<void(SILBasicBlock *)> leakingBlocksCallback) {
640-
return checkValueImpl(value, consumingUses, nonConsumingUses, errorBehavior,
630+
return checkValueImpl(value, consumingUses, nonConsumingUses, errorBuilder,
641631
leakingBlocksCallback);
642632
}
643633

644634
bool LinearLifetimeChecker::completeConsumingUseSet(
645635
SILValue value, Operand *consumingUse,
646636
function_ref<void(SILBasicBlock::iterator)> visitor) {
637+
ErrorBuilder errorBuilder(*value->getFunction(),
638+
ErrorBehaviorKind::ReturnFalse);
647639
auto error =
648-
checkValue(value, {consumingUse}, {}, ErrorBehaviorKind::ReturnFalse,
640+
checkValue(value, {consumingUse}, {}, errorBuilder,
649641
[&](SILBasicBlock *block) { return visitor(block->begin()); });
650642

651643
if (!error.getFoundError()) {
@@ -659,7 +651,8 @@ bool LinearLifetimeChecker::completeConsumingUseSet(
659651
bool LinearLifetimeChecker::validateLifetime(
660652
SILValue value, ArrayRef<Operand *> consumingUses,
661653
ArrayRef<Operand *> nonConsumingUses) {
662-
return !checkValue(value, consumingUses, nonConsumingUses,
663-
ErrorBehaviorKind::ReturnFalse)
654+
ErrorBuilder errorBuilder(*value->getFunction(),
655+
ErrorBehaviorKind::ReturnFalse);
656+
return !checkValue(value, consumingUses, nonConsumingUses, errorBuilder)
664657
.getFoundError();
665658
}

0 commit comments

Comments
 (0)