]> granicus.if.org Git - clang/commitdiff
[CFG] Add extra context to C++ constructor statement elements.
authorArtem Dergachev <artem.dergachev@gmail.com>
Thu, 8 Feb 2018 22:58:15 +0000 (22:58 +0000)
committerArtem Dergachev <artem.dergachev@gmail.com>
Thu, 8 Feb 2018 22:58:15 +0000 (22:58 +0000)
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

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@324668 91177308-0d34-0410-b5e6-96231b3b80d8

14 files changed:
include/clang/Analysis/AnalysisDeclContext.h
include/clang/Analysis/CFG.h
include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
lib/Analysis/AnalysisDeclContext.cpp
lib/Analysis/CFG.cpp
lib/StaticAnalyzer/Core/AnalysisManager.cpp
lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
lib/StaticAnalyzer/Core/ExprEngine.cpp
lib/StaticAnalyzer/Core/PathDiagnostic.cpp
test/Analysis/analyzer-config.c
test/Analysis/analyzer-config.cpp
test/Analysis/cfg-rich-constructors.cpp [new file with mode: 0644]
test/Analysis/cfg.cpp

index c6ea57ded18696ff373a6720cf8adcd2801ff886..63d467616b4d32bb2c19e1cd562842eb0af02e93 100644 (file)
@@ -439,6 +439,7 @@ public:
                              bool synthesizeBodies = false,
                              bool addStaticInitBranches = false,
                              bool addCXXNewAllocator = true,
+                             bool addRichCXXConstructors = true,
                              CodeInjector *injector = nullptr);
 
   AnalysisDeclContext *getContext(const Decl *D);
index cfedb2aa8ed5d7a1451d0e4eb4261e8208c76812..8236e043840fbdca1ca6cbdf60835e970cd720cd 100644 (file)
@@ -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"
@@ -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,
@@ -117,7 +121,9 @@ public:
 
 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());
@@ -126,10 +132,66 @@ public:
 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;
   }
 };
 
@@ -137,7 +199,7 @@ private:
 /// constructor's initialization list.
 class CFGInitializer : public CFGElement {
 public:
-  CFGInitializer(CXXCtorInitializer *initializer)
+  explicit CFGInitializer(CXXCtorInitializer *initializer)
       : CFGElement(Initializer, initializer) {}
 
   CXXCtorInitializer* getInitializer() const {
@@ -747,6 +809,11 @@ public:
     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);
@@ -855,6 +922,7 @@ public:
     bool AddStaticInitBranches = false;
     bool AddCXXNewAllocator = false;
     bool AddCXXDefaultInitExprInCtors = false;
+    bool AddRichCXXConstructors = false;
 
     BuildOptions() = default;
 
index 5ddf7d79c8d785457ab940bc27511eea8e223d3a..f6bd78884fd8adc42e76bc7d0c90c4b30b854b98 100644 (file)
@@ -231,6 +231,9 @@ private:
   /// \sa IncludeLoopExitInCFG
   Optional<bool> IncludeLoopExitInCFG;
 
+  /// \sa IncludeRichConstructorsInCFG
+  Optional<bool> IncludeRichConstructorsInCFG;
+
   /// \sa mayInlineCXXStandardLibrary
   Optional<bool> InlineCXXStandardLibrary;
   
@@ -444,6 +447,13 @@ public:
   /// 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.
   ///
index ef06d12795ff288d1a832a195edec2439a227384..baac989706affebf4d6f87080027b88f6bf72bca 100644 (file)
@@ -194,7 +194,7 @@ public:
   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);
 
index 9874bb87b28add535d7ef8e9e0dbf3f686a3c04f..2b8259bad720b9a4940462014b39743a64266dd5 100644 (file)
@@ -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;
@@ -78,6 +79,7 @@ AnalysisDeclContextManager::AnalysisDeclContextManager(
   cfgBuildOptions.AddLoopExit = addLoopExit;
   cfgBuildOptions.AddStaticInitBranches = addStaticInitBranch;
   cfgBuildOptions.AddCXXNewAllocator = addCXXNewAllocator;
+  cfgBuildOptions.AddRichCXXConstructors = addRichCXXConstructors;
 }
 
 void AnalysisDeclContextManager::clear() { Contexts.clear(); }
index 714b85d3a9fff835e1891d6f388c6f0b372d5e7e..63f1d724eb7a4eb17c6664f2a7e51395a126cfe3 100644 (file)
@@ -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;
   
@@ -643,6 +648,18 @@ private:
     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();
@@ -682,6 +699,20 @@ private:
     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());
   }
@@ -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
@@ -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);
 }
@@ -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;
 }
 
@@ -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: {
@@ -4343,8 +4402,8 @@ public:
 
           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)
@@ -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()
index 1cc08f0d9fe75676ea0662cee7ab61a7dbacb3cc..53199f746c398e94eae304446129ed8841fcc45d 100644 (file)
@@ -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),
index ca05e38608c70f638011ba98dbe1841a0e0bb1b8..0e3e207c15eac966068e1720e072175827d906ec 100644 (file)
@@ -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() {
index 3df69e891b7d3fc229dde983484dd7261cad7c17..fef7116d905f7b06af145d4e6d4974447ea6a198 100644 (file)
@@ -454,10 +454,11 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred,
 
   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(),
@@ -479,7 +480,7 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred,
 }
 
 static bool shouldRemoveDeadBindings(AnalysisManager &AMgr,
-                                     const CFGStmt S,
+                                     const Stmt *S,
                                      const ExplodedNode *Pred,
                                      const LocationContext *LC) {
 
@@ -492,17 +493,17 @@ static bool shouldRemoveDeadBindings(AnalysisManager &AMgr,
     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,
@@ -594,20 +595,20 @@ 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);
 
index 3d4b377627f60d034fdc13b31b5dc7cea5528cee..561490b09aaa1ca0d4502663840b4d1e747650a3 100644 (file)
@@ -551,6 +551,7 @@ getLocationForCaller(const StackFrameContext *SFC,
 
   switch (Source.getKind()) {
   case CFGElement::Statement:
+  case CFGElement::Constructor:
     return PathDiagnosticLocation(Source.castAs<CFGStmt>().getStmt(),
                                   SM, CallerCtx);
   case CFGElement::Initializer: {
index 770ff3b404fa476c8532ba3689d235cf0ac5a616..89d6ccca5adeee828e0acbf0992c425d5ef78b13 100644 (file)
@@ -15,6 +15,7 @@ void foo() {
 // 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
@@ -32,4 +33,4 @@ void foo() {
 // CHECK-NEXT: unroll-loops = false
 // CHECK-NEXT: widen-loops = false
 // CHECK-NEXT: [stats]
-// CHECK-NEXT: num-entries = 20
+// CHECK-NEXT: num-entries = 21
index a6e1df87458a798da51bea696d665af113974087..2629aeeb968a96d917f2abcae3e3a25ca99369f3 100644 (file)
@@ -26,6 +26,7 @@ public:
 // 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
@@ -43,4 +44,4 @@ public:
 // CHECK-NEXT: unroll-loops = false
 // CHECK-NEXT: widen-loops = false
 // CHECK-NEXT: [stats]
-// CHECK-NEXT: num-entries = 25
+// CHECK-NEXT: num-entries = 26
diff --git a/test/Analysis/cfg-rich-constructors.cpp b/test/Analysis/cfg-rich-constructors.cpp
new file mode 100644 (file)
index 0000000..0b5a2c8
--- /dev/null
@@ -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
index 208277343cdc4f0ee550159807d6d20606472013..38e2248174922a06248f9dadf0fd3ae2732d22f3 100644 (file)
@@ -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: 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
@@ -116,7 +127,8 @@ public:
 // 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
@@ -138,7 +150,8 @@ void test_deletedtor() {
 // 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
@@ -331,7 +344,8 @@ int test_enum_with_extension_default(enum MyEnum value) {
 // 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
@@ -363,7 +377,8 @@ void test_placement_new() {
 // 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