|
| 1 | +//===------ YieldOnceCheck.cpp - Check usage of yields in accessors ------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +#define DEBUG_TYPE "yield-once-check" |
| 14 | +#include "swift/AST/DiagnosticsSIL.h" |
| 15 | +#include "swift/SIL/BasicBlockUtils.h" |
| 16 | +#include "swift/SILOptimizer/PassManager/Transforms.h" |
| 17 | +#include "llvm/ADT/DenseSet.h" |
| 18 | + |
| 19 | +using namespace swift; |
| 20 | + |
| 21 | +namespace { |
| 22 | + |
| 23 | +class YieldOnceCheck : public SILFunctionTransform { |
| 24 | + |
| 25 | + template <typename... T, typename... U> |
| 26 | + static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc, |
| 27 | + Diag<T...> diag, U &&... args) { |
| 28 | + return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...); |
| 29 | + } |
| 30 | + |
| 31 | + /// A state that is associated with basic blocks to record whether a basic |
| 32 | + /// block appears before a yield or after a yield, or could be in either |
| 33 | + /// state, which is denoted by 'Conflict'. |
| 34 | + enum YieldState { BeforeYield, AfterYield, Conflict }; |
| 35 | + |
| 36 | + struct BBState { |
| 37 | + YieldState yieldState = BeforeYield; |
| 38 | + |
| 39 | + private: |
| 40 | + // For AfterYield and Conflict states, track the yield instruction. |
| 41 | + llvm::Optional<SILInstruction *> yieldInst = None; |
| 42 | + // For Conflict state, track the first instruction that is found to be in |
| 43 | + // conflict state. This should be the first instruction of a join node in |
| 44 | + // the CFG. |
| 45 | + llvm::Optional<SILInstruction *> conflictInst = None; |
| 46 | + |
| 47 | + public: |
| 48 | + void updateToAfterYield(SILInstruction *yldInst) { |
| 49 | + assert(yieldState == BeforeYield); |
| 50 | + assert(yldInst != nullptr); |
| 51 | + |
| 52 | + yieldState = AfterYield; |
| 53 | + yieldInst = yldInst; |
| 54 | + } |
| 55 | + |
| 56 | + void updateToConflict(SILInstruction *yldInst, SILInstruction *confInst) { |
| 57 | + assert(yieldState != Conflict); |
| 58 | + assert(yldInst != nullptr && confInst != nullptr); |
| 59 | + |
| 60 | + yieldState = Conflict; |
| 61 | + yieldInst = yldInst; |
| 62 | + conflictInst = confInst; |
| 63 | + } |
| 64 | + |
| 65 | + SILInstruction *getYieldInstruction() const { |
| 66 | + assert(yieldState == AfterYield || yieldState == Conflict); |
| 67 | + |
| 68 | + return yieldInst.getValue(); |
| 69 | + } |
| 70 | + |
| 71 | + SILInstruction *getConflictInstruction() const { |
| 72 | + assert(yieldState == Conflict); |
| 73 | + |
| 74 | + return conflictInst.getValue(); |
| 75 | + } |
| 76 | + }; |
| 77 | + |
| 78 | + /// Checks whether there is exactly one yield before a return in every path |
| 79 | + /// in the control-flow graph. |
| 80 | + /// Diagnostics are not reported for nodes unreachable from the entry, and |
| 81 | + /// also for nodes that may not reach the exit of the control-flow graph. |
| 82 | + void diagnoseYieldOnceUsage(SILFunction &fun) { |
| 83 | + // Do a traversal of the basic blocks starting from the entry node |
| 84 | + // in a breadth-first fashion. The traversal order is not important for |
| 85 | + // correctness, but it could change the errors diagnosed when there are |
| 86 | + // multiple errors. Breadth-first search could diagnose errors along |
| 87 | + // shorter paths. |
| 88 | + llvm::DenseMap<SILBasicBlock *, BBState> visitedBBs; |
| 89 | + SmallVector<SILBasicBlock *, 16> worklist; |
| 90 | + |
| 91 | + auto *entryBB = &(*fun.begin()); |
| 92 | + visitedBBs.try_emplace(entryBB); |
| 93 | + worklist.push_back(entryBB); |
| 94 | + |
| 95 | + // Track that last return instruction seen by the analysis, if any, before |
| 96 | + // encountering a yield. |
| 97 | + // This is needed for emitting diagnostics when there is no yield |
| 98 | + // instruction along any path reaching a return instruction. |
| 99 | + llvm::Optional<SILInstruction *> returnInst = None; |
| 100 | + ASTContext &astCtx = fun.getModule().getASTContext(); |
| 101 | + |
| 102 | + while (!worklist.empty()) { |
| 103 | + SILBasicBlock *bb = worklist.pop_back_val(); |
| 104 | + BBState state = visitedBBs[bb]; |
| 105 | + |
| 106 | + auto *term = bb->getTerminator(); |
| 107 | + if (isa<ReturnInst>(term)) { |
| 108 | + // A conflict state implies that there is a path to return before |
| 109 | + // seeing a yield. |
| 110 | + if (state.yieldState == YieldState::Conflict) { |
| 111 | + diagnose(astCtx, term->getLoc().getSourceLoc(), |
| 112 | + diag::possible_return_before_yield); |
| 113 | + // Add a note to the instruction where the conflict first appeared. |
| 114 | + diagnose(astCtx, |
| 115 | + state.getConflictInstruction()->getLoc().getSourceLoc(), |
| 116 | + diag::conflicting_join); |
| 117 | + return; |
| 118 | + } |
| 119 | + // If the state is BeforeYield, it is an error. But, defer emitting |
| 120 | + // diagnostics until we see a Conflict state or have visited all nodes |
| 121 | + // in order to gather more information. |
| 122 | + if (state.yieldState != YieldState::AfterYield) { |
| 123 | + returnInst = term; |
| 124 | + } |
| 125 | + continue; |
| 126 | + } |
| 127 | + |
| 128 | + // Check whether there are multiple yields. |
| 129 | + if (isa<YieldInst>(term)) { |
| 130 | + if (state.yieldState != YieldState::BeforeYield) { |
| 131 | + diagnose(astCtx, term->getLoc().getSourceLoc(), |
| 132 | + diag::multiple_yields); |
| 133 | + // Add a note that points to the previous yield. |
| 134 | + diagnose(astCtx, state.getYieldInstruction()->getLoc().getSourceLoc(), |
| 135 | + diag::previous_yield); |
| 136 | + return; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + for (auto &succ : term->getSuccessors()) { |
| 141 | + SILBasicBlock *succBB = succ.getBB(); |
| 142 | + |
| 143 | + // Optimistically try to set our current state as the state |
| 144 | + // of the successor. |
| 145 | + auto insertResult = visitedBBs.try_emplace(succBB, state); |
| 146 | + |
| 147 | + // If the insertion was successful, it means we are seeing the successor |
| 148 | + // for the first time. Propogate the state and add the successor to the |
| 149 | + // worklist. |
| 150 | + if (insertResult.second) { |
| 151 | + worklist.insert(worklist.begin(), succBB); |
| 152 | + |
| 153 | + // When the successor follows a 'yield', update the successor state. |
| 154 | + if (isa<YieldInst>(term)) { |
| 155 | + insertResult.first->second.updateToAfterYield(term); |
| 156 | + } |
| 157 | + continue; |
| 158 | + } |
| 159 | + |
| 160 | + const auto &succState = insertResult.first->second; |
| 161 | + if (succState.yieldState == Conflict) { |
| 162 | + // The successor is already in the error state, so we need |
| 163 | + // not propagate anything. Diagnostics will be reported, where |
| 164 | + // appropriate, when the successor is removed from the worklist. |
| 165 | + continue; |
| 166 | + } |
| 167 | + |
| 168 | + // If the successor state is not a conflict but the current state is, |
| 169 | + // propagate the conflict down to the successor. |
| 170 | + // (This is needed to identify the conflicting yields and |
| 171 | + // present good diagnostics.) |
| 172 | + if (state.yieldState == Conflict) { |
| 173 | + worklist.insert(worklist.begin(), succBB); |
| 174 | + insertResult.first->second = state; |
| 175 | + continue; |
| 176 | + } |
| 177 | + |
| 178 | + // Here, neither 'state' nor 'succState' is equal to 'Conflict'. |
| 179 | + |
| 180 | + if (state.yieldState != succState.yieldState) { |
| 181 | + // We have found that the successor can appear before and |
| 182 | + // also after a yield. Therefore, set the state as a conflict and |
| 183 | + // propagate it to its successors to emit diagnostics. |
| 184 | + // (Note that the successor must be a join node in the control graph |
| 185 | + // for this scenario to happen.) |
| 186 | + auto *yieldInst = (state.yieldState == YieldState::AfterYield) |
| 187 | + ? state.getYieldInstruction() |
| 188 | + : succState.getYieldInstruction(); |
| 189 | + insertResult.first->second.updateToConflict(yieldInst, |
| 190 | + &*(succBB->begin())); |
| 191 | + worklist.insert(worklist.begin(), succBB); |
| 192 | + |
| 193 | + continue; |
| 194 | + // Even though at this point we know there has to be an error as |
| 195 | + // there is an inconsistent state, we cannot stop here as we do not |
| 196 | + // know for sure whether the error will result in multiple yields |
| 197 | + // or a return before a yield. |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + if (returnInst.hasValue()) { |
| 203 | + // Here, we haven't seen a yield along any path that leads to this return. |
| 204 | + // Otherwise, the analysis must have propagated a conflict state to this |
| 205 | + // return instruction, which must have been diagnosed earlier. |
| 206 | + diagnose(astCtx, returnInst.getValue()->getLoc().getSourceLoc(), |
| 207 | + diag::return_before_yield); |
| 208 | + return; |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + /// The entry point to the transformation. |
| 213 | + void run() override { |
| 214 | + auto *fun = getFunction(); |
| 215 | + |
| 216 | + if (fun->getLoweredFunctionType()->getCoroutineKind() != |
| 217 | + SILCoroutineKind::YieldOnce) |
| 218 | + return; |
| 219 | + |
| 220 | + diagnoseYieldOnceUsage(*fun); |
| 221 | + } |
| 222 | +}; |
| 223 | + |
| 224 | +} // end anonymous namespace |
| 225 | + |
| 226 | +SILTransform *swift::createYieldOnceCheck() { |
| 227 | + return new YieldOnceCheck(); |
| 228 | +} |
0 commit comments