mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
There are several kinds of scopes at which it is required that an
address be initialized:
(1) the whole function -- for inout argument to the function
(2) the region a coroutine is active -- for an inout yielded by a
coroutine into the function
(3) the region of a memory access -- for a `begin_access [modify]`.
The move checker enforces that they are initialized at that point by
adding instructions at which the field must be live to liveness.
Previously, all such scopes used the end of the function as the point at
which the memory had to have been reinitialized. Here, the relevant end
of scope markers are used instead.
More importantly, here the diagnostic is made to vary--the diagnostic,
that is, that is issued in the face an address not being initialized at
the end of these different kind of scopes.
1014 lines
37 KiB
C++
1014 lines
37 KiB
C++
//===--- MoveOnlyDiagnostics.cpp ------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2022 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-move-only-checker"
|
|
|
|
#include "MoveOnlyDiagnostics.h"
|
|
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/DiagnosticsSIL.h"
|
|
#include "swift/AST/Stmt.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/SIL/BasicBlockBits.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SIL/FieldSensitivePrunedLiveness.h"
|
|
#include "swift/SIL/SILArgument.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#include "MoveOnlyTypeUtils.h"
|
|
using namespace swift;
|
|
using namespace swift::siloptimizer;
|
|
|
|
static llvm::cl::opt<bool> SilentlyEmitDiagnostics(
|
|
"move-only-diagnostics-silently-emit-diagnostics",
|
|
llvm::cl::desc(
|
|
"For testing purposes, emit the diagnostic silently so we can "
|
|
"filecheck the result of emitting an error from the move checkers"),
|
|
llvm::cl::init(false));
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Utilities
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <typename... T, typename... U>
|
|
static void diagnose(ASTContext &context, SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
// See if the consuming use is an owned moveonly_to_copyable whose only
|
|
// user is a return. In that case, use the return loc instead. We do this
|
|
// b/c it is illegal to put a return value location on a non-return value
|
|
// instruction... so we have to hack around this slightly.
|
|
auto loc = inst->getLoc();
|
|
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(inst)) {
|
|
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
|
|
loc = ri->getLoc();
|
|
}
|
|
}
|
|
|
|
// If for testing reasons we want to return that we emitted an error but not
|
|
// emit the actual error itself, return early.
|
|
if (SilentlyEmitDiagnostics)
|
|
return;
|
|
|
|
context.Diags.diagnose(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static void diagnose(ASTContext &context, SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
// If for testing reasons we want to return that we emitted an error but not
|
|
// emit the actual error itself, return early.
|
|
if (SilentlyEmitDiagnostics)
|
|
return;
|
|
|
|
context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
/// Helper function that actually implements getVariableNameForValue. Do not
|
|
/// call it directly! Call the unary variants instead.
|
|
static void getVariableNameForValue(SILValue value2,
|
|
SILValue searchValue,
|
|
SmallString<64> &resultingString) {
|
|
// Before we do anything, lets see if we have an exact debug_value on our
|
|
// mmci. In such a case, we can end early and are done.
|
|
if (auto *use = getAnyDebugUse(value2)) {
|
|
if (auto debugVar = DebugVarCarryingInst(use->getUser())) {
|
|
assert(debugVar.getKind() == DebugVarCarryingInst::Kind::DebugValue);
|
|
resultingString += debugVar.getName();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Otherwise, we need to look at our mark_unresolved_non_copyable_value's
|
|
// operand.
|
|
StackList<llvm::PointerUnion<SILInstruction *, SILValue>> variableNamePath(
|
|
value2->getFunction());
|
|
while (searchValue) {
|
|
if (auto *allocInst = dyn_cast<AllocationInst>(searchValue)) {
|
|
// If the instruction itself doesn't carry any variable info, see whether
|
|
// it's copied from another place that does.
|
|
if (!allocInst->getDecl()) {
|
|
if (auto copy = allocInst->getSingleUserOfType<CopyAddrInst>()) {
|
|
if (copy->getDest() == allocInst
|
|
&& !copy->isTakeOfSrc()
|
|
&& copy->isInitializationOfDest()) {
|
|
searchValue = copy->getSrc();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
variableNamePath.push_back(allocInst);
|
|
break;
|
|
}
|
|
|
|
if (auto *globalAddrInst = dyn_cast<GlobalAddrInst>(searchValue)) {
|
|
variableNamePath.push_back(globalAddrInst);
|
|
break;
|
|
}
|
|
|
|
if (auto *oeInst = dyn_cast<OpenExistentialAddrInst>(searchValue)) {
|
|
searchValue = oeInst->getOperand();
|
|
continue;
|
|
}
|
|
|
|
if (auto *rei = dyn_cast<RefElementAddrInst>(searchValue)) {
|
|
variableNamePath.push_back(rei);
|
|
searchValue = rei->getOperand();
|
|
continue;
|
|
}
|
|
|
|
if (auto *fArg = dyn_cast<SILFunctionArgument>(searchValue)) {
|
|
variableNamePath.push_back({fArg});
|
|
break;
|
|
}
|
|
|
|
auto getNamePathComponentFromCallee = [&](FullApplySite call) -> llvm::Optional<SILValue> {
|
|
// Use the name of the property being accessed if we can get to it.
|
|
if (isa<FunctionRefBaseInst>(call.getCallee())
|
|
|| isa<MethodInst>(call.getCallee())) {
|
|
variableNamePath.push_back(call.getCallee()->getDefiningInstruction());
|
|
// Try to name the base of the property if this is a method.
|
|
if (call.getSubstCalleeType()->hasSelfParam()) {
|
|
return call.getSelfArgument();
|
|
} else {
|
|
return SILValue();
|
|
}
|
|
}
|
|
return llvm::None;
|
|
};
|
|
|
|
// Read or modify accessor.
|
|
if (auto bai = dyn_cast_or_null<BeginApplyInst>(searchValue->getDefiningInstruction())) {
|
|
if (auto selfParam = getNamePathComponentFromCallee(bai)) {
|
|
searchValue = *selfParam;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Addressor accessor.
|
|
if (auto ptrToAddr = dyn_cast<PointerToAddressInst>(stripAccessMarkers(searchValue))) {
|
|
// The addressor can either produce the raw pointer itself or an `UnsafePointer` stdlib type wrapping it.
|
|
ApplyInst *addressorInvocation;
|
|
if (auto structExtract = dyn_cast<StructExtractInst>(ptrToAddr->getOperand())) {
|
|
addressorInvocation = dyn_cast<ApplyInst>(structExtract->getOperand());
|
|
} else {
|
|
addressorInvocation = dyn_cast<ApplyInst>(ptrToAddr->getOperand());
|
|
}
|
|
|
|
if (addressorInvocation) {
|
|
if (auto selfParam = getNamePathComponentFromCallee(addressorInvocation)) {
|
|
searchValue = *selfParam;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we do not do an exact match, see if we can find a debug_var inst. If
|
|
// we do, we always break since we have a root value.
|
|
if (auto *use = getAnyDebugUse(searchValue)) {
|
|
if (auto debugVar = DebugVarCarryingInst(use->getUser())) {
|
|
assert(debugVar.getKind() == DebugVarCarryingInst::Kind::DebugValue);
|
|
variableNamePath.push_back(use->getUser());
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Otherwise, try to see if we have a single value instruction we can look
|
|
// through.
|
|
if (isa<BeginBorrowInst>(searchValue) || isa<LoadInst>(searchValue) ||
|
|
isa<LoadBorrowInst>(searchValue) || isa<BeginAccessInst>(searchValue) ||
|
|
isa<MarkUnresolvedNonCopyableValueInst>(searchValue) ||
|
|
isa<ProjectBoxInst>(searchValue) || isa<CopyValueInst>(searchValue)) {
|
|
searchValue = cast<SingleValueInstruction>(searchValue)->getOperand(0);
|
|
continue;
|
|
}
|
|
|
|
// If we do not pattern match successfully, just set resulting string to
|
|
// unknown and return early.
|
|
resultingString += "unknown";
|
|
return;
|
|
}
|
|
|
|
auto nameFromDecl = [&](Decl *d) -> StringRef {
|
|
if (d) {
|
|
if (auto accessor = dyn_cast<AccessorDecl>(d)) {
|
|
return accessor->getStorage()->getBaseName().userFacingName();
|
|
}
|
|
if (auto vd = dyn_cast<ValueDecl>(d)) {
|
|
return vd->getBaseName().userFacingName();
|
|
}
|
|
}
|
|
|
|
return "<unknown decl>";
|
|
};
|
|
|
|
// Walk backwards, constructing our string.
|
|
while (true) {
|
|
auto next = variableNamePath.pop_back_val();
|
|
|
|
if (auto *inst = next.dyn_cast<SILInstruction *>()) {
|
|
if (auto i = DebugVarCarryingInst(inst)) {
|
|
resultingString += i.getName();
|
|
} else if (auto i = VarDeclCarryingInst(inst)) {
|
|
resultingString += i.getName();
|
|
} else if (auto f = dyn_cast<FunctionRefBaseInst>(inst)) {
|
|
if (auto dc = f->getInitiallyReferencedFunction()->getDeclContext()) {
|
|
resultingString += nameFromDecl(dc->getAsDecl());
|
|
} else {
|
|
resultingString += "<unknown decl>";
|
|
}
|
|
} else if (auto m = dyn_cast<MethodInst>(inst)) {
|
|
resultingString += nameFromDecl(m->getMember().getDecl());
|
|
} else {
|
|
resultingString += "<unknown decl>";
|
|
}
|
|
} else {
|
|
auto value = next.get<SILValue>();
|
|
if (auto *fArg = dyn_cast<SILFunctionArgument>(value))
|
|
resultingString += fArg->getDecl()->getBaseName().userFacingName();
|
|
}
|
|
|
|
if (variableNamePath.empty())
|
|
return;
|
|
|
|
resultingString += '.';
|
|
}
|
|
}
|
|
|
|
static void getVariableNameForValue(MarkUnresolvedNonCopyableValueInst *mmci,
|
|
SmallString<64> &resultingString) {
|
|
return getVariableNameForValue(mmci, mmci->getOperand(), resultingString);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Misc Diagnostics
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void DiagnosticEmitter::emitCheckerDoesntUnderstandDiagnostic(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue) {
|
|
// If we failed to canonicalize ownership, there was something in the SIL
|
|
// that copy propagation did not understand. Emit a we did not understand
|
|
// error.
|
|
if (markedValue->getType().isMoveOnlyWrapped()) {
|
|
diagnose(
|
|
fn->getASTContext(), markedValue,
|
|
diag::sil_movechecking_not_understand_consumable_and_assignable);
|
|
} else {
|
|
diagnose(fn->getASTContext(), markedValue,
|
|
diag::sil_movechecking_not_understand_moveonly);
|
|
}
|
|
registerDiagnosticEmitted(markedValue);
|
|
emittedCheckerDoesntUnderstandDiagnostic = true;
|
|
}
|
|
|
|
void DiagnosticEmitter::emitCheckedMissedCopyError(SILInstruction *copyInst) {
|
|
diagnose(copyInst->getFunction()->getASTContext(), copyInst,
|
|
diag::sil_movechecking_bug_missed_copy);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitReinitAfterDiscardError(SILInstruction *badReinit,
|
|
SILInstruction *discard) {
|
|
assert(isa<DropDeinitInst>(discard));
|
|
assert(badReinit->getLoc() && "missing loc!");
|
|
assert(discard->getLoc() && "missing loc!");
|
|
|
|
diagnose(badReinit->getFunction()->getASTContext(),
|
|
badReinit,
|
|
diag::sil_movechecking_reinit_after_discard);
|
|
|
|
diagnose(discard->getFunction()->getASTContext(), discard,
|
|
diag::sil_movechecking_discard_self_here);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
|
|
SILInstruction *leftoverDestroy,
|
|
SILInstruction *discard) {
|
|
assert(isa<DropDeinitInst>(discard));
|
|
|
|
// A good location is one that has some connection with the original source
|
|
// and corresponds to an exit of the function.
|
|
auto hasGoodLocation = [](SILInstruction *si) -> bool {
|
|
if (!si)
|
|
return false;
|
|
|
|
SILLocation loc = si->getLoc();
|
|
if (loc.isNull())
|
|
return false;
|
|
|
|
switch (loc.getKind()) {
|
|
case SILLocation::ReturnKind:
|
|
case SILLocation::ImplicitReturnKind:
|
|
return true;
|
|
|
|
case SILLocation::RegularKind: {
|
|
Decl *decl = loc.getAsASTNode<Decl>();
|
|
if (decl && isa<AbstractFunctionDecl>(decl)) {
|
|
// Having the function itself as a location results in a location at the
|
|
// first line of the function. Find another location.
|
|
return false;
|
|
}
|
|
Stmt *stmt = loc.getAsASTNode<Stmt>();
|
|
if (!stmt)
|
|
return true; // For non-statements, assume it is exiting the func.
|
|
|
|
// Prefer statements that can possibly lead to an exit of the function.
|
|
// This is determined by whether the statement causes an exit of a
|
|
// lexical scope; so a 'break' counts but not a 'continue'.
|
|
switch (stmt->getKind()) {
|
|
case StmtKind::Throw:
|
|
case StmtKind::Return:
|
|
case StmtKind::Yield:
|
|
case StmtKind::Break:
|
|
case StmtKind::Then:
|
|
case StmtKind::Fail:
|
|
case StmtKind::PoundAssert:
|
|
return true;
|
|
|
|
case StmtKind::Continue:
|
|
case StmtKind::Brace:
|
|
case StmtKind::Defer:
|
|
case StmtKind::If:
|
|
case StmtKind::Guard:
|
|
case StmtKind::While:
|
|
case StmtKind::Do:
|
|
case StmtKind::DoCatch:
|
|
case StmtKind::RepeatWhile:
|
|
case StmtKind::ForEach:
|
|
case StmtKind::Switch:
|
|
case StmtKind::Case:
|
|
case StmtKind::Fallthrough:
|
|
case StmtKind::Discard:
|
|
return false;
|
|
};
|
|
}
|
|
|
|
case SILLocation::InlinedKind:
|
|
case SILLocation::MandatoryInlinedKind:
|
|
case SILLocation::CleanupKind:
|
|
case SILLocation::ArtificialUnreachableKind:
|
|
return false;
|
|
};
|
|
};
|
|
|
|
// An instruction corresponding to the logical place where the value is
|
|
// destroyed. Ideally an exit point of the function reachable from here or
|
|
// some relevant statement.
|
|
SILInstruction *destroyPoint = leftoverDestroy;
|
|
if (!hasGoodLocation(destroyPoint)) {
|
|
// Search for a nearby function exit reachable from this destroy. We do this
|
|
// because the move checker may have injected or hoisted an existing
|
|
// destroy from leaf blocks to some earlier point. For example, if 'd'
|
|
// represents a destroy of self, then we may have this CFG:
|
|
//
|
|
// before: after:
|
|
// . d
|
|
// / \ / \
|
|
// d d . .
|
|
//
|
|
BasicBlockWorkqueue bfsWorklist = {destroyPoint->getParent()};
|
|
while (auto *bb = bfsWorklist.pop()) {
|
|
TermInst *term = bb->getTerminator();
|
|
|
|
// Looking for a block that exits the function or terminates the program.
|
|
if (term->isFunctionExiting() || term->isProgramTerminating()) {
|
|
SILInstruction *candidate = term;
|
|
|
|
// Walk backwards until we find an instruction with any source location.
|
|
// Sometimes a terminator like 'unreachable' may not have one, but one
|
|
// of the preceding instructions will.
|
|
while (candidate && candidate->getLoc().isNull())
|
|
candidate = candidate->getPreviousInstruction();
|
|
|
|
if (candidate && candidate->getLoc()) {
|
|
destroyPoint = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (auto *nextBB : term->getSuccessorBlocks())
|
|
bfsWorklist.pushIfNotVisited(nextBB);
|
|
}
|
|
}
|
|
|
|
assert(destroyPoint->getLoc() && "missing loc!");
|
|
assert(discard->getLoc() && "missing loc!");
|
|
|
|
diagnose(leftoverDestroy->getFunction()->getASTContext(),
|
|
destroyPoint,
|
|
diag::sil_movechecking_discard_missing_consume_self);
|
|
|
|
diagnose(discard->getFunction()->getASTContext(), discard,
|
|
diag::sil_movechecking_discard_self_here);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Object Diagnostics
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void DiagnosticEmitter::emitObjectGuaranteedDiagnostic(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue) {
|
|
auto &astContext = fn->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
// See if we have any closure capture uses and emit a better diagnostic.
|
|
if (getCanonicalizer().hasPartialApplyConsumingUse()) {
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_borrowed_parameter_captured_by_closure,
|
|
varName);
|
|
emitObjectDiagnosticsForPartialApplyUses(varName);
|
|
registerDiagnosticEmitted(markedValue);
|
|
}
|
|
|
|
// If we do not have any non-partial apply consuming uses... just exit early.
|
|
if (!getCanonicalizer().hasNonPartialApplyConsumingUse())
|
|
return;
|
|
|
|
registerDiagnosticEmitted(markedValue);
|
|
|
|
// Check if this value is closure captured. In such a case, emit a special
|
|
// error.
|
|
if (auto *fArg = dyn_cast<SILFunctionArgument>(
|
|
lookThroughCopyValueInsts(markedValue->getOperand()))) {
|
|
if (fArg->isClosureCapture()) {
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_capture_consumed,
|
|
varName);
|
|
emitObjectDiagnosticsForGuaranteedUses(
|
|
true /*ignore partial apply uses*/);
|
|
registerDiagnosticEmitted(markedValue);
|
|
return;
|
|
}
|
|
}
|
|
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_guaranteed_value_consumed, varName);
|
|
|
|
emitObjectDiagnosticsForGuaranteedUses(true /*ignore partial apply uses*/);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitObjectOwnedDiagnostic(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue) {
|
|
registerDiagnosticEmitted(markedValue);
|
|
|
|
auto &astContext = fn->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
// Ok we know that we are going to emit an error. Lets use a little more
|
|
// compile time to emit a nice error.
|
|
InstructionSet consumingUserSet(markedValue->getFunction());
|
|
InstructionSet nonConsumingUserSet(markedValue->getFunction());
|
|
llvm::SmallDenseMap<SILBasicBlock *, SILInstruction *, 8>
|
|
consumingBlockToUserMap;
|
|
llvm::SmallDenseMap<SILBasicBlock *, SILInstruction *, 8>
|
|
nonConsumingBlockToUserMap;
|
|
|
|
// NOTE: We use all lifetime ending and non-lifetime ending users to ensure
|
|
// that we properly identify cases where the actual boundary use is in a loop
|
|
// further down the loop nest from our original use. In such a case, it will
|
|
// not be identified as part of the boundary and instead we will identify a
|
|
// boundary edge which does not provide us with something that we want to
|
|
// error upon.
|
|
for (auto *user : getCanonicalizer().canonicalizer.getLifetimeEndingUsers()) {
|
|
consumingUserSet.insert(user);
|
|
consumingBlockToUserMap.try_emplace(user->getParent(), user);
|
|
}
|
|
for (auto *user :
|
|
getCanonicalizer().canonicalizer.getNonLifetimeEndingUsers()) {
|
|
nonConsumingUserSet.insert(user);
|
|
nonConsumingBlockToUserMap.try_emplace(user->getParent(), user);
|
|
}
|
|
|
|
// Now for each consuming use that needs a copy...
|
|
for (auto *user : getCanonicalizer().consumingUsesNeedingCopy) {
|
|
// First search from user to the end of the block for one of our boundary
|
|
// uses and if it is in the block, emit an error and continue.
|
|
bool foundSingleBlockError = false;
|
|
for (auto ii = std::next(user->getIterator()),
|
|
ie = user->getParent()->end();
|
|
ii != ie; ++ii) {
|
|
if (consumingUserSet.contains(&*ii)) {
|
|
foundSingleBlockError = true;
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_owned_value_consumed_more_than_once,
|
|
varName);
|
|
diagnose(astContext, user,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
diagnose(astContext, &*ii,
|
|
diag::sil_movechecking_consumed_again_here);
|
|
break;
|
|
}
|
|
|
|
if (nonConsumingUserSet.contains(&*ii)) {
|
|
foundSingleBlockError = true;
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_value_used_after_consume, varName);
|
|
diagnose(astContext, user,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
diagnose(astContext, &*ii,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we found a single block error for this user, continue.
|
|
if (foundSingleBlockError)
|
|
continue;
|
|
|
|
// Otherwise, the reason why the consuming use needs to be copied is in a
|
|
// successor block. Lets go look for that user.
|
|
BasicBlockWorklist worklist(markedValue->getFunction());
|
|
for (auto *succBlock : user->getParent()->getSuccessorBlocks())
|
|
worklist.push(succBlock);
|
|
while (auto *nextBlock = worklist.pop()) {
|
|
// First, check if we are visiting the same block as our user block. In
|
|
// such a case, we found a consuming use within a loop.
|
|
if (nextBlock == user->getParent()) {
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_value_consumed_in_a_loop, varName);
|
|
auto d =
|
|
diag::sil_movechecking_consumed_in_loop_here;
|
|
diagnose(astContext, user, d);
|
|
break;
|
|
}
|
|
|
|
{
|
|
auto iter = consumingBlockToUserMap.find(nextBlock);
|
|
if (iter != consumingBlockToUserMap.end()) {
|
|
// We found it... emit the error and break.
|
|
diagnose(
|
|
astContext, markedValue,
|
|
diag::sil_movechecking_owned_value_consumed_more_than_once,
|
|
varName);
|
|
diagnose(astContext, user,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
diagnose(astContext, iter->second,
|
|
diag::sil_movechecking_consumed_again_here);
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto iter = nonConsumingBlockToUserMap.find(nextBlock);
|
|
if (iter != nonConsumingBlockToUserMap.end()) {
|
|
// We found it... emit the error and break.
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_value_used_after_consume, varName);
|
|
diagnose(astContext, user,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
diagnose(astContext, iter->second,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we didn't break, keep walking successors we haven't seen yet.
|
|
for (auto *succBlock : nextBlock->getSuccessorBlocks()) {
|
|
worklist.pushIfNotVisited(succBlock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DiagnosticEmitter::emitObjectDiagnosticsForGuaranteedUses(
|
|
bool ignorePartialApplyUses) const {
|
|
auto &astContext = fn->getASTContext();
|
|
|
|
for (auto *consumingUser : getCanonicalizer().consumingUsesNeedingCopy) {
|
|
if (ignorePartialApplyUses &&
|
|
OSSACanonicalizer::isPartialApplyUser(consumingUser))
|
|
continue;
|
|
diagnose(astContext, consumingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
|
|
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
|
|
if (ignorePartialApplyUses && OSSACanonicalizer::isPartialApplyUser(user))
|
|
continue;
|
|
|
|
diagnose(astContext, user, diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
}
|
|
|
|
void DiagnosticEmitter::emitObjectDiagnosticsForPartialApplyUses(
|
|
StringRef capturedVarName) const {
|
|
auto &astContext = fn->getASTContext();
|
|
|
|
for (auto *user : getCanonicalizer().consumingUsesNeedingCopy) {
|
|
if (!OSSACanonicalizer::isPartialApplyUser(user))
|
|
continue;
|
|
diagnose(astContext,
|
|
user,
|
|
diag::sil_movechecking_consuming_closure_use_here,
|
|
capturedVarName);
|
|
}
|
|
|
|
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
|
|
if (!OSSACanonicalizer::isPartialApplyUser(user))
|
|
continue;
|
|
|
|
diagnose(astContext,
|
|
user,
|
|
diag::sil_movechecking_consuming_closure_use_here,
|
|
capturedVarName);
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Address Diagnostics
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static bool isClosureCapture(MarkUnresolvedNonCopyableValueInst *markedValue) {
|
|
SILValue val = markedValue->getOperand();
|
|
|
|
// Sometimes we've mark-must-check'd a begin_access.
|
|
val = stripAccessMarkers(val);
|
|
|
|
// look past any project-box
|
|
if (auto *pbi = dyn_cast<ProjectBoxInst>(val))
|
|
val = pbi->getOperand();
|
|
|
|
if (auto *fArg = dyn_cast<SILFunctionArgument>(val))
|
|
return fArg->isClosureCapture();
|
|
|
|
return false;
|
|
}
|
|
|
|
void DiagnosticEmitter::emitAddressExclusivityHazardDiagnostic(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue,
|
|
SILInstruction *consumingUser) {
|
|
if (!useWithDiagnostic.insert(consumingUser).second)
|
|
return;
|
|
registerDiagnosticEmitted(markedValue);
|
|
|
|
auto &astContext = markedValue->getFunction()->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting error for exclusivity!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " Consuming use: " << *consumingUser);
|
|
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_bug_exclusivity_violation, varName);
|
|
diagnose(astContext, consumingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitAddressDiagnostic(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue,
|
|
SILInstruction *lastLiveUser, SILInstruction *violatingUser,
|
|
bool isUseConsuming, llvm::Optional<ScopeRequiringFinalInit> scopeKind) {
|
|
if (!useWithDiagnostic.insert(violatingUser).second)
|
|
return;
|
|
registerDiagnosticEmitted(markedValue);
|
|
|
|
auto &astContext = markedValue->getFunction()->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting error!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " Last Live Use: " << *lastLiveUser);
|
|
LLVM_DEBUG(llvm::dbgs() << " Last Live Use Is Consuming? "
|
|
<< (isUseConsuming ? "yes" : "no") << '\n');
|
|
LLVM_DEBUG(llvm::dbgs() << " Violating Use: " << *violatingUser);
|
|
|
|
// If our liveness use is the same as our violating use, then we know that we
|
|
// had a loop. Give a better diagnostic.
|
|
if (lastLiveUser == violatingUser) {
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_value_consumed_in_a_loop, varName);
|
|
diagnose(astContext, violatingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
return;
|
|
}
|
|
|
|
if (scopeKind.has_value()) {
|
|
switch (scopeKind.value()) {
|
|
case ScopeRequiringFinalInit::InoutArgument:
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_not_reinitialized_before_end_of_function,
|
|
varName, isClosureCapture(markedValue));
|
|
break;
|
|
case ScopeRequiringFinalInit::Coroutine:
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_not_reinitialized_before_end_of_coroutine,
|
|
varName);
|
|
break;
|
|
case ScopeRequiringFinalInit::ModifyMemoryAccess:
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_not_reinitialized_before_end_of_access,
|
|
varName, isClosureCapture(markedValue));
|
|
break;
|
|
}
|
|
diagnose(astContext, violatingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
return;
|
|
}
|
|
|
|
// First if we are consuming emit an error for no implicit copy semantics.
|
|
if (isUseConsuming) {
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_owned_value_consumed_more_than_once,
|
|
varName);
|
|
diagnose(astContext, violatingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
diagnose(astContext, lastLiveUser,
|
|
diag::sil_movechecking_consumed_again_here);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, use the "used after consuming use" error.
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_value_used_after_consume, varName);
|
|
diagnose(astContext, violatingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
diagnose(astContext, lastLiveUser,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitInOutEndOfFunctionDiagnostic(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue,
|
|
SILInstruction *violatingUser) {
|
|
if (!useWithDiagnostic.insert(violatingUser).second)
|
|
return;
|
|
registerDiagnosticEmitted(markedValue);
|
|
|
|
assert(cast<SILFunctionArgument>(markedValue->getOperand())
|
|
->getArgumentConvention()
|
|
.isInoutConvention() &&
|
|
"Expected markedValue to be on an inout");
|
|
|
|
auto &astContext = markedValue->getFunction()->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting inout error error!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " Violating Use: " << *violatingUser);
|
|
|
|
// Otherwise, we need to do no implicit copy semantics. If our last use was
|
|
// consuming message:
|
|
diagnose(
|
|
astContext, markedValue,
|
|
diag::sil_movechecking_not_reinitialized_before_end_of_function,
|
|
varName, isClosureCapture(markedValue));
|
|
diagnose(astContext, violatingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitAddressDiagnosticNoCopy(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue,
|
|
SILInstruction *consumingUser) {
|
|
if (!useWithDiagnostic.insert(consumingUser).second)
|
|
return;
|
|
|
|
auto &astContext = markedValue->getFunction()->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting no copy error!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " Consuming Use: " << *consumingUser);
|
|
|
|
// Otherwise, we need to do no implicit copy semantics. If our last use was
|
|
// consuming message:
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_guaranteed_value_consumed, varName);
|
|
diagnose(astContext, consumingUser,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
registerDiagnosticEmitted(markedValue);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitObjectDestructureNeededWithinBorrowBoundary(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue,
|
|
SILInstruction *destructureNeedingUser,
|
|
TypeTreeLeafTypeRange destructureSpan,
|
|
FieldSensitivePrunedLivenessBoundary &boundary) {
|
|
if (!useWithDiagnostic.insert(destructureNeedingUser).second)
|
|
return;
|
|
|
|
auto &astContext = markedValue->getFunction()->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting destructure can't be created error!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " Destructure Needing Use: "
|
|
<< *destructureNeedingUser);
|
|
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_use_after_partial_consume, varName);
|
|
diagnose(astContext, destructureNeedingUser,
|
|
diag::sil_movechecking_partial_consume_here);
|
|
|
|
// Only emit errors for last users that overlap with our needed destructure
|
|
// bits.
|
|
for (auto pair : boundary.getLastUsers()) {
|
|
if (llvm::any_of(destructureSpan.getRange(),
|
|
[&](unsigned index) { return pair.second.test(index); })) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Destructure Boundary Use: " << *pair.first);
|
|
diagnose(astContext, pair.first, diag::sil_movechecking_nonconsuming_use_here);
|
|
}
|
|
}
|
|
registerDiagnosticEmitted(markedValue);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitObjectInstConsumesValueTwice(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue, Operand *firstUse,
|
|
Operand *secondUse) {
|
|
assert(firstUse->getUser() == secondUse->getUser());
|
|
assert(firstUse->isConsuming());
|
|
assert(secondUse->isConsuming());
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting object consumes value twice error!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " User: " << *firstUse->getUser());
|
|
LLVM_DEBUG(llvm::dbgs() << " First Conflicting Operand: "
|
|
<< firstUse->getOperandNumber() << '\n');
|
|
LLVM_DEBUG(llvm::dbgs() << " Second Conflicting Operand: "
|
|
<< secondUse->getOperandNumber() << '\n');
|
|
|
|
auto &astContext = markedValue->getModule().getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_owned_value_consumed_more_than_once,
|
|
varName);
|
|
diagnose(astContext, firstUse->getUser(),
|
|
diag::sil_movechecking_two_consuming_uses_here);
|
|
registerDiagnosticEmitted(markedValue);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitObjectInstConsumesAndUsesValue(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue, Operand *consumingUse,
|
|
Operand *nonConsumingUse) {
|
|
assert(consumingUse->getUser() == nonConsumingUse->getUser());
|
|
assert(consumingUse->isConsuming());
|
|
assert(!nonConsumingUse->isConsuming());
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting object consumeed and used error!\n");
|
|
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
|
|
LLVM_DEBUG(llvm::dbgs() << " User: " << *consumingUse->getUser());
|
|
LLVM_DEBUG(llvm::dbgs() << " Consuming Operand: "
|
|
<< consumingUse->getOperandNumber() << '\n');
|
|
LLVM_DEBUG(llvm::dbgs() << " Non Consuming Operand: "
|
|
<< nonConsumingUse->getOperandNumber() << '\n');
|
|
|
|
auto &astContext = markedValue->getModule().getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
diagnose(astContext, markedValue,
|
|
diag::sil_movechecking_owned_value_consumed_and_used_at_same_time,
|
|
varName);
|
|
diagnose(astContext, consumingUse->getUser(),
|
|
diag::sil_movechecking_consuming_and_non_consuming_uses_here);
|
|
registerDiagnosticEmitted(markedValue);
|
|
}
|
|
|
|
bool DiagnosticEmitter::emitGlobalOrClassFieldLoadedAndConsumed(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue) {
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
SILValue operand = stripAccessMarkers(markedValue->getOperand());
|
|
|
|
// is it a class?
|
|
if (isa<RefElementAddrInst>(operand)) {
|
|
diagnose(markedValue->getModule().getASTContext(),
|
|
markedValue,
|
|
diag::sil_movechecking_notconsumable_but_assignable_was_consumed,
|
|
varName, /*isGlobal=*/false);
|
|
registerDiagnosticEmitted(markedValue);
|
|
return true;
|
|
}
|
|
|
|
// is it a global?
|
|
if (isa<GlobalAddrInst>(operand)) {
|
|
diagnose(markedValue->getModule().getASTContext(),
|
|
markedValue,
|
|
diag::sil_movechecking_notconsumable_but_assignable_was_consumed,
|
|
varName, /*isGlobal=*/true);
|
|
registerDiagnosticEmitted(markedValue);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DiagnosticEmitter::emitAddressEscapingClosureCaptureLoadedAndConsumed(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue) {
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
diagnose(markedValue->getModule().getASTContext(),
|
|
markedValue,
|
|
diag::sil_movechecking_capture_consumed,
|
|
varName);
|
|
registerDiagnosticEmitted(markedValue);
|
|
}
|
|
|
|
void DiagnosticEmitter::emitPromotedBoxArgumentError(
|
|
MarkUnresolvedNonCopyableValueInst *markedValue, SILFunctionArgument *arg) {
|
|
auto &astContext = fn->getASTContext();
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(markedValue, varName);
|
|
|
|
registerDiagnosticEmitted(markedValue);
|
|
|
|
// diagnose consume of capture within a closure
|
|
diagnose(astContext,
|
|
arg->getDecl()->getLoc(),
|
|
diag::sil_movechecking_capture_consumed,
|
|
varName);
|
|
|
|
// Now for each consuming use that needs a copy...
|
|
for (auto *user : getCanonicalizer().consumingUsesNeedingCopy) {
|
|
diagnose(astContext, user, diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
|
|
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
|
|
diagnose(astContext, user, diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
}
|
|
|
|
void DiagnosticEmitter::emitCannotPartiallyMutateError(
|
|
MarkUnresolvedNonCopyableValueInst *address, PartialMutationError error,
|
|
SILInstruction *user, TypeTreeLeafTypeRange usedBits,
|
|
PartialMutation kind) {
|
|
|
|
TypeOffsetSizePair pair(usedBits);
|
|
|
|
SmallString<128> pathString;
|
|
auto rootType = address->getType();
|
|
if (error.type != rootType) {
|
|
llvm::raw_svector_ostream os(pathString);
|
|
auto *fn = address->getFunction();
|
|
pair.constructPathString(error.type, {rootType, fn}, rootType, fn, os);
|
|
}
|
|
|
|
auto &astContext = fn->getASTContext();
|
|
|
|
SmallString<64> varName;
|
|
getVariableNameForValue(address, varName);
|
|
|
|
if (!pathString.empty())
|
|
varName.append(pathString);
|
|
|
|
bool hasPartialConsumption =
|
|
astContext.LangOpts.hasFeature(Feature::MoveOnlyPartialConsumption);
|
|
(void)hasPartialConsumption;
|
|
|
|
switch (error) {
|
|
case PartialMutationError::Kind::FeatureDisabled:
|
|
assert(!hasPartialConsumption);
|
|
switch (kind) {
|
|
case PartialMutation::Kind::Consume:
|
|
diagnose(astContext, user, diag::sil_movechecking_cannot_destructure,
|
|
varName);
|
|
break;
|
|
case PartialMutation::Kind::Reinit:
|
|
diagnose(astContext, user, diag::sil_movechecking_cannot_partially_reinit,
|
|
varName);
|
|
diagnose(astContext, &kind.getEarlierConsumingUse(),
|
|
diag::sil_movechecking_consuming_use_here);
|
|
break;
|
|
}
|
|
registerDiagnosticEmitted(address);
|
|
return;
|
|
case PartialMutationError::Kind::HasDeinit: {
|
|
assert(hasPartialConsumption);
|
|
switch (kind) {
|
|
case PartialMutation::Kind::Consume:
|
|
diagnose(astContext, user,
|
|
diag::sil_movechecking_cannot_destructure_has_deinit, varName);
|
|
break;
|
|
case PartialMutation::Kind::Reinit:
|
|
diagnose(astContext, user,
|
|
diag::sil_movechecking_cannot_partially_reinit_has_deinit,
|
|
varName);
|
|
break;
|
|
}
|
|
registerDiagnosticEmitted(address);
|
|
auto deinitLoc = error.getNominal().getValueTypeDestructor()->getLoc(
|
|
/*SerializedOK=*/false);
|
|
if (!deinitLoc)
|
|
return;
|
|
astContext.Diags.diagnose(deinitLoc, diag::sil_movechecking_deinit_here);
|
|
return;
|
|
}
|
|
}
|
|
}
|