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,11 +617,16 @@ return svalBuilder.evalBinOp(ST, Op, LHS, RHS, T); } - /// Retreives which element is being constructed in a non POD type array. + /// Retreives which element is being constructed in a non-POD type array. static Optional getIndexOfElementToConstruct(ProgramStateRef State, const CXXConstructExpr *E, const LocationContext *LCtx); + /// Retreives which element is being destructed in a non-POD type array. + static Optional + getPendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx); + /// Retreives the size of the array in the pending ArrayInitLoopExpr. static Optional getPendingInitLoop(ProgramStateRef State, const CXXConstructExpr *E, @@ -825,6 +830,27 @@ const CXXConstructExpr *CE, const LocationContext *LCtx); + /// Checks whether our policies allow us to inline a non-POD type array + /// destruction. + /// \param Size The size of the array. + bool shouldInlineArrayDestruction(uint64_t Size); + + /// Prepares the program state for array destruction. If no error happens + /// the function binds a 'PendingArrayDestruction' entry to the state, which + /// it returns along with the index. If any error happens (we fail to read + /// the size, the index would be -1, etc.) the function will return the + /// original state along with an index of 0. The actual element count of the + /// array can be accessed by the optional 'ElementCountVal' parameter. \param + /// State The program state. \param Region The memory region where the array + /// is stored. \param ElementTy The type an element in the array. \param LCty + /// The location context. \param ElementCountVal A pointer to an optional + /// SVal. If specified, the size of the array will be returned in it. It can + /// be Unknown. + std::pair prepareStateForArrayDestruction( + const ProgramStateRef State, const MemRegion *Region, + const QualType &ElementTy, const LocationContext *LCtx, + SVal *ElementCountVal = nullptr); + /// 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, @@ -923,6 +949,16 @@ const CXXConstructExpr *E, const LocationContext *LCtx); + /// Assuming we destruct an array of non-POD types, this method allows us + /// to store which element is to be destructed next. + static ProgramStateRef setPendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx, + unsigned Idx); + + static ProgramStateRef + removePendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx); + /// Sets the size of the array in a pending ArrayInitLoopExpr. static ProgramStateRef setPendingInitLoop(ProgramStateRef State, const CXXConstructExpr *E, Index: clang/lib/Analysis/CFG.cpp =================================================================== --- clang/lib/Analysis/CFG.cpp +++ clang/lib/Analysis/CFG.cpp @@ -1913,9 +1913,10 @@ for (auto *FI : RD->fields()) { // Check for constant size array. Set type to array element type. QualType QT = FI->getType(); - if (const ConstantArrayType *AT = Context->getAsConstantArrayType(QT)) { + // It may be a multidimensional array. + while (const ConstantArrayType *AT = Context->getAsConstantArrayType(QT)) { if (AT->getSize() == 0) - continue; + break; QT = AT->getElementType(); } @@ -5275,8 +5276,19 @@ const CXXTemporary *temp = bindExpr->getTemporary(); return temp->getDestructor(); } + case CFGElement::MemberDtor: { + const FieldDecl *field = castAs().getFieldDecl(); + QualType ty = field->getType(); + + while (const ArrayType *arrayType = astContext.getAsArrayType(ty)) { + ty = arrayType->getElementType(); + } + + const CXXRecordDecl *classDecl = ty->getAsCXXRecordDecl(); + assert(classDecl); + return classDecl->getDestructor(); + } case CFGElement::BaseDtor: - case CFGElement::MemberDtor: // Not yet supported. return nullptr; } Index: clang/lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -48,6 +48,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" @@ -204,6 +205,12 @@ std::pair, unsigned> PendingInitLoopMap; REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingInitLoop, PendingInitLoopMap) + +typedef llvm::ImmutableMap + PendingArrayDestructionMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingArrayDestruction, + PendingArrayDestructionMap) + //===----------------------------------------------------------------------===// // Engine construction and deletion. //===----------------------------------------------------------------------===// @@ -517,6 +524,35 @@ return State->remove(Key); } +Optional +ExprEngine::getPendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx) { + assert(LCtx && "LocationContext shouldn't be null!"); + + return Optional::create( + State->get(LCtx->getStackFrame())); +} + +ProgramStateRef ExprEngine::setPendingArrayDestruction( + ProgramStateRef State, const LocationContext *LCtx, unsigned Idx) { + assert(LCtx && "LocationContext shouldn't be null!"); + + auto Key = LCtx->getStackFrame(); + + return State->set(Key, Idx); +} + +ProgramStateRef +ExprEngine::removePendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx) { + assert(LCtx && "LocationContext shouldn't be null!"); + + auto Key = LCtx->getStackFrame(); + + assert(LCtx && State->contains(Key)); + return State->remove(Key); +} + ProgramStateRef ExprEngine::addObjectUnderConstruction(ProgramStateRef State, const ConstructionContextItem &Item, @@ -1071,6 +1107,41 @@ Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx); } +std::pair +ExprEngine::prepareStateForArrayDestruction(const ProgramStateRef State, + const MemRegion *Region, + const QualType &ElementTy, + const LocationContext *LCtx, + SVal *ElementCountVal) { + + QualType Ty = ElementTy.getDesugaredType(getContext()); + while (const auto *NTy = dyn_cast(Ty)) + Ty = NTy->getElementType().getDesugaredType(getContext()); + + auto ElementCount = getDynamicElementCount(State, Region, svalBuilder, Ty); + + if (ElementCountVal) + *ElementCountVal = ElementCount; + + // Note: the destructors are called in reverse order. + unsigned Idx = 0; + if (auto OptionalIdx = getPendingArrayDestruction(State, LCtx)) { + Idx = *OptionalIdx; + } else { + if (ElementCount.isUnknownOrUndef()) + return {State, 0}; + + Idx = ElementCount.getAsInteger()->getLimitedValue(); + } + + if (Idx == 0) + return {State, 0}; + + --Idx; + + return {setPendingArrayDestruction(State, LCtx, Idx), Idx}; +} + void ExprEngine::ProcessImplicitDtor(const CFGImplicitDtor D, ExplodedNode *Pred) { ExplodedNodeSet Dst; @@ -1120,11 +1191,14 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + const auto *DtorDecl = Dtor.getDestructorDecl(getContext()); const VarDecl *varDecl = Dtor.getVarDecl(); QualType varType = varDecl->getType(); ProgramStateRef state = Pred->getState(); - SVal dest = state->getLValue(varDecl, Pred->getLocationContext()); + const LocationContext *LCtx = Pred->getLocationContext(); + + SVal dest = state->getLValue(varDecl, LCtx); const MemRegion *Region = dest.castAs().getRegion(); if (varType->isReferenceType()) { @@ -1140,14 +1214,43 @@ varType = cast(Region)->getValueType(); } - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). + unsigned Idx = 0; + if (const auto *AT = dyn_cast(varType)) { + SVal ElementCount; + std::tie(state, Idx) = prepareStateForArrayDestruction( + state, Region, varType, LCtx, &ElementCount); + + if (ElementCount.isConstant()) { + uint64_t ArrayLength = ElementCount.getAsInteger()->getLimitedValue(); + assert(ArrayLength && + "An automatic dtor for a 0 length array shouldn't be triggered!"); + + // Still handle this case if we don't have assertions enabled. + if (!ArrayLength) { + static SimpleProgramPointTag PT( + "ExprEngine", "Skipping automatic 0 length array destruction, " + "which shouldn't be in the CFG."); + PostImplicitCall PP(DtorDecl, varDecl->getLocation(), LCtx, &PT); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateSink(PP, Pred->getState(), Pred); + return; + } + } + } + EvalCallOptions CallOpts; Region = makeElementRegion(state, loc::MemRegionVal(Region), varType, - CallOpts.IsArrayCtorOrDtor) + CallOpts.IsArrayCtorOrDtor, Idx) .getAsRegion(); + NodeBuilder Bldr(Pred, Dst, getBuilderContext()); + + static SimpleProgramPointTag PT("ExprEngine", + "Prepare for object destruction"); + PreImplicitCall PP(DtorDecl, varDecl->getLocation(), LCtx, &PT); + Pred = Bldr.generateNode(PP, state, Pred); + Bldr.takeNodes(Pred); + VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), /*IsBase=*/false, Pred, Dst, CallOpts); } @@ -1175,20 +1278,50 @@ return; } + auto getDtorDecl = [](const QualType &DTy) { + const CXXRecordDecl *RD = DTy->getAsCXXRecordDecl(); + return RD->getDestructor(); + }; + + unsigned Idx = 0; EvalCallOptions CallOpts; const MemRegion *ArgR = ArgVal.getAsRegion(); + if (DE->isArrayForm()) { - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). + SVal ElementCount; + std::tie(State, Idx) = + prepareStateForArrayDestruction(State, ArgR, DTy, LCtx, &ElementCount); + CallOpts.IsArrayCtorOrDtor = true; // Yes, it may even be a multi-dimensional array. while (const auto *AT = getContext().getAsArrayType(DTy)) DTy = AT->getElementType(); + + // If we're about to destruct a 0 length array, don't run any of the + // destructors. + if (ElementCount.isConstant() && + ElementCount.getAsInteger()->getLimitedValue() == 0) { + + static SimpleProgramPointTag PT( + "ExprEngine", "Skipping 0 length array delete destruction"); + PostImplicitCall PP(getDtorDecl(DTy), DE->getBeginLoc(), LCtx, &PT); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateNode(PP, Pred->getState(), Pred); + return; + } + if (ArgR) - ArgR = getStoreManager().GetElementZeroRegion(cast(ArgR), DTy); + ArgR = State->getLValue(DTy, svalBuilder.makeArrayIndex(Idx), ArgVal) + .getAsRegion(); } + NodeBuilder Bldr(Pred, Dst, getBuilderContext()); + static SimpleProgramPointTag PT("ExprEngine", + "Prepare for object destruction"); + PreImplicitCall PP(getDtorDecl(DTy), DE->getBeginLoc(), LCtx, &PT); + Pred = Bldr.generateNode(PP, State, Pred); + Bldr.takeNodes(Pred); + VisitCXXDestructor(DTy, ArgR, DE, /*IsBase=*/false, Pred, Dst, CallOpts); } @@ -1214,6 +1347,7 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + const auto *DtorDecl = D.getDestructorDecl(getContext()); const FieldDecl *Member = D.getFieldDecl(); QualType T = Member->getType(); ProgramStateRef State = Pred->getState(); @@ -1225,11 +1359,41 @@ Loc ThisLoc = State->getSVal(ThisStorageLoc).castAs(); SVal FieldVal = State->getLValue(Member, ThisLoc); - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). + unsigned Idx = 0; + if (const auto *AT = dyn_cast(T)) { + SVal ElementCount; + std::tie(State, Idx) = prepareStateForArrayDestruction( + State, FieldVal.getAsRegion(), T, LCtx, &ElementCount); + + if (ElementCount.isConstant()) { + uint64_t ArrayLength = ElementCount.getAsInteger()->getLimitedValue(); + assert(ArrayLength && + "A member dtor for a 0 length array shouldn't be triggered!"); + + // Still handle this case if we don't have assertions enabled. + if (!ArrayLength) { + static SimpleProgramPointTag PT( + "ExprEngine", "Skipping member 0 length array destruction, which " + "shouldn't be in the CFG."); + PostImplicitCall PP(DtorDecl, Member->getLocation(), LCtx, &PT); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateSink(PP, Pred->getState(), Pred); + return; + } + } + } + EvalCallOptions CallOpts; - FieldVal = makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor); + FieldVal = + makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor, Idx); + + NodeBuilder Bldr(Pred, Dst, getBuilderContext()); + + static SimpleProgramPointTag PT("ExprEngine", + "Prepare for object destruction"); + PreImplicitCall PP(DtorDecl, Member->getLocation(), LCtx, &PT); + Pred = Bldr.generateNode(PP, State, Pred); + Bldr.takeNodes(Pred); VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(), /*IsBase=*/false, Pred, Dst, CallOpts); @@ -1280,15 +1444,31 @@ EvalCallOptions CallOpts; CallOpts.IsTemporaryCtorOrDtor = true; if (!MR) { - // If we have no MR, we still need to unwrap the array to avoid destroying - // the whole array at once. Regardless, we'd eventually need to model array - // destructors properly, element-by-element. + // FIXME: If we have no MR, we still need to unwrap the array to avoid + // destroying the whole array at once. + // + // For this case there is no universal solution as there is no way to + // directly create an array of temporary objects. There are some expressions + // however which can create temporary objects and have an array type. + // + // E.g.: std::initializer_list{S(), S()}; + // + // The expression above has a type of 'const struct S[2]' but it's a single + // 'std::initializer_list<>'. The destructors of the 2 temporary 'S()' + // objects will be called anyway, because they are 2 separate objects in 2 + // separate clusters, i.e.: not an array. + // + // Now the 'std::initializer_list<>' is not an array either even though it + // has the type of an array. The point is, we only want to invoke the + // destructor for the initializer list once not twice or so. while (const ArrayType *AT = getContext().getAsArrayType(T)) { T = AT->getElementType(); - CallOpts.IsArrayCtorOrDtor = true; + + // FIXME: Enable this flag once we handle this case properly. + // CallOpts.IsArrayCtorOrDtor = true; } } else { - // We'd eventually need to makeElementRegion() trick here, + // FIXME: 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. } Index: clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -171,13 +171,14 @@ if (const SubRegion *MR = dyn_cast_or_null(V.getAsRegion())) { if (NE->isArray()) { - // TODO: In fact, we need to call the constructor for every - // allocated element, not just the first one! CallOpts.IsArrayCtorOrDtor = true; - auto R = MRMgr.getElementRegion(NE->getType()->getPointeeType(), - svalBuilder.makeArrayIndex(Idx), MR, - SVB.getContext()); + auto Ty = NE->getType()->getPointeeType(); + while (const auto *AT = getContext().getAsArrayType(Ty)) + Ty = AT->getElementType(); + + auto R = MRMgr.getElementRegion(Ty, svalBuilder.makeArrayIndex(Idx), + MR, SVB.getContext()); return loc::MemRegionVal(R); } Index: clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -195,6 +195,33 @@ return RuntimeCallee->getCanonicalDecl() != StaticDecl->getCanonicalDecl(); } +// Returns the number of elements in the array currently being destructed. +// If the element count is not found 0 will be returned. +static unsigned getElementCountOfArrayBeingDestructed( + const CallEvent &Call, const ProgramStateRef State, SValBuilder &SVB) { + assert(isa(Call) && + "The call event is not a destructor call!"); + + const auto &DtorCall = cast(Call); + + auto ThisVal = DtorCall.getCXXThisVal(); + + if (auto ThisElementRegion = dyn_cast(ThisVal.getAsRegion())) { + auto ArrayRegion = ThisElementRegion->getAsArrayOffset().getRegion(); + auto ElementType = ThisElementRegion->getElementType(); + + auto ElementCount = + getDynamicElementCount(State, ArrayRegion, SVB, ElementType); + + if (ElementCount.isUnknownOrUndef()) + return 0; + + return ElementCount.getAsInteger()->getLimitedValue(); + } + + return 0; +} + /// The call exit is simulated with a sequence of nodes, which occur between /// CallExitBegin and CallExitEnd. The following operations occur between the /// two program points: @@ -234,6 +261,19 @@ // but we want to evaluate it as many times as many elements the array has. bool ShouldRepeatCall = false; + if (const auto *DtorDecl = + dyn_cast_or_null(Call->getDecl())) { + if (auto Idx = getPendingArrayDestruction(state, callerCtx)) { + ShouldRepeatCall = *Idx > 0; + + auto ThisVal = svalBuilder.getCXXThis(DtorDecl->getParent(), calleeCtx); + state = state->killBinding(ThisVal); + + if (!ShouldRepeatCall) + state = removePendingArrayDestruction(state, callerCtx); + } + } + // If the callee returns an expression, bind its value to CallExpr. if (CE) { if (const ReturnStmt *RS = dyn_cast_or_null(LastSt)) { @@ -818,11 +858,6 @@ !Opts.MayInlineCXXAllocator) return CIP_DisallowedOnce; - // FIXME: We don't handle constructors or destructors for arrays properly. - // Even once we do, we still need to be careful about implicitly-generated - // 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) { if (!shouldInlineArrayConstruction(Pred->getState(), CtorExpr, CurLC)) return CIP_DisallowedOnce; @@ -877,9 +912,12 @@ assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; - // FIXME: We don't handle destructors for arrays properly. - if (CallOpts.IsArrayCtorOrDtor) - return CIP_DisallowedOnce; + if (CallOpts.IsArrayCtorOrDtor) { + if (!shouldInlineArrayDestruction(getElementCountOfArrayBeingDestructed( + Call, Pred->getState(), svalBuilder))) { + return CIP_DisallowedOnce; + } + } // Allow disabling temporary destructor inlining with a separate option. if (CallOpts.IsTemporaryCtorOrDtor && @@ -1096,13 +1134,19 @@ if (!CE) return false; - auto Type = CE->getType(); - // FIXME: Handle other arrays types. - if (const auto *CAT = dyn_cast(Type)) { - unsigned Size = getContext().getConstantArrayElementCount(CAT); - - return Size <= AMgr.options.maxBlockVisitOnPath; + if (const auto *CAT = dyn_cast(CE->getType())) { + unsigned ArrSize = getContext().getConstantArrayElementCount(CAT); + + // This might seem conter-intuitive at first glance, but the functions are + // closely related. Reasoning about destructors depends only on the type + // of the expression that initialized the memory region, which is the + // CXXConstructExpr. So to avoid code repetition, the work is delegated + // to the function that reasons about destructor inlining. Also note that + // if the constructors of the array elements are inlined, the destructors + // can also be inlined and if the destructors can be inline, it's safe to + // inline the constructors. + return shouldInlineArrayDestruction(ArrSize); } // Check if we're inside an ArrayInitLoopExpr, and it's sufficiently small. @@ -1112,6 +1156,14 @@ return false; } +bool ExprEngine::shouldInlineArrayDestruction(uint64_t Size) { + + uint64_t maxAllowedSize = AMgr.options.maxBlockVisitOnPath; + + // Declaring a 0 element array is also possible. + return Size <= maxAllowedSize && Size > 0; +} + bool ExprEngine::shouldRepeatCtorCall(ProgramStateRef State, const CXXConstructExpr *E, const LocationContext *LCtx) { Index: clang/lib/StaticAnalyzer/Core/ProgramState.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -216,8 +216,6 @@ } ProgramStateRef ProgramState::killBinding(Loc LV) const { - assert(!isa(LV) && "Use invalidateRegion instead."); - Store OldStore = getStore(); const StoreRef &newStore = getStateManager().StoreMgr->killBinding(OldStore, LV); Index: clang/test/Analysis/dtor-array.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/dtor-array.cpp @@ -0,0 +1,283 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config c++-inlining=destructors -verify -std=c++11 %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config c++-inlining=destructors -verify -std=c++17 %s + +void clang_analyzer_eval(bool); +void clang_analyzer_checkInlined(bool); +void clang_analyzer_warnIfReached(); + +int a, b, c, d; + +struct InlineDtor { + static int cnt; + static int dtorCalled; + ~InlineDtor() { + switch (dtorCalled % 4) { + case 0: + a = cnt++; + break; + case 1: + b = cnt++; + break; + case 2: + c = cnt++; + break; + case 3: + d = cnt++; + break; + } + + ++dtorCalled; + } +}; + +int InlineDtor::cnt = 0; +int InlineDtor::dtorCalled = 0; + +void foo() { + InlineDtor::cnt = 0; + InlineDtor::dtorCalled = 0; + InlineDtor arr[4]; +} + +void testAutoDtor() { + foo(); + + clang_analyzer_eval(a == 0); // expected-warning {{TRUE}} + clang_analyzer_eval(b == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(c == 2); // expected-warning {{TRUE}} + clang_analyzer_eval(d == 3); // expected-warning {{TRUE}} +} + +void testDeleteDtor() { + InlineDtor::cnt = 10; + InlineDtor::dtorCalled = 0; + + InlineDtor *arr = new InlineDtor[4]; + delete[] arr; + + clang_analyzer_eval(a == 10); // expected-warning {{TRUE}} + clang_analyzer_eval(b == 11); // expected-warning {{TRUE}} + clang_analyzer_eval(c == 12); // expected-warning {{TRUE}} + clang_analyzer_eval(d == 13); // expected-warning {{TRUE}} +} + +struct MemberDtor { + InlineDtor arr[4]; +}; + +void testMemberDtor() { + InlineDtor::cnt = 5; + InlineDtor::dtorCalled = 0; + + MemberDtor *MD = new MemberDtor{}; + delete MD; + + clang_analyzer_eval(a == 5); // expected-warning {{TRUE}} + clang_analyzer_eval(b == 6); // expected-warning {{TRUE}} + clang_analyzer_eval(c == 7); // expected-warning {{TRUE}} + clang_analyzer_eval(d == 8); // expected-warning {{TRUE}} +} + +struct MultipleMemberDtor +{ + InlineDtor arr[4]; + InlineDtor arr2[4]; +}; + +void testMultipleMemberDtor() { + InlineDtor::cnt = 30; + InlineDtor::dtorCalled = 0; + + MultipleMemberDtor *MD = new MultipleMemberDtor{}; + delete MD; + + clang_analyzer_eval(a == 34); // expected-warning {{TRUE}} + clang_analyzer_eval(b == 35); // expected-warning {{TRUE}} + clang_analyzer_eval(c == 36); // expected-warning {{TRUE}} + clang_analyzer_eval(d == 37); // expected-warning {{TRUE}} +} + +int EvalOrderArr[4]; + +struct EvalOrder +{ + int ctor = 0; + static int dtorCalled; + static int ctorCalled; + + EvalOrder() { ctor = ctorCalled++; }; + + ~EvalOrder() { EvalOrderArr[ctor] = dtorCalled++; } +}; + +int EvalOrder::ctorCalled = 0; +int EvalOrder::dtorCalled = 0; + +void dtorEvaluationOrder() { + EvalOrder::ctorCalled = 0; + EvalOrder::dtorCalled = 0; + + EvalOrder* eptr = new EvalOrder[4]; + delete[] eptr; + + clang_analyzer_eval(EvalOrder::dtorCalled == 4); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrder::dtorCalled == EvalOrder::ctorCalled); // expected-warning {{TRUE}} + + clang_analyzer_eval(EvalOrderArr[0] == 3); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[1] == 2); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[2] == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[3] == 0); // expected-warning {{TRUE}} +} + +struct EmptyDtor { + ~EmptyDtor(){}; +}; + +struct DefaultDtor { + ~DefaultDtor() = default; +}; + +// This function used to fail on an assertion. +void no_crash() { + EmptyDtor* eptr = new EmptyDtor[4]; + delete[] eptr; + clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} + + DefaultDtor* dptr = new DefaultDtor[4]; + delete[] dptr; + clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} +} + +// This snippet used to crash. +namespace crash2 +{ + template class unique_ptr { + typedef _Tp *pointer; + pointer __ptr_; + +public: + unique_ptr(pointer __p) : __ptr_(__p) {} + ~unique_ptr() { reset(); } + pointer get() { return __ptr_;} + void reset() {} +}; + +struct S; + +S *makeS(); +int bar(S *x, S *y); + +void foo() { + unique_ptr x(makeS()), y(makeS()); + bar(x.get(), y.get()); +} + +void bar() { + foo(); + clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} +} + +} // namespace crash2 + +// This snippet used to crash. +namespace crash3 +{ +struct InlineDtor { + ~InlineDtor() {} +}; +struct MultipleMemberDtor +{ + InlineDtor arr[4]; + InlineDtor arr2[4]; +}; + +void foo(){ + auto *arr = new MultipleMemberDtor[4]; + delete[] arr; + clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} +} +} // namespace crash3 + +void zeroLength(){ + InlineDtor::dtorCalled = 0; + + auto *arr = new InlineDtor[0]; + delete[] arr; + + auto *arr2 = new InlineDtor[2][0][2]; + delete[] arr2; + + auto *arr3 = new InlineDtor[0][2][2]; + delete[] arr3; + + auto *arr4 = new InlineDtor[2][2][0]; + delete[] arr4; + + // FIXME: Should be TRUE once the constructors are handled properly. + clang_analyzer_eval(InlineDtor::dtorCalled == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}} +} + + +void evalOrderPrep() { + EvalOrderArr[0] = 0; + EvalOrderArr[1] = 0; + EvalOrderArr[2] = 0; + EvalOrderArr[3] = 0; + + EvalOrder::ctorCalled = 0; + EvalOrder::dtorCalled = 0; +} + +void multidimensionalPrep(){ + EvalOrder::ctorCalled = 0; + EvalOrder::dtorCalled = 0; + + EvalOrder arr[2][2]; +} + +void multidimensional(){ + evalOrderPrep(); + multidimensionalPrep(); + + clang_analyzer_eval(EvalOrder::dtorCalled == 4); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrder::dtorCalled == EvalOrder::ctorCalled); // expected-warning {{TRUE}} + + clang_analyzer_eval(EvalOrderArr[0] == 3); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[1] == 2); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[2] == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[3] == 0); // expected-warning {{TRUE}} +} + +void multidimensionalHeap() { + evalOrderPrep(); + + auto* eptr = new EvalOrder[2][2]; + delete[] eptr; + + clang_analyzer_eval(EvalOrder::dtorCalled == 4); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrder::dtorCalled == EvalOrder::ctorCalled); // expected-warning {{TRUE}} + + clang_analyzer_eval(EvalOrderArr[0] == 3); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[1] == 2); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[2] == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[3] == 0); // expected-warning {{TRUE}} +} + +struct MultiWrapper{ + EvalOrder arr[2][2]; +}; + +void multidimensionalMember(){ + evalOrderPrep(); + + auto* mptr = new MultiWrapper; + delete mptr; + + clang_analyzer_eval(EvalOrder::dtorCalled == 4); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrder::dtorCalled == EvalOrder::ctorCalled); // expected-warning {{TRUE}} + + clang_analyzer_eval(EvalOrderArr[0] == 3); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[1] == 2); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[2] == 1); // expected-warning {{TRUE}} + clang_analyzer_eval(EvalOrderArr[3] == 0); // expected-warning {{TRUE}} +} Index: clang/test/Analysis/new.cpp =================================================================== --- clang/test/Analysis/new.cpp +++ clang/test/Analysis/new.cpp @@ -3,6 +3,7 @@ #include "Inputs/system-header-simulator-cxx.h" void clang_analyzer_eval(bool); +void clang_analyzer_warnIfReached(); typedef __typeof__(sizeof(int)) size_t; extern "C" void *malloc(size_t); @@ -323,9 +324,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 - clang_analyzer_eval(true); // expected-warning{{TRUE}} + delete[] p; + clang_analyzer_warnIfReached(); // no-warning } // Invalidate Region even in case of default destructor