void State::checkDataflowEndState(DeadEndBlocks &deBlocks) { if (!successorBlocksThatMustBeVisited.empty()) { // If we are asked to store any leaking blocks, put them in the leaking // blocks array. if (leakingBlocks) { copy(successorBlocksThatMustBeVisited, std::back_inserter(*leakingBlocks)); } // If we are supposed to error on leaks, do so now. error.handleLeak([&] { llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n" << "Error! Found a leak due to a consuming post-dominance " "failure!\n" << " Value: " << *value << " Post Dominating Failure Blocks:\n"; for (auto *succBlock : successorBlocksThatMustBeVisited) { llvm::errs() << " bb" << succBlock->getDebugID(); } llvm::errs() << '\n'; }); // Otherwise... see if we have any other failures. This signals the user // wants us to tell it where to insert compensating destroys. } // Make sure that we do not have any lifetime ending uses left to visit that // are not transitively unreachable blocks.... so return early. if (blocksWithNonConsumingUses.empty()) { return; } // If we do have remaining blocks, then these non lifetime ending uses must be // outside of our "alive" blocks implying a use-after free. for (auto &pair : blocksWithNonConsumingUses) { if (deBlocks.isDeadEnd(pair.first)) { continue; } error.handleUseAfterFree([&] { llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n" << "Found use after free due to unvisited non lifetime " "ending uses?!\n" << "Value: " << *value << " Remaining Users:\n"; for (auto &pair : blocksWithNonConsumingUses) { llvm::errs() << "User:"******"Block: bb" << pair.first->getDebugID() << "\n"; } llvm::errs() << "\n"; }); } }
bool State::checkDataflowEndState(DeadEndBlocks &deBlocks) { // Make sure that we visited all successor blocks that we needed to visit to // make sure we didn't leak. if (!successorBlocksThatMustBeVisited.empty()) { return handleError([&] { llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n" << "Error! Found a leak due to a consuming post-dominance failure!\n" << " Value: " << *value << " Post Dominating Failure Blocks:\n"; for (auto *succBlock : successorBlocksThatMustBeVisited) { llvm::errs() << " bb" << succBlock->getDebugID(); } llvm::errs() << '\n'; }); } // Make sure that we do not have any lifetime ending uses left to visit that // are not transitively unreachable blocks.... so return early. if (blocksWithNonConsumingUses.empty()) { return true; } // If we do have remaining blocks, then these non lifetime ending uses must be // outside of our "alive" blocks implying a use-after free. for (auto &pair : blocksWithNonConsumingUses) { if (deBlocks.isDeadEnd(pair.first)) { continue; } return handleError([&] { llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n" << "Found use after free due to unvisited non lifetime " "ending uses?!\n" << "Value: " << *value << " Remaining Users:\n"; for (auto &pair : blocksWithNonConsumingUses) { llvm::errs() << "User:"******"Block: bb" << pair.first->getDebugID() << "\n"; } llvm::errs() << "\n"; }); } // If all of our remaining blocks were dead uses, then return true. We are // good. return true; }
void State::performDataflow(DeadEndBlocks &deBlocks) { LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n"); // Until the worklist is empty... while (!worklist.empty()) { // Grab the next block to visit. SILBasicBlock *block = worklist.pop_back_val(); LLVM_DEBUG(llvm::dbgs() << " Visiting Block: bb" << block->getDebugID() << '\n'); // Since the block is on our worklist, we know already that it is not a // block with lifetime ending uses, due to the invariants of our loop. // First remove BB from the SuccessorBlocksThatMustBeVisited list. This // ensures that when the algorithm terminates, we know that BB was not the // beginning of a non-covered path to the exit. successorBlocksThatMustBeVisited.remove(block); // Then remove BB from BlocksWithNonLifetimeEndingUses so we know that // this block was properly joint post-dominated by our lifetime ending // users. blocksWithNonConsumingUses.erase(block); // Ok, now we know that we do not have an overconsume. If this block does // not end in a no return function, we need to update our state for our // successors to make sure by the end of the traversal we visit them. // // We must consider such no-return blocks since we may be running during // SILGen before NoReturn folding has run. for (auto *succBlock : block->getSuccessorBlocks()) { // If we already visited the successor, there is nothing to do since we // already visited the successor. if (visitedBlocks.count(succBlock)) continue; // Then check if the successor is a transitively unreachable block. In // such a case, we ignore it since we are going to leak along that path. if (deBlocks.isDeadEnd(succBlock)) continue; // Otherwise, add the successor to our SuccessorBlocksThatMustBeVisited // set to ensure that we assert if we do not visit it by the end of the // algorithm. successorBlocksThatMustBeVisited.insert(succBlock); } // If we are at the dominating block of our walk, continue. There is nothing // further to do since we do not want to visit the predecessors of our // dominating block. On the other hand, we do want to add its successors to // the successorBlocksThatMustBeVisited set. if (block == value->getParentBlock()) continue; // Then for each predecessor of this block: // // 1. If we have visited the predecessor already, then it is not a block // with lifetime ending uses. If it is a block with uses, then we have a // double release... so assert. If not, we continue. // // 2. We add the predecessor to the worklist if we have not visited it yet. for (auto *predBlock : block->getPredecessorBlocks()) { // Check if we have an over consume. checkPredsForDoubleConsume(predBlock); if (visitedBlocks.count(predBlock)) { continue; } visitedBlocks.insert(predBlock); worklist.push_back(predBlock); } } }