blob: ca4ea8fc4262346b578dda61860c032b03a6d26b [file] [log] [blame]
//===------ YieldOnceCheck.cpp - Check usage of yields in accessors ------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// This pass statically verifies that yield-once coroutines, such as the
// generalized accessors `read` and `modify`, yield exactly once in every
// invocation, and diagnoses any violation of this property. This pass uses a
// linear-time, data-flow analysis to check that every path in the control-flow
// graph of the coroutine has a yield instruction before a return instruction.
#define DEBUG_TYPE "yield-once-check"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Stmt.h"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/CFG.h"
#include "swift/SIL/Dominance.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "llvm/ADT/BreadthFirstIterator.h"
#include "llvm/ADT/DenseSet.h"
using namespace swift;
namespace {
class YieldOnceCheck : public SILFunctionTransform {
template <typename... T, typename... U>
static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
Diag<T...> diag, U &&... args) {
return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
/// Data-flow analysis state that is associated with basic blocks.
struct BBState {
/// Indicates whether a basic block is encountered before seeing a yield
/// (BeforeYield) or after seeing a yield (AfterYield), or in both states
/// (Conflict). This enum is a semi-lattice where Conflict is the top and
/// the merge of BeforeYield and AfterYield states is Conflict.
enum YieldState { BeforeYield, AfterYield, Conflict } yieldState;
private:
// The following states are maintained for emitting diagnostics.
/// For AfterYield and Conflict states, this field records the yield
/// instruction that was seen while propagating the state.
SILInstruction *yieldInst;
/// For Conflict state, these fields record the basic blocks that
/// propagated the 'AfterYield' and 'BeforeYield' states which resulted
/// in the Conflict.
SILBasicBlock *yieldingPred;
SILBasicBlock *nonYieldingPred;
BBState(YieldState yState, SILInstruction *yieldI, SILBasicBlock *yieldPred,
SILBasicBlock *noYieldPred)
: yieldState(yState), yieldInst(yieldI), yieldingPred(yieldPred),
nonYieldingPred(noYieldPred) {}
public:
static BBState getInitialState() {
return BBState(BeforeYield, nullptr, nullptr, nullptr);
}
static BBState getAfterYieldState(SILInstruction *yieldI) {
assert(yieldI);
return BBState(AfterYield, yieldI, nullptr, nullptr);
}
static BBState getConflictState(SILInstruction *yieldI,
SILBasicBlock *yieldPred,
SILBasicBlock *noYieldPred) {
assert(yieldI && yieldPred && noYieldPred);
return BBState(Conflict, yieldI, yieldPred, noYieldPred);
}
SILInstruction *getYieldInstruction() const {
assert(yieldState == AfterYield || yieldState == Conflict);
return yieldInst;
}
SILBasicBlock *getYieldingPred() {
assert(yieldState == Conflict);
return yieldingPred;
}
SILBasicBlock *getNonYieldingPred() {
assert(yieldState == Conflict);
return nonYieldingPred;
}
};
/// A structure that records an error found during the analysis along with
/// some context information that will be used by diagnostics.
struct YieldError {
/// The kind of error.
enum Kind { MultipleYield, ReturnBeforeYield, ReturnOnConflict } errorKind;
/// The termination instruction where the error should be reported.
SILInstruction *termInst;
/// The input state when the error is encountered.
BBState inState;
private:
YieldError(Kind kind, SILInstruction *term, BBState state)
: errorKind(kind), termInst(term), inState(state) {}
public:
static YieldError getMultipleYieldError(YieldInst *yield, BBState state) {
assert(state.yieldState != BBState::BeforeYield);
return YieldError(MultipleYield, yield, state);
}
static YieldError getReturnBeforeYieldError(ReturnInst *returnI,
BBState state) {
assert(state.yieldState == BBState::BeforeYield);
return YieldError(ReturnBeforeYield, returnI, state);
}
static YieldError getReturnOnConflict(ReturnInst *returnI, BBState state) {
assert(state.yieldState == BBState::Conflict);
return YieldError(ReturnOnConflict, returnI, state);
}
};
/// Transfer function of the data-flow analysis.
///
/// \param bb Basic block that should be processed
/// \param inState BBState at the start of the basic block
/// \param error out parameter that will contain information about
/// an error that is detected.
/// \return the state at the exit of the basic block if it can be computed
/// and None otherwise.
static Optional<BBState>
transferStateThroughBasicBlock(SILBasicBlock *bb, BBState inState,
Optional<YieldError> &error) {
error = None;
auto *term = bb->getTerminator();
if (auto *returnInst = dyn_cast<ReturnInst>(term)) {
if (inState.yieldState == BBState::BeforeYield) {
error = YieldError::getReturnBeforeYieldError(returnInst, inState);
return None;
}
if (inState.yieldState == BBState::Conflict) {
error = YieldError::getReturnOnConflict(returnInst, inState);
return None;
}
return inState;
}
if (auto *yieldInst = dyn_cast<YieldInst>(term)) {
if (inState.yieldState != BBState::BeforeYield) {
error = YieldError::getMultipleYieldError(yieldInst, inState);
return None;
}
// If the current state is BeforeYield and if the basic block ends in a
// yield the new state is AfterYield.
return inState.getAfterYieldState(term);
}
// We cannot have throws within generalized accessors.
assert(!isa<ThrowInst>(term));
return inState;
}
/// Merge operation of the data-flow analysis.
///
/// \param mergeBlock the basic block that is reached with two states
/// \param oldState the previous state at the entry of the basic block
/// \param newState the current state at the entry of the basic block
/// \param newStatePred the predecessor of 'mergeBlock' that has propagated
/// the 'newState'.
/// \param bbToStateMap a map from the basic blocks visited by the analysis
/// to the BBStates in which they were seen. This is used to identify
/// blocks that propagate conflicting states when the merge results
/// in a conflict.
/// \return the new state obtained by merging the oldState with the newState
static BBState merge(SILBasicBlock *mergeBlock, BBState oldState,
BBState newState, SILBasicBlock *newStatePred,
llvm::DenseMap<SILBasicBlock *, BBState> &bbToStateMap) {
auto oldYieldState = oldState.yieldState;
auto newYieldState = newState.yieldState;
if (oldYieldState == BBState::Conflict) {
return oldState;
}
if (newYieldState == BBState::Conflict) {
return newState;
}
if (oldYieldState == newYieldState) {
return oldState;
}
// Here, one state is AfterYield and the other one is BeforeYield.
// Merging them will result in Conflict.
assert((newYieldState == BBState::AfterYield &&
oldYieldState == BBState::BeforeYield) ||
(newYieldState == BBState::BeforeYield &&
oldYieldState == BBState::AfterYield));
// For diagnostics, find another predecessor of 'mergeBlock' that was
// previously seen by the analysis. This predecessor would have
// propagated the 'oldState'.
SILBasicBlock *oldStatePred = nullptr;
for (auto predBB : mergeBlock->getPredecessorBlocks()) {
if (predBB != newStatePred && bbToStateMap.count(predBB)) {
oldStatePred = predBB;
break;
}
}
assert(oldStatePred);
if (oldState.yieldState == BBState::BeforeYield) {
return BBState::getConflictState(newState.getYieldInstruction(),
/* yieldPred */ newStatePred,
/* noYieldPred */ oldStatePred);
} else {
return BBState::getConflictState(oldState.getYieldInstruction(),
/* yieldPred */ oldStatePred,
/* noYieldPred */ newStatePred);
}
}
/// Perform a data-flow analysis to check whether there is exactly one
/// yield before a return in every path in the control-flow graph.
/// Diagnostics are not reported for nodes unreachable from the entry of
/// the control-flow graph.
void diagnoseYieldOnceUsage(SILFunction &fun) {
llvm::DenseMap<SILBasicBlock *, BBState> bbToStateMap;
SmallVector<SILBasicBlock *, 16> worklist;
auto *entryBB = fun.getEntryBlock();
bbToStateMap.try_emplace(entryBB, BBState::getInitialState());
worklist.push_back(entryBB);
// ReturnBeforeYield errors, which denote that no paths yield before
// returning, are not diagnosed until the analysis completes, in order to
// distinguish them from ReturnOnConflict errors, which happen when some
// paths yield and some don't.
Optional<YieldError> returnBeforeYieldError = None;
// The algorithm uses a worklist to propagate the state through basic
// blocks until a fix point. Since the state lattice has height one, each
// basic block will be visited at most twice, and at most once if there are
// no conflicts (which are errors). The basic blocks are added to the
// worklist in a breadth-first fashion. The order of visiting basic blocks
// is not important for correctness, but it could change the errors
// diagnosed when there are multiple errors. Breadth-first order diagnoses
// errors along shorter paths to return.
while (!worklist.empty()) {
SILBasicBlock *bb = worklist.pop_back_val();
const BBState &state = bbToStateMap.find(bb)->getSecond();
Optional<YieldError> errorResult = None;
auto resultState = transferStateThroughBasicBlock(bb, state, errorResult);
if (!resultState.hasValue()) {
auto error = errorResult.getValue();
// ReturnBeforeYield errors will not be reported until the analysis
// completes. So record it and continue.
if (error.errorKind == YieldError::ReturnBeforeYield) {
if (!returnBeforeYieldError.hasValue()) {
returnBeforeYieldError = error;
}
continue;
}
emitDiagnostics(error, fun, bbToStateMap);
return;
}
auto nextState = resultState.getValue();
for (auto *succBB : bb->getSuccessorBlocks()) {
// Optimistically try to set the state of the successor as next state.
auto insertResult = bbToStateMap.try_emplace(succBB, nextState);
// If the insertion was successful, it means we are seeing the successor
// for the first time. Add the successor to the worklist.
if (insertResult.second) {
worklist.insert(worklist.begin(), succBB);
continue;
}
// Here the successor already has a state. Merge the current and
// previous states and propagate it if it is different from the
// old state.
const auto &oldState = insertResult.first->second;
auto mergedState = merge(succBB, oldState, nextState, bb, bbToStateMap);
if (mergedState.yieldState == oldState.yieldState)
continue;
// Even though at this point there has to be an error since there is an
// inconsistency between states coming along two different paths,
// continue propagation of this conflict state to determine
// whether this results in multiple-yields error or return-on-conflict
// error.
insertResult.first->second = mergedState;
worklist.insert(worklist.begin(), succBB);
}
}
if (returnBeforeYieldError.hasValue()) {
emitDiagnostics(returnBeforeYieldError.getValue(), fun, bbToStateMap);
}
}
void emitDiagnostics(YieldError &error, SILFunction &fun,
llvm::DenseMap<SILBasicBlock *, BBState> &visitedBBs) {
ASTContext &astCtx = fun.getModule().getASTContext();
switch (error.errorKind) {
case YieldError::ReturnBeforeYield: {
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
diag::return_before_yield);
return;
}
case YieldError::MultipleYield: {
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
diag::multiple_yields);
// Add a note that points to the previous yield.
diagnose(astCtx,
error.inState.getYieldInstruction()->getLoc().getSourceLoc(),
diag::previous_yield);
return;
}
case YieldError::ReturnOnConflict: {
// Emit an error on the return statement.
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
diag::possible_return_before_yield);
// Here, the yield state of 'error' is Conflict.
auto &conflictState = error.inState;
// Emit a note that pin points the branch construct that resulted in
// this conflict. Note that a conflict state is created at a merge block
// when along one incoming edge the analysis sees a BeforeYield state
// and along another it sees an AfterYield state.
// Also note that, by the definition of the merge operation,
// 'error.yieldingPred()' is the immediate predecessor of the merge block
// that propagates AfterYield state, and 'error.nonYieldingPred()' is
// the immediate predecessor of the merge block that propagates a
// BeforeYield state.
auto yieldPred = conflictState.getYieldingPred();
auto noYieldPred = conflictState.getNonYieldingPred();
// Step 1: find a branching SIL instruction where one branch has
// 'yieldPred' and another branch has 'noYieldPred'. For instance,
// in the following example, cond_br is the instruction to find.
// cond_br bb1, bb2
// bb1:
// yield resume yieldPred, unwind err
// bb2:
// br noYieldPred
// yieldPred:
// br mergePoint
// noYieldPred:
// br mergePoint
// mergePoint:
// ...
// Intuitively, the conflicting branch is the instruction where
// 'yieldPred' and 'noYieldPred' meet when traversing the CFG in the
// reverse order. More formally, the branching instruction is a
// "lowest common ancestor" of 'yieldPred' and 'noYieldPred' in the
// the DAG obtained from the CFG by ignoring back edges of loops.
//
// Note that the lowest common ancestor may not be unique in a DAG.
// But, any such ancestor could be considered as the conflicting branch as
// 'yieldPred' and 'noYieldPred' will belong to two different branches of
// every such ancestor.
// Find all transitive predecessors of 'yieldPred' that were visited
// during the analysis
SmallPtrSet<SILBasicBlock *, 8> predecessorsOfYieldPred;
for (auto *predBB :
llvm::breadth_first<llvm::Inverse<SILBasicBlock *>>(yieldPred)) {
if (visitedBBs.count(predBB)) {
predecessorsOfYieldPred.insert(predBB);
}
}
// Find the first predecessor of 'noYieldPred' that is also a predecessor
// of 'yieldPred', in the breadth-first search order of the reversed CFG.
SILBasicBlock *lowestCommonAncestorBB = nullptr;
SmallPtrSet<SILBasicBlock *, 8> predecessorsOfNoYieldPred;
for (auto *pred :
llvm::breadth_first<llvm::Inverse<SILBasicBlock *>>(noYieldPred)) {
if (!visitedBBs.count(pred)) {
continue;
}
if (predecessorsOfYieldPred.count(pred)) {
lowestCommonAncestorBB = pred;
break;
}
predecessorsOfNoYieldPred.insert(pred);
}
assert(lowestCommonAncestorBB);
auto *conflictingBranch = lowestCommonAncestorBB->getTerminator();
// Step 2: Find the target basic block of the 'conflictingBranch' that
// doesn't yield.
SILBasicBlock *noYieldTarget = nullptr;
for (auto *succ : lowestCommonAncestorBB->getSuccessorBlocks()) {
if (predecessorsOfNoYieldPred.count(succ)) {
noYieldTarget = succ;
break;
}
}
assert(noYieldTarget);
// Step 3: Report specialized diagnostics for each kind of conflicting
// branch.
// For conditional-branch instructions, which correspond to the
// conditions of 'if', 'where' or 'guard' statements, report for what
// truth value the branch doesn't yield.
if (auto *condbr = dyn_cast<CondBranchInst>(conflictingBranch)) {
diagnose(astCtx, condbr->getLoc().getSourceLoc(),
diag::branch_doesnt_yield,
/*non-yielding branch*/ condbr->getTrueBB() == noYieldTarget);
return;
}
// For switch_enum instructions, report the case that doesn't yield.
enum SwitchCaseKind { Default, OptionNil, OptionSome };
if (auto *switchEnum = dyn_cast<SwitchEnumInstBase>(conflictingBranch)) {
auto enumCaseLoc = noYieldTarget->begin()->getLoc().getSourceLoc();
if (switchEnum->hasDefault() &&
switchEnum->getDefaultBB() == noYieldTarget) {
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, Default);
return;
}
// Find the case identifier that doesn't yield.
NullablePtr<EnumElementDecl> enumElemDecl =
switchEnum->getUniqueCaseForDestination(noYieldTarget);
assert(enumElemDecl.isNonNull());
// Specialize diagnostics for cases of an optional.
if (enumElemDecl.get() == astCtx.getOptionalNoneDecl()) {
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, OptionNil);
} else if (enumElemDecl.get() == astCtx.getOptionalSomeDecl()) {
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, OptionSome);
} else {
diagnose(astCtx, enumCaseLoc, diag::named_case_doesnt_yield,
enumElemDecl.get()->getName());
}
return;
}
// For switch_value instructions, report the case number that doesn't
// yield.
if (auto *switchValue = dyn_cast<SwitchValueInst>(conflictingBranch)) {
auto enumCaseLoc = noYieldTarget->begin()->getLoc().getSourceLoc();
if (switchValue->hasDefault() &&
switchValue->getDefaultBB() == noYieldTarget) {
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, Default);
return;
}
// Find the case that doesn't yield.
Optional<unsigned> caseNumberOpt =
switchValue->getUniqueCaseForDestination(noYieldTarget);
assert(caseNumberOpt.hasValue());
auto caseNumber = caseNumberOpt.getValue();
diagnose(
astCtx, enumCaseLoc, diag::switch_value_case_doesnt_yield,
(Twine(caseNumber) + llvm::getOrdinalSuffix(caseNumber)).str());
return;
}
// For try-apply instructions, report whether throwing or non-throwing
// case doesn't yield.
if (auto *tryApply = dyn_cast<TryApplyInst>(conflictingBranch)) {
diagnose(astCtx, tryApply->getLoc().getSourceLoc(),
diag::try_branch_doesnt_yield,
/*does error case not yield?*/ tryApply->getErrorBB() ==
noYieldTarget);
return;
}
llvm_unreachable("unexpected branch resulting in conflicting yield "
"states found in generalized accessor");
}
}
}
/// The entry point to the transformation.
void run() override {
auto *fun = getFunction();
if (fun->getLoweredFunctionType()->getCoroutineKind() !=
SILCoroutineKind::YieldOnce)
return;
diagnoseYieldOnceUsage(*fun);
}
};
} // end anonymous namespace
SILTransform *swift::createYieldOnceCheck() {
return new YieldOnceCheck();
}