Index: include/clang/Analysis/AnalysisDeclContext.h =================================================================== --- include/clang/Analysis/AnalysisDeclContext.h +++ include/clang/Analysis/AnalysisDeclContext.h @@ -439,6 +439,7 @@ bool synthesizeBodies = false, bool addStaticInitBranches = false, bool addCXXNewAllocator = true, + bool addRichCXXConstructors = true, CodeInjector *injector = nullptr); AnalysisDeclContext *getContext(const Decl *D); Index: include/clang/Analysis/CFG.h =================================================================== --- include/clang/Analysis/CFG.h +++ include/clang/Analysis/CFG.h @@ -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 @@ 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 @@ 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(Data1.getPointer()); @@ -126,10 +132,66 @@ 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(); + *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(C)); + } + + const ConstructionContext *getConstructionContext() const { + return static_cast(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 @@ /// 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 @@ 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 @@ bool AddStaticInitBranches = false; bool AddCXXNewAllocator = false; bool AddCXXDefaultInitExprInCtors = false; + bool AddRichCXXConstructors = false; BuildOptions() = default; Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -231,6 +231,9 @@ /// \sa IncludeLoopExitInCFG Optional IncludeLoopExitInCFG; + /// \sa IncludeRichConstructorsInCFG + Optional IncludeRichConstructorsInCFG; + /// \sa mayInlineCXXStandardLibrary Optional InlineCXXStandardLibrary; @@ -444,6 +447,13 @@ /// 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: include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h =================================================================== --- include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -194,7 +194,7 @@ 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: lib/Analysis/AnalysisDeclContext.cpp =================================================================== --- lib/Analysis/AnalysisDeclContext.cpp +++ lib/Analysis/AnalysisDeclContext.cpp @@ -67,7 +67,8 @@ 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 @@ cfgBuildOptions.AddLoopExit = addLoopExit; cfgBuildOptions.AddStaticInitBranches = addStaticInitBranch; cfgBuildOptions.AddCXXNewAllocator = addCXXNewAllocator; + cfgBuildOptions.AddRichCXXConstructors = addRichCXXConstructors; } void AnalysisDeclContextManager::clear() { Contexts.clear(); } Index: lib/Analysis/CFG.cpp =================================================================== --- lib/Analysis/CFG.cpp +++ lib/Analysis/CFG.cpp @@ -472,6 +472,11 @@ using LabelSetTy = llvm::SmallSetVector; 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 @@ 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 @@ B->appendStmt(const_cast(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 @@ return nullptr; } +void CFGBuilder::EnterConstructionContextIfNecessary(Stmt *Trigger, + Stmt *Child) { + if (!BuildOpts.AddRichCXXConstructors) + return; + if (!Child) + return; + if (auto *Constructor = dyn_cast(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::VisitCXXConstructExpr(CXXConstructExpr *C, AddStmtChoice asc) { autoCreateBlock(); - appendStmt(Block, C); + appendConstructor(Block, C); return VisitChildren(C); } @@ -3882,15 +3933,22 @@ autoCreateBlock(); appendStmt(Block, NE); + EnterConstructionContextIfNecessary( + NE, const_cast(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 @@ 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 @@ switch (stmt->getStmtClass()) { case Stmt::DeclStmtClass: - DeclMap[cast(stmt)->getSingleDecl()] = P; - break; + DeclMap[cast(stmt)->getSingleDecl()] = P; + break; case Stmt::IfStmtClass: { const VarDecl *var = cast(stmt)->getConditionVariable(); if (var) @@ -4575,14 +4634,19 @@ if (isa(S)) { OS << " (OperatorCall)"; - } - else if (isa(S)) { + } else if (isa(S)) { OS << " (BindTemporary)"; - } - else if (const CXXConstructExpr *CCE = dyn_cast(S)) { - OS << " (CXXConstructExpr, " << CCE->getType().getAsString() << ")"; - } - else if (const CastExpr *CE = dyn_cast(S)) { + } else if (const CXXConstructExpr *CCE = dyn_cast(S)) { + OS << " (CXXConstructExpr, "; + if (Optional CE = E.getAs()) { + if (const Stmt *S = CE->getTriggerStmt()) + Helper.handledStmt((const_cast(S)), OS); + else + llvm_unreachable("Unexpected trigger kind!"); + OS << ", "; + } + OS << CCE->getType().getAsString() << ")"; + } else if (const CastExpr *CE = dyn_cast(S)) { OS << " (" << CE->getStmtClassName() << ", " << CE->getCastKindName() << ", " << CE->getType().getAsString() Index: lib/StaticAnalyzer/Core/AnalysisManager.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -29,6 +29,7 @@ Options.shouldSynthesizeBodies(), Options.shouldConditionalizeStaticInitializers(), /*addCXXNewAllocator=*/true, + Options.includeRichConstructorsInCFG(), injector), Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC), CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -204,7 +204,13 @@ 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: lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngine.cpp +++ lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -454,10 +454,11 @@ switch (E.getKind()) { case CFGElement::Statement: - ProcessStmt(const_cast(E.castAs().getStmt()), Pred); + case CFGElement::Constructor: + ProcessStmt(E.castAs().getStmt(), Pred); return; case CFGElement::Initializer: - ProcessInitializer(E.castAs().getInitializer(), Pred); + ProcessInitializer(E.castAs(), Pred); return; case CFGElement::NewAllocator: ProcessNewAllocator(E.castAs().getAllocatorExpr(), @@ -479,7 +480,7 @@ } static bool shouldRemoveDeadBindings(AnalysisManager &AMgr, - const CFGStmt S, + const Stmt *S, const ExplodedNode *Pred, const LocationContext *LC) { @@ -492,17 +493,17 @@ return true; // Is this on a non-expression? - if (!isa(S.getStmt())) + if (!isa(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(S.getStmt())); + return !PM.isConsumedExpr(cast(S)); } void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, @@ -594,20 +595,20 @@ } } -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: lib/StaticAnalyzer/Core/PathDiagnostic.cpp =================================================================== --- lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -551,6 +551,7 @@ switch (Source.getKind()) { case CFGElement::Statement: + case CFGElement::Constructor: return PathDiagnosticLocation(Source.castAs().getStmt(), SM, CallerCtx); case CFGElement::Initializer: { Index: test/Analysis/analyzer-config.c =================================================================== --- test/Analysis/analyzer-config.c +++ test/Analysis/analyzer-config.c @@ -15,6 +15,7 @@ // 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 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 20 +// CHECK-NEXT: num-entries = 21 Index: test/Analysis/analyzer-config.cpp =================================================================== --- test/Analysis/analyzer-config.cpp +++ test/Analysis/analyzer-config.cpp @@ -26,6 +26,7 @@ // 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 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 25 +// CHECK-NEXT: num-entries = 26 Index: test/Analysis/cfg-rich-constructors.cpp =================================================================== --- test/Analysis/cfg-rich-constructors.cpp +++ test/Analysis/cfg-rich-constructors.cpp @@ -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: test/Analysis/cfg.cpp =================================================================== --- test/Analysis/cfg.cpp +++ test/Analysis/cfg.cpp @@ -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 @@ // 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 @@ // 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 @@ // 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 @@ // 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