forked from OSchip/llvm-project
[CFG] Add extra context to C++ constructor statement elements.
This patch adds a new CFGStmt sub-class, CFGConstructor, which replaces the regular CFGStmt with CXXConstructExpr in it whenever the CFG has additional information to provide regarding what sort of object is being constructed. It is useful for figuring out what memory is initialized in client of the CFG such as the Static Analyzer, which do not operate by recursive AST traversal, but instead rely on the CFG to provide all the information when they need it. Otherwise, the statement that triggers the construction and defines what memory is being initialized would normally occur after the construct-expression, and the client would need to peek to the next CFG element or use statement parent map to understand the necessary facts about the construct-expression. As a proof of concept, CFGConstructors are added for new-expressions and the respective test cases are provided to demonstrate how it works. For now, the only additional data contained in the CFGConstructor element is the "trigger statement", such as new-expression, which is the parent of the constructor. It will be significantly expanded in later commits. The additional data is organized as an auxiliary structure - the "construction context", which is allocated separately from the CFGElement. Differential Revision: https://reviews.llvm.org/D42672 llvm-svn: 324668
This commit is contained in:
parent
9c2f3c4852
commit
41ffb30716
|
@ -439,6 +439,7 @@ public:
|
||||||
bool synthesizeBodies = false,
|
bool synthesizeBodies = false,
|
||||||
bool addStaticInitBranches = false,
|
bool addStaticInitBranches = false,
|
||||||
bool addCXXNewAllocator = true,
|
bool addCXXNewAllocator = true,
|
||||||
|
bool addRichCXXConstructors = true,
|
||||||
CodeInjector *injector = nullptr);
|
CodeInjector *injector = nullptr);
|
||||||
|
|
||||||
AnalysisDeclContext *getContext(const Decl *D);
|
AnalysisDeclContext *getContext(const Decl *D);
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#ifndef LLVM_CLANG_ANALYSIS_CFG_H
|
#ifndef LLVM_CLANG_ANALYSIS_CFG_H
|
||||||
#define LLVM_CLANG_ANALYSIS_CFG_H
|
#define LLVM_CLANG_ANALYSIS_CFG_H
|
||||||
|
|
||||||
#include "clang/AST/Stmt.h"
|
#include "clang/AST/ExprCXX.h"
|
||||||
#include "clang/Analysis/Support/BumpVector.h"
|
#include "clang/Analysis/Support/BumpVector.h"
|
||||||
#include "clang/Basic/LLVM.h"
|
#include "clang/Basic/LLVM.h"
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
@ -55,11 +55,15 @@ class CFGElement {
|
||||||
public:
|
public:
|
||||||
enum Kind {
|
enum Kind {
|
||||||
// main kind
|
// main kind
|
||||||
Statement,
|
|
||||||
Initializer,
|
Initializer,
|
||||||
NewAllocator,
|
NewAllocator,
|
||||||
LifetimeEnds,
|
LifetimeEnds,
|
||||||
LoopExit,
|
LoopExit,
|
||||||
|
// stmt kind
|
||||||
|
Statement,
|
||||||
|
Constructor,
|
||||||
|
STMT_BEGIN = Statement,
|
||||||
|
STMT_END = Constructor,
|
||||||
// dtor kind
|
// dtor kind
|
||||||
AutomaticObjectDtor,
|
AutomaticObjectDtor,
|
||||||
DeleteDtor,
|
DeleteDtor,
|
||||||
|
@ -117,7 +121,9 @@ public:
|
||||||
|
|
||||||
class CFGStmt : public CFGElement {
|
class CFGStmt : public CFGElement {
|
||||||
public:
|
public:
|
||||||
CFGStmt(Stmt *S) : CFGElement(Statement, S) {}
|
explicit CFGStmt(Stmt *S, Kind K = Statement) : CFGElement(K, S) {
|
||||||
|
assert(isKind(*this));
|
||||||
|
}
|
||||||
|
|
||||||
const Stmt *getStmt() const {
|
const Stmt *getStmt() const {
|
||||||
return static_cast<const Stmt *>(Data1.getPointer());
|
return static_cast<const Stmt *>(Data1.getPointer());
|
||||||
|
@ -126,10 +132,66 @@ public:
|
||||||
private:
|
private:
|
||||||
friend class CFGElement;
|
friend class CFGElement;
|
||||||
|
|
||||||
|
static bool isKind(const CFGElement &E) {
|
||||||
|
return E.getKind() >= STMT_BEGIN && E.getKind() <= STMT_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
CFGStmt() = default;
|
CFGStmt() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is bulky data for CFGConstructor which would not fit into the
|
||||||
|
// CFGElement's room (pair of pointers). Contains the information
|
||||||
|
// necessary to express what memory is being initialized by
|
||||||
|
// the construction.
|
||||||
|
class ConstructionContext {
|
||||||
|
// The construction site - the statement that triggered the construction
|
||||||
|
// for one of its parts. For instance, stack variable declaration statement
|
||||||
|
// triggers construction of itself or its elements if it's an array,
|
||||||
|
// new-expression triggers construction of the newly allocated object(s).
|
||||||
|
Stmt *Trigger = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ConstructionContext() = default;
|
||||||
|
ConstructionContext(Stmt *Trigger) : Trigger(Trigger) {}
|
||||||
|
|
||||||
|
bool isNull() const { return Trigger == nullptr; }
|
||||||
|
|
||||||
|
const Stmt *getTriggerStmt() const { return Trigger; }
|
||||||
|
|
||||||
|
const ConstructionContext *getPersistentCopy(BumpVectorContext &C) const {
|
||||||
|
ConstructionContext *CC = C.getAllocator().Allocate<ConstructionContext>();
|
||||||
|
*CC = *this;
|
||||||
|
return CC;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// CFGConstructor - Represents C++ constructor call. Maintains information
|
||||||
|
/// necessary to figure out what memory is being initialized by the
|
||||||
|
/// constructor expression. For now this is only used by the analyzer's CFG.
|
||||||
|
class CFGConstructor : public CFGStmt {
|
||||||
|
public:
|
||||||
|
explicit CFGConstructor(CXXConstructExpr *CE, const ConstructionContext *C)
|
||||||
|
: CFGStmt(CE, Constructor) {
|
||||||
|
assert(!C->isNull());
|
||||||
|
Data2.setPointer(const_cast<ConstructionContext *>(C));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConstructionContext *getConstructionContext() const {
|
||||||
|
return static_cast<ConstructionContext *>(Data2.getPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
const Stmt *getTriggerStmt() const {
|
||||||
|
return getConstructionContext()->getTriggerStmt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class CFGElement;
|
||||||
|
|
||||||
|
CFGConstructor() = default;
|
||||||
|
|
||||||
static bool isKind(const CFGElement &E) {
|
static bool isKind(const CFGElement &E) {
|
||||||
return E.getKind() == Statement;
|
return E.getKind() == Constructor;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,7 +199,7 @@ private:
|
||||||
/// constructor's initialization list.
|
/// constructor's initialization list.
|
||||||
class CFGInitializer : public CFGElement {
|
class CFGInitializer : public CFGElement {
|
||||||
public:
|
public:
|
||||||
CFGInitializer(CXXCtorInitializer *initializer)
|
explicit CFGInitializer(CXXCtorInitializer *initializer)
|
||||||
: CFGElement(Initializer, initializer) {}
|
: CFGElement(Initializer, initializer) {}
|
||||||
|
|
||||||
CXXCtorInitializer* getInitializer() const {
|
CXXCtorInitializer* getInitializer() const {
|
||||||
|
@ -747,6 +809,11 @@ public:
|
||||||
Elements.push_back(CFGStmt(statement), C);
|
Elements.push_back(CFGStmt(statement), C);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void appendConstructor(CXXConstructExpr *CE, const ConstructionContext &CC,
|
||||||
|
BumpVectorContext &C) {
|
||||||
|
Elements.push_back(CFGConstructor(CE, CC.getPersistentCopy(C)), C);
|
||||||
|
}
|
||||||
|
|
||||||
void appendInitializer(CXXCtorInitializer *initializer,
|
void appendInitializer(CXXCtorInitializer *initializer,
|
||||||
BumpVectorContext &C) {
|
BumpVectorContext &C) {
|
||||||
Elements.push_back(CFGInitializer(initializer), C);
|
Elements.push_back(CFGInitializer(initializer), C);
|
||||||
|
@ -855,6 +922,7 @@ public:
|
||||||
bool AddStaticInitBranches = false;
|
bool AddStaticInitBranches = false;
|
||||||
bool AddCXXNewAllocator = false;
|
bool AddCXXNewAllocator = false;
|
||||||
bool AddCXXDefaultInitExprInCtors = false;
|
bool AddCXXDefaultInitExprInCtors = false;
|
||||||
|
bool AddRichCXXConstructors = false;
|
||||||
|
|
||||||
BuildOptions() = default;
|
BuildOptions() = default;
|
||||||
|
|
||||||
|
|
|
@ -231,6 +231,9 @@ private:
|
||||||
/// \sa IncludeLoopExitInCFG
|
/// \sa IncludeLoopExitInCFG
|
||||||
Optional<bool> IncludeLoopExitInCFG;
|
Optional<bool> IncludeLoopExitInCFG;
|
||||||
|
|
||||||
|
/// \sa IncludeRichConstructorsInCFG
|
||||||
|
Optional<bool> IncludeRichConstructorsInCFG;
|
||||||
|
|
||||||
/// \sa mayInlineCXXStandardLibrary
|
/// \sa mayInlineCXXStandardLibrary
|
||||||
Optional<bool> InlineCXXStandardLibrary;
|
Optional<bool> InlineCXXStandardLibrary;
|
||||||
|
|
||||||
|
@ -444,6 +447,13 @@ public:
|
||||||
/// the values "true" and "false".
|
/// the values "true" and "false".
|
||||||
bool includeLoopExitInCFG();
|
bool includeLoopExitInCFG();
|
||||||
|
|
||||||
|
/// Returns whether or not construction site information should be included
|
||||||
|
/// in the CFG C++ constructor elements.
|
||||||
|
///
|
||||||
|
/// This is controlled by the 'cfg-rich-constructors' config options,
|
||||||
|
/// which accepts the values "true" and "false".
|
||||||
|
bool includeRichConstructorsInCFG();
|
||||||
|
|
||||||
/// Returns whether or not C++ standard library functions may be considered
|
/// Returns whether or not C++ standard library functions may be considered
|
||||||
/// for inlining.
|
/// for inlining.
|
||||||
///
|
///
|
||||||
|
|
|
@ -194,7 +194,7 @@ public:
|
||||||
void processCFGElement(const CFGElement E, ExplodedNode *Pred,
|
void processCFGElement(const CFGElement E, ExplodedNode *Pred,
|
||||||
unsigned StmtIdx, NodeBuilderContext *Ctx) override;
|
unsigned StmtIdx, NodeBuilderContext *Ctx) override;
|
||||||
|
|
||||||
void ProcessStmt(const CFGStmt S, ExplodedNode *Pred);
|
void ProcessStmt(const Stmt *S, ExplodedNode *Pred);
|
||||||
|
|
||||||
void ProcessLoopExit(const Stmt* S, ExplodedNode *Pred);
|
void ProcessLoopExit(const Stmt* S, ExplodedNode *Pred);
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,8 @@ AnalysisDeclContextManager::AnalysisDeclContextManager(
|
||||||
ASTContext &ASTCtx, bool useUnoptimizedCFG, bool addImplicitDtors,
|
ASTContext &ASTCtx, bool useUnoptimizedCFG, bool addImplicitDtors,
|
||||||
bool addInitializers, bool addTemporaryDtors, bool addLifetime,
|
bool addInitializers, bool addTemporaryDtors, bool addLifetime,
|
||||||
bool addLoopExit, bool synthesizeBodies, bool addStaticInitBranch,
|
bool addLoopExit, bool synthesizeBodies, bool addStaticInitBranch,
|
||||||
bool addCXXNewAllocator, CodeInjector *injector)
|
bool addCXXNewAllocator, bool addRichCXXConstructors,
|
||||||
|
CodeInjector *injector)
|
||||||
: Injector(injector), FunctionBodyFarm(ASTCtx, injector),
|
: Injector(injector), FunctionBodyFarm(ASTCtx, injector),
|
||||||
SynthesizeBodies(synthesizeBodies) {
|
SynthesizeBodies(synthesizeBodies) {
|
||||||
cfgBuildOptions.PruneTriviallyFalseEdges = !useUnoptimizedCFG;
|
cfgBuildOptions.PruneTriviallyFalseEdges = !useUnoptimizedCFG;
|
||||||
|
@ -78,6 +79,7 @@ AnalysisDeclContextManager::AnalysisDeclContextManager(
|
||||||
cfgBuildOptions.AddLoopExit = addLoopExit;
|
cfgBuildOptions.AddLoopExit = addLoopExit;
|
||||||
cfgBuildOptions.AddStaticInitBranches = addStaticInitBranch;
|
cfgBuildOptions.AddStaticInitBranches = addStaticInitBranch;
|
||||||
cfgBuildOptions.AddCXXNewAllocator = addCXXNewAllocator;
|
cfgBuildOptions.AddCXXNewAllocator = addCXXNewAllocator;
|
||||||
|
cfgBuildOptions.AddRichCXXConstructors = addRichCXXConstructors;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalysisDeclContextManager::clear() { Contexts.clear(); }
|
void AnalysisDeclContextManager::clear() { Contexts.clear(); }
|
||||||
|
|
|
@ -472,6 +472,11 @@ class CFGBuilder {
|
||||||
using LabelSetTy = llvm::SmallSetVector<LabelDecl *, 8>;
|
using LabelSetTy = llvm::SmallSetVector<LabelDecl *, 8>;
|
||||||
LabelSetTy AddressTakenLabels;
|
LabelSetTy AddressTakenLabels;
|
||||||
|
|
||||||
|
// Information about the currently visited C++ object construction site.
|
||||||
|
// This is set in the construction trigger and read when the constructor
|
||||||
|
// itself is being visited.
|
||||||
|
ConstructionContext CurrentConstructionContext = {};
|
||||||
|
|
||||||
bool badCFG = false;
|
bool badCFG = false;
|
||||||
const CFG::BuildOptions &BuildOpts;
|
const CFG::BuildOptions &BuildOpts;
|
||||||
|
|
||||||
|
@ -643,6 +648,18 @@ private:
|
||||||
return Block;
|
return Block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan the child statement \p Child to find the constructor that might
|
||||||
|
// have been directly triggered by the current node, \p Trigger. If such
|
||||||
|
// constructor has been found, set current construction context to point
|
||||||
|
// to the trigger statement. The construction context will be unset once
|
||||||
|
// it is consumed when the CFG building procedure processes the
|
||||||
|
// construct-expression and adds the respective CFGConstructor element.
|
||||||
|
void EnterConstructionContextIfNecessary(Stmt *Trigger, Stmt *Child);
|
||||||
|
// Unset the construction context after consuming it. This is done immediately
|
||||||
|
// after adding the CFGConstructor element, so there's no need to
|
||||||
|
// do this manually in every Visit... function.
|
||||||
|
void ExitConstructionContext();
|
||||||
|
|
||||||
void autoCreateBlock() { if (!Block) Block = createBlock(); }
|
void autoCreateBlock() { if (!Block) Block = createBlock(); }
|
||||||
CFGBlock *createBlock(bool add_successor = true);
|
CFGBlock *createBlock(bool add_successor = true);
|
||||||
CFGBlock *createNoReturnBlock();
|
CFGBlock *createNoReturnBlock();
|
||||||
|
@ -682,6 +699,20 @@ private:
|
||||||
B->appendStmt(const_cast<Stmt*>(S), cfg->getBumpVectorContext());
|
B->appendStmt(const_cast<Stmt*>(S), cfg->getBumpVectorContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void appendConstructor(CFGBlock *B, CXXConstructExpr *CE) {
|
||||||
|
if (BuildOpts.AddRichCXXConstructors) {
|
||||||
|
if (!CurrentConstructionContext.isNull()) {
|
||||||
|
B->appendConstructor(CE, CurrentConstructionContext,
|
||||||
|
cfg->getBumpVectorContext());
|
||||||
|
ExitConstructionContext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid construction context found. Fall back to statement.
|
||||||
|
B->appendStmt(CE, cfg->getBumpVectorContext());
|
||||||
|
}
|
||||||
|
|
||||||
void appendInitializer(CFGBlock *B, CXXCtorInitializer *I) {
|
void appendInitializer(CFGBlock *B, CXXCtorInitializer *I) {
|
||||||
B->appendInitializer(I, cfg->getBumpVectorContext());
|
B->appendInitializer(I, cfg->getBumpVectorContext());
|
||||||
}
|
}
|
||||||
|
@ -1116,6 +1147,26 @@ static const VariableArrayType *FindVA(const Type *t) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CFGBuilder::EnterConstructionContextIfNecessary(Stmt *Trigger,
|
||||||
|
Stmt *Child) {
|
||||||
|
if (!BuildOpts.AddRichCXXConstructors)
|
||||||
|
return;
|
||||||
|
if (!Child)
|
||||||
|
return;
|
||||||
|
if (auto *Constructor = dyn_cast<CXXConstructExpr>(Child)) {
|
||||||
|
assert(CurrentConstructionContext.isNull() &&
|
||||||
|
"Already within a construction context!");
|
||||||
|
CurrentConstructionContext = ConstructionContext(Trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFGBuilder::ExitConstructionContext() {
|
||||||
|
assert(!CurrentConstructionContext.isNull() &&
|
||||||
|
"Cannot exit construction context without the context!");
|
||||||
|
CurrentConstructionContext = ConstructionContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// BuildCFG - Constructs a CFG from an AST (a Stmt*). The AST can represent an
|
/// BuildCFG - Constructs a CFG from an AST (a Stmt*). The AST can represent an
|
||||||
/// arbitrary statement. Examples include a single expression or a function
|
/// arbitrary statement. Examples include a single expression or a function
|
||||||
/// body (compound statement). The ownership of the returned CFG is
|
/// body (compound statement). The ownership of the returned CFG is
|
||||||
|
@ -3872,7 +3923,7 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E,
|
||||||
CFGBlock *CFGBuilder::VisitCXXConstructExpr(CXXConstructExpr *C,
|
CFGBlock *CFGBuilder::VisitCXXConstructExpr(CXXConstructExpr *C,
|
||||||
AddStmtChoice asc) {
|
AddStmtChoice asc) {
|
||||||
autoCreateBlock();
|
autoCreateBlock();
|
||||||
appendStmt(Block, C);
|
appendConstructor(Block, C);
|
||||||
|
|
||||||
return VisitChildren(C);
|
return VisitChildren(C);
|
||||||
}
|
}
|
||||||
|
@ -3882,15 +3933,22 @@ CFGBlock *CFGBuilder::VisitCXXNewExpr(CXXNewExpr *NE,
|
||||||
autoCreateBlock();
|
autoCreateBlock();
|
||||||
appendStmt(Block, NE);
|
appendStmt(Block, NE);
|
||||||
|
|
||||||
|
EnterConstructionContextIfNecessary(
|
||||||
|
NE, const_cast<CXXConstructExpr *>(NE->getConstructExpr()));
|
||||||
|
|
||||||
if (NE->getInitializer())
|
if (NE->getInitializer())
|
||||||
Block = Visit(NE->getInitializer());
|
Block = Visit(NE->getInitializer());
|
||||||
|
|
||||||
if (BuildOpts.AddCXXNewAllocator)
|
if (BuildOpts.AddCXXNewAllocator)
|
||||||
appendNewAllocator(Block, NE);
|
appendNewAllocator(Block, NE);
|
||||||
|
|
||||||
if (NE->isArray())
|
if (NE->isArray())
|
||||||
Block = Visit(NE->getArraySize());
|
Block = Visit(NE->getArraySize());
|
||||||
|
|
||||||
for (CXXNewExpr::arg_iterator I = NE->placement_arg_begin(),
|
for (CXXNewExpr::arg_iterator I = NE->placement_arg_begin(),
|
||||||
E = NE->placement_arg_end(); I != E; ++I)
|
E = NE->placement_arg_end(); I != E; ++I)
|
||||||
Block = Visit(*I);
|
Block = Visit(*I);
|
||||||
|
|
||||||
return Block;
|
return Block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4210,11 +4268,12 @@ std::unique_ptr<CFG> CFG::buildCFG(const Decl *D, Stmt *Statement,
|
||||||
const CXXDestructorDecl *
|
const CXXDestructorDecl *
|
||||||
CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const {
|
CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const {
|
||||||
switch (getKind()) {
|
switch (getKind()) {
|
||||||
case CFGElement::Statement:
|
|
||||||
case CFGElement::Initializer:
|
case CFGElement::Initializer:
|
||||||
case CFGElement::NewAllocator:
|
case CFGElement::NewAllocator:
|
||||||
case CFGElement::LoopExit:
|
case CFGElement::LoopExit:
|
||||||
case CFGElement::LifetimeEnds:
|
case CFGElement::LifetimeEnds:
|
||||||
|
case CFGElement::Statement:
|
||||||
|
case CFGElement::Constructor:
|
||||||
llvm_unreachable("getDestructorDecl should only be used with "
|
llvm_unreachable("getDestructorDecl should only be used with "
|
||||||
"ImplicitDtors");
|
"ImplicitDtors");
|
||||||
case CFGElement::AutomaticObjectDtor: {
|
case CFGElement::AutomaticObjectDtor: {
|
||||||
|
@ -4343,8 +4402,8 @@ public:
|
||||||
|
|
||||||
switch (stmt->getStmtClass()) {
|
switch (stmt->getStmtClass()) {
|
||||||
case Stmt::DeclStmtClass:
|
case Stmt::DeclStmtClass:
|
||||||
DeclMap[cast<DeclStmt>(stmt)->getSingleDecl()] = P;
|
DeclMap[cast<DeclStmt>(stmt)->getSingleDecl()] = P;
|
||||||
break;
|
break;
|
||||||
case Stmt::IfStmtClass: {
|
case Stmt::IfStmtClass: {
|
||||||
const VarDecl *var = cast<IfStmt>(stmt)->getConditionVariable();
|
const VarDecl *var = cast<IfStmt>(stmt)->getConditionVariable();
|
||||||
if (var)
|
if (var)
|
||||||
|
@ -4575,14 +4634,19 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper,
|
||||||
|
|
||||||
if (isa<CXXOperatorCallExpr>(S)) {
|
if (isa<CXXOperatorCallExpr>(S)) {
|
||||||
OS << " (OperatorCall)";
|
OS << " (OperatorCall)";
|
||||||
}
|
} else if (isa<CXXBindTemporaryExpr>(S)) {
|
||||||
else if (isa<CXXBindTemporaryExpr>(S)) {
|
|
||||||
OS << " (BindTemporary)";
|
OS << " (BindTemporary)";
|
||||||
}
|
} else if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(S)) {
|
||||||
else if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(S)) {
|
OS << " (CXXConstructExpr, ";
|
||||||
OS << " (CXXConstructExpr, " << CCE->getType().getAsString() << ")";
|
if (Optional<CFGConstructor> CE = E.getAs<CFGConstructor>()) {
|
||||||
}
|
if (const Stmt *S = CE->getTriggerStmt())
|
||||||
else if (const CastExpr *CE = dyn_cast<CastExpr>(S)) {
|
Helper.handledStmt((const_cast<Stmt *>(S)), OS);
|
||||||
|
else
|
||||||
|
llvm_unreachable("Unexpected trigger kind!");
|
||||||
|
OS << ", ";
|
||||||
|
}
|
||||||
|
OS << CCE->getType().getAsString() << ")";
|
||||||
|
} else if (const CastExpr *CE = dyn_cast<CastExpr>(S)) {
|
||||||
OS << " (" << CE->getStmtClassName() << ", "
|
OS << " (" << CE->getStmtClassName() << ", "
|
||||||
<< CE->getCastKindName()
|
<< CE->getCastKindName()
|
||||||
<< ", " << CE->getType().getAsString()
|
<< ", " << CE->getType().getAsString()
|
||||||
|
|
|
@ -29,6 +29,7 @@ AnalysisManager::AnalysisManager(
|
||||||
Options.shouldSynthesizeBodies(),
|
Options.shouldSynthesizeBodies(),
|
||||||
Options.shouldConditionalizeStaticInitializers(),
|
Options.shouldConditionalizeStaticInitializers(),
|
||||||
/*addCXXNewAllocator=*/true,
|
/*addCXXNewAllocator=*/true,
|
||||||
|
Options.includeRichConstructorsInCFG(),
|
||||||
injector),
|
injector),
|
||||||
Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC),
|
Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC),
|
||||||
CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr),
|
CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr),
|
||||||
|
|
|
@ -204,7 +204,13 @@ bool AnalyzerOptions::includeLifetimeInCFG() {
|
||||||
|
|
||||||
bool AnalyzerOptions::includeLoopExitInCFG() {
|
bool AnalyzerOptions::includeLoopExitInCFG() {
|
||||||
return getBooleanOption(IncludeLoopExitInCFG, "cfg-loopexit",
|
return getBooleanOption(IncludeLoopExitInCFG, "cfg-loopexit",
|
||||||
/* Default = */ false);
|
/* Default = */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalyzerOptions::includeRichConstructorsInCFG() {
|
||||||
|
return getBooleanOption(IncludeRichConstructorsInCFG,
|
||||||
|
"cfg-rich-constructors",
|
||||||
|
/* Default = */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AnalyzerOptions::mayInlineCXXStandardLibrary() {
|
bool AnalyzerOptions::mayInlineCXXStandardLibrary() {
|
||||||
|
|
|
@ -454,10 +454,11 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred,
|
||||||
|
|
||||||
switch (E.getKind()) {
|
switch (E.getKind()) {
|
||||||
case CFGElement::Statement:
|
case CFGElement::Statement:
|
||||||
ProcessStmt(const_cast<Stmt*>(E.castAs<CFGStmt>().getStmt()), Pred);
|
case CFGElement::Constructor:
|
||||||
|
ProcessStmt(E.castAs<CFGStmt>().getStmt(), Pred);
|
||||||
return;
|
return;
|
||||||
case CFGElement::Initializer:
|
case CFGElement::Initializer:
|
||||||
ProcessInitializer(E.castAs<CFGInitializer>().getInitializer(), Pred);
|
ProcessInitializer(E.castAs<CFGInitializer>(), Pred);
|
||||||
return;
|
return;
|
||||||
case CFGElement::NewAllocator:
|
case CFGElement::NewAllocator:
|
||||||
ProcessNewAllocator(E.castAs<CFGNewAllocator>().getAllocatorExpr(),
|
ProcessNewAllocator(E.castAs<CFGNewAllocator>().getAllocatorExpr(),
|
||||||
|
@ -479,7 +480,7 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred,
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool shouldRemoveDeadBindings(AnalysisManager &AMgr,
|
static bool shouldRemoveDeadBindings(AnalysisManager &AMgr,
|
||||||
const CFGStmt S,
|
const Stmt *S,
|
||||||
const ExplodedNode *Pred,
|
const ExplodedNode *Pred,
|
||||||
const LocationContext *LC) {
|
const LocationContext *LC) {
|
||||||
|
|
||||||
|
@ -492,17 +493,17 @@ static bool shouldRemoveDeadBindings(AnalysisManager &AMgr,
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Is this on a non-expression?
|
// Is this on a non-expression?
|
||||||
if (!isa<Expr>(S.getStmt()))
|
if (!isa<Expr>(S))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Run before processing a call.
|
// Run before processing a call.
|
||||||
if (CallEvent::isCallStmt(S.getStmt()))
|
if (CallEvent::isCallStmt(S))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Is this an expression that is consumed by another expression? If so,
|
// Is this an expression that is consumed by another expression? If so,
|
||||||
// postpone cleaning out the state.
|
// postpone cleaning out the state.
|
||||||
ParentMap &PM = LC->getAnalysisDeclContext()->getParentMap();
|
ParentMap &PM = LC->getAnalysisDeclContext()->getParentMap();
|
||||||
return !PM.isConsumedExpr(cast<Expr>(S.getStmt()));
|
return !PM.isConsumedExpr(cast<Expr>(S));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
|
void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
|
||||||
|
@ -594,20 +595,20 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprEngine::ProcessStmt(const CFGStmt S,
|
void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) {
|
||||||
ExplodedNode *Pred) {
|
|
||||||
// Reclaim any unnecessary nodes in the ExplodedGraph.
|
// Reclaim any unnecessary nodes in the ExplodedGraph.
|
||||||
G.reclaimRecentlyAllocatedNodes();
|
G.reclaimRecentlyAllocatedNodes();
|
||||||
|
|
||||||
const Stmt *currStmt = S.getStmt();
|
|
||||||
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
|
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
|
||||||
currStmt->getLocStart(),
|
currStmt->getLocStart(),
|
||||||
"Error evaluating statement");
|
"Error evaluating statement");
|
||||||
|
|
||||||
// Remove dead bindings and symbols.
|
// Remove dead bindings and symbols.
|
||||||
ExplodedNodeSet CleanedStates;
|
ExplodedNodeSet CleanedStates;
|
||||||
if (shouldRemoveDeadBindings(AMgr, S, Pred, Pred->getLocationContext())){
|
if (shouldRemoveDeadBindings(AMgr, currStmt, Pred,
|
||||||
removeDead(Pred, CleanedStates, currStmt, Pred->getLocationContext());
|
Pred->getLocationContext())) {
|
||||||
|
removeDead(Pred, CleanedStates, currStmt,
|
||||||
|
Pred->getLocationContext());
|
||||||
} else
|
} else
|
||||||
CleanedStates.Add(Pred);
|
CleanedStates.Add(Pred);
|
||||||
|
|
||||||
|
|
|
@ -551,6 +551,7 @@ getLocationForCaller(const StackFrameContext *SFC,
|
||||||
|
|
||||||
switch (Source.getKind()) {
|
switch (Source.getKind()) {
|
||||||
case CFGElement::Statement:
|
case CFGElement::Statement:
|
||||||
|
case CFGElement::Constructor:
|
||||||
return PathDiagnosticLocation(Source.castAs<CFGStmt>().getStmt(),
|
return PathDiagnosticLocation(Source.castAs<CFGStmt>().getStmt(),
|
||||||
SM, CallerCtx);
|
SM, CallerCtx);
|
||||||
case CFGElement::Initializer: {
|
case CFGElement::Initializer: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ void foo() {
|
||||||
// CHECK-NEXT: cfg-implicit-dtors = true
|
// CHECK-NEXT: cfg-implicit-dtors = true
|
||||||
// CHECK-NEXT: cfg-lifetime = false
|
// CHECK-NEXT: cfg-lifetime = false
|
||||||
// CHECK-NEXT: cfg-loopexit = false
|
// CHECK-NEXT: cfg-loopexit = false
|
||||||
|
// CHECK-NEXT: cfg-rich-constructors = true
|
||||||
// CHECK-NEXT: cfg-temporary-dtors = false
|
// CHECK-NEXT: cfg-temporary-dtors = false
|
||||||
// CHECK-NEXT: exploration_strategy = dfs
|
// CHECK-NEXT: exploration_strategy = dfs
|
||||||
// CHECK-NEXT: faux-bodies = true
|
// CHECK-NEXT: faux-bodies = true
|
||||||
|
@ -32,4 +33,4 @@ void foo() {
|
||||||
// CHECK-NEXT: unroll-loops = false
|
// CHECK-NEXT: unroll-loops = false
|
||||||
// CHECK-NEXT: widen-loops = false
|
// CHECK-NEXT: widen-loops = false
|
||||||
// CHECK-NEXT: [stats]
|
// CHECK-NEXT: [stats]
|
||||||
// CHECK-NEXT: num-entries = 20
|
// CHECK-NEXT: num-entries = 21
|
||||||
|
|
|
@ -26,6 +26,7 @@ public:
|
||||||
// CHECK-NEXT: cfg-implicit-dtors = true
|
// CHECK-NEXT: cfg-implicit-dtors = true
|
||||||
// CHECK-NEXT: cfg-lifetime = false
|
// CHECK-NEXT: cfg-lifetime = false
|
||||||
// CHECK-NEXT: cfg-loopexit = false
|
// CHECK-NEXT: cfg-loopexit = false
|
||||||
|
// CHECK-NEXT: cfg-rich-constructors = true
|
||||||
// CHECK-NEXT: cfg-temporary-dtors = false
|
// CHECK-NEXT: cfg-temporary-dtors = false
|
||||||
// CHECK-NEXT: exploration_strategy = dfs
|
// CHECK-NEXT: exploration_strategy = dfs
|
||||||
// CHECK-NEXT: faux-bodies = true
|
// CHECK-NEXT: faux-bodies = true
|
||||||
|
@ -43,4 +44,4 @@ public:
|
||||||
// CHECK-NEXT: unroll-loops = false
|
// CHECK-NEXT: unroll-loops = false
|
||||||
// CHECK-NEXT: widen-loops = false
|
// CHECK-NEXT: widen-loops = false
|
||||||
// CHECK-NEXT: [stats]
|
// CHECK-NEXT: [stats]
|
||||||
// CHECK-NEXT: num-entries = 25
|
// CHECK-NEXT: num-entries = 26
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 %s > %t 2>&1
|
||||||
|
// RUN: FileCheck --input-file=%t %s
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public:
|
||||||
|
C();
|
||||||
|
C(C *);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef __typeof(sizeof(int)) size_t;
|
||||||
|
void *operator new(size_t size, void *placement);
|
||||||
|
|
||||||
|
namespace operator_new {
|
||||||
|
|
||||||
|
// CHECK: void operatorNewWithConstructor()
|
||||||
|
// CHECK: 1: CFGNewAllocator(C *)
|
||||||
|
// CHECK-NEXT: 2: (CXXConstructExpr, [B1.3], class C)
|
||||||
|
// CHECK-NEXT: 3: new C([B1.2])
|
||||||
|
void operatorNewWithConstructor() {
|
||||||
|
new C();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: void operatorNewWithConstructorWithOperatorNewWithContstructor()
|
||||||
|
// CHECK: 1: CFGNewAllocator(C *)
|
||||||
|
// CHECK-NEXT: 2: CFGNewAllocator(C *)
|
||||||
|
// CHECK-NEXT: 3: (CXXConstructExpr, [B1.4], class C)
|
||||||
|
// CHECK-NEXT: 4: new C([B1.3])
|
||||||
|
// CHECK-NEXT: 5: [B1.4] (CXXConstructExpr, [B1.6], class C)
|
||||||
|
// CHECK-NEXT: 6: new C([B1.5])
|
||||||
|
void operatorNewWithConstructorWithOperatorNewWithContstructor() {
|
||||||
|
new C(new C());
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: void operatorPlacementNewWithConstructorWithinPlacementArgument()
|
||||||
|
// CHECK: 1: CFGNewAllocator(C *)
|
||||||
|
// CHECK-NEXT: 2: (CXXConstructExpr, [B1.3], class C)
|
||||||
|
// CHECK-NEXT: 3: new C([B1.2])
|
||||||
|
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
|
||||||
|
// CHECK-NEXT: 5: CFGNewAllocator(C *)
|
||||||
|
// CHECK-NEXT: 6: (CXXConstructExpr, [B1.7], class C)
|
||||||
|
// CHECK-NEXT: 7: new ([B1.4]) C([B1.6])
|
||||||
|
void operatorPlacementNewWithConstructorWithinPlacementArgument() {
|
||||||
|
new (new C()) C();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace operator_new
|
|
@ -1,5 +1,16 @@
|
||||||
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 %s > %t 2>&1
|
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 -analyzer-config cfg-rich-constructors=false %s > %t 2>&1
|
||||||
// RUN: FileCheck --input-file=%t %s
|
// RUN: FileCheck --input-file=%t -check-prefixes=CHECK,WARNINGS %s
|
||||||
|
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 -analyzer-config cfg-rich-constructors=true %s > %t 2>&1
|
||||||
|
// RUN: FileCheck --input-file=%t -check-prefixes=CHECK,ANALYZER %s
|
||||||
|
|
||||||
|
// This file tests how we construct two different flavors of the Clang CFG -
|
||||||
|
// the CFG used by the Sema analysis-based warnings and the CFG used by the
|
||||||
|
// static analyzer. The difference in the behavior is checked via FileCheck
|
||||||
|
// prefixes (WARNINGS and ANALYZER respectively). When introducing new analyzer
|
||||||
|
// flags, no new run lines should be added - just these flags would go to the
|
||||||
|
// respective line depending on where is it turned on and where is it turned
|
||||||
|
// off. Feel free to add tests that test only one of the CFG flavors if you're
|
||||||
|
// not sure how the other flavor is supposed to work in your case.
|
||||||
|
|
||||||
// CHECK-LABEL: void checkWrap(int i)
|
// CHECK-LABEL: void checkWrap(int i)
|
||||||
// CHECK: ENTRY
|
// CHECK: ENTRY
|
||||||
|
@ -116,7 +127,8 @@ public:
|
||||||
// CHECK-NEXT: Succs (1): B1
|
// CHECK-NEXT: Succs (1): B1
|
||||||
// CHECK: [B1]
|
// CHECK: [B1]
|
||||||
// CHECK-NEXT: 1: CFGNewAllocator(A *)
|
// CHECK-NEXT: 1: CFGNewAllocator(A *)
|
||||||
// CHECK-NEXT: 2: (CXXConstructExpr, class A)
|
// WARNINGS-NEXT: 2: (CXXConstructExpr, class A)
|
||||||
|
// ANALYZER-NEXT: 2: (CXXConstructExpr, [B1.3], class A)
|
||||||
// CHECK-NEXT: 3: new A([B1.2])
|
// CHECK-NEXT: 3: new A([B1.2])
|
||||||
// CHECK-NEXT: 4: A *a = new A();
|
// CHECK-NEXT: 4: A *a = new A();
|
||||||
// CHECK-NEXT: 5: a
|
// CHECK-NEXT: 5: a
|
||||||
|
@ -138,7 +150,8 @@ void test_deletedtor() {
|
||||||
// CHECK: [B1]
|
// CHECK: [B1]
|
||||||
// CHECK-NEXT: 1: 5
|
// CHECK-NEXT: 1: 5
|
||||||
// CHECK-NEXT: 2: CFGNewAllocator(A *)
|
// CHECK-NEXT: 2: CFGNewAllocator(A *)
|
||||||
// CHECK-NEXT: 3: (CXXConstructExpr, class A [5])
|
// WARNINGS-NEXT: 3: (CXXConstructExpr, class A [5])
|
||||||
|
// ANALYZER-NEXT: 3: (CXXConstructExpr, [B1.4], class A [5])
|
||||||
// CHECK-NEXT: 4: new A {{\[\[}}B1.1]]
|
// CHECK-NEXT: 4: new A {{\[\[}}B1.1]]
|
||||||
// CHECK-NEXT: 5: A *a = new A [5];
|
// CHECK-NEXT: 5: A *a = new A [5];
|
||||||
// CHECK-NEXT: 6: a
|
// CHECK-NEXT: 6: a
|
||||||
|
@ -331,7 +344,8 @@ int test_enum_with_extension_default(enum MyEnum value) {
|
||||||
// CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, ArrayToPointerDecay, int *)
|
// CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, ArrayToPointerDecay, int *)
|
||||||
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
|
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
|
||||||
// CHECK-NEXT: 5: CFGNewAllocator(MyClass *)
|
// CHECK-NEXT: 5: CFGNewAllocator(MyClass *)
|
||||||
// CHECK-NEXT: 6: (CXXConstructExpr, class MyClass)
|
// WARNINGS-NEXT: 6: (CXXConstructExpr, class MyClass)
|
||||||
|
// ANALYZER-NEXT: 6: (CXXConstructExpr, [B1.7], class MyClass)
|
||||||
// CHECK-NEXT: 7: new ([B1.4]) MyClass([B1.6])
|
// CHECK-NEXT: 7: new ([B1.4]) MyClass([B1.6])
|
||||||
// CHECK-NEXT: 8: MyClass *obj = new (buffer) MyClass();
|
// CHECK-NEXT: 8: MyClass *obj = new (buffer) MyClass();
|
||||||
// CHECK-NEXT: Preds (1): B2
|
// CHECK-NEXT: Preds (1): B2
|
||||||
|
@ -363,7 +377,8 @@ void test_placement_new() {
|
||||||
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
|
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
|
||||||
// CHECK-NEXT: 5: 5
|
// CHECK-NEXT: 5: 5
|
||||||
// CHECK-NEXT: 6: CFGNewAllocator(MyClass *)
|
// CHECK-NEXT: 6: CFGNewAllocator(MyClass *)
|
||||||
// CHECK-NEXT: 7: (CXXConstructExpr, class MyClass [5])
|
// WARNINGS-NEXT: 7: (CXXConstructExpr, class MyClass [5])
|
||||||
|
// ANALYZER-NEXT: 7: (CXXConstructExpr, [B1.8], class MyClass [5])
|
||||||
// CHECK-NEXT: 8: new ([B1.4]) MyClass {{\[\[}}B1.5]]
|
// CHECK-NEXT: 8: new ([B1.4]) MyClass {{\[\[}}B1.5]]
|
||||||
// CHECK-NEXT: 9: MyClass *obj = new (buffer) MyClass [5];
|
// CHECK-NEXT: 9: MyClass *obj = new (buffer) MyClass [5];
|
||||||
// CHECK-NEXT: Preds (1): B2
|
// CHECK-NEXT: Preds (1): B2
|
||||||
|
|
Loading…
Reference in New Issue