Files
swift-mirror/lib/SILOptimizer/Mandatory/SendNonSendable.cpp
Michael Gottesman 8bea3518f5 [rbi] Remove highlights from errors.
I have noticed over time when working on the command line, we often times
highlight too large of an expression due to the locations provided to us by
earlier parts of the compiler. This isn't technically necessary and the
following doesn't look nice... so remove it.

```
test5.swift:171:16: error: sending 'x' risks causing data races [#SendingRisksDataRace]
169 |   let _ = {
170 |     nonescapingAsyncUse { @MainActor in
171 | _ _ _ _ _u_s_e_V_a_l_u_e_(_x_)
    |                |- error: sending 'x' risks causing data races [#SendingRisksDataRace]
    |                `- note: task-isolated 'x' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses
172 |     }
173 |   }
```
2025-12-11 17:29:20 -08:00

3760 lines
136 KiB
C++

//===--- SendNonSendable.cpp ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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 "send-non-sendable"
#include "swift/SIL/PrunedLiveness.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/Concurrency.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Expr.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/Type.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/FrozenMultiMap.h"
#include "swift/Basic/ImmutablePointerSet.h"
#include "swift/SIL/BasicBlockData.h"
#include "swift/SIL/BasicBlockDatastructures.h"
#include "swift/SIL/DynamicCasts.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/NodeDatastructures.h"
#include "swift/SIL/OperandDatastructures.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/PatternMatch.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/Test.h"
#include "swift/SILOptimizer/Analysis/RegionAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/PartitionUtils.h"
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
#include "swift/Sema/Concurrency.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Debug.h"
using namespace swift;
using namespace swift::PartitionPrimitives;
using namespace swift::PatternMatch;
using namespace swift::regionanalysisimpl;
namespace {
using SendingOperandSetFactory = Partition::SendingOperandSetFactory;
using Element = PartitionPrimitives::Element;
using Region = PartitionPrimitives::Region;
} // namespace
// This option is used so we can test typed errors. Typed errors are a fallback
// case which are emitted when we are unable to infer the name of a value. We in
// most cases do succeed inferring, so it makes sense to add an asserts only
// option that can be used by the compiler to test that we emit these correctly.
static llvm::cl::opt<bool> ForceTypedErrors(
"sil-regionbasedisolation-force-use-of-typed-errors",
llvm::cl::desc("Force the usage of typed instead of named errors to make "
"it easier to test typed errors"),
llvm::cl::Hidden);
//===----------------------------------------------------------------------===//
// MARK: Utilities
//===----------------------------------------------------------------------===//
static SILValue stripFunctionConversions(SILValue val) {
while (true) {
if (auto ti = dyn_cast<ThinToThickFunctionInst>(val)) {
val = ti->getOperand();
continue;
}
if (auto cfi = dyn_cast<ConvertFunctionInst>(val)) {
val = cfi->getOperand();
continue;
}
if (auto cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(val)) {
val = cvt->getOperand();
continue;
}
// Look through thunks.
if (auto pai = dyn_cast<PartialApplyInst>(val)) {
if (pai->getCalleeFunction()->isThunk()) {
val = pai->getArgument(0);
continue;
}
}
break;
}
return val;
}
/// Find the most conservative diagnostic behavior by taking the max over all
/// DiagnosticBehavior for the captured values.
static std::optional<DiagnosticBehavior>
getDiagnosticBehaviorLimitForOperands(SILFunction *fn,
ArrayRef<Operand *> capturedValues) {
std::optional<DiagnosticBehavior> diagnosticBehavior;
for (auto value : capturedValues) {
auto lhs = diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified);
auto limit = value->get()->getType().getConcurrencyDiagnosticBehavior(fn);
auto rhs = limit.value_or(DiagnosticBehavior::Unspecified);
auto result = lhs.merge(rhs);
if (result != DiagnosticBehavior::Unspecified)
diagnosticBehavior = result;
}
return diagnosticBehavior;
}
static std::optional<SILDeclRef> getDeclRefForCallee(SILInstruction *inst) {
auto fas = FullApplySite::isa(inst);
if (!fas)
return {};
SILValue calleeOrigin = fas.getCalleeOrigin();
while (true) {
// Intentionally don't lookup through dynamic_function_ref and
// previous_dynamic_function_ref as the target of those functions is not
// statically known.
if (auto *fri = dyn_cast<FunctionRefInst>(calleeOrigin)) {
if (auto *callee = fri->getReferencedFunctionOrNull()) {
if (auto declRef = callee->getDeclRef())
return declRef;
}
}
if (auto *mi = dyn_cast<MethodInst>(calleeOrigin)) {
return mi->getMember();
}
if (auto *pai = dyn_cast<PartialApplyInst>(calleeOrigin)) {
calleeOrigin = pai->getCalleeOrigin();
continue;
}
return {};
}
}
static std::optional<ValueDecl *> getSendingApplyCallee(SILInstruction *inst) {
auto declRef = getDeclRefForCallee(inst);
if (!declRef)
return {};
auto *decl = declRef->getDecl();
if (!decl)
return {};
return decl;
}
static Expr *inferArgumentExprFromApplyExpr(ApplyExpr *sourceApply,
FullApplySite fai,
const Operand *op) {
Expr *foundExpr = nullptr;
// If we have self, then infer it.
if (fai.hasSelfArgument() && op == &fai.getSelfArgumentOperand()) {
if (auto callExpr = dyn_cast<CallExpr>(sourceApply))
if (auto calledExpr =
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee()))
foundExpr = calledExpr->getBase();
} else {
// Otherwise, try to infer using the operand of the ApplyExpr.
unsigned argNum = [&]() -> unsigned {
if (fai.isCalleeOperand(*op))
return op->getOperandNumber();
return fai.getAppliedArgIndexWithoutIndirectResults(*op);
}();
// Something happened that we do not understand.
if (argNum >= sourceApply->getArgs()->size()) {
return nullptr;
}
foundExpr = sourceApply->getArgs()->getExpr(argNum);
// If we have an erasure expression, lets use the original type. We do
// this since we are not saying the specific parameter that is the
// issue and we are using the type to explain it to the user.
if (auto *erasureExpr = dyn_cast<ErasureExpr>(foundExpr))
foundExpr = erasureExpr->getSubExpr();
}
return foundExpr;
}
/// Attempt to infer a name for \p value. Returns none if we fail or if we are
/// asked to force typed errors since we are testing.
static std::optional<Identifier> inferNameHelper(SILValue value) {
if (ForceTypedErrors)
return {};
return VariableNameInferrer::inferName(value);
}
/// Attempt to infer a name and root for \p value. Returns none if we fail or if
/// we are asked to force typed errors since we are testing.
static std::optional<std::pair<Identifier, SILValue>>
inferNameAndRootHelper(SILValue value) {
if (ForceTypedErrors)
return {};
return VariableNameInferrer::inferNameAndRoot(value);
}
/// Find a use corresponding to the potentially recursive capture of \p
/// initialOperand that would be appropriate for diagnostics.
///
/// \returns the use and the function argument that is used. We return the
/// function argument since it is a clever way to correctly grab the name of the
/// captured value since the ValueDecl will point at the actual ValueDecl in the
/// AST that is captured.
static std::optional<std::pair<Operand *, SILArgument *>>
findClosureUse(Operand *initialOperand) {
// We have to use a small vector worklist here since we are iterating through
// uses from different functions.
llvm::SmallVector<std::pair<Operand *, SILArgument *>, 64> worklist;
llvm::SmallPtrSet<Operand *, 8> visitedOperand;
// Initialize our worklist with uses in the initial closure. We do not want to
// analyze uses in the original function.
{
auto as = ApplySite::isa(initialOperand->getUser());
if (!as)
return {};
auto *f = as.getCalleeFunction();
if (!f || f->empty())
return {};
unsigned argumentIndex = as.getCalleeArgIndex(*initialOperand);
auto *arg = f->getArgument(argumentIndex);
for (auto *use : arg->getUses()) {
worklist.emplace_back(use, arg);
visitedOperand.insert(use);
}
}
while (!worklist.empty()) {
auto pair = worklist.pop_back_val();
auto *op = pair.first;
auto *fArg = pair.second;
auto *user = op->getUser();
// Ignore incidental uses that are not specifically ignored use. We want to
// visit those since they represent `let _ = $VAR` and `_ = $VAR`
if (isIncidentalUse(user) && !isa<IgnoredUseInst>(user))
continue;
// Look through some insts we do not care about.
if (isa<CopyValueInst, BeginBorrowInst, ProjectBoxInst, BeginAccessInst>(
user) ||
isMoveOnlyWrapperUse(user) ||
// We want to treat move_value [var_decl] as a real use since we are
// assigning to a var.
(isa<MoveValueInst>(user) &&
!cast<MoveValueInst>(user)->isFromVarDecl())) {
for (auto result : user->getResults()) {
for (auto *use : result->getUses()) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
}
}
continue;
}
// See if we have a callee function. In such a case, find our operand in the
// callee and visit its uses.
if (auto as = dyn_cast<PartialApplyInst>(op->getUser())) {
if (auto *f = as->getCalleeFunction(); f && !f->empty()) {
auto *fArg = f->getArgument(ApplySite(as).getCalleeArgIndex(*op));
for (auto *use : fArg->getUses()) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
}
continue;
}
}
// See if we have a full apply site that was from a closure that was
// immediately invoked. In such a case, we can emit a better diagnostic in
// the called closure.
if (auto fas = FullApplySite::isa(op->getUser())) {
if (auto *f = fas.getCalleeFunction(); f && !f->empty()) {
auto *fArg = cast<SILFunctionArgument>(
f->getArgument(fas.getCalleeArgIndex(*op)));
if (fArg->isClosureCapture()) {
for (auto *use : fArg->getUses()) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
}
continue;
}
}
}
// Otherwise, we have a real use. Return it and the function argument that
// it was derived from.
return pair;
}
return {};
}
//===----------------------------------------------------------------------===//
// MARK: Diagnostics
//===----------------------------------------------------------------------===//
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseError(ASTContext &context, SourceLoc loc,
Diag<T...> diag, U &&...args) {
return std::move(context.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseError(ASTContext &context, SILLocation loc,
Diag<T...> diag, U &&...args) {
return ::diagnoseError(context, loc.getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseError(const PartitionOp &op, Diag<T...> diag,
U &&...args) {
return ::diagnoseError(op.getSourceInst()->getFunction()->getASTContext(),
op.getSourceLoc().getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseError(const Operand *op, Diag<T...> diag,
U &&...args) {
return ::diagnoseError(op->getUser()->getFunction()->getASTContext(),
op->getUser()->getLoc().getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseError(const SILInstruction *inst,
Diag<T...> diag, U &&...args) {
return ::diagnoseError(inst->getFunction()->getASTContext(),
inst->getLoc().getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseNote(ASTContext &context, SourceLoc loc,
Diag<T...> diag, U &&...args) {
return context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseNote(ASTContext &context, SILLocation loc,
Diag<T...> diag, U &&...args) {
return ::diagnoseNote(context, loc.getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseNote(const PartitionOp &op, Diag<T...> diag,
U &&...args) {
return ::diagnoseNote(op.getSourceInst()->getFunction()->getASTContext(),
op.getSourceLoc().getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseNote(const Operand *op, Diag<T...> diag,
U &&...args) {
return ::diagnoseNote(op->getUser()->getFunction()->getASTContext(),
op->getUser()->getLoc().getSourceLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
static InFlightDiagnostic diagnoseNote(const SILInstruction *inst,
Diag<T...> diag, U &&...args) {
return ::diagnoseNote(inst->getFunction()->getASTContext(),
inst->getLoc().getSourceLoc(), diag,
std::forward<U>(args)...);
}
//===----------------------------------------------------------------------===//
// MARK: Require Liveness
//===----------------------------------------------------------------------===//
namespace {
class BlockLivenessInfo {
// Generation counter so we do not need to reallocate.
unsigned generation = 0;
SILInstruction *firstRequireInst = nullptr;
void resetIfNew(unsigned newGeneration) {
if (generation == newGeneration)
return;
generation = newGeneration;
firstRequireInst = nullptr;
}
public:
SILInstruction *getInst(unsigned callerGeneration) {
resetIfNew(callerGeneration);
return firstRequireInst;
}
void setInst(unsigned callerGeneration, SILInstruction *newValue) {
resetIfNew(callerGeneration);
firstRequireInst = newValue;
}
};
/// We only want to emit errors for the first requires along a path from a
/// sending instruction. We discover this by walking from user blocks to
struct RequireLiveness {
unsigned generation;
SILInstruction *sendingInst;
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo;
InstructionSet allRequires;
InstructionSetWithSize finalRequires;
/// If we have requires in the def block before our send, this is the
/// first require.
SILInstruction *firstRequireBeforeSendInDefBlock = nullptr;
RequireLiveness(unsigned generation, Operand *sendingOp,
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo)
: generation(generation), sendingInst(sendingOp->getUser()),
blockLivenessInfo(blockLivenessInfo),
allRequires(sendingOp->getParentFunction()),
finalRequires(sendingOp->getParentFunction()) {}
template <typename Collection>
void process(Collection collection);
/// Attempt to process requireInst for our def block. Returns false if
/// requireInst was before our def and we need to do interprocedural
/// processing. Returns true if requireInst was after our seningInst and we
/// were able to appropriately determine if we should emit it or not.
void processDefBlock();
/// Process all requires in block, updating blockLivenessInfo.
void processNonDefBlock(SILBasicBlock *block);
};
} // namespace
void RequireLiveness::processDefBlock() {
REGIONBASEDISOLATION_LOG(llvm::dbgs() << " Processing def block!\n");
// First walk from the beginning of the block to the send instruction to
// see if we have any requires before our def. Once we find one, we can skip
// the traversal and jump straight to the send.
for (auto ii = sendingInst->getParent()->begin(),
ie = sendingInst->getIterator();
ii != ie; ++ii) {
if (allRequires.contains(&*ii) && !firstRequireBeforeSendInDefBlock) {
firstRequireBeforeSendInDefBlock = &*ii;
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Found send before def: "
<< *firstRequireBeforeSendInDefBlock);
break;
}
}
// Then walk from our sendingInst to the end of the block looking for the
// first require inst. Once we find it... return.
//
// NOTE: We start walking at the sendingInst since the sendingInst could use
// the requireInst as well.
for (auto ii = sendingInst->getIterator(),
ie = sendingInst->getParent()->end();
ii != ie; ++ii) {
if (!allRequires.contains(&*ii))
continue;
finalRequires.insert(&*ii);
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Found send after def: " << *ii);
return;
}
}
void RequireLiveness::processNonDefBlock(SILBasicBlock *block) {
// Walk from the bottom to the top... assigning to our block state.
auto blockState = blockLivenessInfo.get(block);
for (auto &inst : llvm::make_range(block->rbegin(), block->rend())) {
if (!finalRequires.contains(&inst))
continue;
blockState.get()->setInst(generation, &inst);
}
}
template <typename Collection>
void RequireLiveness::process(Collection requireInstList) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "==> Performing Require Liveness for: " << *sendingInst);
// Then put all of our requires into our allRequires set.
BasicBlockWorklist initializingWorklist(sendingInst->getFunction());
for (auto require : requireInstList) {
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Require Inst: " << **require);
allRequires.insert(*require);
initializingWorklist.pushIfNotVisited(require->getParent());
}
// Then process our def block to see if we have any requires before and after
// the sendingInst...
processDefBlock();
// If we found /any/ requries after the sendingInst, we can bail early since
// that is guaranteed to dominate all further requires.
if (!finalRequires.empty()) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs()
<< " Found send after def in def block! Exiting early!\n");
return;
}
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Did not find send after def in def "
"block! Walking blocks!\n");
// If we found a send in the def block before our def, add it to the block
// state for the def.
if (firstRequireBeforeSendInDefBlock) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs()
<< " Found a require before send! Adding to block state!\n");
auto blockState = blockLivenessInfo.get(sendingInst->getParent());
blockState.get()->setInst(generation, firstRequireBeforeSendInDefBlock);
}
// Then for each require block that isn't a def block send, find the
// earliest send inst.
while (auto *requireBlock = initializingWorklist.pop()) {
auto blockState = blockLivenessInfo.get(requireBlock);
for (auto &inst : *requireBlock) {
if (!allRequires.contains(&inst))
continue;
REGIONBASEDISOLATION_LOG(llvm::dbgs() << " Mapping Block bb"
<< requireBlock->getDebugID()
<< " to: " << inst);
blockState.get()->setInst(generation, &inst);
break;
}
}
// Then walk from our def block looking for setInst blocks.
auto *sendingBlock = sendingInst->getParent();
BasicBlockWorklist worklist(sendingInst->getFunction());
for (auto *succBlock : sendingBlock->getSuccessorBlocks())
worklist.pushIfNotVisited(succBlock);
while (auto *next = worklist.pop()) {
// Check if we found an earliest requires... if so, add that to final
// requires and continue. We don't want to visit successors.
auto blockState = blockLivenessInfo.get(next);
if (auto *inst = blockState.get()->getInst(generation)) {
finalRequires.insert(inst);
continue;
}
// Do not look at successors of the sending block.
if (next == sendingBlock)
continue;
// Otherwise, we did not find a requires and need to search further
// successors.
for (auto *succBlock : next->getSuccessorBlocks())
worklist.pushIfNotVisited(succBlock);
}
}
//===----------------------------------------------------------------------===//
// MARK: Forward Declaration Of SendNonSendableImpl
//===----------------------------------------------------------------------===//
namespace {
/// Wrapper around a SILInstruction that internally specifies whether we are
/// dealing with an inout reinitialization needed or if it is just a normal
/// use after send.
class RequireInst {
public:
enum Kind {
UseAfterSend,
InOutReinitializationNeeded,
};
private:
llvm::PointerIntPair<SILInstruction *, 1> instAndKind;
RequireInst(SILInstruction *inst, Kind kind) : instAndKind(inst, kind) {}
public:
static RequireInst forUseAfterSend(SILInstruction *inst) {
return {inst, Kind::UseAfterSend};
}
static RequireInst forInOutReinitializationNeeded(SILInstruction *inst) {
return {inst, Kind::InOutReinitializationNeeded};
}
SILInstruction *getInst() const { return instAndKind.getPointer(); }
Kind getKind() const { return Kind(instAndKind.getInt()); }
SILInstruction *operator*() const { return getInst(); }
SILInstruction *operator->() const { return getInst(); }
};
class SendNonSendableImpl {
RegionAnalysisFunctionInfo *info;
SmallFrozenMultiMap<Operand *, RequireInst, 8> sendingOpToRequireInstMultiMap;
SmallVector<PartitionOpError, 8> foundVerbatimErrors;
public:
SendNonSendableImpl(RegionAnalysisFunctionInfo *info) : info(info) {}
void emitDiagnostics();
private:
void runDiagnosticEvaluator();
void emitUseAfterSendDiagnostics();
void emitVerbatimErrors();
};
} // namespace
//===----------------------------------------------------------------------===//
// MARK: Diagnostic Evaluator
//===----------------------------------------------------------------------===//
namespace {
struct DiagnosticEvaluator final
: PartitionOpEvaluatorBaseImpl<DiagnosticEvaluator> {
RegionAnalysisFunctionInfo *info;
SmallFrozenMultiMap<Operand *, RequireInst, 8>
&sendingOpToRequireInstMultiMap;
/// An error that we know how to emit verbatim without needing to preprocess.
///
/// A contrasting case here is the use after send error where we need to pair
/// sending operands to require insts.
SmallVectorImpl<PartitionOpError> &foundVerbatimErrors;
DiagnosticEvaluator(Partition &workingPartition,
RegionAnalysisFunctionInfo *info,
SmallFrozenMultiMap<Operand *, RequireInst, 8>
&sendingOpToRequireInstMultiMap,
SmallVectorImpl<PartitionOpError> &foundVerbatimErrors,
SendingOperandToStateMap &operandToStateMap)
: PartitionOpEvaluatorBaseImpl(
workingPartition, info->getOperandSetFactory(), operandToStateMap),
info(info),
sendingOpToRequireInstMultiMap(sendingOpToRequireInstMultiMap),
foundVerbatimErrors(foundVerbatimErrors) {}
void handleLocalUseAfterSend(LocalUseAfterSendError &&error) const {
const auto &partitionOp = *error.op;
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
// Ignore this if we are erroring on a mutable base of a Sendable value and
// if when we sent the value's region was not closure captured.
if (error.op->getOptions().containsOnly(
PartitionOp::Flag::RequireOfMutableBaseOfSendableValue) &&
!operandToStateMap.get(error.sendingOp).isClosureCaptured)
return;
sendingOpToRequireInstMultiMap.insert(
error.sendingOp, RequireInst::forUseAfterSend(partitionOp.getSourceInst()));
}
void handleInOutSendingNotInitializedAtExitError(
InOutSendingNotInitializedAtExitError &&error) const {
const PartitionOp &partitionOp = *error.op;
Operand *sendingOp = error.sendingOp;
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
sendingOpToRequireInstMultiMap.insert(
sendingOp, RequireInst::forInOutReinitializationNeeded(
partitionOp.getSourceInst()));
}
void handleUnknownCodePattern(UnknownCodePatternError &&error) const {
const PartitionOp &op = *error.op;
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(op.getSourceInst(),
diag::regionbasedisolation_unknown_pattern);
}
void handleError(PartitionOpError &&error) {
switch (error.getKind()) {
case PartitionOpError::LocalUseAfterSend: {
return handleLocalUseAfterSend(
std::move(error).getLocalUseAfterSendError());
}
case PartitionOpError::InOutSendingNotDisconnectedAtExit:
case PartitionOpError::InOutSendingReturned:
case PartitionOpError::SentNeverSendable:
case PartitionOpError::AssignNeverSendableIntoSendingResult:
case PartitionOpError::NonSendableIsolationCrossingResult:
case PartitionOpError::InOutSendingParametersInSameRegion:
// We are going to process these later... but dump so we can see that we
// handled an error here. The rest of the explicit handlers will dump as
// appropriate if they want to emit an error here (some will squelch the
// error).
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
foundVerbatimErrors.emplace_back(std::move(error));
return;
case PartitionOpError::InOutSendingNotInitializedAtExit: {
return handleInOutSendingNotInitializedAtExitError(
std::move(error).getInOutSendingNotInitializedAtExitError());
}
case PartitionOpError::UnknownCodePattern: {
return handleUnknownCodePattern(
std::move(error).getUnknownCodePatternError());
}
}
llvm_unreachable("Covered switch isn't covered?!");
}
bool isActorDerived(Element element) const {
return info->getValueMap().getIsolationRegion(element).isActorIsolated();
}
/// If \p element's representative is an indirect out parameter, return
/// that parameter.
SILValue getIndirectOutParameter(Element element) const {
auto rep = info->getValueMap().getRepresentativeValue(element);
if (!rep)
return {};
if (auto value = dyn_cast_or_null<SILFunctionArgument>(rep.maybeGetValue());
value && value->getArgumentConvention().isIndirectOutParameter())
return value;
return {};
}
SILValue getInOutSendingParameter(Element elt) const {
auto rep = info->getValueMap().getRepresentativeValue(elt);
if (!rep)
return {};
if (auto value = dyn_cast_or_null<SILFunctionArgument>(rep.maybeGetValue());
value && value->getArgumentConvention().isInoutConvention() &&
value->isSending())
return value;
return {};
}
bool isTaskIsolatedDerived(Element element) const {
return info->getValueMap().getIsolationRegion(element).isTaskIsolated();
}
SILIsolationInfo::Kind hasSpecialDerivation(Element element) const {
return info->getValueMap().getIsolationRegion(element).getKind();
}
SILIsolationInfo getIsolationRegionInfo(Element element) const {
return info->getValueMap().getIsolationRegion(element);
}
/// Only return an element if we are already tracking value and it is
/// non-Sendable.
///
/// TODO: Can we return only
std::optional<Element> getElement(SILValue value) const {
auto trackableValue = info->getValueMap().getTrackableValue(value);
if (trackableValue.value.isSendable())
return {};
return trackableValue.value.getID();
}
SILValue getRepresentative(SILValue value) const {
return info->getValueMap()
.getTrackableValue(value)
.value.getRepresentative()
.maybeGetValue();
}
RepresentativeValue getRepresentativeValue(Element element) const {
return info->getValueMap().getRepresentativeValue(element);
}
bool isClosureCaptured(Element element, Operand *op) const {
auto value = info->getValueMap().maybeGetRepresentative(element);
if (!value)
return false;
return info->isClosureCaptured(value, op);
}
};
} // namespace
void SendNonSendableImpl::runDiagnosticEvaluator() {
// Then for each block...
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Walking blocks for diagnostics.\n");
for (auto [block, blockState] : info->getRange()) {
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< "|--> Block bb" << block.getDebugID() << "\n");
if (!blockState.getLiveness()) {
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Dead block... skipping!\n");
continue;
}
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "Entry Partition: ";
blockState.getEntryPartition().print(llvm::dbgs()));
// Grab its entry partition and setup an evaluator for the partition that
// has callbacks that emit diagnsotics...
Partition workingPartition = blockState.getEntryPartition();
DiagnosticEvaluator eval(
workingPartition, info, sendingOpToRequireInstMultiMap,
foundVerbatimErrors, info->getSendingOperandToStateMap());
// And then evaluate all of our partition ops on the entry partition.
for (auto &partitionOp : blockState.getPartitionOps()) {
eval.apply(partitionOp);
}
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Exit Partition: ";
workingPartition.print(llvm::dbgs()));
}
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< "Finished walking blocks for diagnostics.\n");
// Now that we have found all of our sendingInsts/Requires emit errors.
sendingOpToRequireInstMultiMap.setFrozen();
}
//===----------------------------------------------------------------------===//
// MARK: UseAfterSend Diagnostic Inference
//===----------------------------------------------------------------------===//
namespace {
class UseAfterSendDiagnosticEmitter {
Operand *sendingOp;
SmallVectorImpl<RequireInst> &requireInsts;
bool emittedErrorDiagnostic = false;
public:
UseAfterSendDiagnosticEmitter(Operand *sendingOp,
SmallVectorImpl<RequireInst> &requireInsts)
: sendingOp(sendingOp), requireInsts(requireInsts) {}
~UseAfterSendDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return sendingOp->getFunction(); }
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return sendingOp->get()->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
/// Attempts to retrieve and return the callee declaration.
std::optional<const ValueDecl *> getSendingCallee() const {
return getSendingApplyCallee(sendingOp->getUser());
}
void
emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
SILIsolationInfo namedValuesIsolationInfo,
ApplyIsolationCrossing isolationCrossing) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
// Then emit the note with greater context.
auto descriptiveKindStr =
namedValuesIsolationInfo.printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
bool isDisconnected = namedValuesIsolationInfo.isDisconnected();
if (auto callee = getSendingCallee()) {
diagnoseNote(
loc, diag::regionbasedisolation_named_info_send_yields_race_callee,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callee.value(), callerIsolationStr);
} else {
diagnoseNote(loc, diag::regionbasedisolation_named_info_send_yields_race,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callerIsolationStr);
}
emitRequireInstDiagnostics();
}
void
emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
SILIsolationInfo namedValuesIsolationInfo,
ApplyIsolationCrossing isolationCrossing,
const ValueDecl *callee) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
// Then emit the note with greater context.
auto descriptiveKindStr =
namedValuesIsolationInfo.printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
bool isDisconnected = namedValuesIsolationInfo.isDisconnected();
diagnoseNote(loc,
diag::regionbasedisolation_named_info_send_yields_race_callee,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callee, callerIsolationStr);
emitRequireInstDiagnostics();
}
void emitNamedAsyncLetNoIsolationCrossingError(SILLocation loc,
Identifier name) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
diagnoseNote(
loc, diag::regionbasedisolation_named_nonisolated_asynclet_name, name);
emitRequireInstDiagnostics();
}
void emitTypedIsolationCrossing(SILLocation loc, Type inferredType,
ApplyIsolationCrossing isolationCrossing) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
if (auto callee = getSendingCallee()) {
diagnoseNote(loc, diag::regionbasedisolation_type_use_after_send_callee,
inferredType, calleeIsolationStr, callee.value(),
callerIsolationStr);
} else {
diagnoseNote(loc, diag::regionbasedisolation_type_use_after_send,
inferredType, calleeIsolationStr, callerIsolationStr);
}
emitRequireInstDiagnostics();
}
void emitNamedUseofStronglySentValue(SILLocation loc, Identifier name) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
// Then emit the note with greater context.
diagnoseNote(
loc, diag::regionbasedisolation_named_value_used_after_explicit_sending,
name);
// Finally the require points.
emitRequireInstDiagnostics();
}
void emitTypedUseOfStronglySentValue(SILLocation loc, Type inferredType) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
if (auto callee = getSendingCallee()) {
diagnoseNote(loc,
diag::regionbasedisolation_typed_use_after_sending_callee,
inferredType, callee.value());
} else {
diagnoseNote(loc, diag::regionbasedisolation_typed_use_after_sending,
inferredType);
}
emitRequireInstDiagnostics();
}
void emitNamedIsolationCrossingDueToCapture(
SILLocation loc, Identifier name,
SILIsolationInfo namedValuesIsolationInfo,
ApplyIsolationCrossing isolationCrossing) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
auto descriptiveKindStr =
namedValuesIsolationInfo.printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
bool isDisconnected = namedValuesIsolationInfo.isDisconnected();
diagnoseNote(loc,
diag::regionbasedisolation_named_isolated_closure_yields_race,
isDisconnected, descriptiveKindStr, name, calleeIsolationStr,
callerIsolationStr);
emitRequireInstDiagnostics();
}
void emitTypedIsolationCrossingDueToCapture(
SILLocation loc, Type inferredType,
ApplyIsolationCrossing isolationCrossing) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
diagnoseNote(loc,
diag::regionbasedisolation_type_isolated_capture_yields_race,
inferredType, calleeIsolationStr, callerIsolationStr);
emitRequireInstDiagnostics();
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(sendingOp->getUser(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
private:
ASTContext &getASTContext() const {
return sendingOp->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
void emitRequireInstDiagnostics() {
// Now actually emit the require notes.
while (!requireInsts.empty()) {
auto require = requireInsts.pop_back_val();
switch (require.getKind()) {
case RequireInst::UseAfterSend:
diagnoseNote(*require, diag::regionbasedisolation_maybe_race);
continue;
case RequireInst::InOutReinitializationNeeded:
diagnoseNote(
*require,
diag::regionbasedisolation_inout_sending_must_be_reinitialized);
continue;
}
llvm_unreachable("Covered switch isn't covered?!");
}
}
};
class UseAfterSendDiagnosticInferrer {
Operand *sendingOp;
UseAfterSendDiagnosticEmitter diagnosticEmitter;
RegionAnalysisValueMap &valueMap;
SendingOperandToStateMap &sendingOpToStateMap;
SILLocation baseLoc = SILLocation::invalid();
Type baseInferredType;
struct AutoClosureWalker;
public:
UseAfterSendDiagnosticInferrer(Operand *sendOp,
SmallVectorImpl<RequireInst> &requireInsts,
RegionAnalysisValueMap &valueMap,
SendingOperandToStateMap &sendingOpToStateMap)
: sendingOp(sendOp), diagnosticEmitter(sendOp, requireInsts),
valueMap(valueMap), sendingOpToStateMap(sendingOpToStateMap),
baseLoc(sendOp->getUser()->getLoc()),
baseInferredType(sendOp->get()->getType().getASTType()) {}
void infer();
Operand *getSendingOperand() const { return sendingOp; }
private:
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
void initForApply(Operand *op, ApplyExpr *expr);
void initForAutoclosure(Operand *op, AutoClosureExpr *expr);
};
} // namespace
bool UseAfterSendDiagnosticInferrer::initForIsolatedPartialApply(
Operand *op, AbstractClosureExpr *ace) {
auto diagnosticPair = findClosureUse(op);
if (!diagnosticPair) {
return false;
}
auto *diagnosticOp = diagnosticPair->first;
ApplyIsolationCrossing crossing(
*op->getFunction()->getActorIsolation(),
*diagnosticOp->getFunction()->getActorIsolation());
auto &state = sendingOpToStateMap.get(sendingOp);
if (auto rootValueAndName = inferNameAndRootHelper(sendingOp->get())) {
diagnosticEmitter.emitNamedIsolationCrossingDueToCapture(
diagnosticOp->getUser()->getLoc(), rootValueAndName->first,
state.isolationInfo.getIsolationInfo(), crossing);
return true;
}
diagnosticEmitter.emitTypedIsolationCrossingDueToCapture(
diagnosticOp->getUser()->getLoc(), baseInferredType, crossing);
return true;
}
void UseAfterSendDiagnosticInferrer::initForApply(Operand *op,
ApplyExpr *sourceApply) {
auto isolationCrossing = sourceApply->getIsolationCrossing().value();
// Grab out full apply site and see if we can find a better expr.
SILInstruction *i = const_cast<SILInstruction *>(op->getUser());
auto fai = FullApplySite::isa(i);
assert(!fai.getArgumentConvention(*op).isIndirectOutParameter() &&
"An indirect out parameter is never sent");
auto *foundExpr = inferArgumentExprFromApplyExpr(sourceApply, fai, op);
auto inferredArgType =
foundExpr ? foundExpr->findOriginalType() : baseInferredType;
diagnosticEmitter.emitTypedIsolationCrossing(baseLoc, inferredArgType,
isolationCrossing);
}
/// This walker visits an AutoClosureExpr and looks for uses of a specific
/// captured value. We want to error on the uses in the autoclosure.
struct UseAfterSendDiagnosticInferrer::AutoClosureWalker : ASTWalker {
UseAfterSendDiagnosticInferrer &foundTypeInfo;
ValueDecl *targetDecl;
SILIsolationInfo targetDeclIsolationInfo;
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
AutoClosureWalker(UseAfterSendDiagnosticInferrer &foundTypeInfo,
ValueDecl *targetDecl,
SILIsolationInfo targetDeclIsolationInfo)
: foundTypeInfo(foundTypeInfo), targetDecl(targetDecl),
targetDeclIsolationInfo(targetDeclIsolationInfo) {}
Expr *lookThroughArgExpr(Expr *expr) {
while (true) {
if (auto *memberRefExpr = dyn_cast<MemberRefExpr>(expr)) {
expr = memberRefExpr->getBase();
continue;
}
if (auto *cvt = dyn_cast<ImplicitConversionExpr>(expr)) {
expr = cvt->getSubExpr();
continue;
}
if (auto *e = dyn_cast<ForceValueExpr>(expr)) {
expr = e->getSubExpr();
continue;
}
if (auto *t = dyn_cast<TupleElementExpr>(expr)) {
expr = t->getBase();
continue;
}
return expr;
}
}
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
if (auto *declRef = dyn_cast<DeclRefExpr>(expr)) {
// If this decl ref expr was not visited as part of a callExpr and is our
// target decl... emit a simple async let error.
if (!visitedCallExprDeclRefExprs.count(declRef)) {
if (declRef->getDecl() == targetDecl) {
foundTypeInfo.diagnosticEmitter
.emitNamedAsyncLetNoIsolationCrossingError(
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier());
return Action::Continue(expr);
}
}
}
// If we have a call expr, see if any of its arguments will cause our sent
// value to be sent into another isolation domain.
if (auto *callExpr = dyn_cast<CallExpr>(expr)) {
// Search callExpr's arguments to see if we have our targetDecl.
auto *argList = callExpr->getArgs();
for (auto pair : llvm::enumerate(argList->getArgExprs())) {
auto *arg = lookThroughArgExpr(pair.value());
auto *declRef = dyn_cast<DeclRefExpr>(arg);
if (!declRef)
continue;
if (declRef->getDecl() != targetDecl)
continue;
// Found our target!
visitedCallExprDeclRefExprs.insert(declRef);
auto isolationCrossing = callExpr->getIsolationCrossing();
// If we do not have an isolation crossing, then we must be just sending
// a value in a nonisolated fashion into an async let. So emit the
// simple async let error.
if (!isolationCrossing) {
foundTypeInfo.diagnosticEmitter
.emitNamedAsyncLetNoIsolationCrossingError(
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier());
continue;
}
// Otherwise, we are calling an actor-isolated function in the async
// let. Emit a better error.
// See if we can find a valueDecl/name for our callee so we can
// emit a nicer error.
ConcreteDeclRef concreteDecl =
callExpr->getDirectCallee()->getReferencedDecl();
// If we do not find a direct one, see if we are calling a method
// on a nominal type.
if (!concreteDecl) {
if (auto *dot =
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee())) {
concreteDecl = dot->getSemanticFn()->getReferencedDecl();
}
}
if (!concreteDecl)
continue;
auto *valueDecl = concreteDecl.getDecl();
assert(valueDecl && "Should be non-null if concreteDecl is valid");
if (auto isolationCrossing = callExpr->getIsolationCrossing()) {
// If we have an isolation crossing, use that information.
if (valueDecl->hasName()) {
foundTypeInfo.diagnosticEmitter.emitNamedIsolationCrossingError(
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier(),
targetDeclIsolationInfo, *isolationCrossing, valueDecl);
continue;
}
// Otherwise default back to the "callee" error.
foundTypeInfo.diagnosticEmitter.emitNamedIsolationCrossingError(
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier(),
targetDeclIsolationInfo, *isolationCrossing);
continue;
}
}
}
return Action::Continue(expr);
}
};
void UseAfterSendDiagnosticInferrer::infer() {
// Otherwise, see if our operand's instruction is a sending parameter.
if (auto fas = FullApplySite::isa(sendingOp->getUser())) {
assert(!fas.getArgumentConvention(*sendingOp).isIndirectOutParameter() &&
"We should never send an indirect out parameter");
if (fas.getArgumentParameterInfo(*sendingOp)
.hasOption(SILParameterInfo::Sending)) {
// First try to do the named diagnostic if we can find a name.
if (auto rootValueAndName = inferNameAndRootHelper(sendingOp->get())) {
return diagnosticEmitter.emitNamedUseofStronglySentValue(
baseLoc, rootValueAndName->first);
}
// See if we have an ApplyExpr and if we can infer a better type.
Type type = baseInferredType;
if (auto *applyExpr =
sendingOp->getUser()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto *foundExpr =
inferArgumentExprFromApplyExpr(applyExpr, fas, sendingOp))
type = foundExpr->findOriginalType();
}
// Otherwise, emit the typed diagnostic.
return diagnosticEmitter.emitTypedUseOfStronglySentValue(baseLoc, type);
}
}
auto loc = sendingOp->getUser()->getLoc();
// If we have a partial_apply that is actor isolated, see if we found a
// send error due to us sending a value into the partial apply.
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
if (ace->getActorIsolation().isActorIsolated()) {
if (initForIsolatedPartialApply(sendingOp, ace)) {
return;
}
}
}
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
// Before we do anything further, see if we can find a name and emit a name
// error.
if (auto rootValueAndName = inferNameAndRootHelper(sendingOp->get())) {
auto &state = sendingOpToStateMap.get(sendingOp);
return diagnosticEmitter.emitNamedIsolationCrossingError(
baseLoc, rootValueAndName->first,
state.isolationInfo.getIsolationInfo(),
*sourceApply->getIsolationCrossing());
}
// Otherwise, try to infer from the ApplyExpr.
return initForApply(sendingOp, sourceApply);
}
if (auto fas = FullApplySite::isa(sendingOp->getUser())) {
if (auto isolationCrossing = fas.getIsolationCrossing()) {
return diagnosticEmitter.emitTypedIsolationCrossing(
baseLoc, baseInferredType, *isolationCrossing);
}
}
auto *autoClosureExpr = loc.getAsASTNode<AutoClosureExpr>();
if (!autoClosureExpr) {
return diagnosticEmitter.emitUnknownPatternError();
}
auto *i = sendingOp->getUser();
auto pai = ApplySite::isa(i);
unsigned captureIndex = pai.getASTAppliedArgIndex(*sendingOp);
auto &state = sendingOpToStateMap.get(sendingOp);
auto captureInfo =
autoClosureExpr->getCaptureInfo().getCaptures()[captureIndex];
auto *captureDecl = captureInfo.getDecl();
AutoClosureWalker walker(*this, captureDecl,
state.isolationInfo.getIsolationInfo());
autoClosureExpr->walk(walker);
}
// Top level entrypoint for use after send diagnostics.
void SendNonSendableImpl::emitUseAfterSendDiagnostics() {
auto *function = info->getFunction();
BasicBlockData<BlockLivenessInfo> blockLivenessInfo(function);
// We use a generation counter so we can lazily reset blockLivenessInfo
// since we cannot clear it without iterating over it.
unsigned blockLivenessInfoGeneration = 0;
if (sendingOpToRequireInstMultiMap.empty())
return;
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "Emitting Error. Kind: Use After Send diagnostics.\n");
for (auto [sendingOp, requireInsts] :
sendingOpToRequireInstMultiMap.getRange()) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "Sending Op. Number: " << sendingOp->getOperandNumber()
<< ". User: " << *sendingOp->getUser());
// Then look for our requires before we emit any error. We want to emit a
// single we don't understand error if we do not find the require.
bool didEmitRequireNote = false;
InstructionSet requireInstsUnique(function);
RequireLiveness liveness(blockLivenessInfoGeneration, sendingOp,
blockLivenessInfo);
++blockLivenessInfoGeneration;
liveness.process(requireInsts);
SmallVector<RequireInst, 8> requireInstsForError;
for (auto require : requireInsts) {
// We can have multiple of the same require insts if we had a require
// and an assign from the same instruction. Our liveness checking
// above doesn't care about that, but we still need to make sure we do
// not emit twice.
if (!requireInstsUnique.insert(*require))
continue;
// If this was not a last require, do not emit an error.
if (!liveness.finalRequires.contains(*require))
continue;
requireInstsForError.push_back(require);
didEmitRequireNote = true;
}
// If we did not emit a require, emit an "unknown pattern" error that
// tells the user to file a bug. This importantly ensures that we can
// guarantee that we always find the require if we successfully compile.
if (!didEmitRequireNote) {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(sendingOp, diag::regionbasedisolation_unknown_pattern);
continue;
}
UseAfterSendDiagnosticInferrer diagnosticInferrer(
sendingOp, requireInstsForError, info->getValueMap(),
info->getSendingOperandToStateMap());
diagnosticInferrer.infer();
}
}
//===----------------------------------------------------------------------===//
// MARK: Send Never-Sent Diagnostic Inference
//===----------------------------------------------------------------------===//
namespace {
class SendNeverSentDiagnosticEmitter {
/// The use that actually caused the send.
Operand *sendingOperand;
/// The never-sent value that is in the same region as \p
/// sendingOperand.get().
llvm::PointerUnion<SILValue, SILInstruction *> neverSent;
/// The region info that describes the dynamic dataflow derived isolation
/// region info for the never-sent value.
///
/// This is equal to the merge of the IsolationRegionInfo from all elements in
/// the never-sent value's region when the error was diagnosed.
SILDynamicMergedIsolationInfo isolationRegionInfo;
bool emittedErrorDiagnostic = false;
public:
SendNeverSentDiagnosticEmitter(
Operand *sendingOperand,
llvm::PointerUnion<SILValue, SILInstruction *> neverSent,
SILDynamicMergedIsolationInfo isolationRegionInfo)
: sendingOperand(sendingOperand), neverSent(neverSent),
isolationRegionInfo(isolationRegionInfo) {}
~SendNeverSentDiagnosticEmitter() {
if (!emittedErrorDiagnostic) {
emitUnknownPatternError();
} else if (auto proto = isolationRegionInfo.getIsolationInfo()
.getIsolatedConformance()) {
// If the diagnostic comes from a (potentially) isolated conformance,
// add a note saying so and indicating where the isolated conformance
// can come in.
if (auto value = isolationRegionInfo.getIsolationInfo().getIsolatedValue()) {
if (auto loc = value.getLoc()) {
diagnoseNote(
loc, diag::regionbasedisolation_isolated_conformance_introduced,
proto);
}
}
}
}
Operand *getOperand() const { return sendingOperand; }
SILFunction *getFunction() const { return getOperand()->getFunction(); }
SILValue getNeverSentValue() const { return neverSent.dyn_cast<SILValue>(); }
SILInstruction *getNeverSentActorIntroducingInst() const {
return neverSent.dyn_cast<SILInstruction *>();
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
// If the failure is due to an isolated conformance, downgrade the error
// to a warning prior to Swift 7.
if (isolationRegionInfo.getIsolationInfo().getIsolatedConformance() &&
!sendingOperand->get()
->getType()
.getASTType()
->getASTContext()
.isLanguageModeAtLeast(
version::Version::getFutureMajorLanguageVersion()))
return DiagnosticBehavior::Warning;
return sendingOperand->get()->getType().getConcurrencyDiagnosticBehavior(
getOperand()->getFunction());
}
/// Attempts to retrieve and return the callee declaration.
std::optional<const ValueDecl *> getSendingCallee() const {
return getSendingApplyCallee(sendingOperand->getUser());
}
SILLocation getLoc() const { return sendingOperand->getUser()->getLoc(); }
/// Return the isolation region info for \p getNeverSentValue().
SILDynamicMergedIsolationInfo getIsolationRegionInfo() const {
return isolationRegionInfo;
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(getOperand()->getUser(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emitUnknownUse(SILLocation loc) {
// TODO: This will eventually be an unknown pattern error.
diagnoseError(loc, diag::regionbasedisolation_task_or_actor_isolated_sent)
.limitBehaviorIf(getBehaviorLimit());
}
void emitPassToApply(SILLocation loc, Type inferredType,
ApplyIsolationCrossing crossing) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), crossing.getCalleeIsolation());
if (auto callee = getSendingCallee()) {
diagnoseNote(
loc,
diag::regionbasedisolation_typed_sendneversendable_via_arg_callee,
descriptiveKindStr, inferredType, calleeIsolationStr, callee.value());
} else {
diagnoseNote(loc,
diag::regionbasedisolation_typed_sendneversendable_via_arg,
descriptiveKindStr, inferredType, calleeIsolationStr);
}
}
void emitNamedFunctionArgumentClosure(SILLocation loc, Identifier name,
ApplyIsolationCrossing crossing) {
emitNamedOnlyError(loc, name);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), crossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), crossing.getCallerIsolation());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
diagnoseNote(loc,
diag::regionbasedisolation_named_isolated_closure_yields_race,
isDisconnected, descriptiveKindStr, name, calleeIsolationStr,
callerIsolationStr);
}
void emitTypedSendingNeverSendableToSendingParam(SILLocation loc,
Type inferredType) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
if (auto callee = getSendingCallee()) {
diagnoseNote(
loc, diag::regionbasedisolation_typed_tns_passed_to_sending_callee,
descriptiveKindStr, inferredType, callee.value());
} else {
diagnoseNote(loc, diag::regionbasedisolation_typed_tns_passed_to_sending,
descriptiveKindStr, inferredType);
}
}
/// Emit an error for a case where we have captured a value like an actor and
/// thus a sending closure has become actor isolated (and thus unable to be
/// sent).
void emitClosureErrorWithCapturedActor(Operand *partialApplyOp,
Operand *actualUse,
SILArgument *fArg) {
auto descriptiveKindStr =
getIsolationRegionInfo().printForCodeDiagnostic(getFunction());
diagnoseError(partialApplyOp,
diag::regionbasedisolation_typed_tns_passed_sending_closure,
descriptiveKindStr)
.limitBehaviorIf(getDiagnosticBehaviorLimitForOperands(
actualUse->getFunction(), {actualUse}));
descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
diagnoseNote(actualUse, diag::regionbasedisolation_closure_captures_actor,
fArg->getDecl()->getName(), descriptiveKindStr);
}
/// Emit a typed error for an isolated closure being passed as a sending
/// parameter.
///
/// \arg partialApplyOp the operand of the outermost partial apply.
/// \arg actualUse the operand inside the closure that actually caused the
/// capture to occur. This maybe inside a different function from the partial
/// apply since we want to support a use inside a recursive closure.
void emitSendingClosureParamDirectlyIsolated(Operand *partialApplyOp,
Operand *actualUse,
SILArgument *fArg) {
auto descriptiveKindStr =
getIsolationRegionInfo().printForCodeDiagnostic(getFunction());
diagnoseError(partialApplyOp,
diag::regionbasedisolation_typed_tns_passed_sending_closure,
descriptiveKindStr)
.limitBehaviorIf(getDiagnosticBehaviorLimitForOperands(
actualUse->getFunction(), {actualUse}));
// If we have a closure capture box, emit a special diagnostic.
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
if (cast<SILFunctionArgument>(fArg)->isClosureCapture() &&
fArg->getType().is<SILBoxType>()) {
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_boxed_value_task_isolated;
diagnoseNote(actualUse, diag, fArg->getDecl());
return;
}
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
diagnoseNote(actualUse, diag, fArg->getDecl()->getName());
return;
}
descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value;
diagnoseNote(actualUse, diag, descriptiveKindStr,
fArg->getDecl()->getName());
}
void emitSendingClosureMultipleCapturedOperandError(
SILLocation loc, ArrayRef<Operand *> capturedOperands) {
// Our caller should have passed at least one operand. Emit an unknown error
// to signal we need a bug report.
if (capturedOperands.empty()) {
emitUnknownPatternError();
return;
}
auto descriptiveKindStr =
getIsolationRegionInfo()->printForCodeDiagnostic(getFunction());
auto emitMainError = [&] {
auto behaviorLimit = getDiagnosticBehaviorLimitForOperands(
getFunction(), capturedOperands);
diagnoseError(loc,
diag::regionbasedisolation_typed_tns_passed_sending_closure,
descriptiveKindStr)
.limitBehaviorIf(behaviorLimit);
};
if (capturedOperands.size() == 1) {
auto captured = capturedOperands.front();
auto actualUseInfo = findClosureUse(captured);
// If we fail to find actual use info, emit an unknown error.
if (!actualUseInfo) {
emitUnknownPatternError();
return;
}
if (getIsolationRegionInfo()->isTaskIsolated()) {
emitMainError();
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
diagnoseNote(actualUseInfo->first, diag,
actualUseInfo->second->getDecl()->getName());
return;
}
emitMainError();
descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_region;
diagnoseNote(actualUseInfo->first, diag, descriptiveKindStr,
actualUseInfo->second->getDecl()->getName());
return;
}
emitMainError();
bool emittedDiagnostic = false;
for (auto captured : capturedOperands) {
auto actualUseInfo = findClosureUse(captured);
if (!actualUseInfo)
continue;
emittedDiagnostic = true;
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_multiple_value;
diagnoseNote(actualUseInfo->first, diag,
actualUseInfo->second->getDecl()->getName());
}
// Check if we did not emit a diagnostic. In such a case, we need to emit an
// unknown patten error so that we get a bug report from the user.
if (!emittedDiagnostic) {
emitUnknownPatternError();
}
}
void emitNamedOnlyError(SILLocation loc, Identifier name) {
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
}
void emitNamedAsyncLetCapture(SILLocation loc, Identifier name,
SILIsolationInfo sentValueIsolation) {
assert(!getIsolationRegionInfo().isDisconnected() &&
"Should never be disconnected?!");
emitNamedOnlyError(loc, name);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
diagnoseNote(loc, diag::regionbasedisolation_named_send_nt_asynclet_capture,
name, descriptiveKindStr);
}
void emitNamedIsolation(SILLocation loc, Identifier name,
ApplyIsolationCrossing isolationCrossing) {
emitNamedOnlyError(loc, name);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
if (auto callee = getSendingCallee()) {
diagnoseNote(loc,
diag::regionbasedisolation_named_send_never_sendable_callee,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callee.value(), descriptiveKindStr);
} else {
diagnoseNote(loc, diag::regionbasedisolation_named_send_never_sendable,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
descriptiveKindStr);
}
}
void emitNamedSendingNeverSendableToSendingParam(SILLocation loc,
Identifier varName) {
emitNamedOnlyError(loc, varName);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
auto diag = diag::regionbasedisolation_named_send_into_sending_param;
diagnoseNote(loc, diag, isDisconnected, descriptiveKindStr, varName);
}
void emitNamedSendingReturn(SILLocation loc, Identifier varName) {
emitNamedOnlyError(loc, varName);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
auto diag = diag::regionbasedisolation_named_nosend_send_into_result;
diagnoseNote(loc, diag, isDisconnected, descriptiveKindStr, varName,
descriptiveKindStr);
}
private:
ASTContext &getASTContext() const {
return getOperand()->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseError(op->getUser()->getLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseNote(op->getUser()->getLoc(), diag,
std::forward<U>(args)...);
}
};
class SentNeverSendableDiagnosticInferrer {
struct AutoClosureWalker;
RegionAnalysisValueMap &valueMap;
SendNeverSentDiagnosticEmitter diagnosticEmitter;
using SentNeverSendableError = PartitionOpError::SentNeverSendableError;
public:
SentNeverSendableDiagnosticInferrer(RegionAnalysisValueMap &valueMap,
SentNeverSendableError error)
: valueMap(valueMap),
diagnosticEmitter(error.op->getSourceOp(),
valueMap.getRepresentative(error.sentElement),
error.isolationRegionInfo) {}
/// Gathers diagnostics. Returns false if we emitted a "I don't understand
/// error". If we emit such an error, we should bail without emitting any
/// further diagnostics, since we may not have any diagnostics or be in an
/// inconcistent state.
bool run();
private:
/// \p actualCallerIsolation is used to override the caller isolation we use
/// when emitting the error if the closure would have the incorrect one.
bool initForIsolatedPartialApply(
Operand *op, AbstractClosureExpr *ace,
std::optional<ActorIsolation> actualCallerIsolation = {});
bool initForSendingPartialApply(FullApplySite fas, Operand *pai);
std::optional<unsigned>
getIsolatedValuePartialApplyIndex(PartialApplyInst *pai,
SILValue isolatedValue) {
for (auto &paiOp : ApplySite(pai).getArgumentOperands()) {
if (valueMap.getTrackableValue(paiOp.get()).value.getRepresentative() ==
isolatedValue) {
return ApplySite(pai).getASTAppliedArgIndex(paiOp);
}
}
return {};
}
};
} // namespace
bool SentNeverSendableDiagnosticInferrer::initForSendingPartialApply(
FullApplySite fas, Operand *callsiteOp) {
// This is the partial apply that is being passed as a sending parameter.
auto *sendingPAI =
dyn_cast<PartialApplyInst>(stripFunctionConversions(callsiteOp->get()));
if (!sendingPAI)
return false;
// Make sure that we only handle closure literals.
//
// TODO: This should be marked on closures at the SIL level... I shouldn't
// have to refer to the AST.
if (!sendingPAI->getLoc().getAsASTNode<ClosureExpr>())
return false;
// Ok, we have a closure literal. First we handle a potential capture of
// 'self' before we do anything by looping over our captured parameters.
//
// DISCUSSION: The reason why we do this early is that as a later heuristic,
// we check if any of the values are directly task isolated (i.e. they are
// actually the task isolated value, not a value that is in the same region as
// something that is task isolated). This could potentially result in us
// emitting a task isolated error instead of an actor isolated error here if
// for some reason SILGen makes the self capture come later in the capture
// list. From a compile time perspective, going over a list of captures twice
// is not going to hurt especially since we are going to emit a diagnostic
// here anyways.
for (auto &sendingPAIOp : sendingPAI->getArgumentOperands()) {
// NOTE: If we access a field on self in the closure, we will still just
// capture self... so we do not have to handle that case due to the way
// SILGen codegens today. This is also true if we use a capture list [x =
// self.field] (i.e. a closure that captures a field from self is still
// nonisolated).
if (!sendingPAIOp.get()->getType().isAnyActor())
continue;
auto *fArg = dyn_cast<SILFunctionArgument>(
lookThroughOwnershipInsts(sendingPAIOp.get()));
if (!fArg || !fArg->isSelf())
continue;
auto capturedValue = findClosureUse(&sendingPAIOp);
if (!capturedValue) {
// If we failed to find the direct capture of self, emit an unknown code
// pattern error so the user knows to send a bug report. This should never
// fail.
diagnosticEmitter.emitUnknownPatternError();
return true;
}
// Otherwise, emit our captured actor error.
diagnosticEmitter.emitClosureErrorWithCapturedActor(
&sendingPAIOp, capturedValue->first, capturedValue->second);
return true;
}
// Ok, we know that we have a closure expr. We now need to find the specific
// closure captured value that is actor or task isolated. Then we search for
// the potentially recursive closure use so we can show a nice loc to the
// user.
auto maybeIsolatedValue =
diagnosticEmitter.getIsolationRegionInfo()->maybeGetIsolatedValue();
// If we do not find an actual task isolated value while looping below, this
// contains the non sendable captures of the partial apply that we want to
// emit a more heuristic based error for. See documentation below.
SmallVector<Operand *, 8> nonSendableOps;
for (auto &sendingPAIOp : sendingPAI->getArgumentOperands()) {
// If our value's rep is task isolated or is the dynamic isolated
// value... then we are done. This is a 'correct' error value to emit.
auto trackableValue = valueMap.getTrackableValue(sendingPAIOp.get());
if (trackableValue.value.isSendable())
continue;
auto rep = trackableValue.value.getRepresentative().maybeGetValue();
nonSendableOps.push_back(&sendingPAIOp);
if (trackableValue.value.getIsolationRegionInfo().isTaskIsolated() ||
rep == maybeIsolatedValue) {
if (auto capturedValue = findClosureUse(&sendingPAIOp)) {
diagnosticEmitter.emitSendingClosureParamDirectlyIsolated(
callsiteOp, capturedValue->first, capturedValue->second);
return true;
}
}
}
// If we did not find a clear answer in terms of an isolated value, we emit a
// more general error based on:
//
// 1. If we have one non-Sendable value then we know that must be the value.
// 2. Otherwise, we emit a generic captured non-Sendable value error to give
// people something to work off of.
diagnosticEmitter.emitSendingClosureMultipleCapturedOperandError(
callsiteOp->getUser()->getLoc(), nonSendableOps);
return true;
}
bool SentNeverSendableDiagnosticInferrer::initForIsolatedPartialApply(
Operand *op, AbstractClosureExpr *ace,
std::optional<ActorIsolation> actualCallerIsolation) {
auto diagnosticPair = findClosureUse(op);
if (!diagnosticPair)
return false;
auto *diagnosticOp = diagnosticPair->first;
ApplyIsolationCrossing crossing(
*op->getFunction()->getActorIsolation(),
*diagnosticOp->getFunction()->getActorIsolation());
// We do not need to worry about failing to infer a name here since we are
// going to be returning some form of a SILFunctionArgument which is always
// easy to find a name for.
if (auto rootValueAndName = inferNameAndRootHelper(op->get())) {
diagnosticEmitter.emitNamedFunctionArgumentClosure(
diagnosticOp->getUser()->getLoc(), rootValueAndName->first, crossing);
return true;
}
return false;
}
/// This walker visits an AutoClosureExpr and looks for uses of a specific
/// captured value. We want to error on the uses in the autoclosure.
struct SentNeverSendableDiagnosticInferrer::AutoClosureWalker : ASTWalker {
SendNeverSentDiagnosticEmitter &foundTypeInfo;
ValueDecl *targetDecl;
SILIsolationInfo targetDeclIsolationInfo;
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
SILLocation captureLoc;
bool isAsyncLet;
AutoClosureWalker(SendNeverSentDiagnosticEmitter &foundTypeInfo,
ValueDecl *targetDecl,
SILIsolationInfo targetDeclIsolationInfo,
SILLocation captureLoc, bool isAsyncLet)
: foundTypeInfo(foundTypeInfo), targetDecl(targetDecl),
targetDeclIsolationInfo(targetDeclIsolationInfo),
captureLoc(captureLoc), isAsyncLet(isAsyncLet) {}
Expr *lookThroughArgExpr(Expr *expr) {
while (true) {
if (auto *memberRefExpr = dyn_cast<MemberRefExpr>(expr)) {
expr = memberRefExpr->getBase();
continue;
}
if (auto *cvt = dyn_cast<ImplicitConversionExpr>(expr)) {
expr = cvt->getSubExpr();
continue;
}
if (auto *e = dyn_cast<ForceValueExpr>(expr)) {
expr = e->getSubExpr();
continue;
}
if (auto *t = dyn_cast<TupleElementExpr>(expr)) {
expr = t->getBase();
continue;
}
return expr;
}
}
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
if (auto *declRef = dyn_cast<DeclRefExpr>(expr)) {
// If this decl ref expr was not visited as part of a callExpr and is our
// target decl... emit a simple async let error.
//
// This occurs if we do:
//
// ```
// let x = ...
// async let y = x
// ```
if (declRef->getDecl() == targetDecl) {
foundTypeInfo.emitNamedAsyncLetCapture(captureLoc,
targetDecl->getBaseIdentifier(),
targetDeclIsolationInfo);
return Action::Continue(expr);
}
}
return Action::Continue(expr);
}
};
bool SentNeverSendableDiagnosticInferrer::run() {
// We need to find the isolation info.
auto *op = diagnosticEmitter.getOperand();
auto loc = op->getUser()->getLoc();
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
// First see if we have a 'sending' argument.
if (auto fas = FullApplySite::isa(op->getUser())) {
if (fas.getArgumentParameterInfo(*op).hasOption(
SILParameterInfo::Sending)) {
// Before we do anything, lets see if we are passing a sendable closure
// literal. If we do, we want to emit a special error that states which
// captured value caused the actual error.
if (initForSendingPartialApply(fas, op))
return true;
// See if we can infer a name from the value.
if (auto varName = inferNameHelper(op->get())) {
diagnosticEmitter.emitNamedSendingNeverSendableToSendingParam(
loc, *varName);
return true;
}
Type type = op->get()->getType().getASTType();
if (auto *inferredArgExpr =
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
type = inferredArgExpr->findOriginalType();
}
diagnosticEmitter.emitTypedSendingNeverSendableToSendingParam(loc,
type);
return true;
}
}
// First try to get the apply from the isolation crossing.
auto isolation = sourceApply->getIsolationCrossing();
// If we could not infer an isolation...
if (!isolation) {
// Otherwise, emit a "we don't know error" that tells the user to file a
// bug.
diagnosticEmitter.emitUnknownPatternError();
return false;
}
assert(isolation && "Expected non-null");
// Then if we are calling a closure expr. If so, we should use the loc of
// the closure.
if (auto *closureExpr =
dyn_cast<AbstractClosureExpr>(sourceApply->getFn())) {
initForIsolatedPartialApply(op, closureExpr,
isolation->getCallerIsolation());
return true;
}
// See if we can infer a name from the value.
if (auto name = inferNameHelper(op->get())) {
diagnosticEmitter.emitNamedIsolation(loc, *name, *isolation);
return true;
}
// Attempt to find the specific sugared ASTType if we can to emit a better
// diagnostic.
Type type = op->get()->getType().getASTType();
if (auto fas = FullApplySite::isa(op->getUser())) {
if (auto *inferredArgExpr =
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
type = inferredArgExpr->findOriginalType();
}
}
diagnosticEmitter.emitPassToApply(loc, type, *isolation);
return true;
}
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
if (ace->getActorIsolation().isActorIsolated()) {
if (initForIsolatedPartialApply(op, ace)) {
return true;
}
}
}
// See if we are in SIL and have an apply site specified isolation.
if (auto fas = FullApplySite::isa(op->getUser())) {
if (auto isolation = fas.getIsolationCrossing()) {
diagnosticEmitter.emitPassToApply(loc, op->get()->getType().getASTType(),
*isolation);
return true;
}
}
if (auto *ri = dyn_cast<ReturnInst>(op->getUser())) {
auto fType = ri->getFunction()->getLoweredFunctionType();
if (fType->getNumResults() &&
fType->getResults()[0].hasOption(SILResultInfo::IsSending)) {
assert(llvm::all_of(fType->getResults(),
[](SILResultInfo resultInfo) {
return resultInfo.hasOption(
SILResultInfo::IsSending);
}) &&
"All result info must be the same... if that changes... update "
"this code!");
if (auto name = inferNameHelper(op->get())) {
diagnosticEmitter.emitNamedSendingReturn(loc, *name);
return true;
}
} else {
assert(llvm::none_of(fType->getResults(),
[](SILResultInfo resultInfo) {
return resultInfo.hasOption(
SILResultInfo::IsSending);
}) &&
"All result info must be the same... if that changes... update "
"this code!");
}
}
// If we are failing due to an autoclosure... see if we can find the captured
// value that is causing the issue.
if (auto *autoClosureExpr = loc.getAsASTNode<AutoClosureExpr>()) {
// To split up this work, we only do this for async let for now.
if (autoClosureExpr->getThunkKind() == AutoClosureExpr::Kind::AsyncLet) {
auto *i = op->getUser();
auto pai = ApplySite::isa(i);
unsigned captureIndex = pai.getASTAppliedArgIndex(*op);
auto captureInfo =
autoClosureExpr->getCaptureInfo().getCaptures()[captureIndex];
auto loc = RegularLocation(captureInfo.getLoc(), false /*implicit*/);
AutoClosureWalker walker(
diagnosticEmitter, captureInfo.getDecl(),
diagnosticEmitter.getIsolationRegionInfo().getIsolationInfo(), loc,
autoClosureExpr->getThunkKind() == AutoClosureExpr::Kind::AsyncLet);
autoClosureExpr->walk(walker);
return true;
}
}
diagnosticEmitter.emitUnknownUse(loc);
return true;
}
//===----------------------------------------------------------------------===//
// MARK: InOutSendingReturnedError Error Emitter
//===----------------------------------------------------------------------===//
namespace {
class InOutSendingReturnedDiagnosticEmitter {
/// A region analysis function info that we use if we need to determine which
/// incoming block edge had an error upon it.
RegionAnalysisFunctionInfo *raFuncInfo;
/// The function exiting inst where the 'inout sending' parameter was actor
/// isolated.
TermInst *functionExitingInst;
/// The 'inout sending' param that we are emitting an error for.
SILValue inoutSendingParam;
/// The element number of the 'inout sending' param.
Element inoutSendingParamElement;
/// If we did not return the 'inout sending' param directly but instead
/// returned something else in the same region. This is that value.
///
/// If we actually returned the inout sending parameter this is SILValue().
SILValue returnedValue;
/// If we had a failure due to returning a value that is actor isolated due to
/// it being in the same region as an out parameter. THis is the actor
/// isolation.
SILDynamicMergedIsolationInfo isolationInfo;
bool emittedErrorDiagnostic = false;
public:
class LastValueEnum;
InOutSendingReturnedDiagnosticEmitter(
RegionAnalysisFunctionInfo *raFuncInfo, TermInst *functionExitingInst,
SILValue inoutSendingParam, Element inoutSendingParamElement,
SILValue erroringValue, SILDynamicMergedIsolationInfo isolationInfo)
: raFuncInfo(raFuncInfo), functionExitingInst(functionExitingInst),
inoutSendingParam(inoutSendingParam),
inoutSendingParamElement(inoutSendingParamElement),
returnedValue(erroringValue), isolationInfo(isolationInfo) {}
~InOutSendingReturnedDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return inoutSendingParam->getFunction(); }
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return inoutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emit();
/// Called if we return the actual inout sending value.
void emitReturnInOutSendingError(SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto inoutSendingParamName = inferNameHelper(inoutSendingParam);
if (!inoutSendingParamName) {
return emitUnknownPatternError();
}
diagnoseError(
loc, diag::regionbasedisolation_inout_sending_cannot_be_returned_param,
*inoutSendingParamName)
.limitBehaviorIf(getBehaviorLimit());
if (isolationInfo->isActorIsolated()) {
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_actor_param,
*inoutSendingParamName,
isolationInfo->printForDiagnostics(getFunction()));
return;
}
diagnoseNote(
loc,
diag::regionbasedisolation_inout_sending_cannot_be_returned_note_param,
*inoutSendingParamName);
}
void emitValueError(SILValue value,
SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto inoutSendingParamName = inferNameHelper(inoutSendingParam);
if (!inoutSendingParamName) {
return emitUnknownPatternError();
}
std::optional<Identifier> erroringEltName = inferNameHelper(value);
if (!erroringEltName) {
return emitUnknownPatternError();
}
diagnoseError(
loc, diag::regionbasedisolation_inout_sending_cannot_be_returned_value,
*erroringEltName)
.limitBehaviorIf(getBehaviorLimit());
if (isolationInfo->isActorIsolated()) {
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_actor_value,
*erroringEltName, *inoutSendingParamName,
isolationInfo->printForDiagnostics(getFunction()));
return;
}
diagnoseNote(
loc,
diag::regionbasedisolation_inout_sending_cannot_be_returned_note_value,
*erroringEltName, *inoutSendingParamName);
}
/// Print \p value in a standard way. Allow for loc to override the specific
/// location used to emit the diagnostic fi non-standard.
void emitLastValueEnum(const LastValueEnum &value,
SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc = SILLocation::invalid());
/// Emit an error for use when we are processing the incoming value of a phi
/// argument or of the dynamic value of an indirect out parameter.
bool emitOutParamIncomingValueError(const LastValueEnum &value);
void emitDeclRefError(SILDeclRef declRef,
SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto inoutSendingParamName = inferNameHelper(inoutSendingParam);
if (!inoutSendingParamName) {
return emitUnknownPatternError();
}
diagnoseError(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_value_result,
declRef.getAbstractFunctionDecl())
.limitBehaviorIf(getBehaviorLimit());
if (isolationInfo->isActorIsolated()) {
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_actor_return_value,
declRef.getAbstractFunctionDecl(), *inoutSendingParamName,
isolationInfo.printForDiagnostics(getFunction()));
return;
}
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_return_value,
declRef.getAbstractFunctionDecl(), *inoutSendingParamName);
}
ASTContext &getASTContext() const {
return functionExitingInst->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
};
} // namespace
static SILValue lookThroughAccess(SILValue value) {
while (true) {
// We look through access, copies, and loads.
if (auto *bai = dyn_cast<BeginAccessInst>(value)) {
value = bai->getOperand();
continue;
}
if (auto *cvi = dyn_cast<CopyValueInst>(value)) {
value = cvi->getOperand();
continue;
}
if (auto *bbi = dyn_cast<BeginBorrowInst>(value);
bbi && !bbi->isFromVarDecl()) {
value = bbi->getOperand();
continue;
}
if (auto *mvi = dyn_cast<MoveValueInst>(value);
mvi && !mvi->isFromVarDecl()) {
value = mvi->getOperand();
continue;
}
if (auto *li = dyn_cast<LoadInst>(value)) {
value = li->getOperand();
continue;
}
if (auto *lbi = dyn_cast<LoadBorrowInst>(value)) {
value = lbi->getOperand();
continue;
}
return value;
}
}
class InOutSendingReturnedDiagnosticEmitter::LastValueEnum {
public:
enum Kind {
None,
ApplySite,
CopyAddr,
Store,
Value,
ReturnTerm,
};
private:
Kind kind;
std::variant<FullApplySite, SILValue, CopyAddrInst *, StoreInst *, Operand *>
value;
LastValueEnum() : kind(Kind::None), value() {}
LastValueEnum(FullApplySite inst) : kind(Kind::ApplySite), value(inst) {}
LastValueEnum(SILValue value) : kind(Kind::Value), value(value) {}
LastValueEnum(CopyAddrInst *inst) : kind(Kind::CopyAddr), value(inst) {}
LastValueEnum(StoreInst *inst) : kind(Kind::Store), value(inst) {}
LastValueEnum(Operand *branchUse) : kind(Kind::ReturnTerm), value(branchUse) {
assert(isa<TermInst>(branchUse->getUser()));
assert(branchUse->getUser()->getLoc().getKind() == SILLocation::ReturnKind);
}
public:
static LastValueEnum get(SILValue value);
static LastValueEnum get(SILInstruction *inst);
static LastValueEnum get(Operand *use);
static bool isSupported(Operand *use);
operator bool() const { return getKind() != Kind::None; }
Kind getKind() const { return kind; }
bool isApplySite() const { return getKind() == Kind::ApplySite; }
bool isValue() const { return getKind() == Kind::Value; }
bool isStore() const { return getKind() == Kind::Store; }
bool isCopyAddr() const { return getKind() == Kind::CopyAddr; }
/// Is this a terminator instruction that is an incoming value to an epilogue
/// block and thus forms part of the return from a function.
bool isReturnTerm() const { return getKind() == Kind::ReturnTerm; }
FullApplySite getFullApplySite() const {
return std::get<FullApplySite>(value);
}
SILDeclRef getDeclRef() const {
return getFullApplySite().getCalleeFunction()->getDeclRef();
}
SILValue getValue() const {
switch (getKind()) {
case None:
case ApplySite:
return SILValue();
case CopyAddr:
return getCopyAddr()->getAllOperands()[CopyLikeInstruction::Src].get();
case Store:
return getStore()->getAllOperands()[CopyLikeInstruction::Src].get();
case Value:
return std::get<SILValue>(value);
case ReturnTerm:
return std::get<Operand *>(value)->get();
}
}
CopyAddrInst *getCopyAddr() const { return std::get<CopyAddrInst *>(value); }
StoreInst *getStore() const { return std::get<StoreInst *>(value); }
Operand *getReturnTerm() const { return std::get<Operand *>(value); }
SILBasicBlock *getParentBlock() const {
switch (getKind()) {
case None:
return nullptr;
case ApplySite:
return getFullApplySite()->getParent();
case Value:
return getValue()->getParentBlock();
case CopyAddr:
return getCopyAddr()->getParentBlock();
case Store:
return getStore()->getParentBlock();
case ReturnTerm:
return getReturnTerm()->getParentBlock();
}
}
SILInstruction *getDefiningInsertionPoint() const {
switch (getKind()) {
case None:
return nullptr;
case ApplySite:
return *getFullApplySite();
case Value:
return getValue()->getDefiningInsertionPoint();
case CopyAddr:
return getCopyAddr();
case Store:
return getStore();
case ReturnTerm:
return getReturnTerm()->getUser();
}
}
SILLocation getLoc() const {
switch (getKind()) {
case None:
return SILLocation::invalid();
case ApplySite:
case Value:
case CopyAddr:
case Store:
case ReturnTerm:
return getDefiningInsertionPoint()->getLoc();
}
}
bool operator==(SILValue otherValue) const {
switch (getKind()) {
case None:
case ApplySite:
return false;
case CopyAddr:
case Store:
case Value:
case ReturnTerm:
return lookThroughAccess(getValue()) == lookThroughAccess(otherValue);
}
}
static bool
findLastValuesForOutParam(SILFunctionArgument *fArg,
SmallVectorImpl<LastValueEnum> &lastValues);
void print(llvm::raw_ostream &os) const {
switch (getKind()) {
case None:
os << "none\n";
return;
case ApplySite:
os << "apply site: " << *getFullApplySite();
return;
case CopyAddr:
os << "copy_addr: " << *getCopyAddr();
return;
case Store:
os << "store: " << *getStore();
return;
case Value:
os << "value: " << *getValue();
break;
case ReturnTerm:
os << "return term: " << *getReturnTerm()->getUser();
}
}
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
};
InOutSendingReturnedDiagnosticEmitter::LastValueEnum
InOutSendingReturnedDiagnosticEmitter::LastValueEnum::get(
SILInstruction *user) {
if (auto *cai = dyn_cast<CopyAddrInst>(user)) {
return {cai};
}
if (auto *si = dyn_cast<StoreInst>(user)) {
return {si};
}
if (auto fas = FullApplySite::isa(user)) {
return {fas};
}
return {};
}
InOutSendingReturnedDiagnosticEmitter::LastValueEnum
InOutSendingReturnedDiagnosticEmitter::LastValueEnum::get(Operand *use) {
if (auto value = get(use->getUser()))
return value;
if (auto *ti = dyn_cast<TermInst>(use->getUser())) {
if (ti->getLoc().getKind() == SILLocation::ReturnKind) {
return {use};
}
}
return {};
}
bool InOutSendingReturnedDiagnosticEmitter::LastValueEnum::isSupported(
Operand *use) {
if (auto fas = FullApplySite::isa(use->getUser())) {
if (fas.isIndirectResultOperand(*use)) {
auto *fn = fas.getCalleeFunction();
return fn && fn->getDeclRef();
}
}
if (auto *ti = dyn_cast<TermInst>(use->getUser())) {
if (ti->getLoc().getKind() == SILLocation::ReturnKind) {
return true;
}
}
return isa<CopyAddrInst, StoreInst>(use->getUser());
}
InOutSendingReturnedDiagnosticEmitter::LastValueEnum
InOutSendingReturnedDiagnosticEmitter::LastValueEnum::get(SILValue value) {
if (auto *inst = value->getDefiningInstruction()) {
if (auto e = get(inst))
return e;
}
return {value};
}
void InOutSendingReturnedDiagnosticEmitter::emitLastValueEnum(
const LastValueEnum &value, SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
switch (value.getKind()) {
case LastValueEnum::None:
llvm::report_fatal_error("Found .none enum elt");
case LastValueEnum::ApplySite:
emitDeclRefError(value.getDeclRef(), isolationInfo,
loc ? loc : value.getLoc());
return;
case LastValueEnum::Store:
case LastValueEnum::CopyAddr:
emitValueError(value.getValue(), isolationInfo, loc ? loc : value.getLoc());
return;
case LastValueEnum::Value:
emitValueError(value.getValue(), isolationInfo,
loc ? loc : functionExitingInst->getLoc());
return;
case LastValueEnum::ReturnTerm:
emitValueError(value.getValue(), isolationInfo, loc ? loc : value.getLoc());
return;
}
llvm::report_fatal_error("Should never hit this");
}
bool InOutSendingReturnedDiagnosticEmitter::emitOutParamIncomingValueError(
const LastValueEnum &finalValue) {
auto *block = finalValue.getParentBlock();
auto state = raFuncInfo->getBlockState(block);
assert(state.isNonNull());
// Grab the exit partition of the block. We make a copy here on purpose
// since it /is/ possible for DiagnosticEvaluator to modify
auto workingExitPartition = state.get()->getExitPartition();
// See if given the exit partition of the block if we would emit an error
// when we execute an InOutSendingAtFunctionExit partition op.
SmallFrozenMultiMap<Operand *, RequireInst, 8> sendingOpToRequireInstMultiMap;
SmallVector<PartitionOpError, 2> errors;
DiagnosticEvaluator evaluator(workingExitPartition, raFuncInfo,
sendingOpToRequireInstMultiMap, errors,
raFuncInfo->getSendingOperandToStateMap());
PartitionOp op = PartitionOp::InOutSendingAtFunctionExit(
inoutSendingParamElement, finalValue.getDefiningInsertionPoint());
evaluator.apply(op);
// Check if we got an error. If we did not, then this is not one of the
// guilty code paths. Continue.
if (errors.empty()) {
return false;
}
// Loop through the errors to find our InOutReturnedError. We emit one error
// per return... but we emit for multiple returns separate diagnostics.
while (!errors.empty()) {
auto err = errors.pop_back_val();
if (err.getKind() != PartitionOpError::InOutSendingReturned)
continue;
auto castError = std::move(err).getInOutSendingReturnedError();
if (castError.inoutSendingElement != inoutSendingParamElement)
continue;
// Ok, we found an error. Lets emit it and return early.
if (finalValue == inoutSendingParam) {
emitReturnInOutSendingError(isolationInfo, finalValue.getLoc());
return true;
}
// Force us to use the location of the finalValue. We do not want to use the
// function exiting inst.
emitLastValueEnum(finalValue, isolationInfo, finalValue.getLoc());
return true;
}
return false;
}
void InOutSendingReturnedDiagnosticEmitter::emit() {
// Check if we had a separate erroring value that is our returned value. If we
// do not then we just returned the 'inout sending' parameter. Emit a special
// message and return.
if (inoutSendingParam == returnedValue) {
return emitReturnInOutSendingError(isolationInfo,
functionExitingInst->getLoc());
}
// Before we do anything, see if we have a phi argument that is an epilogue
// phi. In such a case, we want to process the incoming values.
if (auto *phi = dyn_cast<SILPhiArgument>(returnedValue)) {
bool handledValue = false;
phi->visitIncomingPhiOperands([&](Operand *op) {
if (op->getUser()->getLoc().getKind() != SILLocation::ReturnKind)
return true;
handledValue |=
emitOutParamIncomingValueError(LastValueEnum::get(op->get()));
return true;
});
if (handledValue)
return;
}
// Check if we have an indirect out parameter as our erroring returned value.
auto *fArg = dyn_cast<SILFunctionArgument>(returnedValue);
if (!fArg || !fArg->isIndirectResult()) {
// If we do not have one... emit a normal error.
return emitLastValueEnum(LastValueEnum::get(returnedValue), isolationInfo);
}
SmallVector<LastValueEnum, 8> finalValues;
if (!LastValueEnum::findLastValuesForOutParam(fArg, finalValues))
return emitLastValueEnum(LastValueEnum::get(returnedValue), isolationInfo);
// If we have a single value, then we have found our value.
if (finalValues.size() == 1) {
if (finalValues[0] == inoutSendingParam) {
return emitReturnInOutSendingError(isolationInfo,
finalValues[0].getLoc());
}
emitLastValueEnum(finalValues[0], isolationInfo);
return;
}
// If we have multiple finalValues, then that means that we had multiple
// return values. In such a case, we must have a single epilog block
// that our finalValues jointly dominate. That means that we need to
// determine which block the error actually occured along and emit an
// error for the value in that block. We know we are going to emit an
// error, so we can spend more time finding the better diagnostic since
// we are going to fail.
for (auto finalValue : finalValues) {
emitOutParamIncomingValueError(finalValue);
}
}
bool InOutSendingReturnedDiagnosticEmitter::LastValueEnum::
findLastValuesForOutParam(SILFunctionArgument *fArg,
SmallVectorImpl<LastValueEnum> &lastValues) {
assert(fArg->isIndirectResult());
SmallVector<SILBasicBlock *, 8> visitedBlocks;
SSAPrunedLiveness prunedLiveRange(fArg->getFunction(), &visitedBlocks);
// Look for copy_addr and store uses into fArg. They should all be complete
// stores.
prunedLiveRange.initializeDef(fArg);
for (auto *use : fArg->getUses()) {
if (LastValueEnum::isSupported(use)) {
prunedLiveRange.updateForUse(use->getUser(), false /*lifetime ending*/);
continue;
}
return false;
}
PrunedLivenessBoundary boundary;
prunedLiveRange.computeBoundary(boundary);
for (auto *user : boundary.lastUsers) {
if (auto e = LastValueEnum::get(user)) {
lastValues.emplace_back(e);
continue;
}
llvm::report_fatal_error("Out of sync with earlier check");
}
return true;
}
//===----------------------------------------------------------------------===//
// MARK: InOutSendingNotDisconnected Error Emitter
//===----------------------------------------------------------------------===//
namespace {
class InOutSendingNotDisconnectedDiagnosticEmitter {
/// The function exiting inst where the 'inout sending' parameter was actor
/// isolated.
TermInst *functionExitingInst;
/// The 'inout sending' param that we are emitting an error for.
SILValue inoutSendingParam;
/// The dynamic actor isolated region info of our 'inout sending' value's
/// region at the terminator inst.
SILDynamicMergedIsolationInfo actorIsolatedRegionInfo;
bool emittedErrorDiagnostic = false;
public:
InOutSendingNotDisconnectedDiagnosticEmitter(
TermInst *functionExitingInst, SILValue inoutSendingParam,
SILDynamicMergedIsolationInfo actorIsolatedRegionInfo)
: functionExitingInst(functionExitingInst),
inoutSendingParam(inoutSendingParam),
actorIsolatedRegionInfo(actorIsolatedRegionInfo) {}
~InOutSendingNotDisconnectedDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return inoutSendingParam->getFunction(); }
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return inoutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emit();
ASTContext &getASTContext() const {
return functionExitingInst->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
};
} // namespace
void InOutSendingNotDisconnectedDiagnosticEmitter::emit() {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto varName = inferNameHelper(inoutSendingParam);
if (!varName) {
return emitUnknownPatternError();
}
// Then emit the note with greater context.
auto descriptiveKindStr =
actorIsolatedRegionInfo.printForDiagnostics(getFunction());
diagnoseError(
functionExitingInst,
diag::regionbasedisolation_inout_sending_cannot_be_actor_isolated,
*varName, descriptiveKindStr)
.limitBehaviorIf(getBehaviorLimit());
diagnoseNote(
functionExitingInst,
diag::regionbasedisolation_inout_sending_cannot_be_actor_isolated_note,
*varName, descriptiveKindStr);
}
//===----------------------------------------------------------------------===//
// MARK: AssignIsolatedIntoSendingResultDiagnosticEmitter
//===----------------------------------------------------------------------===//
namespace {
class AssignIsolatedIntoSendingResultDiagnosticEmitter {
/// The use that actually caused the send.
Operand *srcOperand;
/// The specific out sending result.
SILFunctionArgument *outSendingResult;
/// The never-sent value that is in the same region as \p
/// outSendingResult.
SILValue neverSentValue;
/// The region info that describes the dynamic dataflow derived isolation
/// region info for the never-sent value.
///
/// This is equal to the merge of the IsolationRegionInfo from all elements in
/// the never-sent value's region when the error was diagnosed.
SILDynamicMergedIsolationInfo isolatedValueIsolationRegionInfo;
bool emittedErrorDiagnostic = false;
public:
AssignIsolatedIntoSendingResultDiagnosticEmitter(
Operand *srcOperand, SILFunctionArgument *outSendingResult,
SILValue neverSentValue,
SILDynamicMergedIsolationInfo isolatedValueIsolationRegionInfo)
: srcOperand(srcOperand), outSendingResult(outSendingResult),
neverSentValue(neverSentValue),
isolatedValueIsolationRegionInfo(isolatedValueIsolationRegionInfo) {}
~AssignIsolatedIntoSendingResultDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return srcOperand->getFunction(); }
std::optional<DiagnosticBehavior> getConcurrencyDiagnosticBehavior() const {
return outSendingResult->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(srcOperand->getUser(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getConcurrencyDiagnosticBehavior());
}
void emit();
ASTContext &getASTContext() const {
return srcOperand->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseError(op->getUser()->getLoc(), diag,
std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseNote(op->getUser()->getLoc(), diag,
std::forward<U>(args)...);
}
};
} // namespace
/// Look through values looking for our out parameter. We want to tightly
/// control this to be conservative... so we handroll this.
static SILValue findOutParameter(SILValue v) {
while (true) {
SILValue temp = v;
if (auto *initOpt = dyn_cast<InitEnumDataAddrInst>(temp)) {
if (initOpt->getElement()->getParentEnum() ==
initOpt->getFunction()->getASTContext().getOptionalDecl()) {
temp = initOpt->getOperand();
}
}
if (temp == v) {
return v;
}
v = temp;
}
}
void AssignIsolatedIntoSendingResultDiagnosticEmitter::emit() {
// Then emit the note with greater context.
auto descriptiveKindStr =
isolatedValueIsolationRegionInfo.printForDiagnostics(getFunction());
// Grab the var name if we can find it.
if (auto varName = VariableNameInferrer::inferName(srcOperand->get())) {
// In general, when we do an assignment like this, we assume that srcOperand
// and our outSendingResult have the same type. This doesn't always happen
// though especially if our outSendingResult is used as an out parameter of
// a class_method. Check for such a case and if so, add to the end of our
// string a path component for that class_method.
if (srcOperand->get()->getType() != outSendingResult->getType()) {
if (auto fas = FullApplySite::isa(srcOperand->getUser())) {
if (fas.hasSelfArgument() &&
fas.getSelfArgument() == srcOperand->get() &&
fas.getNumIndirectSILResults() == 1) {
// First check if our function argument is exactly our out parameter.
bool canEmit = outSendingResult == fas.getIndirectSILResults()[0];
// If that fails, see if we are storing into a temporary
// alloc_stack. In such a case, find the root value that the temporary
// is initialized to and see if that is our target function
// argument. In such a case, we also want to add the decl name to our
// type.
if (!canEmit) {
canEmit = outSendingResult ==
findOutParameter(fas.getIndirectSILResults()[0]);
}
if (canEmit) {
if (auto *callee =
dyn_cast_or_null<MethodInst>(fas.getCalleeOrigin())) {
SmallString<64> resultingString;
resultingString.append(varName->str());
resultingString += '.';
resultingString += VariableNameInferrer::getNameFromDecl(
callee->getMember().getDecl());
varName = fas->getFunction()->getASTContext().getIdentifier(
resultingString);
}
}
}
}
}
diagnoseError(
srcOperand,
diag::regionbasedisolation_out_sending_cannot_be_actor_isolated_named,
*varName, descriptiveKindStr)
.limitBehaviorIf(getConcurrencyDiagnosticBehavior());
diagnoseNote(
srcOperand,
diag::
regionbasedisolation_out_sending_cannot_be_actor_isolated_note_named,
*varName, descriptiveKindStr);
return;
}
Type type = neverSentValue->getType().getASTType();
diagnoseError(
srcOperand,
diag::regionbasedisolation_out_sending_cannot_be_actor_isolated_type,
type, descriptiveKindStr)
.limitBehaviorIf(getConcurrencyDiagnosticBehavior());
diagnoseNote(
srcOperand,
diag::regionbasedisolation_out_sending_cannot_be_actor_isolated_note_type,
type, descriptiveKindStr);
diagnoseNote(srcOperand, diag::regionbasedisolation_type_is_non_sendable,
type);
}
//===----------------------------------------------------------------------===//
// MARK: NonSendableIsolationCrossingResult Emitter
//===----------------------------------------------------------------------===//
/// Add Fix-It text for the given nominal type to adopt Sendable.
static void addSendableFixIt(const NominalTypeDecl *nominal,
InFlightDiagnostic &diag, bool unchecked) {
if (nominal->getInherited().empty()) {
SourceLoc fixItLoc = nominal->getBraces().Start;
diag.fixItInsert(fixItLoc,
unchecked ? ": @unchecked Sendable" : ": Sendable");
} else {
auto fixItLoc = nominal->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc,
unchecked ? ", @unchecked Sendable" : ", Sendable");
}
}
/// Add Fix-It text for the given generic param declaration type to adopt
/// Sendable.
static void addSendableFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag) {
if (genericArgument->getInherited().empty()) {
auto fixItLoc = genericArgument->getLoc();
diag.fixItInsertAfter(fixItLoc, ": Sendable");
} else {
auto fixItLoc = genericArgument->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc, " & Sendable");
}
}
namespace {
struct NonSendableIsolationCrossingResultDiagnosticEmitter {
RegionAnalysisValueMap &valueMap;
using Error = PartitionOpError::NonSendableIsolationCrossingResultError;
Error error;
bool emittedErrorDiagnostic = false;
/// The value assigned as the equivalence class representative. It is
/// guaranteed to be from the isolation crossing function since we never treat
/// isolation crossing functions as being look through.
SILValue representative;
NonSendableIsolationCrossingResultDiagnosticEmitter(
RegionAnalysisValueMap &valueMap, Error &&error)
: valueMap(valueMap), error(std::move(error)),
representative(valueMap.getRepresentative(error.returnValueElement)) {}
SILFunction *getFunction() const {
return error.op->getSourceInst()->getFunction();
}
void emit();
ASTContext &getASTContext() const {
return error.op->getSourceInst()->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return representative->getType().getConcurrencyDiagnosticBehavior(
representative->getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(error.op->getSourceInst(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
Type getType() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
return applyExpr->getType();
}
// If we do not have an ApplyExpr, see if we can just infer the type from
// the SILFunction type. This is only used in SIL test cases.
if (auto fas = FullApplySite::isa(error.op->getSourceInst())) {
return fas.getSubstCalleeType()
->getAllResultsSubstType(fas.getModule(),
fas.getFunction()->getTypeExpansionContext())
.getASTType();
}
return Type();
}
const ValueDecl *getCalledDecl() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto calledValue =
applyExpr->getCalledValue(true /*look through conversions*/)) {
return calledValue;
}
}
return nullptr;
}
std::optional<ApplyIsolationCrossing> getIsolationCrossing() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto isolationCrossing = applyExpr->getIsolationCrossing()) {
return *isolationCrossing;
}
}
// If we have a SIL based test case, just return the actual isolation
// crossing.
if (auto fas = FullApplySite::isa(error.op->getSourceInst())) {
if (auto isolationCrossing = fas.getIsolationCrossing())
return *isolationCrossing;
}
return {};
}
};
} // namespace
void NonSendableIsolationCrossingResultDiagnosticEmitter::emit() {
auto isolationCrossing = getIsolationCrossing();
if (!isolationCrossing)
return emitUnknownPatternError();
auto calleeIsolationStr = SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing->getCalleeIsolation());
auto callerIsolationStr = SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing->getCallerIsolation());
auto type = getType();
if (getCalledDecl()) {
diagnoseError(error.op->getSourceInst(),
diag::rbi_isolation_crossing_result, type, calleeIsolationStr,
getCalledDecl(), callerIsolationStr)
.limitBehaviorIf(getBehaviorLimit());
} else {
diagnoseError(error.op->getSourceInst(),
diag::rbi_isolation_crossing_result_no_decl, type,
calleeIsolationStr, callerIsolationStr)
.limitBehaviorIf(getBehaviorLimit());
}
if (type->is<FunctionType>()) {
diagnoseNote(error.op->getSourceInst(),
diag::rbi_nonsendable_function_type);
return;
}
auto *moduleDecl = error.op->getSourceInst()->getModule().getSwiftModule();
if (auto *nominal = type->getNominalOrBoundGenericNominal()) {
// If the nominal type is in the current module, suggest adding `Sendable`
// if it makes sense.
if (nominal->getParentModule() == moduleDecl &&
(isa<StructDecl>(nominal) || isa<EnumDecl>(nominal))) {
auto note = nominal->diagnose(diag::rbi_add_nominal_sendable_conformance,
nominal);
addSendableFixIt(nominal, note, /*unchecked*/ false);
} else {
nominal->diagnose(diag::rbi_non_sendable_nominal, nominal);
}
return;
}
if (auto genericArchetype = type->getAs<ArchetypeType>()) {
auto interfaceType = genericArchetype->getInterfaceType();
if (auto genericParamType = interfaceType->getAs<GenericTypeParamType>()) {
auto *genericParamTypeDecl = genericParamType->getDecl();
if (genericParamTypeDecl &&
genericParamTypeDecl->getModuleContext() == moduleDecl) {
auto diag = genericParamTypeDecl->diagnose(
diag::rbi_add_generic_parameter_sendable_conformance, type);
addSendableFixIt(genericParamTypeDecl, diag);
return;
}
}
}
}
//===----------------------------------------------------------------------===//
// MARK: InOutSendingParametersInSameRegionError
//===----------------------------------------------------------------------===//
namespace {
class InOutSendingParametersInSameRegionDiagnosticEmitter {
/// The function exiting inst where the 'inout sending' parameter was actor
/// isolated.
TermInst *functionExitingInst;
/// The first 'inout sending' param in the region.
SILValue firstInOutSendingParam;
/// The second 'inout sending' param in the region.
SILValue secondInOutSendingParam;
bool emittedErrorDiagnostic = false;
public:
InOutSendingParametersInSameRegionDiagnosticEmitter(
TermInst *functionExitingInst, SILValue firstInOutSendingParam,
SILValue secondInOutSendingParam)
: functionExitingInst(functionExitingInst),
firstInOutSendingParam(firstInOutSendingParam),
secondInOutSendingParam(secondInOutSendingParam) {}
~InOutSendingParametersInSameRegionDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const {
return functionExitingInst->getFunction();
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
auto first =
firstInOutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
auto second =
secondInOutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
if (!first)
return second;
if (!second)
return first;
return first->merge(*second);
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emit();
ASTContext &getASTContext() const {
return functionExitingInst->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilLanguageMode(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
};
} // namespace
void InOutSendingParametersInSameRegionDiagnosticEmitter::emit() {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto firstName = inferNameHelper(firstInOutSendingParam);
if (!firstName) {
return emitUnknownPatternError();
}
auto secondName = inferNameHelper(secondInOutSendingParam);
if (!secondName) {
return emitUnknownPatternError();
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_inout_sending_in_same_region,
*firstName, *secondName)
.limitBehaviorIf(getBehaviorLimit());
diagnoseNote(functionExitingInst,
diag::regionbasedisolation_inout_sending_in_same_region_note,
*firstName, *secondName);
}
//===----------------------------------------------------------------------===//
// MARK: Top Level Entrypoint
//===----------------------------------------------------------------------===//
void SendNonSendableImpl::emitVerbatimErrors() {
for (auto &erasedError : foundVerbatimErrors) {
switch (erasedError.getKind()) {
case PartitionOpError::UnknownCodePattern:
case PartitionOpError::LocalUseAfterSend:
case PartitionOpError::InOutSendingNotInitializedAtExit:
llvm_unreachable("Handled elsewhere");
case PartitionOpError::AssignNeverSendableIntoSendingResult: {
auto error =
std::move(erasedError).getAssignNeverSendableIntoSendingResultError();
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
AssignIsolatedIntoSendingResultDiagnosticEmitter emitter(
error.op->getSourceOp(), error.destValue, error.srcValue,
error.srcIsolationRegionInfo);
emitter.emit();
continue;
}
case PartitionOpError::InOutSendingNotDisconnectedAtExit: {
auto error =
std::move(erasedError).getInOutSendingNotDisconnectedAtExitError();
auto inoutSendingVal =
info->getValueMap().getRepresentative(error.inoutSendingElement);
auto isolationRegionInfo = error.isolationInfo;
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
InOutSendingNotDisconnectedDiagnosticEmitter emitter(
cast<TermInst>(error.op->getSourceInst()), inoutSendingVal,
isolationRegionInfo);
emitter.emit();
continue;
}
case PartitionOpError::InOutSendingReturned: {
auto error = std::move(erasedError).getInOutSendingReturnedError();
auto inoutSendingVal =
info->getValueMap().getRepresentative(error.inoutSendingElement);
auto returnedValue =
info->getValueMap().getRepresentative(error.returnedValue);
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
InOutSendingReturnedDiagnosticEmitter emitter(
info, cast<TermInst>(error.op->getSourceInst()), inoutSendingVal,
error.inoutSendingElement, returnedValue, error.isolationInfo);
emitter.emit();
continue;
}
case PartitionOpError::SentNeverSendable: {
auto e = std::move(erasedError).getSentNeverSendableError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
SentNeverSendableDiagnosticInferrer diagnosticInferrer(
info->getValueMap(), std::move(e));
diagnosticInferrer.run();
continue;
}
case PartitionOpError::NonSendableIsolationCrossingResult: {
auto e =
std::move(erasedError).getNonSendableIsolationCrossingResultError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
NonSendableIsolationCrossingResultDiagnosticEmitter diagnosticInferrer(
info->getValueMap(), std::move(e));
diagnosticInferrer.emit();
continue;
}
case PartitionOpError::InOutSendingParametersInSameRegion: {
auto e =
std::move(erasedError).getInOutSendingParametersInSameRegionError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
auto firstParam =
info->getValueMap().getRepresentativeValue(e.firstInoutSendingParam);
// Walk through our secondInOutSendingParams and use one that is not
// ignore.
for (auto paramElt : e.otherInOutSendingParams) {
auto paramValue =
info->getValueMap().getRepresentativeValue(paramElt).getValue();
if (auto behavior =
paramValue->getType().getConcurrencyDiagnosticBehavior(
info->getFunction());
behavior && *behavior == DiagnosticBehavior::Ignore) {
continue;
}
InOutSendingParametersInSameRegionDiagnosticEmitter diagnosticEmitter(
cast<TermInst>(e.op->getSourceInst()), firstParam.getValue(),
paramValue);
diagnosticEmitter.emit();
}
continue;
}
}
llvm_unreachable("Covered switch isn't covered?!");
}
}
/// Once we have reached a fixpoint, this routine runs over all blocks again
/// reporting any failures by applying our ops to the converged dataflow
/// state.
void SendNonSendableImpl::emitDiagnostics() {
auto *function = info->getFunction();
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Emitting diagnostics for function "
<< function->getName() << "\n");
runDiagnosticEvaluator();
emitUseAfterSendDiagnostics();
emitVerbatimErrors();
}
namespace {
class SendNonSendable : public SILFunctionTransform {
void run() override {
SILFunction *function = getFunction();
auto *functionInfo = getAnalysis<RegionAnalysis>()->get(function);
if (!functionInfo->isSupportedFunction()) {
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< "===> SKIPPING UNSUPPORTED FUNCTION: "
<< function->getName() << '\n');
return;
}
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "===> PROCESSING: " << function->getName() << '\n');
SendNonSendableImpl impl(functionInfo);
impl.emitDiagnostics();
}
};
} // end anonymous namespace
SILTransform *swift::createSendNonSendable() { return new SendNonSendable(); }