mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-30 21:06:05 +00:00

This was missing a call to `ignoreCFGOmittedNodes()`. As a result, the function would erroneously conclude that a block did not contain an expression consumed in a different block if the expression in question was surrounded by a `ParenExpr` in the consuming block. The patch adds a test that triggers this scenario (and fails without the fix). To prevent this kind of bug in the future, the patch also adds a new method `blockForStmt()` to `AdornedCFG` that calls `ignoreCFGOmittedNodes()` and is preferred over accessing `getStmtToBlock()` directly.
190 lines
6.6 KiB
C++
190 lines
6.6 KiB
C++
//===- AdornedCFG.cpp ---------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines an `AdornedCFG` class that is used by dataflow analyses
|
|
// that run over Control-Flow Graphs (CFGs).
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Analysis/FlowSensitive/AdornedCFG.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/Stmt.h"
|
|
#include "clang/Analysis/CFG.h"
|
|
#include "clang/Analysis/FlowSensitive/ASTOps.h"
|
|
#include "llvm/ADT/BitVector.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include <utility>
|
|
|
|
namespace clang {
|
|
namespace dataflow {
|
|
|
|
/// Returns a map from statements to basic blocks that contain them.
|
|
static llvm::DenseMap<const Stmt *, const CFGBlock *>
|
|
buildStmtToBasicBlockMap(const CFG &Cfg) {
|
|
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock;
|
|
for (const CFGBlock *Block : Cfg) {
|
|
if (Block == nullptr)
|
|
continue;
|
|
|
|
for (const CFGElement &Element : *Block) {
|
|
auto Stmt = Element.getAs<CFGStmt>();
|
|
if (!Stmt)
|
|
continue;
|
|
|
|
StmtToBlock[Stmt->getStmt()] = Block;
|
|
}
|
|
}
|
|
// Some terminator conditions don't appear as a `CFGElement` anywhere else -
|
|
// for example, this is true if the terminator condition is a `&&` or `||`
|
|
// operator.
|
|
// We associate these conditions with the block the terminator appears in,
|
|
// but only if the condition has not already appeared as a regular
|
|
// `CFGElement`. (The `insert()` below does nothing if the key already exists
|
|
// in the map.)
|
|
for (const CFGBlock *Block : Cfg) {
|
|
if (Block != nullptr)
|
|
if (const Stmt *TerminatorCond = Block->getTerminatorCondition())
|
|
StmtToBlock.insert({TerminatorCond, Block});
|
|
}
|
|
// Terminator statements typically don't appear as a `CFGElement` anywhere
|
|
// else, so we want to associate them with the block that they terminate.
|
|
// However, there are some important special cases:
|
|
// - The conditional operator is a type of terminator, but it also appears
|
|
// as a regular `CFGElement`, and we want to associate it with the block
|
|
// in which it appears as a `CFGElement`.
|
|
// - The `&&` and `||` operators are types of terminators, but like the
|
|
// conditional operator, they can appear as a regular `CFGElement` or
|
|
// as a terminator condition (see above).
|
|
// We process terminators last to make sure that we only associate them with
|
|
// the block they terminate if they haven't previously occurred as a regular
|
|
// `CFGElement` or as a terminator condition.
|
|
for (const CFGBlock *Block : Cfg) {
|
|
if (Block != nullptr)
|
|
if (const Stmt *TerminatorStmt = Block->getTerminatorStmt())
|
|
StmtToBlock.insert({TerminatorStmt, Block});
|
|
}
|
|
return StmtToBlock;
|
|
}
|
|
|
|
static llvm::BitVector findReachableBlocks(const CFG &Cfg) {
|
|
llvm::BitVector BlockReachable(Cfg.getNumBlockIDs(), false);
|
|
|
|
llvm::SmallVector<const CFGBlock *> BlocksToVisit;
|
|
BlocksToVisit.push_back(&Cfg.getEntry());
|
|
while (!BlocksToVisit.empty()) {
|
|
const CFGBlock *Block = BlocksToVisit.back();
|
|
BlocksToVisit.pop_back();
|
|
|
|
if (BlockReachable[Block->getBlockID()])
|
|
continue;
|
|
|
|
BlockReachable[Block->getBlockID()] = true;
|
|
|
|
for (const CFGBlock *Succ : Block->succs())
|
|
if (Succ)
|
|
BlocksToVisit.push_back(Succ);
|
|
}
|
|
|
|
return BlockReachable;
|
|
}
|
|
|
|
static llvm::DenseSet<const CFGBlock *>
|
|
buildContainsExprConsumedInDifferentBlock(
|
|
const CFG &Cfg, const internal::StmtToBlockMap &StmtToBlock) {
|
|
llvm::DenseSet<const CFGBlock *> Result;
|
|
|
|
auto CheckChildExprs = [&Result, &StmtToBlock](const Stmt *S,
|
|
const CFGBlock *Block) {
|
|
for (const Stmt *Child : S->children()) {
|
|
if (!isa_and_nonnull<Expr>(Child))
|
|
continue;
|
|
const CFGBlock *ChildBlock = StmtToBlock.lookup(*Child);
|
|
if (ChildBlock != Block)
|
|
Result.insert(ChildBlock);
|
|
}
|
|
};
|
|
|
|
for (const CFGBlock *Block : Cfg) {
|
|
if (Block == nullptr)
|
|
continue;
|
|
|
|
for (const CFGElement &Element : *Block)
|
|
if (auto S = Element.getAs<CFGStmt>())
|
|
CheckChildExprs(S->getStmt(), Block);
|
|
|
|
if (const Stmt *TerminatorCond = Block->getTerminatorCondition())
|
|
CheckChildExprs(TerminatorCond, Block);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
namespace internal {
|
|
|
|
StmtToBlockMap::StmtToBlockMap(const CFG &Cfg)
|
|
: StmtToBlock(buildStmtToBasicBlockMap(Cfg)) {}
|
|
|
|
} // namespace internal
|
|
|
|
llvm::Expected<AdornedCFG> AdornedCFG::build(const FunctionDecl &Func) {
|
|
if (!Func.doesThisDeclarationHaveABody())
|
|
return llvm::createStringError(
|
|
std::make_error_code(std::errc::invalid_argument),
|
|
"Cannot analyze function without a body");
|
|
|
|
return build(Func, *Func.getBody(), Func.getASTContext());
|
|
}
|
|
|
|
llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S,
|
|
ASTContext &C) {
|
|
if (D.isTemplated())
|
|
return llvm::createStringError(
|
|
std::make_error_code(std::errc::invalid_argument),
|
|
"Cannot analyze templated declarations");
|
|
|
|
// The shape of certain elements of the AST can vary depending on the
|
|
// language. We currently only support C++.
|
|
if (!C.getLangOpts().CPlusPlus || C.getLangOpts().ObjC)
|
|
return llvm::createStringError(
|
|
std::make_error_code(std::errc::invalid_argument),
|
|
"Can only analyze C++");
|
|
|
|
CFG::BuildOptions Options;
|
|
Options.PruneTriviallyFalseEdges = true;
|
|
Options.AddImplicitDtors = true;
|
|
Options.AddTemporaryDtors = true;
|
|
Options.AddInitializers = true;
|
|
Options.AddCXXDefaultInitExprInCtors = true;
|
|
Options.AddLifetime = true;
|
|
|
|
// Ensure that all sub-expressions in basic blocks are evaluated.
|
|
Options.setAllAlwaysAdd();
|
|
|
|
auto Cfg = CFG::buildCFG(&D, &S, &C, Options);
|
|
if (Cfg == nullptr)
|
|
return llvm::createStringError(
|
|
std::make_error_code(std::errc::invalid_argument),
|
|
"CFG::buildCFG failed");
|
|
|
|
internal::StmtToBlockMap StmtToBlock(*Cfg);
|
|
|
|
llvm::BitVector BlockReachable = findReachableBlocks(*Cfg);
|
|
|
|
llvm::DenseSet<const CFGBlock *> ContainsExprConsumedInDifferentBlock =
|
|
buildContainsExprConsumedInDifferentBlock(*Cfg, StmtToBlock);
|
|
|
|
return AdornedCFG(D, std::move(Cfg), std::move(StmtToBlock),
|
|
std::move(BlockReachable),
|
|
std::move(ContainsExprConsumedInDifferentBlock));
|
|
}
|
|
|
|
} // namespace dataflow
|
|
} // namespace clang
|