Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -617,6 +617,11 @@ return svalBuilder.evalBinOp(ST, Op, LHS, RHS, T); } + /// Retreives which element is being constructed in a non POD type array. + static Optional + getIndexOfElementToConstruct(ProgramStateRef State, const Expr *E, + const LocationContext *LCtx); + /// By looking at a certain item that may be potentially part of an object's /// ConstructionContext, retrieve such object's location. A particular /// statement can be transparently passed as \p Item in most cases. @@ -708,10 +713,19 @@ /// fully implemented it sometimes indicates that it failed via its /// out-parameter CallOpts; in such cases a fake temporary region is /// returned, which is better than nothing but does not represent - /// the actual behavior of the program. - SVal computeObjectUnderConstruction( - const Expr *E, ProgramStateRef State, const LocationContext *LCtx, - const ConstructionContext *CC, EvalCallOptions &CallOpts); + /// the actual behavior of the program. The Idx property is used if we + /// construct an array of objects. In that case it points to the index + /// of the continous memory region. + /// E.g.: + /// For `int arr[4]` this index can be 0,1,2,3. + /// For `int arr2[3][3]` this index can be 0,1,...,7,8. + /// A multi-dimensional array is also a continous memory location in a + /// row major order, so for arr[0][0] Idx is 0 and for arr[2][2] Idx is 8 + SVal computeObjectUnderConstruction(const Expr *E, ProgramStateRef State, + const LocationContext *LCtx, + const ConstructionContext *CC, + EvalCallOptions &CallOpts, + unsigned Idx = 0); /// Update the program state with all the path-sensitive information /// that's necessary to perform construction of an object with a given @@ -724,12 +738,16 @@ /// A convenient wrapper around computeObjectUnderConstruction /// and updateObjectsUnderConstruction. - std::pair handleConstructionContext( - const Expr *E, ProgramStateRef State, const LocationContext *LCtx, - const ConstructionContext *CC, EvalCallOptions &CallOpts) { - SVal V = computeObjectUnderConstruction(E, State, LCtx, CC, CallOpts); - return std::make_pair( - updateObjectsUnderConstruction(V, E, State, LCtx, CC, CallOpts), V); + std::pair + handleConstructionContext(const Expr *E, ProgramStateRef State, + const LocationContext *LCtx, + const ConstructionContext *CC, + EvalCallOptions &CallOpts, unsigned Idx = 0) { + + SVal V = computeObjectUnderConstruction(E, State, LCtx, CC, CallOpts, Idx); + State = updateObjectsUnderConstruction(V, E, State, LCtx, CC, CallOpts); + + return std::make_pair(State, V); } private: @@ -796,6 +814,15 @@ const ExplodedNode *Pred, const EvalCallOptions &CallOpts = {}); + /// Chekcs whether out policies allow us to inline a non POD type array + /// construction. + bool shouldInlineArrayConstruction(const ArrayType *Type); + + /// Checks whether we construct an array of non POD type, and decides if the + /// constructor should be inkoved once again. + bool shouldRepeatCtorCall(ProgramStateRef State, const CXXConstructExpr *E, + const LocationContext *LCtx); + void inlineCall(WorkList *WList, const CallEvent &Call, const Decl *D, NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State); @@ -847,7 +874,8 @@ /// If the type is not an array type at all, the original value is returned. /// Otherwise the "IsArray" flag is set. static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue, - QualType &Ty, bool &IsArray); + QualType &Ty, bool &IsArray, + unsigned Idx = 0); /// For a DeclStmt or CXXInitCtorInitializer, walk backward in the current CFG /// block to find the constructor expression that directly constructed into @@ -878,6 +906,16 @@ const ObjCForCollectionStmt *O, const LocationContext *LC); private: + /// Assuming we construct an array of non POD types, this method allows us + /// to store which element is to be constructed next. + static ProgramStateRef + setIndexOfElementToConstruct(ProgramStateRef State, const Expr *E, + const LocationContext *LCtx, unsigned Idx); + + static ProgramStateRef + removeIndexOfElementToConstruct(ProgramStateRef State, const Expr *E, + const LocationContext *LCtx); + /// Store the location of a C++ object corresponding to a statement /// until the statement is actually encountered. For example, if a DeclStmt /// has CXXConstructExpr as its initializer, the object would be considered Index: clang/lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -185,6 +185,11 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction, ObjectsUnderConstructionMap) +typedef llvm::ImmutableMap, + unsigned> + IndexOfElementToConstructMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(IndexOfElementToConstruct, + IndexOfElementToConstructMap) //===----------------------------------------------------------------------===// // Engine construction and deletion. //===----------------------------------------------------------------------===// @@ -441,6 +446,33 @@ return State; } +ProgramStateRef +ExprEngine::setIndexOfElementToConstruct(ProgramStateRef State, const Expr *E, + const LocationContext *LCtx, + unsigned Idx) { + auto Key = std::make_pair(E, LCtx->getStackFrame()); + + assert(!State->contains(Key) || Idx > 0); + + return State->set(Key, Idx); +} + +Optional +ExprEngine::getIndexOfElementToConstruct(ProgramStateRef State, const Expr *E, + const LocationContext *LCtx) { + + return Optional::create( + State->get({E, LCtx->getStackFrame()})); +} + +ProgramStateRef ExprEngine::removeIndexOfElementToConstruct( + ProgramStateRef State, const Expr *E, const LocationContext *LCtx) { + auto Key = std::make_pair(E, LCtx->getStackFrame()); + + assert(E && State->contains(Key)); + return State->remove(Key); +} + ProgramStateRef ExprEngine::addObjectUnderConstruction(ProgramStateRef State, const ConstructionContextItem &Item, @@ -448,9 +480,27 @@ ConstructedObjectKey Key(Item, LC->getStackFrame()); // FIXME: Currently the state might already contain the marker due to // incorrect handling of temporaries bound to default parameters. + // The state will already contain the marker if we construct elements + // in an array, as we visit the same statement multiple times before + // the array declaration. The marker is removed when we visit the + // DeclStmt. + + const Expr *E = nullptr; + + if (auto DS = dyn_cast_or_null(Item.getStmtOrNull())) { + if (auto VD = dyn_cast_or_null(DS->getSingleDecl())) + E = dyn_cast(VD->getInit()); + } + + if (!E && !Item.getStmtOrNull()) { + auto CtorInit = Item.getCXXCtorInitializer(); + E = CtorInit->getInit(); + } + assert(!State->get(Key) || Key.getItem().getKind() == - ConstructionContextItem::TemporaryDestructorKind); + ConstructionContextItem::TemporaryDestructorKind || + State->contains({E, LC})); return State->set(Key, V); } @@ -582,6 +632,69 @@ } } +static void printIndicesOfElementsToConstructJson( + raw_ostream &Out, ProgramStateRef State, const char *NL, + const LocationContext *LCtx, const ASTContext &Context, + unsigned int Space = 0, bool IsDot = false) { + using KeyT = std::pair; + + PrintingPolicy PP = + LCtx->getAnalysisDeclContext()->getASTContext().getPrintingPolicy(); + + ++Space; + bool HasItem = false; + + // Store the last key. + KeyT LastKey; + for (const auto &I : State->get()) { + const KeyT &Key = I.first; + if (Key.second != LCtx) + continue; + + if (!HasItem) { + Out << "[" << NL; + HasItem = true; + } + + LastKey = Key; + } + + for (const auto &I : State->get()) { + const KeyT &Key = I.first; + unsigned Value = I.second; + if (Key.second != LCtx) + continue; + + Indent(Out, Space, IsDot) << "{ "; + + // Expr + const Expr *E = Key.first; + Out << "\"stmt_id\": " << E->getID(Context); + + // Kind - hack to display the current index + Out << ", \"kind\": \"Cur: " << Value - 1 << "\""; + + // Pretty-print + Out << ", \"pretty\": "; + Out << "\"" << E->getStmtClassName() << " " + << E->getSourceRange().printToString(Context.getSourceManager()) << " '" + << QualType::getAsString(E->getType().split(), PP); + Out << "'\""; + + Out << ", \"value\": \"Next: " << Value << "\" }"; + + if (Key != LastKey) + Out << ','; + Out << NL; + } + + if (HasItem) + Indent(Out, --Space, IsDot) << ']'; // End of "location_context". + else { + Out << "null "; + } +} + void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State, const LocationContext *LCtx, const char *NL, unsigned int Space, bool IsDot) const { @@ -595,7 +708,24 @@ }); --Space; - Indent(Out, Space, IsDot) << "]," << NL; // End of "constructing_objects". + Indent(Out, Space, IsDot) << "],"; // End of "constructing_objects". + } else { + Out << "null,"; + } + + Indent(Out, Space, IsDot) << "\"index_of_element\": "; + if (LCtx && !State->get().isEmpty()) { + ++Space; + + auto &Context = getContext(); + Out << '[' << NL; + LCtx->printJson(Out, NL, Space, IsDot, [&](const LocationContext *LC) { + printIndicesOfElementsToConstructJson(Out, State, NL, LC, Context, Space, + IsDot); + }); + + --Space; + Indent(Out, Space, IsDot) << "]," << NL; // End of "index_of_element". } else { Out << "null," << NL; } Index: clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -94,15 +94,18 @@ } } - SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, - QualType &Ty, bool &IsArray) { + QualType &Ty, bool &IsArray, + unsigned Idx) { SValBuilder &SVB = State->getStateManager().getSValBuilder(); ASTContext &Ctx = SVB.getContext(); - while (const ArrayType *AT = Ctx.getAsArrayType(Ty)) { - Ty = AT->getElementType(); - LValue = State->getLValue(Ty, SVB.makeZeroArrayIndex(), LValue); + if (const ArrayType *AT = Ctx.getAsArrayType(Ty)) { + while (AT) { + Ty = AT->getElementType(); + AT = dyn_cast(AT->getElementType()); + } + LValue = State->getLValue(Ty, SVB.makeArrayIndex(Idx), LValue); IsArray = true; } @@ -111,7 +114,7 @@ SVal ExprEngine::computeObjectUnderConstruction( const Expr *E, ProgramStateRef State, const LocationContext *LCtx, - const ConstructionContext *CC, EvalCallOptions &CallOpts) { + const ConstructionContext *CC, EvalCallOptions &CallOpts, unsigned Idx) { SValBuilder &SVB = getSValBuilder(); MemRegionManager &MRMgr = SVB.getRegionManager(); ASTContext &ACtx = SVB.getContext(); @@ -126,7 +129,7 @@ const auto *Var = cast(DS->getSingleDecl()); QualType Ty = Var->getType(); return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty, - CallOpts.IsArrayCtorOrDtor); + CallOpts.IsArrayCtorOrDtor, Idx); } case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { @@ -159,7 +162,7 @@ QualType Ty = Field->getType(); return makeZeroElementRegion(State, FieldVal, Ty, - CallOpts.IsArrayCtorOrDtor); + CallOpts.IsArrayCtorOrDtor, Idx); } case ConstructionContext::NewAllocatedObjectKind: { if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { @@ -172,8 +175,12 @@ // TODO: In fact, we need to call the constructor for every // allocated element, not just the first one! CallOpts.IsArrayCtorOrDtor = true; - return loc::MemRegionVal(getStoreManager().GetElementZeroRegion( - MR, NE->getType()->getPointeeType())); + + auto R = MRMgr.getElementRegion(NE->getType()->getPointeeType(), + svalBuilder.makeArrayIndex(Idx), MR, + SVB.getContext()); + + return loc::MemRegionVal(R); } return V; } @@ -484,10 +491,6 @@ } } - // FIXME: Handle arrays, which run the same constructor for every element. - // For now, we just run the first constructor (which should still invalidate - // the entire array). - EvalCallOptions CallOpts; auto C = getCurrentCFGElement().getAs(); assert(C || getCurrentCFGElement().getAs()); @@ -500,9 +503,15 @@ // Inherited constructors are always base class constructors. assert(CE && !CIE && "A complete constructor is inherited?!"); + unsigned Idx = 0; + if (CE->getType()->isArrayType()) { + Idx = getIndexOfElementToConstruct(State, CE, LCtx).getValueOr(0u); + State = setIndexOfElementToConstruct(State, CE, LCtx, Idx + 1); + } + // The target region is found from construction context. std::tie(State, Target) = - handleConstructionContext(CE, State, LCtx, CC, CallOpts); + handleConstructionContext(CE, State, LCtx, CC, CallOpts, Idx); break; } case CXXConstructExpr::CK_VirtualBase: { @@ -894,14 +903,15 @@ SVal Result = symVal; if (CNE->isArray()) { - // FIXME: allocating an array requires simulating the constructors. - // For now, just return a symbolicated region. + if (const auto *NewReg = cast_or_null(symVal.getAsRegion())) { QualType ObjTy = CNE->getType()->getPointeeType(); const ElementRegion *EleReg = - getStoreManager().GetElementZeroRegion(NewReg, ObjTy); + MRMgr.getElementRegion(ObjTy, svalBuilder.makeArrayIndex(0), NewReg, + svalBuilder.getContext()); Result = loc::MemRegionVal(EleReg); } + State = State->BindExpr(CNE, Pred->getLocationContext(), Result); Bldr.generateNode(CNE, Pred, State); return; Index: clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -227,6 +227,7 @@ // Step 2: generate node with bound return value: CEBNode -> BindedRetNode. + bool ShouldLoopCall = false; // If the callee returns an expression, bind its value to CallExpr. if (CE) { if (const ReturnStmt *RS = dyn_cast_or_null(LastSt)) { @@ -255,6 +256,12 @@ SVal ThisV = state->getSVal(This); ThisV = state->getSVal(ThisV.castAs()); state = state->BindExpr(CCE, callerCtx, ThisV); + + ShouldLoopCall = shouldRepeatCtorCall(state, CCE, callerCtx); + + if (!ShouldLoopCall && + getIndexOfElementToConstruct(state, CCE, callerCtx)) + state = removeIndexOfElementToConstruct(state, CCE, callerCtx); } if (const auto *CNE = dyn_cast(CE)) { @@ -358,9 +365,10 @@ // Enqueue the next element in the block. for (ExplodedNodeSet::iterator PSI = Dst.begin(), PSE = Dst.end(); - PSI != PSE; ++PSI) { - Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), - calleeCtx->getIndex()+1); + PSI != PSE; ++PSI) { + unsigned Idx = calleeCtx->getIndex() + (ShouldLoopCall ? 0 : 1); + + Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), Idx); } } } @@ -800,8 +808,11 @@ // initializers for array fields in default move/copy constructors. // We still allow construction into ElementRegion targets when they don't // represent array elements. - if (CallOpts.IsArrayCtorOrDtor) - return CIP_DisallowedOnce; + if (CallOpts.IsArrayCtorOrDtor) { + if (!shouldInlineArrayConstruction( + dyn_cast(CtorExpr->getType()))) + return CIP_DisallowedOnce; + } // Inlining constructors requires including initializers in the CFG. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); @@ -852,7 +863,7 @@ assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; - // FIXME: We don't handle constructors or destructors for arrays properly. + // FIXME: We don't handle destructors for arrays properly. if (CallOpts.IsArrayCtorOrDtor) return CIP_DisallowedOnce; @@ -1065,6 +1076,59 @@ return true; } +static unsigned computeConstantArraySize(const ConstantArrayType *CAT) { + + assert(CAT); + + // In case of multi dimensional arrays the type is + // ConstantArrayType + // `ConstantArrayType + // `... + // + // To get the count of the elements we traverse this chain + // and multiply the sizes of the sub arrays together. + + unsigned Size = 1; + while (CAT) { + Size *= CAT->getSize().getLimitedValue(); + CAT = dyn_cast(CAT->getElementType()); + } + + return Size; +} + +bool ExprEngine::shouldInlineArrayConstruction(const ArrayType *Type) { + if (!Type) + return false; + + // FIXME: Handle other arrays types. + if (const auto *CAT = dyn_cast(Type)) { + unsigned Size = computeConstantArraySize(CAT); + + return Size <= AMgr.options.maxBlockVisitOnPath; + } + + return false; +} + +bool ExprEngine::shouldRepeatCtorCall(ProgramStateRef State, + const CXXConstructExpr *E, + const LocationContext *LCtx) { + + if (!E) + return false; + + auto Ty = E->getType(); + + // FIXME: Handle non constant array types + if (const auto *CAT = dyn_cast(Ty)) { + unsigned Size = computeConstantArraySize(CAT); + return Size > getIndexOfElementToConstruct(State, E, LCtx); + } + + return false; +} + static bool isTrivialObjectAssignment(const CallEvent &Call) { const CXXInstanceCall *ICall = dyn_cast(&Call); if (!ICall) Index: clang/test/Analysis/ctor-array.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/ctor-array.cpp @@ -0,0 +1,160 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-disable-checker=cplusplus -analyzer-config c++-inlining=constructors -verify %s + +#include "Inputs/system-header-simulator-cxx.h" + +void clang_analyzer_eval(bool); + +struct s { + int x; + int y; +}; + +void a1(void) { + s arr[3]; + int x = arr[0].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void a2(void) { + s arr[3]; + int x = arr[1].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void a3(void) { + s arr[3]; + int x = arr[2].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +struct s2 { + int x; + int y = 2; +}; + +void b1(void) { + s2 arr[3]; + + clang_analyzer_eval(arr[0].y == 2); // expected-warning{{TRUE}} + int x = arr[0].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void b2(void) { + s2 arr[3]; + + clang_analyzer_eval(arr[1].y == 2); // expected-warning{{TRUE}} + int x = arr[1].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void b3(void) { + s2 arr[3]; + + clang_analyzer_eval(arr[2].y == 2); // expected-warning{{TRUE}} + int x = arr[2].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void c1(void) { + { + s2 arr[2]; + arr[1].x = 3; + + clang_analyzer_eval(arr[1].y == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].x == 3); // expected-warning{{TRUE}} + } + + { + s2 arr[2]; + + clang_analyzer_eval(arr[1].y == 2); // expected-warning{{TRUE}} + int x = arr[1].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} + } +} + +struct s3 { + int x = 1; + int y = 2; +}; + +struct s4 { + s3 arr[2]; + s sarr[2]; +}; + +void e1(void) { + s4 arr[2]; + + clang_analyzer_eval(arr[0].arr[0].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].arr[0].y == 2); // expected-warning{{TRUE}} + + clang_analyzer_eval(arr[0].arr[1].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].arr[1].y == 2); // expected-warning{{TRUE}} + + clang_analyzer_eval(arr[1].arr[0].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].arr[0].y == 2); // expected-warning{{TRUE}} + + clang_analyzer_eval(arr[1].arr[1].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].arr[1].y == 2); // expected-warning{{TRUE}} + + int x = arr[1].sarr[1].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void f1(void) { + s2 arr[2][2]; + + clang_analyzer_eval(arr[1][1].y == 2); // expected-warning{{TRUE}} + int x = arr[1][1].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +struct s5 { + static int c; + int x; + + s5() : x(c++) {} +}; + +void g1(void) { + // FIXME: This test requires -analyzer-disable-checker=cplusplus, + // because of the checker's weird behaviour in case of arrays. + // E.g.: + // s3 *arr = new s3[4]; + // s3 *arr2 = new (arr + 1) s3[1]; + // ^~~~~~~~~~~~~~~~~~~ + // warning: 12 bytes is possibly not enough + // for array allocation which requires + // 4 bytes. + + s5::c = 0; + s5 *arr = new s5[4]; + new (arr + 1) s5[3]; + + clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].x == 4); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[2].x == 5); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[3].x == 6); // expected-warning{{TRUE}} +} + +void g2(void) { + s5::c = 0; + s5 arr[4]; + + clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[2].x == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[3].x == 3); // expected-warning{{TRUE}} +} + +void g3(void) { + s5::c = 0; + s5 arr[2][2]; + + clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0][1].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1][0].x == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1][1].x == 3); // expected-warning{{TRUE}} +} \ No newline at end of file Index: clang/test/Analysis/ctor.mm =================================================================== --- clang/test/Analysis/ctor.mm +++ clang/test/Analysis/ctor.mm @@ -581,12 +581,11 @@ } void testArrayNew() { - // FIXME: Pending proper implementation of constructors for 'new[]'. raw_pair *p = new raw_pair[2](); - clang_analyzer_eval(p[0].p1 == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(p[0].p2 == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(p[1].p1 == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(p[1].p2 == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(p[0].p1 == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(p[0].p2 == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(p[1].p1 == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(p[1].p2 == 0); // expected-warning{{TRUE}} } struct initializing_pair { Index: clang/test/Analysis/cxxctr-array-evalcall-analysis-order.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/cxxctr-array-evalcall-analysis-order.cpp @@ -0,0 +1,50 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.AnalysisOrder \ +// RUN: -analyzer-config debug.AnalysisOrder:PreCall=true \ +// RUN: -analyzer-config debug.AnalysisOrder:PostCall=true \ +// RUN: 2>&1 | FileCheck %s + +// This test ensures that eval::Call event will be triggered for constructors. + +class C { +public: + C(){}; +}; + +void stack() { + C arr[4]; + C *arr2 = new C[4]; + C arr3[2][2]; +} + +// C arr[4]; +// CHECK: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// +// C *arr2 = new C[4]; +// CHECK-NEXT: PreCall (operator new[]) [CXXAllocatorCall] +// CHECK-NEXT: PostCall (operator new[]) [CXXAllocatorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// +// C arr3[2][2]; +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall] +// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall] \ No newline at end of file Index: clang/test/Analysis/dtor.cpp =================================================================== --- clang/test/Analysis/dtor.cpp +++ clang/test/Analysis/dtor.cpp @@ -140,10 +140,8 @@ IntWrapper arr[2]; // There should be no undefined value warnings here. - // Eventually these should be TRUE as well, but right now - // we can't handle array constructors. - clang_analyzer_eval(arr[0].x == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(arr[1].x == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].x == 0); // expected-warning{{TRUE}} arr[0].x = &i; arr[1].x = &j; @@ -312,10 +310,8 @@ IntWrapper arr[2][2]; // There should be no undefined value warnings here. - // Eventually these should be TRUE as well, but right now - // we can't handle array constructors. - clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{TRUE}} arr[0][0].x = &i; arr[1][1].x = &j; @@ -597,5 +593,5 @@ void overrideDoubleDelete() { auto *a = new CustomOperators(); delete a; - delete a; // expected-warning@581 {{Attempt to free released memory}} + delete a; // expected-warning@577 {{Attempt to free released memory}} } Index: clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot +++ clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot @@ -24,6 +24,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "environment": null, "checker_messages": [ { "checker": "alpha.core.FooChecker", "messages": [ Index: clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot +++ clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot @@ -17,6 +17,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": [ { "checker": "FooChecker", "messages": [ "Foo: Bar" @@ -75,6 +76,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": [ { "checker": "FooChecker", "messages": [ "Foo: Bar", Index: clang/test/Analysis/exploded-graph-rewriter/constraints.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -24,6 +24,7 @@ "environment": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 0] }" } Index: clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -17,6 +17,7 @@ "environment": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 10] }" } @@ -55,6 +56,7 @@ "environment": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 5] }" } @@ -83,6 +85,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null } } Index: clang/test/Analysis/exploded-graph-rewriter/environment.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -46,6 +46,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "environment": { "pointer": "0x2", Index: clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -18,6 +18,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "environment": { "pointer": "0x2", @@ -73,6 +74,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "environment": { "pointer": "0x2", @@ -122,6 +124,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "environment": { "pointer": "0x2", Index: clang/test/Analysis/exploded-graph-rewriter/store.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/store.dot +++ clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -34,6 +34,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "store": { "pointer": "0x2", Index: clang/test/Analysis/exploded-graph-rewriter/store_diff.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -20,6 +20,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "store": { "pointer": "0x2", @@ -74,6 +75,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": null, "store": { "pointer": "0x5", Index: clang/test/Analysis/exploded-graph-rewriter/topology.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/topology.dot +++ clang/test/Analysis/exploded-graph-rewriter/topology.dot @@ -24,6 +24,7 @@ "constraints": null, "dynamic_types": null, "constructing_objects": null, + "index_of_element": null, "checker_messages": [ { "checker": "foo", "messages": ["bar"] } ], Index: clang/test/Analysis/handle_constructors_with_new_array.cpp =================================================================== --- clang/test/Analysis/handle_constructors_with_new_array.cpp +++ clang/test/Analysis/handle_constructors_with_new_array.cpp @@ -59,12 +59,9 @@ init_in_body a2[1]; init_default_member a3[1]; - // FIXME: Should be TRUE, not FALSE. - clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}} - // FIXME: Should be TRUE, not FALSE. - clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}} - // FIXME: Should be TRUE, not FALSE. - clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}} + clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} } void test_dynamic_aggregate() { @@ -73,12 +70,9 @@ auto *a2 = new init_in_body[1]; auto *a3 = new init_default_member[1]; - // FIXME: Should be TRUE, not FALSE. - clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}} - // FIXME: Should be TRUE, not FALSE. - clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}} - // FIXME: Should be TRUE, not FALSE. - clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}} + clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} delete[] a1; delete[] a2; Index: clang/test/Analysis/new-ctor-conservative.cpp =================================================================== --- clang/test/Analysis/new-ctor-conservative.cpp +++ clang/test/Analysis/new-ctor-conservative.cpp @@ -25,9 +25,14 @@ void checkNewArray() { S *s = new S[10]; - // FIXME: Should be true once we inline array constructors. + + // FIXME: Handle big array construction clang_analyzer_eval(s[0].x == 1); // expected-warning{{UNKNOWN}} clang_analyzer_eval(s[1].x == 1); // expected-warning{{UNKNOWN}} + + s = new S[4]; + clang_analyzer_eval(s[0].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(s[1].x == 1); // expected-warning{{TRUE}} } struct NullS { Index: clang/test/Analysis/new.cpp =================================================================== --- clang/test/Analysis/new.cpp +++ clang/test/Analysis/new.cpp @@ -323,8 +323,8 @@ void testArrayDestr() { NoReturnDtor *p = new NoReturnDtor[2]; - delete[] p; // Calls the base destructor which aborts, checked below - //TODO: clang_analyzer_eval should not be called + delete[] p; // Calls the base destructor which aborts, checked below + // TODO: clang_analyzer_eval should not be called clang_analyzer_eval(true); // expected-warning{{TRUE}} } Index: clang/test/Analysis/operator-calls.cpp =================================================================== --- clang/test/Analysis/operator-calls.cpp +++ clang/test/Analysis/operator-calls.cpp @@ -104,28 +104,44 @@ // synthesized assignment operator being undefined. void testNoWarning() { B v, u; - u = v; + + v.a[0].a = 0; + v.a[1].a = 0; + v.a[2].a = 0; + + u = v; // expected-warning@99 {{Assigned value is garbage or undefined}} } void testNoWarningMove() { B v, u; - u = static_cast(v); + + v.a[0].a = 0; + v.a[1].a = 0; + v.a[2].a = 0; + + u = static_cast(v); // expected-warning@100 {{Assigned value is garbage or undefined}} } void testConsistency() { B v, u; + v.x = 0; + v.a[0].a = 24; v.a[1].a = 47; v.a[2].a = 42; u = v; + clang_analyzer_eval(u.a[0].a == -24); // expected-warning{{TRUE}} clang_analyzer_eval(u.a[1].a == -47); // expected-warning{{TRUE}} clang_analyzer_eval(u.a[2].a == -42); // expected-warning{{TRUE}} } void testConsistencyMove() { B v, u; + v.x = 0; + v.a[0].a = 24; v.a[1].a = 47; v.a[2].a = 42; u = static_cast(v); + clang_analyzer_eval(u.a[0].a == 25); // expected-warning{{TRUE}} clang_analyzer_eval(u.a[1].a == 48); // expected-warning{{TRUE}} clang_analyzer_eval(u.a[2].a == 43); // expected-warning{{TRUE}} } Index: clang/utils/analyzer/exploded-graph-rewriter.py =================================================================== --- clang/utils/analyzer/exploded-graph-rewriter.py +++ clang/utils/analyzer/exploded-graph-rewriter.py @@ -144,7 +144,8 @@ # A deserialized Environment. This class can also hold other entities that -# are similar to Environment, such as Objects Under Construction. +# are similar to Environment, such as Objects Under Construction or +# Indices Of Elements Under Construction. class GenericEnvironment: def __init__(self, json_e): self.frames = [EnvironmentFrame(f) for f in json_e] @@ -269,6 +270,7 @@ 'constraints': None, 'dynamic_types': None, 'constructing_objects': None, + 'index_of_element': None, 'checker_messages': None } @@ -296,6 +298,10 @@ GenericEnvironment(json_ps['constructing_objects']) \ if json_ps['constructing_objects'] is not None else None + self.index_of_element = \ + GenericEnvironment(json_ps['index_of_element']) \ + if json_ps['index_of_element'] is not None else None + self.checker_messages = CheckerMessages(json_ps['checker_messages']) \ if json_ps['checker_messages'] is not None else None @@ -796,6 +802,9 @@ self.visit_environment_in_state('constructing_objects', 'Objects Under Construction', s, prev_s) + self.visit_environment_in_state('index_of_element', + 'Indices Of Elements Under Construction', + s, prev_s) self.visit_checker_messages_in_state(s, prev_s) def visit_node(self, node):