|
15 | 15 | #include "MoveOnlyDiagnostics.h"
|
16 | 16 |
|
17 | 17 | #include "swift/AST/DiagnosticsSIL.h"
|
| 18 | +#include "swift/AST/Stmt.h" |
18 | 19 | #include "swift/Basic/Defer.h"
|
19 | 20 | #include "swift/SIL/BasicBlockBits.h"
|
20 | 21 | #include "swift/SIL/BasicBlockDatastructures.h"
|
@@ -195,38 +196,114 @@ void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
|
195 | 196 | SILInstruction *discard) {
|
196 | 197 | assert(isa<DropDeinitInst>(discard));
|
197 | 198 |
|
198 |
| - // An instruction corresponding to the logical place where the value is |
199 |
| - // destroyed. Ideally an exit point of the function reachable from here. |
200 |
| - // If for some reason we can't find an exit, then just use the original. |
201 |
| - SILInstruction *logicalDestroyLocation = leftoverDestroy; |
202 |
| - |
203 |
| - // Search for a function exit reachable from this destroy. We do this because |
204 |
| - // the move checker may have injected or hoisted an existing destroy from leaf |
205 |
| - // blocks to some earlier point. For example, if 'd' represents a destroy of |
206 |
| - // self, then we may have this CFG: |
207 |
| - // |
208 |
| - // before: after: |
209 |
| - // . d |
210 |
| - // / \ / \ |
211 |
| - // d d . . |
212 |
| - // |
213 |
| - BasicBlockWorklist worklist(logicalDestroyLocation->getFunction()); |
214 |
| - worklist.push(logicalDestroyLocation->getParent()); |
215 |
| - while (auto *bb = worklist.pop()) { |
216 |
| - TermInst *term = bb->getTerminator(); |
217 |
| - |
218 |
| - // Looking for a block that exits the function or terminates the program |
219 |
| - if (term->getNumSuccessors() == 0) { |
220 |
| - logicalDestroyLocation = term; |
221 |
| - break; |
| 199 | + // A good location is one that has some connection with the original source |
| 200 | + // and corresponds to an exit of the function. |
| 201 | + auto hasGoodLocation = [](SILInstruction *si) -> bool { |
| 202 | + if (!si) |
| 203 | + return false; |
| 204 | + |
| 205 | + SILLocation loc = si->getLoc(); |
| 206 | + if (loc.isNull()) |
| 207 | + return false; |
| 208 | + |
| 209 | + switch (loc.getKind()) { |
| 210 | + case SILLocation::ReturnKind: |
| 211 | + case SILLocation::ImplicitReturnKind: |
| 212 | + return true; |
| 213 | + |
| 214 | + case SILLocation::RegularKind: { |
| 215 | + Stmt *stmt = loc.getAsASTNode<Stmt>(); |
| 216 | + if (!stmt) |
| 217 | + return true; // For non-statements, assume it is exiting the func. |
| 218 | + |
| 219 | + // Prefer statements that can possibly lead to an exit of the function. |
| 220 | + // This is determined by whether the statement causes an exit of a |
| 221 | + // lexical scope; so a 'break' counts but not a 'continue'. |
| 222 | + switch (stmt->getKind()) { |
| 223 | + case StmtKind::Throw: |
| 224 | + case StmtKind::Return: |
| 225 | + case StmtKind::Yield: |
| 226 | + case StmtKind::Break: |
| 227 | + case StmtKind::Fail: |
| 228 | + case StmtKind::PoundAssert: |
| 229 | + return true; |
| 230 | + |
| 231 | + case StmtKind::Continue: |
| 232 | + case StmtKind::Brace: |
| 233 | + case StmtKind::Defer: |
| 234 | + case StmtKind::If: |
| 235 | + case StmtKind::Guard: |
| 236 | + case StmtKind::While: |
| 237 | + case StmtKind::Do: |
| 238 | + case StmtKind::DoCatch: |
| 239 | + case StmtKind::RepeatWhile: |
| 240 | + case StmtKind::ForEach: |
| 241 | + case StmtKind::Switch: |
| 242 | + case StmtKind::Case: |
| 243 | + case StmtKind::Fallthrough: |
| 244 | + case StmtKind::Discard: |
| 245 | + return false; |
| 246 | + }; |
222 | 247 | }
|
223 | 248 |
|
224 |
| - for (auto *nextBB : term->getSuccessorBlocks()) |
225 |
| - worklist.pushIfNotVisited(nextBB); |
| 249 | + case SILLocation::InlinedKind: |
| 250 | + case SILLocation::MandatoryInlinedKind: |
| 251 | + case SILLocation::CleanupKind: |
| 252 | + case SILLocation::ArtificialUnreachableKind: |
| 253 | + return false; |
| 254 | + }; |
| 255 | + }; |
| 256 | + |
| 257 | + // An instruction corresponding to the logical place where the value is |
| 258 | + // destroyed. Ideally an exit point of the function reachable from here or |
| 259 | + // some relevant statement. |
| 260 | + SILInstruction *destroyPoint = leftoverDestroy; |
| 261 | + if (!hasGoodLocation(destroyPoint)) { |
| 262 | + // Search for a nearby function exit reachable from this destroy. We do this |
| 263 | + // because the move checker may have injected or hoisted an existing |
| 264 | + // destroy from leaf blocks to some earlier point. For example, if 'd' |
| 265 | + // represents a destroy of self, then we may have this CFG: |
| 266 | + // |
| 267 | + // before: after: |
| 268 | + // . d |
| 269 | + // / \ / \ |
| 270 | + // d d . . |
| 271 | + // |
| 272 | + BasicBlockSet visited(destroyPoint->getFunction()); |
| 273 | + std::deque<SILBasicBlock *> bfsWorklist = {destroyPoint->getParent()}; |
| 274 | + while (auto *bb = bfsWorklist.front()) { |
| 275 | + visited.insert(bb); |
| 276 | + bfsWorklist.pop_front(); |
| 277 | + |
| 278 | + TermInst *term = bb->getTerminator(); |
| 279 | + |
| 280 | + // Looking for a block that exits the function or terminates the program. |
| 281 | + if (term->isFunctionExiting() || term->isProgramTerminating()) { |
| 282 | + SILInstruction *candidate = term; |
| 283 | + |
| 284 | + // Walk backwards until we find an instruction with any source location. |
| 285 | + // Sometimes a terminator like 'unreachable' may not have one, but one |
| 286 | + // of the preceding instructions will. |
| 287 | + while (candidate && candidate->getLoc().isNull()) |
| 288 | + candidate = candidate->getPreviousInstruction(); |
| 289 | + |
| 290 | + if (candidate && candidate->getLoc()) { |
| 291 | + destroyPoint = candidate; |
| 292 | + break; |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + for (auto *nextBB : term->getSuccessorBlocks()) |
| 297 | + if (!visited.contains(nextBB)) |
| 298 | + bfsWorklist.push_back(nextBB); |
| 299 | + } |
226 | 300 | }
|
227 | 301 |
|
| 302 | + assert(destroyPoint->getLoc() && "missing loc!"); |
| 303 | + assert(discard->getLoc() && "missing loc!"); |
| 304 | + |
228 | 305 | diagnose(leftoverDestroy->getFunction()->getASTContext(),
|
229 |
| - logicalDestroyLocation, |
| 306 | + destroyPoint, |
230 | 307 | diag::sil_movechecking_discard_missing_consume_self);
|
231 | 308 |
|
232 | 309 | diagnose(discard->getFunction()->getASTContext(), discard,
|
|
0 commit comments