//===--- 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 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 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 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 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 lifetimeEndingUsers; /// The list of non lifetime ending users that we found. Only valid if check /// is successful. SmallVector 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 implicitRegularUsers; /// The set of blocks that we have visited. SmallPtrSetImpl &visitedBlocks; public: SILValueOwnershipChecker( DeadEndBlocks &deadEndBlocks, SILValue value, ErrorBehaviorKind errorBehavior, llvm::SmallPtrSetImpl &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 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; using user_array = TransformArrayRef; /// 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(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(regularUsers), transform); } private: bool checkUses(); bool gatherUsers(SmallVectorImpl &lifetimeEndingUsers, SmallVectorImpl ®ularUsers, SmallVectorImpl &implicitRegularUsers); bool checkValueWithoutLifetimeEndingUses(); bool checkFunctionArgWithoutLifetimeEndingUses(SILFunctionArgument *arg); bool checkYieldWithoutLifetimeEndingUses(BeginApplyResult *yield); bool isGuaranteedFunctionArgWithLifetimeEndingUses( SILFunctionArgument *arg, const SmallVectorImpl &lifetimeEndingUsers) const; bool isSubobjectProjectionWithLifetimeEndingUses( SILValue value, const SmallVectorImpl &lifetimeEndingUsers) const; /// Depending on our initialization, either return false or call Func and /// throw an error. bool handleError(function_ref &&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 &lifetimeEndingUsers, SmallVectorImpl &nonLifetimeEndingUsers, SmallVectorImpl &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 users; std::copy(value->use_begin(), value->use_end(), std::back_inserter(users)); auto addCondBranchToList = [](SmallVectorImpl &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(user)) { addCondBranchToList(lifetimeEndingUsers, cbi, op->getOperandNumber()); } else { lifetimeEndingUsers.emplace_back(user); } } else { LLVM_DEBUG(llvm::dbgs() << " Regular User: " << *user); if (auto *cbi = dyn_cast(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 endBorrowInsts; for (unsigned i : indices(nonLifetimeEndingUsers)) { if (auto *bbi = dyn_cast( nonLifetimeEndingUsers[i].getInst())) { copy(bbi->getEndBorrows(), 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(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(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(value)) { if (checkFunctionArgWithoutLifetimeEndingUses(arg)) { return true; } } if (auto *yield = dyn_cast(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 &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 &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(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(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 liveBlocks; if (deadEndBlocks) { SILValueOwnershipChecker(*deadEndBlocks, *this, errorBehavior, liveBlocks) .check(); } else { DeadEndBlocks deadEndBlocks(f); SILValueOwnershipChecker(deadEndBlocks, *this, errorBehavior, liveBlocks) .check(); } #endif }