Skip to content

Commit

Permalink
[CFG] Add extra context to C++ constructor statement elements.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
haoNoQ committed Feb 8, 2018
1 parent 9c2f3c4 commit 41ffb30
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 38 deletions.
1 change: 1 addition & 0 deletions clang/include/clang/Analysis/AnalysisDeclContext.h
Expand Up @@ -439,6 +439,7 @@ class AnalysisDeclContextManager {
bool synthesizeBodies = false,
bool addStaticInitBranches = false,
bool addCXXNewAllocator = true,
bool addRichCXXConstructors = true,
CodeInjector *injector = nullptr);

AnalysisDeclContext *getContext(const Decl *D);
Expand Down
78 changes: 73 additions & 5 deletions clang/include/clang/Analysis/CFG.h
Expand Up @@ -15,7 +15,7 @@
#ifndef 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/Basic/LLVM.h"
#include "llvm/ADT/DenseMap.h"
Expand Down Expand Up @@ -55,11 +55,15 @@ class CFGElement {
public:
enum Kind {
// main kind
Statement,
Initializer,
NewAllocator,
LifetimeEnds,
LoopExit,
// stmt kind
Statement,
Constructor,
STMT_BEGIN = Statement,
STMT_END = Constructor,
// dtor kind
AutomaticObjectDtor,
DeleteDtor,
Expand Down Expand Up @@ -117,7 +121,9 @@ class CFGElement {

class CFGStmt : public CFGElement {
public:
CFGStmt(Stmt *S) : CFGElement(Statement, S) {}
explicit CFGStmt(Stmt *S, Kind K = Statement) : CFGElement(K, S) {
assert(isKind(*this));
}

const Stmt *getStmt() const {
return static_cast<const Stmt *>(Data1.getPointer());
Expand All @@ -126,18 +132,74 @@ class CFGStmt : public CFGElement {
private:
friend class CFGElement;

static bool isKind(const CFGElement &E) {
return E.getKind() >= STMT_BEGIN && E.getKind() <= STMT_END;
}

protected:
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) {
return E.getKind() == Statement;
return E.getKind() == Constructor;
}
};

/// CFGInitializer - Represents C++ base or member initializer from
/// constructor's initialization list.
class CFGInitializer : public CFGElement {
public:
CFGInitializer(CXXCtorInitializer *initializer)
explicit CFGInitializer(CXXCtorInitializer *initializer)
: CFGElement(Initializer, initializer) {}

CXXCtorInitializer* getInitializer() const {
Expand Down Expand Up @@ -747,6 +809,11 @@ class CFGBlock {
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,
BumpVectorContext &C) {
Elements.push_back(CFGInitializer(initializer), C);
Expand Down Expand Up @@ -855,6 +922,7 @@ class CFG {
bool AddStaticInitBranches = false;
bool AddCXXNewAllocator = false;
bool AddCXXDefaultInitExprInCtors = false;
bool AddRichCXXConstructors = false;

BuildOptions() = default;

Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
Expand Up @@ -231,6 +231,9 @@ class AnalyzerOptions : public RefCountedBase<AnalyzerOptions> {
/// \sa IncludeLoopExitInCFG
Optional<bool> IncludeLoopExitInCFG;

/// \sa IncludeRichConstructorsInCFG
Optional<bool> IncludeRichConstructorsInCFG;

/// \sa mayInlineCXXStandardLibrary
Optional<bool> InlineCXXStandardLibrary;

Expand Down Expand Up @@ -444,6 +447,13 @@ class AnalyzerOptions : public RefCountedBase<AnalyzerOptions> {
/// the values "true" and "false".
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
/// for inlining.
///
Expand Down
Expand Up @@ -194,7 +194,7 @@ class ExprEngine : public SubEngine {
void processCFGElement(const CFGElement E, ExplodedNode *Pred,
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);

Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Analysis/AnalysisDeclContext.cpp
Expand Up @@ -67,7 +67,8 @@ AnalysisDeclContextManager::AnalysisDeclContextManager(
ASTContext &ASTCtx, bool useUnoptimizedCFG, bool addImplicitDtors,
bool addInitializers, bool addTemporaryDtors, bool addLifetime,
bool addLoopExit, bool synthesizeBodies, bool addStaticInitBranch,
bool addCXXNewAllocator, CodeInjector *injector)
bool addCXXNewAllocator, bool addRichCXXConstructors,
CodeInjector *injector)
: Injector(injector), FunctionBodyFarm(ASTCtx, injector),
SynthesizeBodies(synthesizeBodies) {
cfgBuildOptions.PruneTriviallyFalseEdges = !useUnoptimizedCFG;
Expand All @@ -78,6 +79,7 @@ AnalysisDeclContextManager::AnalysisDeclContextManager(
cfgBuildOptions.AddLoopExit = addLoopExit;
cfgBuildOptions.AddStaticInitBranches = addStaticInitBranch;
cfgBuildOptions.AddCXXNewAllocator = addCXXNewAllocator;
cfgBuildOptions.AddRichCXXConstructors = addRichCXXConstructors;
}

void AnalysisDeclContextManager::clear() { Contexts.clear(); }
Expand Down
86 changes: 75 additions & 11 deletions clang/lib/Analysis/CFG.cpp
Expand Up @@ -472,6 +472,11 @@ class CFGBuilder {
using LabelSetTy = llvm::SmallSetVector<LabelDecl *, 8>;
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;
const CFG::BuildOptions &BuildOpts;

Expand Down Expand Up @@ -643,6 +648,18 @@ class CFGBuilder {
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(); }
CFGBlock *createBlock(bool add_successor = true);
CFGBlock *createNoReturnBlock();
Expand Down Expand Up @@ -682,6 +699,20 @@ class CFGBuilder {
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) {
B->appendInitializer(I, cfg->getBumpVectorContext());
}
Expand Down Expand Up @@ -1116,6 +1147,26 @@ static const VariableArrayType *FindVA(const Type *t) {
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
/// arbitrary statement. Examples include a single expression or a function
/// body (compound statement). The ownership of the returned CFG is
Expand Down Expand Up @@ -3872,7 +3923,7 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E,
CFGBlock *CFGBuilder::VisitCXXConstructExpr(CXXConstructExpr *C,
AddStmtChoice asc) {
autoCreateBlock();
appendStmt(Block, C);
appendConstructor(Block, C);

return VisitChildren(C);
}
Expand All @@ -3882,15 +3933,22 @@ CFGBlock *CFGBuilder::VisitCXXNewExpr(CXXNewExpr *NE,
autoCreateBlock();
appendStmt(Block, NE);

EnterConstructionContextIfNecessary(
NE, const_cast<CXXConstructExpr *>(NE->getConstructExpr()));

if (NE->getInitializer())
Block = Visit(NE->getInitializer());

if (BuildOpts.AddCXXNewAllocator)
appendNewAllocator(Block, NE);

if (NE->isArray())
Block = Visit(NE->getArraySize());

for (CXXNewExpr::arg_iterator I = NE->placement_arg_begin(),
E = NE->placement_arg_end(); I != E; ++I)
Block = Visit(*I);

return Block;
}

Expand Down Expand Up @@ -4210,11 +4268,12 @@ std::unique_ptr<CFG> CFG::buildCFG(const Decl *D, Stmt *Statement,
const CXXDestructorDecl *
CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const {
switch (getKind()) {
case CFGElement::Statement:
case CFGElement::Initializer:
case CFGElement::NewAllocator:
case CFGElement::LoopExit:
case CFGElement::LifetimeEnds:
case CFGElement::Statement:
case CFGElement::Constructor:
llvm_unreachable("getDestructorDecl should only be used with "
"ImplicitDtors");
case CFGElement::AutomaticObjectDtor: {
Expand Down Expand Up @@ -4343,8 +4402,8 @@ class StmtPrinterHelper : public PrinterHelper {

switch (stmt->getStmtClass()) {
case Stmt::DeclStmtClass:
DeclMap[cast<DeclStmt>(stmt)->getSingleDecl()] = P;
break;
DeclMap[cast<DeclStmt>(stmt)->getSingleDecl()] = P;
break;
case Stmt::IfStmtClass: {
const VarDecl *var = cast<IfStmt>(stmt)->getConditionVariable();
if (var)
Expand Down Expand Up @@ -4575,14 +4634,19 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper,

if (isa<CXXOperatorCallExpr>(S)) {
OS << " (OperatorCall)";
}
else if (isa<CXXBindTemporaryExpr>(S)) {
} else if (isa<CXXBindTemporaryExpr>(S)) {
OS << " (BindTemporary)";
}
else if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(S)) {
OS << " (CXXConstructExpr, " << CCE->getType().getAsString() << ")";
}
else if (const CastExpr *CE = dyn_cast<CastExpr>(S)) {
} else if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(S)) {
OS << " (CXXConstructExpr, ";
if (Optional<CFGConstructor> CE = E.getAs<CFGConstructor>()) {
if (const Stmt *S = CE->getTriggerStmt())
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() << ", "
<< CE->getCastKindName()
<< ", " << CE->getType().getAsString()
Expand Down
1 change: 1 addition & 0 deletions clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp
Expand Up @@ -29,6 +29,7 @@ AnalysisManager::AnalysisManager(
Options.shouldSynthesizeBodies(),
Options.shouldConditionalizeStaticInitializers(),
/*addCXXNewAllocator=*/true,
Options.includeRichConstructorsInCFG(),
injector),
Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC),
CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr),
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
Expand Up @@ -204,7 +204,13 @@ bool AnalyzerOptions::includeLifetimeInCFG() {

bool AnalyzerOptions::includeLoopExitInCFG() {
return getBooleanOption(IncludeLoopExitInCFG, "cfg-loopexit",
/* Default = */ false);
/* Default = */ false);
}

bool AnalyzerOptions::includeRichConstructorsInCFG() {
return getBooleanOption(IncludeRichConstructorsInCFG,
"cfg-rich-constructors",
/* Default = */ true);
}

bool AnalyzerOptions::mayInlineCXXStandardLibrary() {
Expand Down

0 comments on commit 41ffb30

Please sign in to comment.