forked from OSchip/llvm-project
[clang][dataflow] Add support for noreturn destructor calls
This is part of the implementation of the dataflow analysis framework. See "[RFC] A dataflow analysis framework for Clang AST" on cfe-dev. Reviewed By: xazax.hun, gribozavr2 Differential Revision: https://reviews.llvm.org/D116022
This commit is contained in:
parent
49f646a9ed
commit
b5c5d8912e
|
@ -0,0 +1,57 @@
|
||||||
|
//===-- ControlFlowContext.h ------------------------------------*- C++ -*-===//
|
||||||
|
//
|
||||||
|
// 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 a ControlFlowContext class that is used by dataflow
|
||||||
|
// analyses that run over Control-Flow Graphs (CFGs).
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CONTROLFLOWCONTEXT_H
|
||||||
|
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CONTROLFLOWCONTEXT_H
|
||||||
|
|
||||||
|
#include "clang/AST/ASTContext.h"
|
||||||
|
#include "clang/AST/Decl.h"
|
||||||
|
#include "clang/AST/Stmt.h"
|
||||||
|
#include "clang/Analysis/CFG.h"
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
#include "llvm/Support/Error.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace clang {
|
||||||
|
namespace dataflow {
|
||||||
|
|
||||||
|
/// Holds CFG and other derived context that is needed to perform dataflow
|
||||||
|
/// analysis.
|
||||||
|
class ControlFlowContext {
|
||||||
|
public:
|
||||||
|
/// Builds a ControlFlowContext from an AST node.
|
||||||
|
static llvm::Expected<ControlFlowContext> build(const Decl *D, Stmt *S,
|
||||||
|
ASTContext *C);
|
||||||
|
|
||||||
|
/// Returns the CFG that is stored in this context.
|
||||||
|
const CFG &getCFG() const { return *Cfg; }
|
||||||
|
|
||||||
|
/// Returns a mapping from statements to basic blocks that contain them.
|
||||||
|
const llvm::DenseMap<const Stmt *, const CFGBlock *> &getStmtToBlock() const {
|
||||||
|
return StmtToBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ControlFlowContext(std::unique_ptr<CFG> Cfg,
|
||||||
|
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock)
|
||||||
|
: Cfg(std::move(Cfg)), StmtToBlock(std::move(StmtToBlock)) {}
|
||||||
|
|
||||||
|
std::unique_ptr<CFG> Cfg;
|
||||||
|
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dataflow
|
||||||
|
} // namespace clang
|
||||||
|
|
||||||
|
#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CONTROLFLOWCONTEXT_H
|
|
@ -21,6 +21,7 @@
|
||||||
#include "clang/AST/ASTContext.h"
|
#include "clang/AST/ASTContext.h"
|
||||||
#include "clang/AST/Stmt.h"
|
#include "clang/AST/Stmt.h"
|
||||||
#include "clang/Analysis/CFG.h"
|
#include "clang/Analysis/CFG.h"
|
||||||
|
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
||||||
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
|
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
|
||||||
#include "llvm/ADT/Any.h"
|
#include "llvm/ADT/Any.h"
|
||||||
|
@ -101,17 +102,12 @@ template <typename LatticeT> struct DataflowAnalysisState {
|
||||||
/// Performs dataflow analysis and returns a mapping from basic block IDs to
|
/// Performs dataflow analysis and returns a mapping from basic block IDs to
|
||||||
/// dataflow analysis states that model the respective basic blocks. Indices
|
/// dataflow analysis states that model the respective basic blocks. Indices
|
||||||
/// of the returned vector correspond to basic block IDs.
|
/// of the returned vector correspond to basic block IDs.
|
||||||
///
|
|
||||||
/// Requirements:
|
|
||||||
///
|
|
||||||
/// `Cfg` must have been built with `CFG::BuildOptions::setAllAlwaysAdd()` to
|
|
||||||
/// ensure that all sub-expressions in a basic block are evaluated.
|
|
||||||
template <typename AnalysisT>
|
template <typename AnalysisT>
|
||||||
std::vector<llvm::Optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>
|
std::vector<llvm::Optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>
|
||||||
runDataflowAnalysis(const CFG &Cfg, AnalysisT &Analysis,
|
runDataflowAnalysis(const ControlFlowContext &CFCtx, AnalysisT &Analysis,
|
||||||
const Environment &InitEnv) {
|
const Environment &InitEnv) {
|
||||||
auto TypeErasedBlockStates =
|
auto TypeErasedBlockStates =
|
||||||
runTypeErasedDataflowAnalysis(Cfg, Analysis, InitEnv);
|
runTypeErasedDataflowAnalysis(CFCtx, Analysis, InitEnv);
|
||||||
std::vector<
|
std::vector<
|
||||||
llvm::Optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>
|
llvm::Optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>
|
||||||
BlockStates;
|
BlockStates;
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "clang/AST/ASTContext.h"
|
#include "clang/AST/ASTContext.h"
|
||||||
#include "clang/AST/Stmt.h"
|
#include "clang/AST/Stmt.h"
|
||||||
#include "clang/Analysis/CFG.h"
|
#include "clang/Analysis/CFG.h"
|
||||||
|
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
|
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
|
||||||
#include "llvm/ADT/Any.h"
|
#include "llvm/ADT/Any.h"
|
||||||
|
@ -87,6 +88,7 @@ struct TypeErasedDataflowAnalysisState {
|
||||||
/// already been transferred. States in `BlockStates` that are set to
|
/// already been transferred. States in `BlockStates` that are set to
|
||||||
/// `llvm::None` represent basic blocks that are not evaluated yet.
|
/// `llvm::None` represent basic blocks that are not evaluated yet.
|
||||||
TypeErasedDataflowAnalysisState transferBlock(
|
TypeErasedDataflowAnalysisState transferBlock(
|
||||||
|
const ControlFlowContext &CFCtx,
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
||||||
const CFGBlock &Block, const Environment &InitEnv,
|
const CFGBlock &Block, const Environment &InitEnv,
|
||||||
TypeErasedDataflowAnalysis &Analysis,
|
TypeErasedDataflowAnalysis &Analysis,
|
||||||
|
@ -97,13 +99,8 @@ TypeErasedDataflowAnalysisState transferBlock(
|
||||||
/// Performs dataflow analysis and returns a mapping from basic block IDs to
|
/// Performs dataflow analysis and returns a mapping from basic block IDs to
|
||||||
/// dataflow analysis states that model the respective basic blocks. Indices
|
/// dataflow analysis states that model the respective basic blocks. Indices
|
||||||
/// of the returned vector correspond to basic block IDs.
|
/// of the returned vector correspond to basic block IDs.
|
||||||
///
|
|
||||||
/// Requirements:
|
|
||||||
///
|
|
||||||
/// `Cfg` must have been built with `CFG::BuildOptions::setAllAlwaysAdd()` to
|
|
||||||
/// ensure that all sub-expressions in a basic block are evaluated.
|
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>
|
||||||
runTypeErasedDataflowAnalysis(const CFG &Cfg,
|
runTypeErasedDataflowAnalysis(const ControlFlowContext &CFCtx,
|
||||||
TypeErasedDataflowAnalysis &Analysis,
|
TypeErasedDataflowAnalysis &Analysis,
|
||||||
const Environment &InitEnv);
|
const Environment &InitEnv);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
add_clang_library(clangAnalysisFlowSensitive
|
add_clang_library(clangAnalysisFlowSensitive
|
||||||
|
ControlFlowContext.cpp
|
||||||
TypeErasedDataflowAnalysis.cpp
|
TypeErasedDataflowAnalysis.cpp
|
||||||
|
|
||||||
LINK_LIBS
|
LINK_LIBS
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
//===- ControlFlowContext.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 a ControlFlowContext class that is used by dataflow
|
||||||
|
// analyses that run over Control-Flow Graphs (CFGs).
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
|
||||||
|
#include "clang/AST/ASTContext.h"
|
||||||
|
#include "clang/AST/Decl.h"
|
||||||
|
#include "clang/AST/Stmt.h"
|
||||||
|
#include "clang/Analysis/CFG.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.hasValue())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
StmtToBlock[Stmt.getValue().getStmt()] = Block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StmtToBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::Expected<ControlFlowContext>
|
||||||
|
ControlFlowContext::build(const Decl *D, Stmt *S, ASTContext *C) {
|
||||||
|
CFG::BuildOptions Options;
|
||||||
|
Options.PruneTriviallyFalseEdges = false;
|
||||||
|
Options.AddImplicitDtors = true;
|
||||||
|
Options.AddTemporaryDtors = true;
|
||||||
|
Options.AddInitializers = 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");
|
||||||
|
|
||||||
|
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock =
|
||||||
|
buildStmtToBasicBlockMap(*Cfg);
|
||||||
|
return ControlFlowContext(std::move(Cfg), std::move(StmtToBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dataflow
|
||||||
|
} // namespace clang
|
|
@ -35,6 +35,7 @@ namespace dataflow {
|
||||||
/// already been transferred. States in `BlockStates` that are set to
|
/// already been transferred. States in `BlockStates` that are set to
|
||||||
/// `llvm::None` represent basic blocks that are not evaluated yet.
|
/// `llvm::None` represent basic blocks that are not evaluated yet.
|
||||||
static TypeErasedDataflowAnalysisState computeBlockInputState(
|
static TypeErasedDataflowAnalysisState computeBlockInputState(
|
||||||
|
const ControlFlowContext &CFCtx,
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
||||||
const CFGBlock &Block, const Environment &InitEnv,
|
const CFGBlock &Block, const Environment &InitEnv,
|
||||||
TypeErasedDataflowAnalysis &Analysis) {
|
TypeErasedDataflowAnalysis &Analysis) {
|
||||||
|
@ -43,7 +44,40 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
|
||||||
// the state of each basic block differently.
|
// the state of each basic block differently.
|
||||||
TypeErasedDataflowAnalysisState State = {Analysis.typeErasedInitialElement(),
|
TypeErasedDataflowAnalysisState State = {Analysis.typeErasedInitialElement(),
|
||||||
InitEnv};
|
InitEnv};
|
||||||
for (const CFGBlock *Pred : Block.preds()) {
|
|
||||||
|
llvm::DenseSet<const CFGBlock *> Preds;
|
||||||
|
Preds.insert(Block.pred_begin(), Block.pred_end());
|
||||||
|
if (Block.getTerminator().isTemporaryDtorsBranch()) {
|
||||||
|
// This handles a special case where the code that produced the CFG includes
|
||||||
|
// a conditional operator with a branch that constructs a temporary and
|
||||||
|
// calls a destructor annotated as noreturn. The CFG models this as follows:
|
||||||
|
//
|
||||||
|
// B1 (contains the condition of the conditional operator) - succs: B2, B3
|
||||||
|
// B2 (contains code that does not call a noreturn destructor) - succs: B4
|
||||||
|
// B3 (contains code that calls a noreturn destructor) - succs: B4
|
||||||
|
// B4 (has temporary destructor terminator) - succs: B5, B6
|
||||||
|
// B5 (noreturn block that is associated with the noreturn destructor call)
|
||||||
|
// B6 (contains code that follows the conditional operator statement)
|
||||||
|
//
|
||||||
|
// The first successor (B5 above) of a basic block with a temporary
|
||||||
|
// destructor terminator (B4 above) is the block that evaluates the
|
||||||
|
// destructor. If that block has a noreturn element then the predecessor
|
||||||
|
// block that constructed the temporary object (B3 above) is effectively a
|
||||||
|
// noreturn block and its state should not be used as input for the state
|
||||||
|
// of the block that has a temporary destructor terminator (B4 above). This
|
||||||
|
// holds regardless of which branch of the ternary operator calls the
|
||||||
|
// noreturn destructor. However, it doesn't cases where a nested ternary
|
||||||
|
// operator includes a branch that contains a noreturn destructor call.
|
||||||
|
//
|
||||||
|
// See `NoreturnDestructorTest` for concrete examples.
|
||||||
|
if (Block.succ_begin()->getReachableBlock()->hasNoReturnElement()) {
|
||||||
|
auto StmtBlock = CFCtx.getStmtToBlock().find(Block.getTerminatorStmt());
|
||||||
|
assert(StmtBlock != CFCtx.getStmtToBlock().end());
|
||||||
|
Preds.erase(StmtBlock->getSecond());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const CFGBlock *Pred : Preds) {
|
||||||
// Skip if the `Block` is unreachable or control flow cannot get past it.
|
// Skip if the `Block` is unreachable or control flow cannot get past it.
|
||||||
if (!Pred || Pred->hasNoReturnElement())
|
if (!Pred || Pred->hasNoReturnElement())
|
||||||
continue;
|
continue;
|
||||||
|
@ -64,6 +98,7 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeErasedDataflowAnalysisState transferBlock(
|
TypeErasedDataflowAnalysisState transferBlock(
|
||||||
|
const ControlFlowContext &CFCtx,
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
||||||
const CFGBlock &Block, const Environment &InitEnv,
|
const CFGBlock &Block, const Environment &InitEnv,
|
||||||
TypeErasedDataflowAnalysis &Analysis,
|
TypeErasedDataflowAnalysis &Analysis,
|
||||||
|
@ -71,7 +106,7 @@ TypeErasedDataflowAnalysisState transferBlock(
|
||||||
const TypeErasedDataflowAnalysisState &)>
|
const TypeErasedDataflowAnalysisState &)>
|
||||||
HandleTransferredStmt) {
|
HandleTransferredStmt) {
|
||||||
TypeErasedDataflowAnalysisState State =
|
TypeErasedDataflowAnalysisState State =
|
||||||
computeBlockInputState(BlockStates, Block, InitEnv, Analysis);
|
computeBlockInputState(CFCtx, BlockStates, Block, InitEnv, Analysis);
|
||||||
for (const CFGElement &Element : Block) {
|
for (const CFGElement &Element : Block) {
|
||||||
// FIXME: Evaluate other kinds of `CFGElement`.
|
// FIXME: Evaluate other kinds of `CFGElement`.
|
||||||
const llvm::Optional<CFGStmt> Stmt = Element.getAs<CFGStmt>();
|
const llvm::Optional<CFGStmt> Stmt = Element.getAs<CFGStmt>();
|
||||||
|
@ -89,21 +124,21 @@ TypeErasedDataflowAnalysisState transferBlock(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>
|
||||||
runTypeErasedDataflowAnalysis(const CFG &Cfg,
|
runTypeErasedDataflowAnalysis(const ControlFlowContext &CFCtx,
|
||||||
TypeErasedDataflowAnalysis &Analysis,
|
TypeErasedDataflowAnalysis &Analysis,
|
||||||
const Environment &InitEnv) {
|
const Environment &InitEnv) {
|
||||||
// FIXME: Consider enforcing that `Cfg` meets the requirements that
|
// FIXME: Consider enforcing that `Cfg` meets the requirements that
|
||||||
// are specified in the header. This could be done by remembering
|
// are specified in the header. This could be done by remembering
|
||||||
// what options were used to build `Cfg` and asserting on them here.
|
// what options were used to build `Cfg` and asserting on them here.
|
||||||
|
|
||||||
PostOrderCFGView POV(&Cfg);
|
PostOrderCFGView POV(&CFCtx.getCFG());
|
||||||
ForwardDataflowWorklist Worklist(Cfg, &POV);
|
ForwardDataflowWorklist Worklist(CFCtx.getCFG(), &POV);
|
||||||
|
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates;
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates;
|
||||||
BlockStates.resize(Cfg.size(), llvm::None);
|
BlockStates.resize(CFCtx.getCFG().size(), llvm::None);
|
||||||
|
|
||||||
// The entry basic block doesn't contain statements so it can be skipped.
|
// The entry basic block doesn't contain statements so it can be skipped.
|
||||||
const CFGBlock &Entry = Cfg.getEntry();
|
const CFGBlock &Entry = CFCtx.getCFG().getEntry();
|
||||||
BlockStates[Entry.getBlockID()] = {Analysis.typeErasedInitialElement(),
|
BlockStates[Entry.getBlockID()] = {Analysis.typeErasedInitialElement(),
|
||||||
InitEnv};
|
InitEnv};
|
||||||
Worklist.enqueueSuccessors(&Entry);
|
Worklist.enqueueSuccessors(&Entry);
|
||||||
|
@ -125,7 +160,7 @@ runTypeErasedDataflowAnalysis(const CFG &Cfg,
|
||||||
const llvm::Optional<TypeErasedDataflowAnalysisState> &OldBlockState =
|
const llvm::Optional<TypeErasedDataflowAnalysisState> &OldBlockState =
|
||||||
BlockStates[Block->getBlockID()];
|
BlockStates[Block->getBlockID()];
|
||||||
TypeErasedDataflowAnalysisState NewBlockState =
|
TypeErasedDataflowAnalysisState NewBlockState =
|
||||||
transferBlock(BlockStates, *Block, InitEnv, Analysis);
|
transferBlock(CFCtx, BlockStates, *Block, InitEnv, Analysis);
|
||||||
|
|
||||||
if (OldBlockState.hasValue() &&
|
if (OldBlockState.hasValue() &&
|
||||||
Analysis.isEqualTypeErased(OldBlockState.getValue().Lattice,
|
Analysis.isEqualTypeErased(OldBlockState.getValue().Lattice,
|
||||||
|
|
|
@ -144,26 +144,3 @@ test::buildStatementToAnnotationMapping(const FunctionDecl *Func,
|
||||||
|
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<const FunctionDecl *, std::unique_ptr<CFG>>
|
|
||||||
test::buildCFG(ASTContext &Context,
|
|
||||||
ast_matchers::internal::Matcher<FunctionDecl> FuncMatcher) {
|
|
||||||
CFG::BuildOptions Options;
|
|
||||||
Options.PruneTriviallyFalseEdges = false;
|
|
||||||
Options.AddInitializers = true;
|
|
||||||
Options.AddImplicitDtors = true;
|
|
||||||
Options.AddTemporaryDtors = true;
|
|
||||||
Options.setAllAlwaysAdd();
|
|
||||||
|
|
||||||
const FunctionDecl *F = ast_matchers::selectFirst<FunctionDecl>(
|
|
||||||
"target",
|
|
||||||
ast_matchers::match(
|
|
||||||
ast_matchers::functionDecl(ast_matchers::isDefinition(), FuncMatcher)
|
|
||||||
.bind("target"),
|
|
||||||
Context));
|
|
||||||
if (F == nullptr)
|
|
||||||
return std::make_pair(nullptr, nullptr);
|
|
||||||
|
|
||||||
return std::make_pair(
|
|
||||||
F, clang::CFG::buildCFG(F, F->getBody(), &Context, Options));
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,9 +20,12 @@
|
||||||
#include "clang/ASTMatchers/ASTMatchers.h"
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
||||||
#include "clang/ASTMatchers/ASTMatchersInternal.h"
|
#include "clang/ASTMatchers/ASTMatchersInternal.h"
|
||||||
#include "clang/Analysis/CFG.h"
|
#include "clang/Analysis/CFG.h"
|
||||||
|
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
|
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
||||||
#include "clang/Basic/LLVM.h"
|
#include "clang/Basic/LLVM.h"
|
||||||
|
#include "clang/Serialization/PCHContainerOperations.h"
|
||||||
|
#include "clang/Tooling/ArgumentsAdjusters.h"
|
||||||
#include "clang/Tooling/Tooling.h"
|
#include "clang/Tooling/Tooling.h"
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
@ -56,12 +59,6 @@ llvm::Expected<llvm::DenseMap<const Stmt *, std::string>>
|
||||||
buildStatementToAnnotationMapping(const FunctionDecl *Func,
|
buildStatementToAnnotationMapping(const FunctionDecl *Func,
|
||||||
llvm::Annotations AnnotatedCode);
|
llvm::Annotations AnnotatedCode);
|
||||||
|
|
||||||
// Creates a CFG from the body of the function that matches `func_matcher`,
|
|
||||||
// suitable to testing a dataflow analysis.
|
|
||||||
std::pair<const FunctionDecl *, std::unique_ptr<CFG>>
|
|
||||||
buildCFG(ASTContext &Context,
|
|
||||||
ast_matchers::internal::Matcher<FunctionDecl> FuncMatcher);
|
|
||||||
|
|
||||||
// Runs dataflow on the body of the function that matches `func_matcher` in code
|
// Runs dataflow on the body of the function that matches `func_matcher` in code
|
||||||
// snippet `code`. Requires: `Analysis` contains a type `Lattice`.
|
// snippet `code`. Requires: `Analysis` contains a type `Lattice`.
|
||||||
template <typename AnalysisT>
|
template <typename AnalysisT>
|
||||||
|
@ -79,7 +76,10 @@ void checkDataflow(
|
||||||
using StateT = DataflowAnalysisState<typename AnalysisT::Lattice>;
|
using StateT = DataflowAnalysisState<typename AnalysisT::Lattice>;
|
||||||
|
|
||||||
llvm::Annotations AnnotatedCode(Code);
|
llvm::Annotations AnnotatedCode(Code);
|
||||||
auto Unit = tooling::buildASTFromCodeWithArgs(AnnotatedCode.code(), Args);
|
auto Unit = tooling::buildASTFromCodeWithArgs(
|
||||||
|
AnnotatedCode.code(), Args, "input.cc", "clang-dataflow-test",
|
||||||
|
std::make_shared<PCHContainerOperations>(),
|
||||||
|
tooling::getClangStripDependencyFileAdjuster(), VirtualMappedFiles);
|
||||||
auto &Context = Unit->getASTContext();
|
auto &Context = Unit->getASTContext();
|
||||||
|
|
||||||
if (Context.getDiagnostics().getClient()->getNumErrors() != 0) {
|
if (Context.getDiagnostics().getClient()->getNumErrors() != 0) {
|
||||||
|
@ -87,12 +87,16 @@ void checkDataflow(
|
||||||
"the test log";
|
"the test log";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<const FunctionDecl *, std::unique_ptr<CFG>> CFGResult =
|
const FunctionDecl *F = ast_matchers::selectFirst<FunctionDecl>(
|
||||||
buildCFG(Context, FuncMatcher);
|
"target",
|
||||||
const auto *F = CFGResult.first;
|
ast_matchers::match(
|
||||||
auto Cfg = std::move(CFGResult.second);
|
ast_matchers::functionDecl(ast_matchers::isDefinition(), FuncMatcher)
|
||||||
ASSERT_TRUE(F != nullptr) << "Could not find target function";
|
.bind("target"),
|
||||||
ASSERT_TRUE(Cfg != nullptr) << "Could not build control flow graph.";
|
Context));
|
||||||
|
ASSERT_TRUE(F != nullptr) << "Could not find target function.";
|
||||||
|
|
||||||
|
auto CFCtx = ControlFlowContext::build(F, F->getBody(), &F->getASTContext());
|
||||||
|
ASSERT_TRUE((bool)CFCtx) << "Could not build ControlFlowContext.";
|
||||||
|
|
||||||
Environment Env;
|
Environment Env;
|
||||||
auto Analysis = MakeAnalysis(Context, Env);
|
auto Analysis = MakeAnalysis(Context, Env);
|
||||||
|
@ -107,7 +111,7 @@ void checkDataflow(
|
||||||
auto &Annotations = *StmtToAnnotations;
|
auto &Annotations = *StmtToAnnotations;
|
||||||
|
|
||||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates =
|
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates =
|
||||||
runTypeErasedDataflowAnalysis(*Cfg, Analysis, Env);
|
runTypeErasedDataflowAnalysis(*CFCtx, Analysis, Env);
|
||||||
|
|
||||||
if (BlockStates.empty()) {
|
if (BlockStates.empty()) {
|
||||||
Expectations({}, Context);
|
Expectations({}, Context);
|
||||||
|
@ -117,13 +121,13 @@ void checkDataflow(
|
||||||
// Compute a map from statement annotations to the state computed for
|
// Compute a map from statement annotations to the state computed for
|
||||||
// the program point immediately after the annotated statement.
|
// the program point immediately after the annotated statement.
|
||||||
std::vector<std::pair<std::string, StateT>> Results;
|
std::vector<std::pair<std::string, StateT>> Results;
|
||||||
for (const CFGBlock *Block : *Cfg) {
|
for (const CFGBlock *Block : CFCtx->getCFG()) {
|
||||||
// Skip blocks that were not evaluated.
|
// Skip blocks that were not evaluated.
|
||||||
if (!BlockStates[Block->getBlockID()].hasValue())
|
if (!BlockStates[Block->getBlockID()].hasValue())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
transferBlock(
|
transferBlock(
|
||||||
BlockStates, *Block, Env, Analysis,
|
*CFCtx, BlockStates, *Block, Env, Analysis,
|
||||||
[&Results, &Annotations](const clang::CFGStmt &Stmt,
|
[&Results, &Annotations](const clang::CFGStmt &Stmt,
|
||||||
const TypeErasedDataflowAnalysisState &State) {
|
const TypeErasedDataflowAnalysisState &State) {
|
||||||
auto It = Annotations.find(Stmt.getStmt());
|
auto It = Annotations.find(Stmt.getStmt());
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#include "TestingSupport.h"
|
||||||
#include "clang/AST/Decl.h"
|
#include "clang/AST/Decl.h"
|
||||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||||
#include "clang/ASTMatchers/ASTMatchers.h"
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
||||||
|
@ -14,15 +15,24 @@
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
||||||
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
|
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
|
||||||
#include "clang/Tooling/Tooling.h"
|
#include "clang/Tooling/Tooling.h"
|
||||||
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
#include "llvm/ADT/SmallSet.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
#include "llvm/Support/Error.h"
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace clang;
|
using namespace clang;
|
||||||
using namespace dataflow;
|
using namespace dataflow;
|
||||||
|
using ::testing::IsEmpty;
|
||||||
|
using ::testing::Pair;
|
||||||
|
using ::testing::UnorderedElementsAre;
|
||||||
|
|
||||||
template <typename AnalysisT>
|
template <typename AnalysisT>
|
||||||
class AnalysisCallback : public ast_matchers::MatchFinder::MatchCallback {
|
class AnalysisCallback : public ast_matchers::MatchFinder::MatchCallback {
|
||||||
|
@ -36,21 +46,12 @@ public:
|
||||||
Stmt *Body = Func->getBody();
|
Stmt *Body = Func->getBody();
|
||||||
assert(Body != nullptr);
|
assert(Body != nullptr);
|
||||||
|
|
||||||
// FIXME: Consider providing a utility that returns a `CFG::BuildOptions`
|
auto CFCtx = llvm::cantFail(
|
||||||
// which is a good default for most clients or a utility that directly
|
ControlFlowContext::build(nullptr, Body, Result.Context));
|
||||||
// builds the `CFG` using default `CFG::BuildOptions`.
|
|
||||||
CFG::BuildOptions Options;
|
|
||||||
Options.AddImplicitDtors = true;
|
|
||||||
Options.AddTemporaryDtors = true;
|
|
||||||
Options.setAllAlwaysAdd();
|
|
||||||
|
|
||||||
std::unique_ptr<CFG> Cfg =
|
|
||||||
CFG::buildCFG(nullptr, Body, Result.Context, Options);
|
|
||||||
assert(Cfg != nullptr);
|
|
||||||
|
|
||||||
AnalysisT Analysis(*Result.Context);
|
AnalysisT Analysis(*Result.Context);
|
||||||
Environment Env;
|
Environment Env;
|
||||||
BlockStates = runDataflowAnalysis(*Cfg, Analysis, Env);
|
BlockStates = runDataflowAnalysis(CFCtx, Analysis, Env);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<
|
std::vector<
|
||||||
|
@ -141,8 +142,175 @@ TEST(DataflowAnalysisTest, NonConvergingAnalysis) {
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
EXPECT_EQ(BlockStates.size(), 4u);
|
EXPECT_EQ(BlockStates.size(), 4u);
|
||||||
EXPECT_FALSE(BlockStates[0].hasValue());
|
EXPECT_TRUE(BlockStates[0].hasValue());
|
||||||
EXPECT_TRUE(BlockStates[1].hasValue());
|
EXPECT_TRUE(BlockStates[1].hasValue());
|
||||||
EXPECT_TRUE(BlockStates[2].hasValue());
|
EXPECT_TRUE(BlockStates[2].hasValue());
|
||||||
EXPECT_TRUE(BlockStates[3].hasValue());
|
EXPECT_TRUE(BlockStates[3].hasValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FunctionCallLattice {
|
||||||
|
llvm::SmallSet<std::string, 8> CalledFunctions;
|
||||||
|
|
||||||
|
bool operator==(const FunctionCallLattice &Other) const {
|
||||||
|
return CalledFunctions == Other.CalledFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
LatticeJoinEffect join(const FunctionCallLattice &Other) {
|
||||||
|
if (Other.CalledFunctions.empty())
|
||||||
|
return LatticeJoinEffect::Unchanged;
|
||||||
|
const size_t size_before = CalledFunctions.size();
|
||||||
|
CalledFunctions.insert(Other.CalledFunctions.begin(),
|
||||||
|
Other.CalledFunctions.end());
|
||||||
|
return CalledFunctions.size() == size_before ? LatticeJoinEffect::Unchanged
|
||||||
|
: LatticeJoinEffect::Changed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &OS, const FunctionCallLattice &L) {
|
||||||
|
std::string S;
|
||||||
|
llvm::raw_string_ostream ROS(S);
|
||||||
|
llvm::interleaveComma(L.CalledFunctions, ROS);
|
||||||
|
return OS << "{" << S << "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionCallAnalysis
|
||||||
|
: public DataflowAnalysis<FunctionCallAnalysis, FunctionCallLattice> {
|
||||||
|
public:
|
||||||
|
explicit FunctionCallAnalysis(ASTContext &Context)
|
||||||
|
: DataflowAnalysis<FunctionCallAnalysis, FunctionCallLattice>(Context) {}
|
||||||
|
|
||||||
|
static FunctionCallLattice initialElement() { return {}; }
|
||||||
|
|
||||||
|
FunctionCallLattice transfer(const Stmt *S, const FunctionCallLattice &E,
|
||||||
|
Environment &Env) {
|
||||||
|
FunctionCallLattice R = E;
|
||||||
|
if (auto *C = dyn_cast<CallExpr>(S)) {
|
||||||
|
if (auto *F = dyn_cast<FunctionDecl>(C->getCalleeDecl())) {
|
||||||
|
R.CalledFunctions.insert(F->getNameInfo().getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return R;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NoreturnDestructorTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
template <typename Matcher>
|
||||||
|
void runDataflow(llvm::StringRef Code, Matcher Expectations) {
|
||||||
|
tooling::FileContentMappings FilesContents;
|
||||||
|
FilesContents.push_back(std::make_pair<std::string, std::string>(
|
||||||
|
"noreturn_destructor_test_defs.h", R"(
|
||||||
|
int foo();
|
||||||
|
|
||||||
|
class Fatal {
|
||||||
|
public:
|
||||||
|
~Fatal() __attribute__((noreturn));
|
||||||
|
int bar();
|
||||||
|
int baz();
|
||||||
|
};
|
||||||
|
|
||||||
|
class NonFatal {
|
||||||
|
public:
|
||||||
|
~NonFatal();
|
||||||
|
int bar();
|
||||||
|
};
|
||||||
|
)"));
|
||||||
|
|
||||||
|
test::checkDataflow<FunctionCallAnalysis>(
|
||||||
|
Code, "target",
|
||||||
|
[](ASTContext &C, Environment &) { return FunctionCallAnalysis(C); },
|
||||||
|
[&Expectations](
|
||||||
|
llvm::ArrayRef<std::pair<
|
||||||
|
std::string, DataflowAnalysisState<FunctionCallLattice>>>
|
||||||
|
Results,
|
||||||
|
ASTContext &) { EXPECT_THAT(Results, Expectations); },
|
||||||
|
{"-fsyntax-only", "-std=c++17"}, FilesContents);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MATCHER_P(HoldsFunctionCallLattice, m,
|
||||||
|
((negation ? "doesn't hold" : "holds") +
|
||||||
|
llvm::StringRef(" a lattice element that ") +
|
||||||
|
::testing::DescribeMatcher<FunctionCallLattice>(m, negation))
|
||||||
|
.str()) {
|
||||||
|
return ExplainMatchResult(m, arg.Lattice, result_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER_P(HasCalledFunctions, m, "") {
|
||||||
|
return ExplainMatchResult(m, arg.CalledFunctions, result_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(NoreturnDestructorTest, ConditionalOperatorBothBranchesReturn) {
|
||||||
|
std::string Code = R"(
|
||||||
|
#include "noreturn_destructor_test_defs.h"
|
||||||
|
|
||||||
|
void target(bool b) {
|
||||||
|
int value = b ? foo() : NonFatal().bar();
|
||||||
|
(void)0;
|
||||||
|
// [[p]]
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
runDataflow(Code, UnorderedElementsAre(
|
||||||
|
Pair("p", HoldsFunctionCallLattice(HasCalledFunctions(
|
||||||
|
UnorderedElementsAre("foo", "bar"))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(NoreturnDestructorTest, ConditionalOperatorLeftBranchReturns) {
|
||||||
|
std::string Code = R"(
|
||||||
|
#include "noreturn_destructor_test_defs.h"
|
||||||
|
|
||||||
|
void target(bool b) {
|
||||||
|
int value = b ? foo() : Fatal().bar();
|
||||||
|
(void)0;
|
||||||
|
// [[p]]
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
runDataflow(Code, UnorderedElementsAre(
|
||||||
|
Pair("p", HoldsFunctionCallLattice(HasCalledFunctions(
|
||||||
|
UnorderedElementsAre("foo"))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(NoreturnDestructorTest, ConditionalOperatorRightBranchReturns) {
|
||||||
|
std::string Code = R"(
|
||||||
|
#include "noreturn_destructor_test_defs.h"
|
||||||
|
|
||||||
|
void target(bool b) {
|
||||||
|
int value = b ? Fatal().bar() : foo();
|
||||||
|
(void)0;
|
||||||
|
// [[p]]
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
runDataflow(Code, UnorderedElementsAre(
|
||||||
|
Pair("p", HoldsFunctionCallLattice(HasCalledFunctions(
|
||||||
|
UnorderedElementsAre("foo"))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(NoreturnDestructorTest, ConditionalOperatorNestedBranchesDoNotReturn) {
|
||||||
|
std::string Code = R"(
|
||||||
|
#include "noreturn_destructor_test_defs.h"
|
||||||
|
|
||||||
|
void target(bool b1, bool b2) {
|
||||||
|
int value = b1 ? foo() : (b2 ? Fatal().bar() : Fatal().baz());
|
||||||
|
(void)0;
|
||||||
|
// [[p]]
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
runDataflow(Code, IsEmpty());
|
||||||
|
// FIXME: Called functions at point `p` should contain "foo".
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(NoreturnDestructorTest, ConditionalOperatorNestedBranchReturns) {
|
||||||
|
std::string Code = R"(
|
||||||
|
#include "noreturn_destructor_test_defs.h"
|
||||||
|
|
||||||
|
void target(bool b1, bool b2) {
|
||||||
|
int value = b1 ? Fatal().bar() : (b2 ? Fatal().baz() : foo());
|
||||||
|
(void)0;
|
||||||
|
// [[p]]
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
runDataflow(Code, UnorderedElementsAre(
|
||||||
|
Pair("p", HoldsFunctionCallLattice(HasCalledFunctions(
|
||||||
|
UnorderedElementsAre("baz", "foo"))))));
|
||||||
|
// FIXME: Called functions at point `p` should contain only "foo".
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue