bool synthesizeBodies = false,
bool addStaticInitBranches = false,
bool addCXXNewAllocator = true,
+ bool addRichCXXConstructors = true,
CodeInjector *injector = nullptr);
AnalysisDeclContext *getContext(const Decl *D);
#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"
public:
enum Kind {
// main kind
- Statement,
Initializer,
NewAllocator,
LifetimeEnds,
LoopExit,
+ // stmt kind
+ Statement,
+ Constructor,
+ STMT_BEGIN = Statement,
+ STMT_END = Constructor,
// dtor kind
AutomaticObjectDtor,
DeleteDtor,
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());
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;
}
};
/// constructor's initialization list.
class CFGInitializer : public CFGElement {
public:
- CFGInitializer(CXXCtorInitializer *initializer)
+ explicit CFGInitializer(CXXCtorInitializer *initializer)
: CFGElement(Initializer, initializer) {}
CXXCtorInitializer* getInitializer() const {
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);
bool AddStaticInitBranches = false;
bool AddCXXNewAllocator = false;
bool AddCXXDefaultInitExprInCtors = false;
+ bool AddRichCXXConstructors = false;
BuildOptions() = default;
/// \sa IncludeLoopExitInCFG
Optional<bool> IncludeLoopExitInCFG;
+ /// \sa IncludeRichConstructorsInCFG
+ Optional<bool> IncludeRichConstructorsInCFG;
+
/// \sa mayInlineCXXStandardLibrary
Optional<bool> InlineCXXStandardLibrary;
/// 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.
///
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);
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;
cfgBuildOptions.AddLoopExit = addLoopExit;
cfgBuildOptions.AddStaticInitBranches = addStaticInitBranch;
cfgBuildOptions.AddCXXNewAllocator = addCXXNewAllocator;
+ cfgBuildOptions.AddRichCXXConstructors = addRichCXXConstructors;
}
void AnalysisDeclContextManager::clear() { Contexts.clear(); }
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;
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();
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());
}
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
CFGBlock *CFGBuilder::VisitCXXConstructExpr(CXXConstructExpr *C,
AddStmtChoice asc) {
autoCreateBlock();
- appendStmt(Block, C);
+ appendConstructor(Block, C);
return VisitChildren(C);
}
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;
}
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: {
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)
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()
Options.shouldSynthesizeBodies(),
Options.shouldConditionalizeStaticInitializers(),
/*addCXXNewAllocator=*/true,
+ Options.includeRichConstructorsInCFG(),
injector),
Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC),
CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr),
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() {
switch (E.getKind()) {
case CFGElement::Statement:
- ProcessStmt(const_cast<Stmt*>(E.castAs<CFGStmt>().getStmt()), Pred);
+ case CFGElement::Constructor:
+ ProcessStmt(E.castAs<CFGStmt>().getStmt(), Pred);
return;
case CFGElement::Initializer:
- ProcessInitializer(E.castAs<CFGInitializer>().getInitializer(), Pred);
+ ProcessInitializer(E.castAs<CFGInitializer>(), Pred);
return;
case CFGElement::NewAllocator:
ProcessNewAllocator(E.castAs<CFGNewAllocator>().getAllocatorExpr(),
}
static bool shouldRemoveDeadBindings(AnalysisManager &AMgr,
- const CFGStmt S,
+ const Stmt *S,
const ExplodedNode *Pred,
const LocationContext *LC) {
return true;
// Is this on a non-expression?
- if (!isa<Expr>(S.getStmt()))
+ if (!isa<Expr>(S))
return true;
// Run before processing a call.
- if (CallEvent::isCallStmt(S.getStmt()))
+ if (CallEvent::isCallStmt(S))
return true;
// Is this an expression that is consumed by another expression? If so,
// postpone cleaning out the state.
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::ProcessStmt(const CFGStmt S,
- ExplodedNode *Pred) {
+void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) {
// Reclaim any unnecessary nodes in the ExplodedGraph.
G.reclaimRecentlyAllocatedNodes();
- const Stmt *currStmt = S.getStmt();
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
currStmt->getLocStart(),
"Error evaluating statement");
// Remove dead bindings and symbols.
ExplodedNodeSet CleanedStates;
- if (shouldRemoveDeadBindings(AMgr, S, Pred, Pred->getLocationContext())){
- removeDead(Pred, CleanedStates, currStmt, Pred->getLocationContext());
+ if (shouldRemoveDeadBindings(AMgr, currStmt, Pred,
+ Pred->getLocationContext())) {
+ removeDead(Pred, CleanedStates, currStmt,
+ Pred->getLocationContext());
} else
CleanedStates.Add(Pred);
switch (Source.getKind()) {
case CFGElement::Statement:
+ case CFGElement::Constructor:
return PathDiagnosticLocation(Source.castAs<CFGStmt>().getStmt(),
SM, CallerCtx);
case CFGElement::Initializer: {
// CHECK-NEXT: cfg-implicit-dtors = true
// CHECK-NEXT: cfg-lifetime = false
// CHECK-NEXT: cfg-loopexit = false
+// CHECK-NEXT: cfg-rich-constructors = true
// CHECK-NEXT: cfg-temporary-dtors = false
// CHECK-NEXT: exploration_strategy = dfs
// CHECK-NEXT: faux-bodies = true
// CHECK-NEXT: unroll-loops = false
// CHECK-NEXT: widen-loops = false
// CHECK-NEXT: [stats]
-// CHECK-NEXT: num-entries = 20
+// CHECK-NEXT: num-entries = 21
// CHECK-NEXT: cfg-implicit-dtors = true
// CHECK-NEXT: cfg-lifetime = false
// CHECK-NEXT: cfg-loopexit = false
+// CHECK-NEXT: cfg-rich-constructors = true
// CHECK-NEXT: cfg-temporary-dtors = false
// CHECK-NEXT: exploration_strategy = dfs
// CHECK-NEXT: faux-bodies = true
// CHECK-NEXT: unroll-loops = false
// CHECK-NEXT: widen-loops = false
// CHECK-NEXT: [stats]
-// CHECK-NEXT: num-entries = 25
+// CHECK-NEXT: num-entries = 26
--- /dev/null
+// 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
-// 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
+// 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 -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: ENTRY
// CHECK-NEXT: Succs (1): B1
// CHECK: [B1]
// 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: 4: A *a = new A();
// CHECK-NEXT: 5: a
// CHECK: [B1]
// CHECK-NEXT: 1: 5
// 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: 5: A *a = new A [5];
// CHECK-NEXT: 6: a
// CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, ArrayToPointerDecay, int *)
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
// 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: 8: MyClass *obj = new (buffer) MyClass();
// CHECK-NEXT: Preds (1): B2
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, BitCast, void *)
// CHECK-NEXT: 5: 5
// 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: 9: MyClass *obj = new (buffer) MyClass [5];
// CHECK-NEXT: Preds (1): B2