Files
swift-mirror/lib/SIL/LinearLifetimeChecker.cpp
Michael Gottesman 42498adc3c [sil-ownership] Tighten up the verification of guaranteed phi arguments.
Now we properly enforce that all guaranteed phi arguments's lifetime is
completely enclosed by the lifetimes of the phi argument's inputs. This is
implemented by inserting an "implicit" regular use into the use stream of the
phi argument's inputs. This is nice since it ensures that when we are verifying
ownership of a specific argument, we will not walk through all
subarguments... instead we just need to verify that the end_borrow is completely
enclosed within the lifetime of the input guaranteed value.

I also fixed a case where we were bailing early out of the SIL Ownership
Verifier causing us to emit an incorrect error. This error would only occur if
we already diagnosed an error.

I also re-added all of the tests that I removed when I replaced
end_borrow_argument with end_borrow.

rdar://33440767
2018-09-06 15:48:40 -07:00

518 lines
21 KiB
C++

//===--- LinearLifetimeChecker.cpp ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This file defines a linear lifetime checker for SIL. A value with a linear
/// lifetime is defined as a value that is guaranteed to be consuming exactly
/// once along any path through the program and has a guarantee that all
/// non-consuming uses and the initial value are joint-postdominated by the set
/// of consuming uses.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-linear-lifetime-checker"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/BranchPropagatedUser.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILFunction.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
using namespace swift;
using namespace swift::ownership;
//===----------------------------------------------------------------------===//
// Declarations
//===----------------------------------------------------------------------===//
namespace {
using BrPropUserAndBlockPair = std::pair<BranchPropagatedUser, SILBasicBlock *>;
struct State {
/// The value that we are checking.
SILValue value;
/// The behavior of the checker when we detect an error. Can either be
/// returning false, returning false with a message emitted to stderr, or an
/// assert.
ErrorBehaviorKind errorBehavior;
/// The blocks that we have already visited.
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks;
/// The set of blocks with consuming uses.
SmallPtrSet<SILBasicBlock *, 8> blocksWithConsumingUses;
/// The set of blocks with non-consuming uses and the associated
/// non-consuming use SILInstruction.
llvm::SmallDenseMap<SILBasicBlock *, BranchPropagatedUser, 8>
blocksWithNonConsumingUses;
/// The worklist that we use when performing our block dataflow.
SmallVector<SILBasicBlock *, 32> worklist;
/// A list of successor blocks that we must visit by the time the algorithm
/// terminates.
SmallPtrSet<SILBasicBlock *, 8> successorBlocksThatMustBeVisited;
State(SILValue value, SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks,
ErrorBehaviorKind errorBehavior)
: value(value), errorBehavior(errorBehavior),
visitedBlocks(visitedBlocks) {}
void initializeAllNonConsumingUses(
ArrayRef<BranchPropagatedUser> nonConsumingUsers);
bool initializeAllConsumingUses(
ArrayRef<BranchPropagatedUser> consumingUsers,
SmallVectorImpl<BrPropUserAndBlockPair> &predsToAddToWorklist);
/// Initializes state for a consuming use. Returns true if we have not yet
/// seen a consuming use in the same block yet. Returns false if detect such a
/// condition so users know that a use-after-free was detected.
bool initializeConsumingUse(BranchPropagatedUser consumingUser,
SILBasicBlock *userBlock);
/// Returns true if the given block contains a non-consuming use that is
/// strictly later in the block than a consuming use. If all
/// non-consuming uses are before the consuming use, the block is
/// removed from the blocksWithNonConsumingUses map to show that the uses
/// were found to properly be post-dominated by a consuming use.
bool checkForSameBlockUseAfterFree(BranchPropagatedUser consumingUser,
SILBasicBlock *userBlock);
/// Once we have marked all of our producing blocks.
bool checkPredsForDoubleConsume(BranchPropagatedUser consumingUser,
SILBasicBlock *userBlock);
bool checkPredsForDoubleConsume(SILBasicBlock *userBlock);
/// Once we have setup all of our consuming/non-consuming blocks and have
/// validated that all intra-block dataflow is safe, perform the inter-block
/// dataflow.
bool performDataflow(DeadEndBlocks &deBlocks);
/// After we have performed the dataflow, check the end state of our dataflow
/// for validity. If this is a linear typed value, return true. Return false
/// otherwise.
bool checkDataflowEndState(DeadEndBlocks &deBlocks);
/// Depending on our initialization, either return false or call Func and
/// throw an error.
bool handleError(llvm::function_ref<void()> &&messagePrinterFunc) const {
if (errorBehavior.shouldPrintMessage()) {
messagePrinterFunc();
}
if (errorBehavior.shouldReturnFalse()) {
return false;
}
assert(errorBehavior.shouldAssert() && "At this point, we should assert");
llvm_unreachable("triggering standard assertion failure routine");
}
};
} // end anonymous namespace
//===----------------------------------------------------------------------===//
// Non Consuming Use Initialization
//===----------------------------------------------------------------------===//
void State::initializeAllNonConsumingUses(
ArrayRef<BranchPropagatedUser> nonConsumingUsers) {
for (BranchPropagatedUser user : nonConsumingUsers) {
auto *userBlock = user.getParent();
// First try to associate User with User->getParent().
auto result =
blocksWithNonConsumingUses.insert(std::make_pair(userBlock, user));
// If the insertion succeeds, then we know that there is no more work to
// be done, so process the next use.
if (result.second)
continue;
// If the insertion fails, then we have at least two non-consuming
// uses in the same block. Since we are performing a liveness type of
// dataflow, we only need the last non-consuming use to show that all
// consuming uses post dominate both.
//
// We begin by checking if the second use is a cond_br use from the previous
// block. In such a case, we always use the already stored value and since
// it is guaranteed to be later than the cond_br use.
if (user.isCondBranchUser()) {
continue;
}
// Then, we check if Use is after Result.first->second in the use list. If
// Use is not later, then we wish to keep the already mapped value, not use,
// so continue.
if (std::find_if(result.first->second.getIterator(), userBlock->end(),
[&user](const SILInstruction &i) -> bool {
return user == &i;
}) == userBlock->end()) {
continue;
}
// At this point, we know that user is later in the Block than
// result.first->second, so store user instead.
result.first->second = user;
}
}
//===----------------------------------------------------------------------===//
// Consuming Use Initialization
//===----------------------------------------------------------------------===//
bool State::initializeAllConsumingUses(
ArrayRef<BranchPropagatedUser> consumingUses,
SmallVectorImpl<std::pair<BranchPropagatedUser, SILBasicBlock *>>
&predsToAddToWorklist) {
bool noErrors = true;
for (BranchPropagatedUser user : consumingUses) {
SILBasicBlock *userBlock = user.getParent();
// First initialize our state for the consuming user. This returns false if
// we found another consuming instruction associated with userBlock and true
// if we successfully associated user with userBlock.
if (!initializeConsumingUse(user, userBlock)) {
// We already handled the error.
noErrors &= handleError([] {});
}
// Then check if the given block has a use after free.
if (checkForSameBlockUseAfterFree(user, userBlock)) {
// We already handled the error.
noErrors &= handleError([] {});
}
// If this user is in the same block as the value, do not visit
// predecessors. We must be extra tolerant here since we allow for
// unreachable code.
if (userBlock == value->getParentBlock())
continue;
// Then for each predecessor of this block...
for (auto *pred : userBlock->getPredecessorBlocks()) {
// If this block is not a block that we have already put on the list, add
// it to the worklist.
predsToAddToWorklist.push_back({user, pred});
}
}
return noErrors;
}
bool State::initializeConsumingUse(BranchPropagatedUser consumingUser,
SILBasicBlock *userBlock) {
// Map this user to the block. If we already have a value for the block, then
// we have a double consume and need to fail.
if (blocksWithConsumingUses.insert(userBlock).second)
return true;
return handleError([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
<< "Found over consume?!\n"
<< "Value: " << *value << "User: " << *consumingUser
<< "Block: bb" << userBlock->getDebugID() << "\n\n";
});
}
bool State::checkForSameBlockUseAfterFree(BranchPropagatedUser consumingUser,
SILBasicBlock *userBlock) {
// If we do not have any consuming uses in the same block as our
// consuming user, then we can not have a same block use-after-free.
auto iter = blocksWithNonConsumingUses.find(userBlock);
if (iter == blocksWithNonConsumingUses.end())
return false;
BranchPropagatedUser nonConsumingUser = iter->second;
// Make sure that the non-consuming use is before the consuming
// use. Otherwise, we have a use after free.
// First check if our consuming user is a cond_br. In such a case, we
// always consider the non-consuming use to be a use after free since
// the cond branch user is in a previous block. So just bail early.
if (consumingUser.isCondBranchUser()) {
return !handleError([&]() {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
<< "Found use after free?!\n"
<< "Value: " << *value
<< "Consuming User: " << *consumingUser
<< "Non Consuming User: " << *iter->second << "Block: bb"
<< userBlock->getDebugID() << "\n\n";
});
}
// Ok. At this point, we know that our consuming user is not a cond branch
// user. Check if our non-consuming user is. In such a case, we know that our
// non-consuming user is properly post-dominated so we can ignore the
// consuming use. and continue.
if (nonConsumingUser.isCondBranchUser()) {
blocksWithNonConsumingUses.erase(iter);
return false;
}
// Otherwise, we know that both of our users are non-cond branch users and
// thus must be instructions in the given block. Make sure that the non
// consuming user is strictly before the consuming user.
if (std::find_if(consumingUser.getIterator(), userBlock->end(),
[&nonConsumingUser](const SILInstruction &i) -> bool {
return nonConsumingUser == &i;
}) != userBlock->end()) {
return !handleError([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
<< "Found use after free?!\n"
<< "Value: " << *value
<< "Consuming User: " << *consumingUser
<< "Non Consuming User: " << *iter->second << "Block: bb"
<< userBlock->getDebugID() << "\n\n";
});
}
// Erase the use since we know that it is properly joint post-dominated.
blocksWithNonConsumingUses.erase(iter);
return false;
}
bool State::checkPredsForDoubleConsume(BranchPropagatedUser consumingUser,
SILBasicBlock *userBlock) {
if (!blocksWithConsumingUses.count(userBlock))
return false;
return !handleError([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
<< "Found over consume?!\n"
<< "Value: " << *value << "User: " << *consumingUser
<< "Block: bb" << userBlock->getDebugID() << "\n\n";
});
}
bool State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
if (!blocksWithConsumingUses.count(userBlock))
return false;
return !handleError([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
<< "Found over consume?!\n"
<< "Value: " << *value << "Block: bb"
<< userBlock->getDebugID() << "\n\n";
});
}
//===----------------------------------------------------------------------===//
// Dataflow
//===----------------------------------------------------------------------===//
bool State::performDataflow(DeadEndBlocks &deBlocks) {
LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n");
// Until the worklist is empty...
while (!worklist.empty()) {
// Grab the next block to visit.
SILBasicBlock *block = worklist.pop_back_val();
LLVM_DEBUG(llvm::dbgs() << " Visiting Block: bb" << block->getDebugID()
<< '\n');
// Since the block is on our worklist, we know already that it is not a
// block with lifetime ending uses, due to the invariants of our loop.
// First remove BB from the SuccessorBlocksThatMustBeVisited list. This
// ensures that when the algorithm terminates, we know that BB was not the
// beginning of a non-covered path to the exit.
successorBlocksThatMustBeVisited.erase(block);
// Then remove BB from BlocksWithNonLifetimeEndingUses so we know that
// this block was properly joint post-dominated by our lifetime ending
// users.
blocksWithNonConsumingUses.erase(block);
// Ok, now we know that we do not have an overconsume. If this block does
// not end in a no return function, we need to update our state for our
// successors to make sure by the end of the traversal we visit them.
//
// We must consider such no-return blocks since we may be running during
// SILGen before NoReturn folding has run.
for (auto *succBlock : block->getSuccessorBlocks()) {
// If we already visited the successor, there is nothing to do since we
// already visited the successor.
if (visitedBlocks.count(succBlock))
continue;
// Then check if the successor is a transitively unreachable block. In
// such a case, we ignore it since we are going to leak along that path.
if (deBlocks.isDeadEnd(succBlock))
continue;
// Otherwise, add the successor to our SuccessorBlocksThatMustBeVisited
// set to ensure that we assert if we do not visit it by the end of the
// algorithm.
successorBlocksThatMustBeVisited.insert(succBlock);
}
// If we are at the dominating block of our walk, continue. There is nothing
// further to do since we do not want to visit the predecessors of our
// dominating block. On the other hand, we do want to add its successors to
// the successorBlocksThatMustBeVisited set.
if (block == value->getParentBlock())
continue;
// Then for each predecessor of this block:
//
// 1. If we have visited the predecessor already, then it is not a block
// with lifetime ending uses. If it is a block with uses, then we have a
// double release... so assert. If not, we continue.
//
// 2. We add the predecessor to the worklist if we have not visited it yet.
for (auto *predBlock : block->getPredecessorBlocks()) {
if (checkPredsForDoubleConsume(predBlock)) {
return handleError([] {});
}
if (visitedBlocks.count(predBlock)) {
continue;
}
visitedBlocks.insert(predBlock);
worklist.push_back(predBlock);
}
}
return true;
}
bool State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
// Make sure that we visited all successor blocks that we needed to visit to
// make sure we didn't leak.
if (!successorBlocksThatMustBeVisited.empty()) {
return handleError([&] {
llvm::errs()
<< "Function: '" << value->getFunction()->getName() << "'\n"
<< "Error! Found a leak due to a consuming post-dominance failure!\n"
<< " Value: " << *value << " Post Dominating Failure Blocks:\n";
for (auto *succBlock : successorBlocksThatMustBeVisited) {
llvm::errs() << " bb" << succBlock->getDebugID();
}
llvm::errs() << '\n';
});
}
// Make sure that we do not have any lifetime ending uses left to visit that
// are not transitively unreachable blocks.... so return early.
if (blocksWithNonConsumingUses.empty()) {
return true;
}
// If we do have remaining blocks, then these non lifetime ending uses must be
// outside of our "alive" blocks implying a use-after free.
for (auto &pair : blocksWithNonConsumingUses) {
if (deBlocks.isDeadEnd(pair.first)) {
continue;
}
return handleError([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
<< "Found use after free due to unvisited non lifetime "
"ending uses?!\n"
<< "Value: " << *value << " Remaining Users:\n";
for (auto &pair : blocksWithNonConsumingUses) {
llvm::errs() << "User:" << *pair.second << "Block: bb"
<< pair.first->getDebugID() << "\n";
}
llvm::errs() << "\n";
});
}
// If all of our remaining blocks were dead uses, then return true. We are
// good.
return true;
}
//===----------------------------------------------------------------------===//
// Top Level Entrypoints
//===----------------------------------------------------------------------===//
bool swift::valueHasLinearLifetime(
SILValue value, ArrayRef<BranchPropagatedUser> consumingUses,
ArrayRef<BranchPropagatedUser> nonConsumingUses,
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks, DeadEndBlocks &deBlocks,
ErrorBehaviorKind errorBehavior) {
assert(!consumingUses.empty() && "Must have at least one consuming user?!");
State state(value, visitedBlocks, errorBehavior);
// First add our non-consuming uses and their blocks to the
// blocksWithNonConsumingUses map. While we do this, if we have multiple uses
// in the same block, we only accept the last use since from a liveness
// perspective that is all we care about.
state.initializeAllNonConsumingUses(nonConsumingUses);
// Then, we go through each one of our consuming users performing the
// following operation:
//
// 1. Verifying that no two consuming users are in the same block. This
// is accomplished by adding the user blocks to the blocksWithConsumingUsers
// list. This avoids double consumes.
//
// 2. Verifying that no predecessor is a block with a consuming use. The
// reason why this is necessary is because we wish to not add elements to the
// worklist twice. Thus we want to check if we have already visited a
// predecessor.
SmallVector<BrPropUserAndBlockPair, 32> predsToAddToWorklist;
state.initializeAllConsumingUses(consumingUses, predsToAddToWorklist);
// If we have a singular consuming use and it is in the same block as value's
// def, we bail early. Any use-after-frees due to non-consuming uses would
// have been detected by initializing our consuming uses. So we are done.
if (consumingUses.size() == 1 &&
consumingUses[0].getParent() == value->getParentBlock()) {
return true;
}
// Ok, we may have multiple consuming uses. Add the user block of each of our
// consuming users to the visited list since we do not want them to be added
// to the successors to visit set.
for (const auto &i : consumingUses) {
state.visitedBlocks.insert(i.getParent());
}
// Now that we have marked all of our producing blocks, we go through our
// predsToAddToWorklist list and add our preds, making sure that none of these
// preds are in blocksWithConsumingUses. This is important so that we do not
// need to re-process.
for (auto pair : predsToAddToWorklist) {
BranchPropagatedUser user = pair.first;
SILBasicBlock *predBlock = pair.second;
// Make sure that the predecessor is not in our blocksWithConsumingUses
// list.
if (state.checkPredsForDoubleConsume(user, predBlock)) {
return state.handleError([] {});
}
if (!state.visitedBlocks.insert(predBlock).second)
continue;
state.worklist.push_back(predBlock);
}
// Now that our algorithm is completely prepared, run the
// dataflow... If we find a failure, return false.
if (!state.performDataflow(deBlocks))
return false;
// ...and then check that the end state shows that we have a valid linear
// typed value.
return state.checkDataflowEndState(deBlocks);
}