blob: 8dcd7d65a8773003ca1d5fc0406a5c53876786c0 [file] [log] [blame]
//===--- SILOwnershipVerifier.cpp -----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-ownership-verifier"
#include "swift/AST/ASTContext.h"
#include "swift/AST/AnyFunctionRef.h"
#include "swift/AST/Decl.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Module.h"
#include "swift/AST/Types.h"
#include "swift/Basic/Range.h"
#include "swift/Basic/STLExtras.h"
#include "swift/Basic/TransformArrayRef.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/BranchPropagatedUser.h"
#include "swift/SIL/Dominance.h"
#include "swift/SIL/DynamicCasts.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/PrettyStackTrace.h"
#include "swift/SIL/Projection.h"
#include "swift/SIL/SILBuiltinVisitor.h"
#include "swift/SIL/SILDebugScope.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILModule.h"
#include "swift/SIL/SILOpenedArchetypesTracker.h"
#include "swift/SIL/SILVTable.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SIL/TypeLowering.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include <algorithm>
using namespace swift;
using namespace swift::ownership;
// This is an option to put the SILOwnershipVerifier in testing mode. This
// causes the following:
//
// 1. Instead of printing an error message and aborting, the verifier will print
// the message and continue. This allows for FileCheck testing of the verifier.
//
// 2. SILInstruction::verifyOperandOwnership() is disabled. This is used for
// verification in SILBuilder. This causes errors to be printed twice, once when
// we build the IR and a second time when we perform a full verification of the
// IR. For testing purposes, we just want the later.
llvm::cl::opt<bool> IsSILOwnershipVerifierTestingEnabled(
"sil-ownership-verifier-enable-testing",
llvm::cl::desc("Put the sil ownership verifier in testing mode. See "
"comment in SILOwnershipVerifier.cpp above option for more "
"information."));
/// This is an option to turn off ownership verification on a specific file. We
/// still emit code as if we are in ownership mode, but we do not verify. This
/// is useful for temporarily turning off verification on tests.
static llvm::cl::opt<bool>
DisableOwnershipVerification("disable-sil-ownership-verification");
//===----------------------------------------------------------------------===//
// SILValueOwnershipChecker
//===----------------------------------------------------------------------===//
namespace {
// TODO: This class uses a bunch of global state like variables. It should be
// refactored into a large state object that is used by functions.
class SILValueOwnershipChecker {
/// The result of performing the check.
llvm::Optional<bool> result;
/// A cache of dead-end basic blocks that we use to determine if we can
/// ignore "leaks".
DeadEndBlocks &deadEndBlocks;
/// The value whose ownership we will check.
SILValue value;
/// The action that the checker should perform on detecting an error.
ErrorBehaviorKind errorBehavior;
/// The list of lifetime ending users that we found. Only valid if check is
/// successful.
SmallVector<BranchPropagatedUser, 16> lifetimeEndingUsers;
/// The list of non lifetime ending users that we found. Only valid if check
/// is successful.
SmallVector<BranchPropagatedUser, 16> regularUsers;
/// The list of implicit non lifetime ending users that we found. This
/// consists of instructions like end_borrow that end a scoped lifetime. We
/// must treat those as regular uses and ensure that our value is not
/// destroyed while that sub-scope is valid.
///
/// TODO: Rename to SubBorrowScopeUsers?
SmallVector<BranchPropagatedUser, 4> implicitRegularUsers;
/// The set of blocks that we have visited.
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks;
public:
SILValueOwnershipChecker(
DeadEndBlocks &deadEndBlocks, SILValue value,
ErrorBehaviorKind errorBehavior,
llvm::SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks)
: result(), deadEndBlocks(deadEndBlocks), value(value),
errorBehavior(errorBehavior), visitedBlocks(visitedBlocks) {
assert(value && "Can not initialize a checker with an empty SILValue");
}
~SILValueOwnershipChecker() = default;
SILValueOwnershipChecker(SILValueOwnershipChecker &) = delete;
SILValueOwnershipChecker(SILValueOwnershipChecker &&) = delete;
bool check() {
if (result.hasValue())
return result.getValue();
LLVM_DEBUG(llvm::dbgs() << "Verifying ownership of: " << *value);
result = checkUses();
if (!result.getValue())
return false;
SmallVector<BranchPropagatedUser, 32> allRegularUsers;
copy(regularUsers, std::back_inserter(allRegularUsers));
copy(implicitRegularUsers, std::back_inserter(allRegularUsers));
auto linearLifetimeResult =
valueHasLinearLifetime(value, lifetimeEndingUsers, allRegularUsers,
visitedBlocks, deadEndBlocks, errorBehavior);
result = !linearLifetimeResult.getFoundError();
return result.getValue();
}
using user_array_transform =
std::function<SILInstruction *(BranchPropagatedUser)>;
using user_array = TransformArrayRef<user_array_transform>;
/// A function that returns a range of lifetime ending users found for the
/// given value.
user_array getLifetimeEndingUsers() const {
assert(result.hasValue() && "Can not call until check() is called");
assert(result.getValue() && "Can not call if check() returned false");
user_array_transform transform(
[](BranchPropagatedUser user) -> SILInstruction * {
return user.getInst();
});
return user_array(ArrayRef<BranchPropagatedUser>(lifetimeEndingUsers),
transform);
}
/// A function that returns a range of regular (i.e. "non lifetime ending")
/// users found for the given value.
user_array getRegularUsers() const {
assert(result.hasValue() && "Can not call until check() is called");
assert(result.getValue() && "Can not call if check() returned false");
user_array_transform transform(
[](BranchPropagatedUser user) -> SILInstruction * {
return user.getInst();
});
return user_array(ArrayRef<BranchPropagatedUser>(regularUsers), transform);
}
private:
bool checkUses();
bool gatherUsers(SmallVectorImpl<BranchPropagatedUser> &lifetimeEndingUsers,
SmallVectorImpl<BranchPropagatedUser> &regularUsers,
SmallVectorImpl<BranchPropagatedUser> &implicitRegularUsers);
bool checkValueWithoutLifetimeEndingUses();
bool checkFunctionArgWithoutLifetimeEndingUses(SILFunctionArgument *arg);
bool checkYieldWithoutLifetimeEndingUses(BeginApplyResult *yield);
bool isGuaranteedFunctionArgWithLifetimeEndingUses(
SILFunctionArgument *arg,
const SmallVectorImpl<BranchPropagatedUser> &lifetimeEndingUsers) const;
bool isSubobjectProjectionWithLifetimeEndingUses(
SILValue value,
const SmallVectorImpl<BranchPropagatedUser> &lifetimeEndingUsers) const;
/// Depending on our initialization, either return false or call Func and
/// throw an error.
bool handleError(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
bool SILValueOwnershipChecker::gatherUsers(
SmallVectorImpl<BranchPropagatedUser> &lifetimeEndingUsers,
SmallVectorImpl<BranchPropagatedUser> &nonLifetimeEndingUsers,
SmallVectorImpl<BranchPropagatedUser> &implicitRegularUsers) {
// See if Value is guaranteed. If we are guaranteed and not forwarding, then
// we need to look through subobject uses for more uses. Otherwise, if we are
// forwarding, we do not create any lifetime ending users/non lifetime ending
// users since we verify against our base.
auto ownershipKind = value.getOwnershipKind();
bool isGuaranteed = ownershipKind == ValueOwnershipKind::Guaranteed;
bool isOwned = ownershipKind == ValueOwnershipKind::Owned;
if (isGuaranteed && isGuaranteedForwardingValue(value))
return true;
// Then gather up our initial list of users.
SmallVector<Operand *, 8> users;
std::copy(value->use_begin(), value->use_end(), std::back_inserter(users));
auto addCondBranchToList = [](SmallVectorImpl<BranchPropagatedUser> &list,
CondBranchInst *cbi, unsigned operandIndex) {
if (cbi->isConditionOperandIndex(operandIndex)) {
list.emplace_back(cbi);
return;
}
bool isTrueOperand = cbi->isTrueOperandIndex(operandIndex);
list.emplace_back(cbi, isTrueOperand ? CondBranchInst::TrueIdx
: CondBranchInst::FalseIdx);
};
bool foundError = false;
while (!users.empty()) {
Operand *op = users.pop_back_val();
SILInstruction *user = op->getUser();
// If this op is a type dependent operand, skip it. It is not interesting
// from an ownership perspective.
if (user->isTypeDependentOperand(*op))
continue;
bool isGuaranteedSubValue = false;
if (isGuaranteed && isGuaranteedForwardingInst(op->getUser())) {
isGuaranteedSubValue = true;
}
auto opOwnershipKindMap = op->getOwnershipKindMap(isGuaranteedSubValue);
// If our ownership kind doesn't match, track that we found an error, emit
// an error message optionally and then continue.
if (!opOwnershipKindMap.canAcceptKind(ownershipKind)) {
foundError = true;
// If we did not support /any/ ownership kind, it means that we found a
// conflicting answer so the kind map that was returned is the empty
// map. Put out a more specific error here.
if (!opOwnershipKindMap.data.any()) {
handleError([&]() {
llvm::errs() << "Function: '" << user->getFunction()->getName()
<< "'\n"
<< "Ill-formed SIL! Unable to compute ownership kind "
"map for user?!\n"
<< "For terminator users, check that successors have "
"compatible ownership kinds.\n"
<< "Value: " << op->get() << "User: " << *user
<< "Operand Number: " << op->getOperandNumber() << '\n'
<< "Conv: " << ownershipKind << "\n\n";
});
continue;
}
handleError([&]() {
llvm::errs() << "Function: '" << user->getFunction()->getName() << "'\n"
<< "Have operand with incompatible ownership?!\n"
<< "Value: " << op->get() << "User: " << *user
<< "Operand Number: " << op->getOperandNumber() << '\n'
<< "Conv: " << ownershipKind << '\n'
<< "OwnershipMap:\n"
<< opOwnershipKindMap << '\n';
});
continue;
}
auto lifetimeConstraint =
opOwnershipKindMap.getLifetimeConstraint(ownershipKind);
if (lifetimeConstraint == UseLifetimeConstraint::MustBeInvalidated) {
LLVM_DEBUG(llvm::dbgs() << " Lifetime Ending User: " << *user);
if (auto *cbi = dyn_cast<CondBranchInst>(user)) {
addCondBranchToList(lifetimeEndingUsers, cbi, op->getOperandNumber());
} else {
lifetimeEndingUsers.emplace_back(user);
}
} else {
LLVM_DEBUG(llvm::dbgs() << " Regular User: " << *user);
if (auto *cbi = dyn_cast<CondBranchInst>(user)) {
addCondBranchToList(nonLifetimeEndingUsers, cbi,
op->getOperandNumber());
} else {
nonLifetimeEndingUsers.emplace_back(user);
}
}
// If our base value is not guaranteed, we do not to try to visit
// subobjects.
if (!isGuaranteed) {
// But if we are owned, check if we have any end_borrows. We
// need to treat these as sub-scope users. We can rely on the
// end_borrow to prevent recursion.
if (isOwned) {
// Do a check if any of our users are begin_borrows. If we find such a
// use, then we want to include the end_borrow associated with the
// begin_borrow in our NonLifetimeEndingUser lists.
//
// For correctness reasons we use indices to make sure that we can
// append to NonLifetimeEndingUsers without needing to deal with
// iterator invalidation.
SmallVector<SILInstruction *, 4> endBorrowInsts;
for (unsigned i : indices(nonLifetimeEndingUsers)) {
if (auto *bbi = dyn_cast<BeginBorrowInst>(
nonLifetimeEndingUsers[i].getInst())) {
copy(makeEndBorrowRange(bbi),
std::back_inserter(implicitRegularUsers));
}
}
}
continue;
}
// If we are guaranteed, but are not a guaranteed forwarding inst,
// just continue. This user is just treated as a normal use.
if (!isGuaranteedForwardingInst(user))
continue;
// At this point, we know that we must have a forwarded subobject. Since the
// base type is guaranteed, we know that the subobject is either guaranteed
// or trivial. We now split into two cases, if the user is a terminator or
// not. If we do not have a terminator, then just add the uses of all of
// User's results to the worklist.
if (user->getResults().size()) {
for (SILValue result : user->getResults()) {
if (result.getOwnershipKind() == ValueOwnershipKind::Any) {
continue;
}
// Now, we /must/ have a guaranteed subobject, so let's assert that the
// user is actually guaranteed and add the subobject's users to our
// worklist.
assert(result.getOwnershipKind() == ValueOwnershipKind::Guaranteed &&
"Our value is guaranteed and this is a forwarding instruction. "
"Should have guaranteed ownership as well.");
copy(result->getUses(), std::back_inserter(users));
}
continue;
}
assert(user->getResults().empty());
auto *ti = dyn_cast<TermInst>(user);
if (!ti) {
continue;
}
// Otherwise if we have a terminator, add any as uses any end_borrow to
// ensure that the subscope is completely enclsed within the super scope. We
// require all of our arguments to be either trivial or guaranteed.
for (auto &succ : ti->getSuccessors()) {
auto *succBlock = succ.getBB();
// If we do not have any arguments, then continue.
if (succBlock->args_empty())
continue;
// Otherwise, make sure that all arguments are trivial or guaranteed. If
// we fail, emit an error.
//
// TODO: We could ignore this error and emit a more specific error on the
// actual terminator.
for (auto *succArg : succBlock->getPhiArguments()) {
// *NOTE* We do not emit an error here since we want to allow for more
// specific errors to be found during use_verification.
//
// TODO: Add a flag that associates the terminator instruction with
// needing to be verified. If it isn't verified appropriately, assert
// when the verifier is destroyed.
auto succArgOwnershipKind = succArg->getOwnershipKind();
if (!succArgOwnershipKind.isCompatibleWith(ownershipKind)) {
// This is where the error would go.
continue;
}
// If we have an any value, just continue.
if (succArgOwnershipKind == ValueOwnershipKind::Any)
continue;
// Otherwise add all end_borrow users for this BBArg to the
// implicit regular user list. We know that BBArg must be
// completely joint post-dominated by these users, so we use
// them to ensure that all of BBArg's uses are completely
// enclosed within the end_borrow of this argument.
for (auto *op : succArg->getUses()) {
if (auto *ebi = dyn_cast<EndBorrowInst>(op->getUser())) {
implicitRegularUsers.push_back(ebi);
}
}
}
}
}
// Return true if we did not have an error and false if we did find an error.
//
// The reason why we use this extra variable is to make sure that when we are
// testing, we print out all mismatching pairs rather than just the first.
return !foundError;
}
bool SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses(
SILFunctionArgument *arg) {
switch (arg->getOwnershipKind()) {
case ValueOwnershipKind::Guaranteed:
case ValueOwnershipKind::Unowned:
case ValueOwnershipKind::Any:
return true;
case ValueOwnershipKind::Owned:
break;
}
if (deadEndBlocks.isDeadEnd(arg->getParent()))
return true;
return !handleError([&] {
llvm::errs() << "Function: '" << arg->getFunction()->getName() << "'\n"
<< " Owned function parameter without life ending uses!\n"
<< "Value: " << *arg << '\n';
});
}
bool SILValueOwnershipChecker::checkYieldWithoutLifetimeEndingUses(
BeginApplyResult *yield) {
switch (yield->getOwnershipKind()) {
case ValueOwnershipKind::Guaranteed:
case ValueOwnershipKind::Unowned:
case ValueOwnershipKind::Any:
return true;
case ValueOwnershipKind::Owned:
break;
}
if (deadEndBlocks.isDeadEnd(yield->getParent()->getParent()))
return true;
return !handleError([&] {
llvm::errs() << "Function: '" << yield->getFunction()->getName() << "'\n"
<< " Owned yield without life ending uses!\n"
<< "Value: " << *yield << '\n';
});
}
bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses() {
LLVM_DEBUG(llvm::dbgs() << " No lifetime ending users?! Bailing early.\n");
if (auto *arg = dyn_cast<SILFunctionArgument>(value)) {
if (checkFunctionArgWithoutLifetimeEndingUses(arg)) {
return true;
}
}
if (auto *yield = dyn_cast<BeginApplyResult>(value)) {
if (checkYieldWithoutLifetimeEndingUses(yield)) {
return true;
}
}
// Check if we are a guaranteed subobject. In such a case, we should never
// have lifetime ending uses, since our lifetime is guaranteed by our
// operand, so there is nothing further to do. So just return true.
if (isGuaranteedForwardingValue(value) &&
value.getOwnershipKind() == ValueOwnershipKind::Guaranteed)
return true;
// If we have an unowned value, then again there is nothing left to do.
if (value.getOwnershipKind() == ValueOwnershipKind::Unowned)
return true;
if (auto *parentBlock = value->getParentBlock()) {
if (deadEndBlocks.isDeadEnd(parentBlock)) {
LLVM_DEBUG(llvm::dbgs() << " Ignoring transitively unreachable value "
<< "without users!\n"
<< " Function: '"
<< value->getFunction()->getName() << "'\n"
<< " Value: " << *value << '\n');
return true;
}
}
if (!isValueAddressOrTrivial(value)) {
return !handleError([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n";
if (value.getOwnershipKind() == ValueOwnershipKind::Owned) {
llvm::errs() << "Error! Found a leaked owned value that was never "
"consumed.\n";
} else {
llvm::errs() << "Non trivial values, non address values, and non "
"guaranteed function args must have at least one "
"lifetime ending use?!\n";
}
llvm::errs() << "Value: " << *value << '\n';
});
}
return true;
}
bool SILValueOwnershipChecker::isGuaranteedFunctionArgWithLifetimeEndingUses(
SILFunctionArgument *arg,
const llvm::SmallVectorImpl<BranchPropagatedUser> &lifetimeEndingUsers)
const {
if (arg->getOwnershipKind() != ValueOwnershipKind::Guaranteed)
return true;
return handleError([&] {
llvm::errs() << " Function: '" << arg->getFunction()->getName() << "'\n"
<< " Guaranteed function parameter with life ending uses!\n"
<< " Value: " << *arg;
for (const auto &user : lifetimeEndingUsers) {
llvm::errs() << " Lifetime Ending User: " << *user;
}
llvm::errs() << '\n';
});
}
bool SILValueOwnershipChecker::isSubobjectProjectionWithLifetimeEndingUses(
SILValue value,
const llvm::SmallVectorImpl<BranchPropagatedUser> &lifetimeEndingUsers)
const {
return handleError([&] {
llvm::errs() << " Function: '" << value->getFunction()->getName()
<< "'\n"
<< " Subobject projection with life ending uses!\n"
<< " Value: " << *value;
for (const auto &user : lifetimeEndingUsers) {
llvm::errs() << " Lifetime Ending User: " << *user;
}
llvm::errs() << '\n';
});
}
bool SILValueOwnershipChecker::checkUses() {
LLVM_DEBUG(llvm::dbgs() << " Gathering and classifying uses!\n");
// First go through V and gather up its uses. While we do this we:
//
// 1. Verify that none of the uses are in the same block. This would be an
// overconsume so in this case we assert.
// 2. Verify that the uses are compatible with our ownership convention.
if (!gatherUsers(lifetimeEndingUsers, regularUsers, implicitRegularUsers)) {
// Silently return false if this fails.
//
// If the user pass in a ErrorBehaviorKind that will assert, we
// will have asserted in gatherUsers(). If we get here the user
// asked us to optionally print out a message and indicate that
// the verification failed.
return false;
}
// We can only have no lifetime ending uses if we have:
//
// 1. A trivial typed value.
// 2. An address type value.
// 3. A guaranteed function argument.
//
// In the first two cases, it is easy to see that there is nothing further to
// do but return false.
//
// In the case of a function argument, one must think about the issues a bit
// more. Specifically, we should have /no/ lifetime ending uses of a
// guaranteed function argument, since a guaranteed function argument should
// outlive the current function always.
if (lifetimeEndingUsers.empty() && checkValueWithoutLifetimeEndingUses()) {
return false;
}
LLVM_DEBUG(llvm::dbgs() << " Found lifetime ending users! Performing "
"initial checks\n");
// See if we have a guaranteed function address. Guaranteed function addresses
// should never have any lifetime ending uses.
if (auto *arg = dyn_cast<SILFunctionArgument>(value)) {
if (!isGuaranteedFunctionArgWithLifetimeEndingUses(arg,
lifetimeEndingUsers)) {
return false;
}
}
// Check if we are an instruction that forwards forwards guaranteed
// ownership. In such a case, we are a subobject projection. We should not
// have any lifetime ending uses.
if (isGuaranteedForwardingValue(value) &&
value.getOwnershipKind() == ValueOwnershipKind::Guaranteed) {
if (!isSubobjectProjectionWithLifetimeEndingUses(value,
lifetimeEndingUsers)) {
return false;
}
}
return true;
}
//===----------------------------------------------------------------------===//
// Top Level Entrypoints
//===----------------------------------------------------------------------===//
void SILInstruction::verifyOperandOwnership() const {
#ifndef NDEBUG
if (DisableOwnershipVerification)
return;
if (isStaticInitializerInst())
return;
// If SILOwnership is not enabled, do not perform verification.
if (!getModule().getOptions().VerifySILOwnership)
return;
// If the given function has unqualified ownership or we have been asked by
// the user not to verify this function, there is nothing to verify.
if (!getFunction()->hasOwnership() ||
!getFunction()->shouldVerifyOwnership())
return;
// If we are testing the verifier, bail so we only print errors once when
// performing a full verification, instead of additionally in the SILBuilder.
if (IsSILOwnershipVerifierTestingEnabled)
return;
// If this is a terminator instruction, do not verify in SILBuilder. This is
// because when building a new function, one must create the destination block
// first which is an unnatural pattern and pretty brittle.
if (isa<TermInst>(this))
return;
ErrorBehaviorKind errorBehavior;
if (IsSILOwnershipVerifierTestingEnabled) {
errorBehavior = ErrorBehaviorKind::PrintMessageAndReturnFalse;
} else {
errorBehavior = ErrorBehaviorKind::PrintMessageAndAssert;
}
for (const Operand &op : getAllOperands()) {
// Skip type dependence operands.
if (isTypeDependentOperand(op))
continue;
SILValue opValue = op.get();
auto operandOwnershipKindMap = op.getOwnershipKindMap();
auto valueOwnershipKind = opValue.getOwnershipKind();
if (operandOwnershipKindMap.canAcceptKind(valueOwnershipKind))
continue;
if (errorBehavior.shouldPrintMessage()) {
llvm::errs() << "Found an operand with a value that is not compatible "
"with the operand's operand ownership kind map.\n";
llvm::errs() << "Value: " << opValue;
llvm::errs() << "Value Ownership Kind: " << valueOwnershipKind << "\n";
llvm::errs() << "Instruction: " << *this;
llvm::errs() << "Operand Ownership Kind Map: " << operandOwnershipKindMap;
}
if (errorBehavior.shouldReturnFalse())
continue;
assert(errorBehavior.shouldAssert() &&
"At this point, we are expected to assert");
llvm_unreachable("triggering standard assertion failure routine");
}
#endif
}
void SILValue::verifyOwnership(DeadEndBlocks *deadEndBlocks) const {
#ifndef NDEBUG
if (DisableOwnershipVerification)
return;
// Since we do not have SILUndef, we now know that getFunction() should return
// a real function. Assert in case this assumption is no longer true.
SILFunction *f = (*this)->getFunction();
assert(f && "Instructions and arguments should have a function");
// If the given function has unqualified ownership or we have been asked by
// the user not to verify this function, there is nothing to verify.
if (!f->hasOwnership() || !f->shouldVerifyOwnership())
return;
ErrorBehaviorKind errorBehavior;
if (IsSILOwnershipVerifierTestingEnabled) {
errorBehavior = ErrorBehaviorKind::PrintMessageAndReturnFalse;
} else {
errorBehavior = ErrorBehaviorKind::PrintMessageAndAssert;
}
llvm::SmallPtrSet<SILBasicBlock *, 32> liveBlocks;
if (deadEndBlocks) {
SILValueOwnershipChecker(*deadEndBlocks, *this, errorBehavior,
liveBlocks)
.check();
} else {
DeadEndBlocks deadEndBlocks(f);
SILValueOwnershipChecker(deadEndBlocks, *this, errorBehavior,
liveBlocks)
.check();
}
#endif
}