diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/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 CXXConstructExpr *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 parameter 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 = {}); + /// Checks whether our 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); @@ -838,7 +865,7 @@ const Expr *InitWithAdjustments, const Expr *Result = nullptr, const SubRegion **OutRegionWithAdjustments = nullptr); - /// Returns a region representing the first element of a (possibly + /// Returns a region representing the `Idx`th element of a (possibly /// multi-dimensional) array, for the purposes of element construction or /// destruction. /// @@ -846,8 +873,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); + static SVal makeElementRegion(ProgramStateRef State, SVal LValue, + 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 +905,17 @@ 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 CXXConstructExpr *E, + const LocationContext *LCtx, unsigned Idx); + + static ProgramStateRef + removeIndexOfElementToConstruct(ProgramStateRef State, + const CXXConstructExpr *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 diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -185,6 +185,17 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction, ObjectsUnderConstructionMap) +// This trait is responsible for storing the index of the element that is to be +// constructed in the next iteration. As a result a CXXConstructExpr is only +// stored if it is array type. Also the index is the index of the continous +// memory region, which is important for multi-dimensional arrays. E.g:: int +// arr[2][2]; assume arr[1][1] will be the next element under construction, so +// the index is 3. +typedef llvm::ImmutableMap< + std::pair, unsigned> + IndexOfElementToConstructMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(IndexOfElementToConstruct, + IndexOfElementToConstructMap) //===----------------------------------------------------------------------===// // Engine construction and deletion. //===----------------------------------------------------------------------===// @@ -441,16 +452,65 @@ return State; } +ProgramStateRef ExprEngine::setIndexOfElementToConstruct( + ProgramStateRef State, const CXXConstructExpr *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 CXXConstructExpr *E, + const LocationContext *LCtx) { + + return Optional::create( + State->get({E, LCtx->getStackFrame()})); +} + +ProgramStateRef +ExprEngine::removeIndexOfElementToConstruct(ProgramStateRef State, + const CXXConstructExpr *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, const LocationContext *LC, SVal V) { ConstructedObjectKey Key(Item, LC->getStackFrame()); + + const CXXConstructExpr *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 = dyn_cast(CtorInit->getInit()); + } + // FIXME: Currently the state might already contain the marker due to // incorrect handling of temporaries bound to default parameters. - assert(!State->get(Key) || - Key.getItem().getKind() == - ConstructionContextItem::TemporaryDestructorKind); + // 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 exit the + // constructor call. + assert((!State->get(Key) || + Key.getItem().getKind() == + ConstructionContextItem::TemporaryDestructorKind || + State->contains({E, LC})) && + "The object is already marked as `UnderConstruction`, when it's not " + "supposed to!"); return State->set(Key, V); } @@ -582,6 +642,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 { @@ -600,6 +723,23 @@ Out << "null," << NL; } + 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; + } + getCheckerManager().runCheckersForPrintStateJson(Out, State, NL, Space, IsDot); } @@ -961,8 +1101,9 @@ // This workaround will just run the first destructor (which will still // invalidate the entire array). EvalCallOptions CallOpts; - Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType, - CallOpts.IsArrayCtorOrDtor).getAsRegion(); + Region = makeElementRegion(state, loc::MemRegionVal(Region), varType, + CallOpts.IsArrayCtorOrDtor) + .getAsRegion(); VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), /*IsBase=*/false, Pred, Dst, CallOpts); @@ -1045,8 +1186,7 @@ // This workaround will just run the first destructor (which will still // invalidate the entire array). EvalCallOptions CallOpts; - FieldVal = makeZeroElementRegion(State, FieldVal, T, - CallOpts.IsArrayCtorOrDtor); + FieldVal = makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor); VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(), /*IsBase=*/false, Pred, Dst, CallOpts); @@ -1105,7 +1245,7 @@ CallOpts.IsArrayCtorOrDtor = true; } } else { - // We'd eventually need to makeZeroElementRegion() trick here, + // We'd eventually need to makeElementRegion() trick here, // but for now we don't have the respective construction contexts, // so MR would always be null in this case. Do nothing for now. } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -94,15 +94,17 @@ } } - -SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, - QualType &Ty, bool &IsArray) { +SVal ExprEngine::makeElementRegion(ProgramStateRef State, SVal LValue, + 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 +113,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(); @@ -125,8 +127,8 @@ const auto *DS = DSCC->getDeclStmt(); const auto *Var = cast(DS->getSingleDecl()); QualType Ty = Var->getType(); - return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty, - CallOpts.IsArrayCtorOrDtor); + return makeElementRegion(State, State->getLValue(Var, LCtx), Ty, + CallOpts.IsArrayCtorOrDtor, Idx); } case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { @@ -158,8 +160,8 @@ } QualType Ty = Field->getType(); - return makeZeroElementRegion(State, FieldVal, Ty, - CallOpts.IsArrayCtorOrDtor); + return makeElementRegion(State, FieldVal, Ty, CallOpts.IsArrayCtorOrDtor, + Idx); } case ConstructionContext::NewAllocatedObjectKind: { if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { @@ -172,8 +174,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 +490,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 +502,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 +902,39 @@ 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(); + // If each element is initialized by their default constructor, the field + // values are properly placed inside the required region, however if an + // initializer list is used, this doesn't happen automatically. + auto *Init = CNE->getInitializer(); + bool isInitList = dyn_cast_or_null(Init); + + QualType ObjTy = + isInitList ? Init->getType() : CNE->getType()->getPointeeType(); const ElementRegion *EleReg = - getStoreManager().GetElementZeroRegion(NewReg, ObjTy); + MRMgr.getElementRegion(ObjTy, svalBuilder.makeArrayIndex(0), NewReg, + svalBuilder.getContext()); Result = loc::MemRegionVal(EleReg); + + // If the array is list initialized, we bind the initializer list to the + // memory region here, otherwise we would lose it. + if (isInitList) { + Bldr.takeNodes(Pred); + Pred = Bldr.generateNode(CNE, Pred, State); + + SVal V = State->getSVal(Init, LCtx); + ExplodedNodeSet evaluated; + evalBind(evaluated, CNE, Pred, Result, V, true); + + Bldr.takeNodes(Pred); + Bldr.addNodes(evaluated); + + Pred = *evaluated.begin(); + State = Pred->getState(); + } } + State = State->BindExpr(CNE, Pred->getLocationContext(), Result); Bldr.generateNode(CNE, Pred, State); return; diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -227,6 +227,13 @@ // Step 2: generate node with bound return value: CEBNode -> BindedRetNode. + // If this variable is set to 'true' the analyzer will evaluate the call + // statement we are about to exit again, instead of continuing the execution + // from the statement after the call. This is useful for non-POD type array + // construction where the CXXConstructExpr is referenced only once in the CFG, + // but we want to evaluate it as many times as many elements the array has. + bool ShouldRepeatCall = 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 +262,12 @@ SVal ThisV = state->getSVal(This); ThisV = state->getSVal(ThisV.castAs()); state = state->BindExpr(CCE, callerCtx, ThisV); + + ShouldRepeatCall = shouldRepeatCtorCall(state, CCE, callerCtx); + + if (!ShouldRepeatCall && + getIndexOfElementToConstruct(state, CCE, callerCtx)) + state = removeIndexOfElementToConstruct(state, CCE, callerCtx); } if (const auto *CNE = dyn_cast(CE)) { @@ -358,9 +371,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() + (ShouldRepeatCall ? 0 : 1); + + Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), Idx); } } } @@ -800,8 +814,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 +869,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 +1082,38 @@ return true; } +bool ExprEngine::shouldInlineArrayConstruction(const ArrayType *Type) { + if (!Type) + return false; + + // FIXME: Handle other arrays types. + if (const auto *CAT = dyn_cast(Type)) { + unsigned Size = getContext().getConstantArrayElementCount(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 = getContext().getConstantArrayElementCount(CAT); + return Size > getIndexOfElementToConstruct(State, E, LCtx); + } + + return false; +} + static bool isTrivialObjectAssignment(const CallEvent &Call) { const CXXInstanceCall *ICall = dyn_cast(&Call); if (!ICall) diff --git a/clang/test/Analysis/ctor-array.cpp b/clang/test/Analysis/ctor-array.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/ctor-array.cpp @@ -0,0 +1,259 @@ +// 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}} +} + +void h1(void) { + s5::c = 0; + s5 a[2][2], b[2][2]; + + clang_analyzer_eval(a[0][0].x == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(a[0][1].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(a[1][0].x == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(a[1][1].x == 3); // expected-warning{{TRUE}} + + clang_analyzer_eval(b[0][0].x == 4); // expected-warning{{TRUE}} + clang_analyzer_eval(b[0][1].x == 5); // expected-warning{{TRUE}} + clang_analyzer_eval(b[1][0].x == 6); // expected-warning{{TRUE}} + clang_analyzer_eval(b[1][1].x == 7); // expected-warning{{TRUE}} +} + +void h2(void) { + s a[2][2], b[2][2]; + + int x = a[1][1].x; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +void h3(void) { + s a[2][2], b[2][2]; + + int x = b[1][1].y; + // expected-warning@-1{{Assigned value is garbage or undefined}} +} + +struct Base { + int x; + int y; + + Base(int x, int y) : x(x), y(y) {} +}; + +struct Derived : public Base { + int i; + int j; + + Derived(int x, int y, int i, int j) : Base(x, y), i(i), j(j) {} +}; + +void delegate(void) { + Derived arr[2] = {{1, 2, 3, 4}, {5, 6, 7, 8}}; + + clang_analyzer_eval(arr[0].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].y == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].i == 3); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].j == 4); // expected-warning{{TRUE}} + + clang_analyzer_eval(arr[1].x == 5); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].y == 6); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].i == 7); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].j == 8); // expected-warning{{TRUE}} +} + +void delegate_heap(void) { + Derived *arr = new Derived[2]{{1, 2, 3, 4}, {5, 6, 7, 8}}; + + clang_analyzer_eval(arr[0].x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].y == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].i == 3); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0].j == 4); // expected-warning{{TRUE}} + + clang_analyzer_eval(arr[1].x == 5); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].y == 6); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].i == 7); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1].j == 8); // expected-warning{{TRUE}} +} + +struct Member { + int x; + int y; +}; + +struct Parent { + Member arr[2]; + + Parent() : arr{{1, 2}, {3, 4}} {} +}; + +void member() { + Parent arr[2]; + + // FIXME: Ideally these are TRUE, but at the moment InitListExpr has no + // knowledge about where the initializer list is used, so we can't bind + // the initializer list to the required region. + clang_analyzer_eval(arr[0].arr[0].x == 1); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[0].arr[0].y == 2); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[0].arr[1].x == 3); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[0].arr[1].y == 4); // expected-warning{{UNKNOWN}} + + clang_analyzer_eval(arr[1].arr[0].x == 1); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[1].arr[0].y == 2); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[1].arr[1].x == 3); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(arr[1].arr[1].y == 4); // expected-warning{{UNKNOWN}} +} diff --git a/clang/test/Analysis/ctor.mm b/clang/test/Analysis/ctor.mm --- a/clang/test/Analysis/ctor.mm +++ b/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 { diff --git a/clang/test/Analysis/cxxctr-array-evalcall-analysis-order.cpp b/clang/test/Analysis/cxxctr-array-evalcall-analysis-order.cpp new file mode 100644 --- /dev/null +++ b/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] diff --git a/clang/test/Analysis/dtor.cpp b/clang/test/Analysis/dtor.cpp --- a/clang/test/Analysis/dtor.cpp +++ b/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}} } diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot --- a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot +++ b/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": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot --- a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot +++ b/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", diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/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] }" } diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/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 } } diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/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", diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/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", diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/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", diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/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", diff --git a/clang/test/Analysis/exploded-graph-rewriter/topology.dot b/clang/test/Analysis/exploded-graph-rewriter/topology.dot --- a/clang/test/Analysis/exploded-graph-rewriter/topology.dot +++ b/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"] } ], diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -47,6 +47,7 @@ // CHECK-NEXT: "dynamic_types": null, // CHECK-NEXT: "dynamic_casts": null, // CHECK-NEXT: "constructing_objects": null, +// CHECK-NEXT: "index_of_element": null, // CHECK-NEXT: "checker_messages": null // CHECK-NEXT: } diff --git a/clang/test/Analysis/handle_constructors_with_new_array.cpp b/clang/test/Analysis/handle_constructors_with_new_array.cpp --- a/clang/test/Analysis/handle_constructors_with_new_array.cpp +++ b/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; diff --git a/clang/test/Analysis/new-ctor-conservative.cpp b/clang/test/Analysis/new-ctor-conservative.cpp --- a/clang/test/Analysis/new-ctor-conservative.cpp +++ b/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 { diff --git a/clang/test/Analysis/new.cpp b/clang/test/Analysis/new.cpp --- a/clang/test/Analysis/new.cpp +++ b/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}} } diff --git a/clang/test/Analysis/operator-calls.cpp b/clang/test/Analysis/operator-calls.cpp --- a/clang/test/Analysis/operator-calls.cpp +++ b/clang/test/Analysis/operator-calls.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,alpha.core,debug.ExprInspection -verify %s +// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,alpha.core,debug.ExprInspection -analyzer-output=text -verify %s void clang_analyzer_eval(bool); struct X0 { }; @@ -29,6 +29,7 @@ void testMemberOperator(IntComparable B) { clang_analyzer_eval(B == 0); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} } @@ -46,7 +47,9 @@ void test(const Convertible &obj) { clang_analyzer_eval((int)obj == 42); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} clang_analyzer_eval(obj); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} } } @@ -82,7 +85,15 @@ // Force a cache-out when we try to conjure a temporary region for the operator call. // ...then, don't crash. clang_analyzer_eval(+(coin ? getSmallOpaque() : getSmallOpaque())); // expected-warning{{UNKNOWN}} + // expected-note@-1{{Assuming 'coin' is 0}} + // expected-note@-2{{'?' condition is false}} + // expected-note@-3{{UNKNOWN}} + // expected-note@-4{{Assuming 'coin' is 0}} + // expected-note@-5{{'?' condition is false}} clang_analyzer_eval(+(coin ? getLargeOpaque() : getLargeOpaque())); // expected-warning{{UNKNOWN}} + // expected-note@-1{{'coin' is 0}} + // expected-note@-2{{'?' condition is false}} + // expected-note@-3{{UNKNOWN}} } } @@ -102,31 +113,53 @@ // This used to produce a warning about the iteration variable in the // synthesized assignment operator being undefined. + // + // Note: The warning we want to avoid can be found in https://bugs.llvm.org/show_bug.cgi?id=16745. + // Back in the day, this function was created we couldn't evaluate non-POD type array construction, + // so we couldn't evaluate the copy assignment either, hence we didn't detect that a field is + // uninitialized. void testNoWarning() { + B v, u; - u = v; + u = v; // expected-warning@110{{Assigned value is garbage or undefined}} + // expected-note@-1{{Calling defaulted copy assignment operator for 'B'}} + // expected-note@110{{Assigned value is garbage or undefined}} } void testNoWarningMove() { B v, u; - u = static_cast(v); + u = static_cast(v); // expected-warning@111{{Assigned value is garbage or undefined}} + // expected-note@-1{{Calling defaulted move assignment operator for 'B'}} + // expected-note@111{{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}} + // expected-note@-1{{TRUE}} clang_analyzer_eval(u.a[1].a == -47); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} clang_analyzer_eval(u.a[2].a == -42); // expected-warning{{TRUE}} + // expected-note@-1{{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}} + // expected-note@-1{{TRUE}} clang_analyzer_eval(u.a[1].a == 48); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} clang_analyzer_eval(u.a[2].a == 43); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} } } diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/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):