Index: clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp +++ clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp @@ -32,24 +32,44 @@ class ContainerModeling : public Checker { - void handleBegin(CheckerContext &C, const Expr *CE, SVal RetVal, - SVal Cont) const; - void handleEnd(CheckerContext &C, const Expr *CE, SVal RetVal, - SVal Cont) const; - void handleAssignment(CheckerContext &C, SVal Cont, const Expr *CE = nullptr, - SVal OldCont = UndefinedVal()) const; - void handleAssign(CheckerContext &C, SVal Cont, const Expr *ContE) const; - void handleClear(CheckerContext &C, SVal Cont, const Expr *ContE) const; - void handlePushBack(CheckerContext &C, SVal Cont, const Expr *ContE) const; - void handlePopBack(CheckerContext &C, SVal Cont, const Expr *ContE) const; - void handlePushFront(CheckerContext &C, SVal Cont, const Expr *ContE) const; - void handlePopFront(CheckerContext &C, SVal Cont, const Expr *ContE) const; - void handleInsert(CheckerContext &C, SVal Cont, SVal Iter) const; - void handleErase(CheckerContext &C, SVal Cont, SVal Iter) const; - void handleErase(CheckerContext &C, SVal Cont, SVal Iter1, SVal Iter2) const; - void handleEraseAfter(CheckerContext &C, SVal Cont, SVal Iter) const; - void handleEraseAfter(CheckerContext &C, SVal Cont, SVal Iter1, - SVal Iter2) const; + void handleAssignment(CheckerContext &C, const Expr *CE, SVal Cont, + Optional = None) const; + + // Handler functions for different container operations. + // SVal Parameters: + // - Cont: The affected container (*this) + // - RetVal: Return value of the operation + // - Iter (or Iter1 and Iter2): Iterator parameters + + void handleBegin(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handleEnd(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handleEmpty(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handleAssign(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handleClear(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handlePushBack(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handlePopBack(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handlePushFront(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handlePopFront(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal RetVal) const; + void handleInsert(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal Iter, SVal RetVal) const; + void handleErase(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal Iter, SVal RetVal) const; + void handleErase(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal Iter1, SVal Iter2, SVal RetVal) const; + void handleEraseAfter(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal Iter, SVal RetVal) const; + void handleEraseAfter(CheckerContext &C, const Expr *CE, const Expr *ContE, + SVal Cont, SVal Iter1, SVal Iter2, SVal RetVal) const; + const NoteTag *getChangeTag(CheckerContext &C, StringRef Text, const MemRegion *ContReg, const Expr *ContE) const; @@ -63,54 +83,50 @@ void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; - using NoItParamFn = void (ContainerModeling::*)(CheckerContext &, SVal, - const Expr *) const; - using OneItParamFn = void (ContainerModeling::*)(CheckerContext &, SVal, - SVal) const; - using TwoItParamFn = void (ContainerModeling::*)(CheckerContext &, SVal, SVal, - SVal) const; - - CallDescriptionMap NoIterParamFunctions = { - {{0, "clear", 0}, - &ContainerModeling::handleClear}, - {{0, "assign", 2}, - &ContainerModeling::handleAssign}, - {{0, "push_back", 1}, - &ContainerModeling::handlePushBack}, - {{0, "emplace_back", 1}, - &ContainerModeling::handlePushBack}, - {{0, "pop_back", 0}, - &ContainerModeling::handlePopBack}, - {{0, "push_front", 1}, - &ContainerModeling::handlePushFront}, - {{0, "emplace_front", 1}, - &ContainerModeling::handlePushFront}, - {{0, "pop_front", 0}, - &ContainerModeling::handlePopFront}, + using ZeroItParamFn = + void (ContainerModeling::*)(CheckerContext &, const Expr *, const Expr *, + SVal, SVal) const; + using OneItParamFn = + void (ContainerModeling::*)(CheckerContext &, const Expr *, const Expr *, + SVal, SVal, SVal) const; + using TwoItParamFn = + void (ContainerModeling::*)(CheckerContext &, const Expr *, const Expr *, + SVal, SVal, SVal, SVal) const; + + CallDescriptionMap ZeroIterParamFunctions = { + // Iterators + {{0, "cbegin", 0}, &ContainerModeling::handleBegin}, + {{0, "cend", 0}, &ContainerModeling::handleEnd}, + {{0, "begin", 0}, &ContainerModeling::handleBegin}, + {{0, "end", 0}, &ContainerModeling::handleEnd}, + + // Capacity + {{0, "empty", 0}, &ContainerModeling::handleEmpty}, + + // Modifiers + {{0, "clear", 0}, &ContainerModeling::handleClear}, + {{0, "assign", 2}, &ContainerModeling::handleAssign}, + {{0, "push_back", 1}, &ContainerModeling::handlePushBack}, + {{0, "emplace_back", 1}, &ContainerModeling::handlePushBack}, + {{0, "pop_back", 0}, &ContainerModeling::handlePopBack}, + {{0, "push_front", 1}, &ContainerModeling::handlePushFront}, + {{0, "emplace_front", 1}, &ContainerModeling::handlePushFront}, + {{0, "pop_front", 0}, &ContainerModeling::handlePopFront}, }; CallDescriptionMap OneIterParamFunctions = { - {{0, "insert", 2}, - &ContainerModeling::handleInsert}, - {{0, "emplace", 2}, - &ContainerModeling::handleInsert}, - {{0, "erase", 1}, - &ContainerModeling::handleErase}, - {{0, "erase_after", 1}, - &ContainerModeling::handleEraseAfter}, + {{0, "insert", 2}, &ContainerModeling::handleInsert}, + {{0, "emplace", 2}, &ContainerModeling::handleInsert}, + {{0, "erase", 1}, &ContainerModeling::handleErase}, + {{0, "erase_after", 1}, &ContainerModeling::handleEraseAfter}, }; CallDescriptionMap TwoIterParamFunctions = { - {{0, "erase", 2}, - &ContainerModeling::handleErase}, - {{0, "erase_after", 2}, - &ContainerModeling::handleEraseAfter}, + {{0, "erase", 2}, &ContainerModeling::handleErase}, + {{0, "erase_after", 2}, &ContainerModeling::handleEraseAfter}, }; - }; -bool isBeginCall(const FunctionDecl *Func); -bool isEndCall(const FunctionDecl *Func); bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg); bool frontModifiable(ProgramStateRef State, const MemRegion *Reg); bool backModifiable(ProgramStateRef State, const MemRegion *Reg); @@ -169,51 +185,45 @@ // Overloaded 'operator=' must be a non-static member function. const auto *InstCall = cast(&Call); if (cast(Func)->isMoveAssignmentOperator()) { - handleAssignment(C, InstCall->getCXXThisVal(), Call.getOriginExpr(), - Call.getArgSVal(0)); + handleAssignment(C, Call.getOriginExpr(), InstCall->getCXXThisVal(), + Call.getArgSVal(0)); return; } - handleAssignment(C, InstCall->getCXXThisVal()); + handleAssignment(C, Call.getOriginExpr(), InstCall->getCXXThisVal()); return; } - } else { - if (const auto *InstCall = dyn_cast(&Call)) { - const NoItParamFn *Handler0 = NoIterParamFunctions.lookup(Call); - if (Handler0) { - (this->**Handler0)(C, InstCall->getCXXThisVal(), - InstCall->getCXXThisExpr()); - return; - } - - const OneItParamFn *Handler1 = OneIterParamFunctions.lookup(Call); - if (Handler1) { - (this->**Handler1)(C, InstCall->getCXXThisVal(), Call.getArgSVal(0)); - return; - } + } else if (const auto *InstCall = dyn_cast(&Call)) { + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; - const TwoItParamFn *Handler2 = TwoIterParamFunctions.lookup(Call); - if (Handler2) { - (this->**Handler2)(C, InstCall->getCXXThisVal(), Call.getArgSVal(0), - Call.getArgSVal(1)); - return; - } + SVal RetVal = Call.getReturnValue(); + if (isIteratorType(Call.getResultType())) + RetVal = getReturnIterator(Call); - const auto *OrigExpr = Call.getOriginExpr(); - if (!OrigExpr) - return; + const ZeroItParamFn *Handler0 = ZeroIterParamFunctions.lookup(Call); + if (Handler0) { + (this->**Handler0)(C, OrigExpr, InstCall->getCXXThisExpr(), + InstCall->getCXXThisVal(), RetVal); + return; + } - if (isBeginCall(Func)) { - handleBegin(C, OrigExpr, Call.getReturnValue(), - InstCall->getCXXThisVal()); - return; - } + const OneItParamFn *Handler1 = OneIterParamFunctions.lookup(Call); + if (Handler1) { + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + (this->**Handler1)(C, OrigExpr, InstCall->getCXXThisExpr(), + InstCall->getCXXThisVal(), Arg0, RetVal); + return; + } - if (isEndCall(Func)) { - handleEnd(C, OrigExpr, Call.getReturnValue(), - InstCall->getCXXThisVal()); - return; - } + const TwoItParamFn *Handler2 = TwoIterParamFunctions.lookup(Call); + if (Handler2) { + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + SVal Arg1 = getIteratorArg(Call, 1, C.blockCount()); + (this->**Handler2)(C, OrigExpr, InstCall->getCXXThisExpr(), + InstCall->getCXXThisVal(), Arg0, Arg1, RetVal); + return; } } } @@ -257,7 +267,8 @@ } void ContainerModeling::handleBegin(CheckerContext &C, const Expr *CE, - SVal RetVal, SVal Cont) const { + const Expr *, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -273,13 +284,14 @@ C.getLocationContext(), C.blockCount()); BeginSym = getContainerBegin(State, ContReg); } + State = setIteratorPosition(State, RetVal, IteratorPosition::getPosition(ContReg, BeginSym)); C.addTransition(State); } void ContainerModeling::handleEnd(CheckerContext &C, const Expr *CE, - SVal RetVal, SVal Cont) const { + const Expr *, SVal Cont, SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -295,13 +307,15 @@ C.getLocationContext(), C.blockCount()); EndSym = getContainerEnd(State, ContReg); } + State = setIteratorPosition(State, RetVal, IteratorPosition::getPosition(ContReg, EndSym)); C.addTransition(State); } -void ContainerModeling::handleAssignment(CheckerContext &C, SVal Cont, - const Expr *CE, SVal OldCont) const { +void ContainerModeling::handleAssignment(CheckerContext &C, const Expr *CE, + SVal Cont, + Optional OldCont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -318,8 +332,8 @@ // In case of move, iterators of the old container (except the past-end // iterators) remain valid but refer to the new container - if (!OldCont.isUndef()) { - const auto *OldContReg = OldCont.getAsRegion(); + if (OldCont.hasValue()) { + const auto *OldContReg = OldCont->getAsRegion(); if (OldContReg) { OldContReg = OldContReg->getMostDerivedObjectRegion(); const auto OldCData = getContainerData(State, OldContReg); @@ -376,8 +390,65 @@ C.addTransition(State); } -void ContainerModeling::handleAssign(CheckerContext &C, SVal Cont, - const Expr *ContE) const { +void ContainerModeling::handleEmpty(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, + SVal RetVal) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // If the container already has a begin symbol then use it. Otherwise first + // create a new one. + auto State = C.getState(); + auto *LCtx = C.getLocationContext(); + + auto BeginSym = getContainerBegin(State, ContReg); + if (!BeginSym) { + State = createContainerBegin(State, ContReg, CE, C.getASTContext().LongTy, + LCtx, C.blockCount()); + BeginSym = getContainerBegin(State, ContReg); + } + auto EndSym = getContainerEnd(State, ContReg); + if (!EndSym) { + State = createContainerEnd(State, ContReg, CE, C.getASTContext().LongTy, + LCtx, C.blockCount()); + EndSym = getContainerEnd(State, ContReg); + } + + // We cannot make assumpotions on `UnknownVal`. Let us conjure a symbol + // instead. + if (RetVal.isUnknown()) { + auto &SymMgr = C.getSymbolManager(); + RetVal = nonloc::SymbolVal(SymMgr.conjureSymbol( + CE, LCtx, C.getASTContext().BoolTy, C.blockCount())); + State = State->BindExpr(CE, LCtx, RetVal); + } + + if (const auto DefRetVal = RetVal.getAs()) { + ProgramStateRef StateEmpty, StateNonEmpty; + std::tie(StateEmpty, StateNonEmpty) = + assumeComparison(State, BeginSym, EndSym, *DefRetVal, OO_EqualEqual); + + if (!StateEmpty && !StateNonEmpty) { + // The return value of the `empty()` call contradicts the emptyness of + // the container. + C.generateSink(State, C.getPredecessor()); + return; + } + + if (StateEmpty) + C.addTransition(StateEmpty); + + if (StateNonEmpty) + C.addTransition(StateNonEmpty); + } +} + +void ContainerModeling::handleAssign(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -390,8 +461,9 @@ C.addTransition(State); } -void ContainerModeling::handleClear(CheckerContext &C, SVal Cont, - const Expr *ContE) const { +void ContainerModeling::handleClear(CheckerContext &C, const Expr *CE, + const Expr *ContE, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -419,8 +491,9 @@ C.addTransition(State, ChangeTag); } -void ContainerModeling::handlePushBack(CheckerContext &C, SVal Cont, - const Expr *ContE) const { +void ContainerModeling::handlePushBack(CheckerContext &C, const Expr *CE, + const Expr *ContE, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -459,8 +532,9 @@ } } -void ContainerModeling::handlePopBack(CheckerContext &C, SVal Cont, - const Expr *ContE) const { +void ContainerModeling::handlePopBack(CheckerContext &C, const Expr *CE, + const Expr *ContE, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -499,8 +573,9 @@ } } -void ContainerModeling::handlePushFront(CheckerContext &C, SVal Cont, - const Expr *ContE) const { +void ContainerModeling::handlePushFront(CheckerContext &C, const Expr *CE, + const Expr *ContE, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -534,8 +609,9 @@ } } -void ContainerModeling::handlePopFront(CheckerContext &C, SVal Cont, - const Expr *ContE) const { +void ContainerModeling::handlePopFront(CheckerContext &C, const Expr *CE, + const Expr *ContE, SVal Cont, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -570,8 +646,9 @@ } } -void ContainerModeling::handleInsert(CheckerContext &C, SVal Cont, - SVal Iter) const { +void ContainerModeling::handleInsert(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, SVal Iter, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -601,8 +678,9 @@ } } -void ContainerModeling::handleErase(CheckerContext &C, SVal Cont, - SVal Iter) const { +void ContainerModeling::handleErase(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, SVal Iter, + SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -635,8 +713,9 @@ C.addTransition(State); } -void ContainerModeling::handleErase(CheckerContext &C, SVal Cont, SVal Iter1, - SVal Iter2) const { +void ContainerModeling::handleErase(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, SVal Iter1, + SVal Iter2, SVal RetVal) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -671,8 +750,9 @@ C.addTransition(State); } -void ContainerModeling::handleEraseAfter(CheckerContext &C, SVal Cont, - SVal Iter) const { +void ContainerModeling::handleEraseAfter(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, SVal Iter, + SVal RetVal) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Iter); if (!Pos) @@ -692,8 +772,9 @@ C.addTransition(State); } -void ContainerModeling::handleEraseAfter(CheckerContext &C, SVal Cont, - SVal Iter1, SVal Iter2) const { +void ContainerModeling::handleEraseAfter(CheckerContext &C, const Expr *CE, + const Expr *, SVal Cont, SVal Iter1, + SVal Iter2, SVal RetVal) const { auto State = C.getState(); const auto *Pos1 = getIteratorPosition(State, Iter1); const auto *Pos2 = getIteratorPosition(State, Iter2); @@ -759,20 +840,6 @@ namespace { -bool isBeginCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - return IdInfo->getName().endswith_lower("begin"); -} - -bool isEndCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - return IdInfo->getName().endswith_lower("end"); -} - const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, const MemRegion *Reg) { auto TI = getDynamicTypeInfo(State, Reg); Index: clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp @@ -33,9 +33,9 @@ std::unique_ptr InvalidatedBugType; - void verifyAccess(CheckerContext &C, const SVal &Val) const; - void reportBug(const StringRef &Message, const SVal &Val, - CheckerContext &C, ExplodedNode *ErrNode) const; + void verifyAccess(CheckerContext &C, SVal Val) const; + void reportBug(const StringRef &Message, SVal Val, CheckerContext &C, + ExplodedNode *ErrNode) const; public: InvalidatedIteratorChecker(); @@ -67,7 +67,8 @@ if (const auto *InstCall = dyn_cast(&Call)) { verifyAccess(C, InstCall->getCXXThisVal()); } else { - verifyAccess(C, Call.getArgSVal(0)); + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + verifyAccess(C, Arg0); } } } @@ -114,7 +115,8 @@ verifyAccess(C, BaseVal); } -void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { +void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, + SVal Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && !Pos->isValid()) { @@ -127,7 +129,7 @@ } void InvalidatedIteratorChecker::reportBug(const StringRef &Message, - const SVal &Val, CheckerContext &C, + SVal Val, CheckerContext &C, ExplodedNode *ErrNode) const { auto R = std::make_unique(*InvalidatedBugType, Message, ErrNode); Index: clang/lib/StaticAnalyzer/Checkers/Iterator.h =================================================================== --- clang/lib/StaticAnalyzer/Checkers/Iterator.h +++ clang/lib/StaticAnalyzer/Checkers/Iterator.h @@ -179,11 +179,30 @@ const SVal &Distance); ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, long Scale); + +// Returns states with comparison results assumed to `true` and `false`. If +// only one of them is possible then the second value of the pair is `nullptr`. +// If none is possible then both values are `nullptr`. +std::pair +assumeComparison(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, + DefinedSVal RetVal, OverloadedOperatorKind Op); + bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc); bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, BinaryOperator::Opcode Opc); +// Returns the value of the iterator argument if it non-class value (a pointer +// or a reference). Otherwise it returns the iterator parameter location (see +// CallEvent::getParameterLocation()). +SVal getIteratorArg(const CallEvent &Call, unsigned Index, unsigned BlockCount); + +// If the call returns an class instance iterator by value then this function +// retrieves and returns it from the construction context of the call. +// Otherwise (for non-class types) it simply returns the return value of the +// call. +SVal getReturnIterator(const CallEvent &Call); + } // namespace iterator } // namespace ento } // namespace clang Index: clang/lib/StaticAnalyzer/Checkers/Iterator.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/Iterator.cpp +++ clang/lib/StaticAnalyzer/Checkers/Iterator.cpp @@ -10,12 +10,17 @@ // //===----------------------------------------------------------------------===// +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" + #include "Iterator.h" namespace clang { namespace ento { namespace iterator { +ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, + SymbolRef Sym2, bool Equal); + bool isIteratorType(const QualType &Type) { if (Type->isPointerType()) return true; @@ -24,6 +29,28 @@ return isIterator(CRD); } +static bool hasOperator(const CXXRecordDecl *CRD, OverloadedOperatorKind Op, + unsigned NumParams) { + for (const auto *Method : CRD->methods()) { + if (!Method->isOverloadedOperator()) + continue; + + if (Method->getOverloadedOperator() == Op && + Method->getNumParams() == NumParams) + return true; + } + + for (auto Base : CRD->bases()) { + const auto *BaseCRD = + Base.getType()->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); + + if (BaseCRD && hasOperator(BaseCRD, Op, NumParams)) + return true; + } + + return false; +} + bool isIterator(const CXXRecordDecl *CRD) { if (!CRD) return false; @@ -33,39 +60,27 @@ Name.endswith_lower("it"))) return false; - bool HasCopyCtor = false, HasCopyAssign = true, HasDtor = false, - HasPreIncrOp = false, HasPostIncrOp = false, HasDerefOp = false; - for (const auto *Method : CRD->methods()) { - if (const auto *Ctor = dyn_cast(Method)) { - if (Ctor->isCopyConstructor()) { - HasCopyCtor = !Ctor->isDeleted() && Ctor->getAccess() == AS_public; - } - continue; - } - if (const auto *Dtor = dyn_cast(Method)) { - HasDtor = !Dtor->isDeleted() && Dtor->getAccess() == AS_public; - continue; - } - if (Method->isCopyAssignmentOperator()) { - HasCopyAssign = !Method->isDeleted() && Method->getAccess() == AS_public; - continue; - } - if (!Method->isOverloadedOperator()) - continue; - const auto OPK = Method->getOverloadedOperator(); - if (OPK == OO_PlusPlus) { - HasPreIncrOp = HasPreIncrOp || (Method->getNumParams() == 0); - HasPostIncrOp = HasPostIncrOp || (Method->getNumParams() == 1); - continue; - } - if (OPK == OO_Star) { - HasDerefOp = (Method->getNumParams() == 0); - continue; - } - } + if ((!CRD->hasSimpleCopyConstructor() && + !CRD->hasUserDeclaredCopyConstructor()) || + (!CRD->hasSimpleCopyAssignment() && + !CRD->hasUserDeclaredCopyAssignment()) || + (!CRD->hasSimpleDestructor() && + !CRD->hasUserDeclaredDestructor())) + return false; + + // Operator Prefix ++ + if (!hasOperator(CRD, OO_PlusPlus, 0)) + return false; + + // Operator Postfix ++ + if (!hasOperator(CRD, OO_PlusPlus, 1)) + return false; + + // Operator * + if (!hasOperator(CRD, OO_Star, 0)) + return false; - return HasCopyCtor && HasCopyAssign && HasDtor && HasPreIncrOp && - HasPostIncrOp && HasDerefOp; + return true; } bool isComparisonOperator(OverloadedOperatorKind OK) { @@ -188,8 +203,6 @@ return State->get(Reg); } else if (const auto Sym = Val.getAsSymbol()) { return State->get(Sym); - } else if (const auto LCVal = Val.getAs()) { - return State->get(LCVal->getRegion()); } return nullptr; } @@ -201,10 +214,8 @@ return State->set(Reg, Pos); } else if (const auto Sym = Val.getAsSymbol()) { return State->set(Sym, Pos); - } else if (const auto LCVal = Val.getAs()) { - return State->set(LCVal->getRegion(), Pos); } - return nullptr; + return State; } ProgramStateRef createIteratorPosition(ProgramStateRef State, const SVal &Val, @@ -296,6 +307,62 @@ return NewState; } +std::pair +assumeComparison(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, + DefinedSVal RetVal, OverloadedOperatorKind Op) { + assert(CXXOperatorCallExpr::isComparisonOp(Op) && + "`Op` must be a comparison operator"); + if (const auto TruthVal = RetVal.getAs()) { + State = relateSymbols(State, Sym1, Sym2, + (Op == OO_EqualEqual) == TruthVal->getValue()); + return std::make_pair(State, nullptr); + } + + ProgramStateRef StateTrue = + relateSymbols(State, Sym1, Sym2, Op == OO_EqualEqual); + ProgramStateRef StateFalse = + relateSymbols(State, Sym1, Sym2, Op != OO_EqualEqual); + + if (StateTrue) + StateTrue = StateTrue->assume(RetVal, true); + if (StateFalse) + StateFalse = StateFalse->assume(RetVal, false); + + return std::make_pair(StateTrue, StateFalse); +} + +ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, + SymbolRef Sym2, bool Equal) { + auto &SVB = State->getStateManager().getSValBuilder(); + + // FIXME: This code should be reworked as follows: + // 1. Subtract the operands using evalBinOp(). + // 2. Assume that the result doesn't overflow. + // 3. Compare the result to 0. + // 4. Assume the result of the comparison. + const auto comparison = + SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(Sym1), + nonloc::SymbolVal(Sym2), SVB.getConditionType()); + + assert(comparison.getAs() && + "Symbol comparison must be a `DefinedSVal`"); + + auto NewState = State->assume(comparison.castAs(), Equal); + if (!NewState) + return nullptr; + + if (const auto CompSym = comparison.getAsSymbol()) { + assert(isa(CompSym) && + "Symbol comparison must be a `SymIntExpr`"); + assert(BinaryOperator::isComparisonOp( + cast(CompSym)->getOpcode()) && + "Symbol comparison must be a comparison"); + return assumeNoOverflow(NewState, cast(CompSym)->getLHS(), 2); + } + + return NewState; +} + bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc) { return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc); @@ -314,6 +381,29 @@ return !State->assume(comparison.castAs(), false); } +SVal getIteratorArg(const CallEvent& Call, unsigned Index, + unsigned BlockCount) { + SVal Arg = Call.getArgSVal(Index); + const Expr *ArgExpr = Call.getArgExpr(Index); + + if (ArgExpr->getValueKind() != VK_RValue) + return Arg; + + if (!ArgExpr->getType().getDesugaredType( + Call.getState()->getStateManager().getContext())->isRecordType()) + return Arg; + + return loc::MemRegionVal(Call.getParameterLocation(Index, BlockCount)); +} + +SVal getReturnIterator(const CallEvent &Call) { + Optional RetValUnderConstr = Call.getReturnValueUnderConstruction(); + if (RetValUnderConstr.hasValue()) + return *RetValUnderConstr; + + return Call.getReturnValue(); +} + } // namespace iterator } // namespace ento } // namespace clang Index: clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp +++ clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp @@ -84,9 +84,8 @@ class IteratorModeling : public Checker, - check::PostStmt, - check::PostStmt, - check::Bind, check::LiveSymbols, check::DeadSymbols> { + check::PostStmt, check::Bind, + check::LiveSymbols, check::DeadSymbols> { using AdvanceFn = void (IteratorModeling::*)(CheckerContext &, const Expr *, SVal, SVal, SVal) const; @@ -98,27 +97,26 @@ const AdvanceFn *Handler) const; void handleComparison(CheckerContext &C, const Expr *CE, SVal RetVal, - const SVal &LVal, const SVal &RVal, - OverloadedOperatorKind Op) const; + SVal LVal, SVal RVal, OverloadedOperatorKind Op) const; void processComparison(CheckerContext &C, ProgramStateRef State, - SymbolRef Sym1, SymbolRef Sym2, const SVal &RetVal, + SymbolRef Sym1, SymbolRef Sym2, SVal RetVal, OverloadedOperatorKind Op) const; - void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, - bool Postfix) const; - void handleDecrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, - bool Postfix) const; + void handleIncrement(CheckerContext &C, SVal RetVal, + SVal Iter, bool Postfix) const; + void handleDecrement(CheckerContext &C, SVal RetVal, + SVal Iter, bool Postfix) const; void handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE, - OverloadedOperatorKind Op, const SVal &RetVal, - const SVal &Iterator, const SVal &Amount) const; + OverloadedOperatorKind Op, SVal RetVal, + SVal Iterator, SVal Amount) const; void handlePtrIncrOrDecr(CheckerContext &C, const Expr *Iterator, OverloadedOperatorKind OK, SVal Offset) const; - void handleAdvance(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, - SVal Amount) const; - void handlePrev(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, - SVal Amount) const; - void handleNext(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, - SVal Amount) const; - void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, + void handleAdvance(CheckerContext &C, const Expr *CE, SVal RetVal, + SVal Iter, SVal Amount) const; + void handlePrev(CheckerContext &C, const Expr *CE, SVal RetVal, + SVal Iter, SVal Amount) const; + void handleNext(CheckerContext &C, const Expr *CE, SVal RetVal, + SVal Iter, SVal Amount) const; + void assignToContainer(CheckerContext &C, const Expr *CE, SVal RetVal, const MemRegion *Cont) const; bool noChangeInAdvance(CheckerContext &C, SVal Iter, const Expr *CE) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, @@ -152,19 +150,13 @@ void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const; void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const; - void checkPostStmt(const MaterializeTemporaryExpr *MTE, - CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; }; bool isSimpleComparisonOperator(OverloadedOperatorKind OK); bool isSimpleComparisonOperator(BinaryOperatorKind OK); -ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val); -ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, - SymbolRef Sym2, bool Equal); -bool isBoundThroughLazyCompoundVal(const Environment &Env, - const MemRegion *Reg); +ProgramStateRef removeIteratorPosition(ProgramStateRef State, SVal Val); const ExplodedNode *findCallEnter(const ExplodedNode *Node, const Expr *Call); } // namespace @@ -198,15 +190,17 @@ auto State = C.getState(); // Already bound to container? - if (getIteratorPosition(State, Call.getReturnValue())) + SVal RetVal = getReturnIterator(Call); + if (getIteratorPosition(State, RetVal)) return; // Copy-like and move constructors if (isa(&Call) && Call.getNumArgs() == 1) { - if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) { - State = setIteratorPosition(State, Call.getReturnValue(), *Pos); + SVal Arg = getIteratorArg(Call, 0, C.blockCount()); + if (const auto *Pos = getIteratorPosition(State, Arg)) { + State = setIteratorPosition(State, RetVal, *Pos); if (cast(Func)->isMoveConstructor()) { - State = removeIteratorPosition(State, Call.getArgSVal(0)); + State = removeIteratorPosition(State, Arg); } C.addTransition(State); return; @@ -224,8 +218,7 @@ C.getASTContext()).getTypePtr() == Call.getResultType().getDesugaredType(C.getASTContext()).getTypePtr()) { if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) { - assignToContainer(C, OrigExpr, Call.getReturnValue(), - Pos->getContainer()); + assignToContainer(C, OrigExpr, RetVal, Pos->getContainer()); return; } } @@ -235,6 +228,9 @@ void IteratorModeling::checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const { auto State = C.getState(); + if (Val.getAs()) + return; + const auto *Pos = getIteratorPosition(State, Val); if (Pos) { State = setIteratorPosition(State, Loc, *Pos); @@ -289,17 +285,6 @@ } } -void IteratorModeling::checkPostStmt(const MaterializeTemporaryExpr *MTE, - CheckerContext &C) const { - /* Transfer iterator state to temporary objects */ - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, C.getSVal(MTE->getSubExpr())); - if (!Pos) - return; - State = setIteratorPosition(State, C.getSVal(MTE), *Pos); - C.addTransition(State); -} - void IteratorModeling::checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const { // Keep symbolic expressions of iterator positions alive @@ -307,8 +292,9 @@ for (const auto &Reg : RegionMap) { const auto Offset = Reg.second.getOffset(); for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) - if (isa(*i)) + if (isa(*i)) { SR.markLive(*i); + } } auto SymbolMap = State->get(); @@ -329,12 +315,7 @@ auto RegionMap = State->get(); for (const auto &Reg : RegionMap) { if (!SR.isLiveRegion(Reg.first)) { - // The region behind the `LazyCompoundVal` is often cleaned up before - // the `LazyCompoundVal` itself. If there are iterator positions keyed - // by these regions their cleanup must be deferred. - if (!isBoundThroughLazyCompoundVal(State->getEnvironment(), Reg.first)) { - State = State->remove(Reg.first); - } + State = State->remove(Reg.first); } } @@ -352,74 +333,82 @@ IteratorModeling::handleOverloadedOperator(CheckerContext &C, const CallEvent &Call, OverloadedOperatorKind Op) const { - if (isSimpleComparisonOperator(Op)) { - const auto *OrigExpr = Call.getOriginExpr(); - if (!OrigExpr) - return; + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; - if (const auto *InstCall = dyn_cast(&Call)) { - handleComparison(C, OrigExpr, Call.getReturnValue(), - InstCall->getCXXThisVal(), Call.getArgSVal(0), Op); - return; - } + if (isSimpleComparisonOperator(Op)) { + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; - handleComparison(C, OrigExpr, Call.getReturnValue(), Call.getArgSVal(0), - Call.getArgSVal(1), Op); + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + if (const auto *InstCall = dyn_cast(&Call)) { + handleComparison(C, OrigExpr, Call.getReturnValue(), + InstCall->getCXXThisVal(), Arg0, Op); return; - } else if (isRandomIncrOrDecrOperator(Op)) { - const auto *OrigExpr = Call.getOriginExpr(); - if (!OrigExpr) - return; + } - if (const auto *InstCall = dyn_cast(&Call)) { - if (Call.getNumArgs() >= 1 && - Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { - handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(), - InstCall->getCXXThisVal(), Call.getArgSVal(0)); - return; - } - } else if (Call.getNumArgs() >= 2) { - const Expr *FirstArg = Call.getArgExpr(0); - const Expr *SecondArg = Call.getArgExpr(1); - const QualType FirstType = FirstArg->getType(); - const QualType SecondType = SecondArg->getType(); - - if (FirstType->isIntegralOrEnumerationType() || - SecondType->isIntegralOrEnumerationType()) { - // In case of operator+ the iterator can be either on the LHS (eg.: - // it + 1), or on the RHS (eg.: 1 + it). Both cases are modeled. - const bool IsIterFirst = FirstType->isStructureOrClassType(); - const SVal FirstArg = Call.getArgSVal(0); - const SVal SecondArg = Call.getArgSVal(1); - const SVal &Iterator = IsIterFirst ? FirstArg : SecondArg; - const SVal &Amount = IsIterFirst ? SecondArg : FirstArg; - - handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(), - Iterator, Amount); - return; - } + SVal Arg1 = getIteratorArg(Call, 1, C.blockCount()); + handleComparison(C, OrigExpr, Call.getReturnValue(), Arg0, Arg1, Op); + return; + } else if (isRandomIncrOrDecrOperator(Op)) { + if (const auto *InstCall = dyn_cast(&Call)) { + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { + handleRandomIncrOrDecr(C, OrigExpr, Op, getReturnIterator(Call), + InstCall->getCXXThisVal(), Call.getArgSVal(0)); + return; } - } else if (isIncrementOperator(Op)) { - if (const auto *InstCall = dyn_cast(&Call)) { - handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), - Call.getNumArgs()); + } else if (Call.getNumArgs() >= 2) { + const Expr *FirstArg = Call.getArgExpr(0); + const Expr *SecondArg = Call.getArgExpr(1); + const QualType FirstType = FirstArg->getType(); + const QualType SecondType = SecondArg->getType(); + + if (FirstType->isIntegralOrEnumerationType() || + SecondType->isIntegralOrEnumerationType()) { + // In case of operator+ the iterator can be either on the LHS (eg.: + // it + 1), or on the RHS (eg.: 1 + it). Both cases are modeled. + const bool IsIterFirst = FirstType->isStructureOrClassType(); + const SVal Iterator = getIteratorArg(Call, IsIterFirst ? 0 : 1, + C.blockCount()); + const SVal Amount = Call.getArgSVal(IsIterFirst ? 1 : 0); + + handleRandomIncrOrDecr(C, OrigExpr, Op, getReturnIterator(Call), + Iterator, Amount); return; } - - handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0), - Call.getNumArgs()); - return; - } else if (isDecrementOperator(Op)) { - if (const auto *InstCall = dyn_cast(&Call)) { - handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), - Call.getNumArgs()); + } else { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + handleRandomIncrOrDecr(C, OrigExpr, Op, getReturnIterator(Call), Arg0, + Call.getArgSVal(1)); return; } + } + } else if (isIncrementOperator(Op)) { + if (const auto *InstCall = dyn_cast(&Call)) { + handleIncrement(C, getReturnIterator(Call), InstCall->getCXXThisVal(), + Call.getNumArgs()); + return; + } - handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0), - Call.getNumArgs()); + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + handleIncrement(C, getReturnIterator(Call), Arg0, Call.getNumArgs()); + return; + } else if (isDecrementOperator(Op)) { + if (const auto *InstCall = dyn_cast(&Call)) { + handleDecrement(C, getReturnIterator(Call), InstCall->getCXXThisVal(), + Call.getNumArgs()); return; } + + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + handleDecrement(C, getReturnIterator(Call), Arg0, Call.getNumArgs()); + return; + } } void @@ -427,9 +416,10 @@ const CallEvent &Call, const Expr *OrigExpr, const AdvanceFn *Handler) const { - if (!C.wasInlined) { - (this->**Handler)(C, OrigExpr, Call.getReturnValue(), - Call.getArgSVal(0), Call.getArgSVal(1)); + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + if (!C.wasInlined) { + (this->**Handler)(C, OrigExpr, getReturnIterator(Call), Arg0, + Call.getArgSVal(1)); return; } @@ -438,23 +428,22 @@ const auto *IdInfo = cast(Call.getDecl())->getIdentifier(); if (IdInfo) { if (IdInfo->getName() == "advance") { - if (noChangeInAdvance(C, Call.getArgSVal(0), OrigExpr)) { - (this->**Handler)(C, OrigExpr, Call.getReturnValue(), - Call.getArgSVal(0), Call.getArgSVal(1)); + if (noChangeInAdvance(C, Arg0, OrigExpr)) { + (this->**Handler)(C, OrigExpr, getReturnIterator(Call), Arg0, + Call.getArgSVal(1)); } } } } void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, - SVal RetVal, const SVal &LVal, - const SVal &RVal, - OverloadedOperatorKind Op) const { + SVal RetVal, SVal LVal, SVal RVal, + OverloadedOperatorKind Op) const { // Record the operands and the operator of the comparison for the next // evalAssume, if the result is a symbolic expression. If it is a concrete // value (only one branch is possible), then transfer the state between // the operands according to the operator and the result - auto State = C.getState(); + auto State = C.getState(); const auto *LPos = getIteratorPosition(State, LVal); const auto *RPos = getIteratorPosition(State, RVal); const MemRegion *Cont = nullptr; @@ -502,41 +491,28 @@ State = State->BindExpr(CE, LCtx, RetVal); } - processComparison(C, State, LPos->getOffset(), RPos->getOffset(), RetVal, Op); -} + if (const auto DefRetVal = RetVal.getAs()) { + ProgramStateRef StateEqual, StateNonEqual; + std::tie(StateEqual, StateNonEqual) = + assumeComparison(State, LPos->getOffset(), RPos->getOffset(), *DefRetVal, + Op); -void IteratorModeling::processComparison(CheckerContext &C, - ProgramStateRef State, SymbolRef Sym1, - SymbolRef Sym2, const SVal &RetVal, - OverloadedOperatorKind Op) const { - if (const auto TruthVal = RetVal.getAs()) { - if ((State = relateSymbols(State, Sym1, Sym2, - (Op == OO_EqualEqual) == - (TruthVal->getValue() != 0)))) { - C.addTransition(State); - } else { + if (!StateEqual && !StateNonEqual) { C.generateSink(State, C.getPredecessor()); + return; } - return; - } - const auto ConditionVal = RetVal.getAs(); - if (!ConditionVal) - return; + if (StateEqual) + C.addTransition(StateEqual); - if (auto StateTrue = relateSymbols(State, Sym1, Sym2, Op == OO_EqualEqual)) { - StateTrue = StateTrue->assume(*ConditionVal, true); - C.addTransition(StateTrue); + if (StateNonEqual) + C.addTransition(StateNonEqual); } - if (auto StateFalse = relateSymbols(State, Sym1, Sym2, Op != OO_EqualEqual)) { - StateFalse = StateFalse->assume(*ConditionVal, false); - C.addTransition(StateFalse); - } } -void IteratorModeling::handleIncrement(CheckerContext &C, const SVal &RetVal, - const SVal &Iter, bool Postfix) const { +void IteratorModeling::handleIncrement(CheckerContext &C, SVal RetVal, + SVal Iter, bool Postfix) const { // Increment the symbolic expressions which represents the position of the // iterator auto State = C.getState(); @@ -561,8 +537,8 @@ C.addTransition(State); } -void IteratorModeling::handleDecrement(CheckerContext &C, const SVal &RetVal, - const SVal &Iter, bool Postfix) const { +void IteratorModeling::handleDecrement(CheckerContext &C, SVal RetVal, + SVal Iter, bool Postfix) const { // Decrement the symbolic expressions which represents the position of the // iterator auto State = C.getState(); @@ -589,9 +565,8 @@ void IteratorModeling::handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE, OverloadedOperatorKind Op, - const SVal &RetVal, - const SVal &Iterator, - const SVal &Amount) const { + SVal RetVal, SVal Iterator, + SVal Amount) const { // Increment or decrement the symbolic expressions which represents the // position of the iterator auto State = C.getState(); @@ -616,7 +591,7 @@ auto AdvancedState = advancePosition(State, Iterator, Op, *Value); if (AdvancedState) { const auto *NewPos = getIteratorPosition(AdvancedState, Iterator); - assert(NewPos && + assert(NewPos && "Iterator should have position after successful advancement"); State = setIteratorPosition(State, TgtVal, *NewPos); @@ -677,17 +652,19 @@ } void IteratorModeling::handlePrev(CheckerContext &C, const Expr *CE, - SVal RetVal, SVal Iter, SVal Amount) const { + SVal RetVal, SVal Iter, + SVal Amount) const { handleRandomIncrOrDecr(C, CE, OO_Minus, RetVal, Iter, Amount); } void IteratorModeling::handleNext(CheckerContext &C, const Expr *CE, - SVal RetVal, SVal Iter, SVal Amount) const { + SVal RetVal, SVal Iter, + SVal Amount) const { handleRandomIncrOrDecr(C, CE, OO_Plus, RetVal, Iter, Amount); } void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE, - const SVal &RetVal, + SVal RetVal, const MemRegion *Cont) const { Cont = Cont->getMostDerivedObjectRegion(); @@ -775,60 +752,14 @@ return OK == BO_EQ || OK == BO_NE; } -ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { +ProgramStateRef removeIteratorPosition(ProgramStateRef State, SVal Val) { if (auto Reg = Val.getAsRegion()) { Reg = Reg->getMostDerivedObjectRegion(); return State->remove(Reg); } else if (const auto Sym = Val.getAsSymbol()) { return State->remove(Sym); - } else if (const auto LCVal = Val.getAs()) { - return State->remove(LCVal->getRegion()); - } - return nullptr; -} - -ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, - SymbolRef Sym2, bool Equal) { - auto &SVB = State->getStateManager().getSValBuilder(); - - // FIXME: This code should be reworked as follows: - // 1. Subtract the operands using evalBinOp(). - // 2. Assume that the result doesn't overflow. - // 3. Compare the result to 0. - // 4. Assume the result of the comparison. - const auto comparison = - SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(Sym1), - nonloc::SymbolVal(Sym2), SVB.getConditionType()); - - assert(comparison.getAs() && - "Symbol comparison must be a `DefinedSVal`"); - - auto NewState = State->assume(comparison.castAs(), Equal); - if (!NewState) - return nullptr; - - if (const auto CompSym = comparison.getAsSymbol()) { - assert(isa(CompSym) && - "Symbol comparison must be a `SymIntExpr`"); - assert(BinaryOperator::isComparisonOp( - cast(CompSym)->getOpcode()) && - "Symbol comparison must be a comparison"); - return assumeNoOverflow(NewState, cast(CompSym)->getLHS(), 2); } - - return NewState; -} - -bool isBoundThroughLazyCompoundVal(const Environment &Env, - const MemRegion *Reg) { - for (const auto &Binding : Env) { - if (const auto LCVal = Binding.second.getAs()) { - if (LCVal->getRegion() == Reg) - return true; - } - } - - return false; + return State; } const ExplodedNode *findCallEnter(const ExplodedNode *Node, const Expr *Call) { Index: clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp @@ -89,18 +89,14 @@ if (const auto *InstCall = dyn_cast(&Call)) { verifyIncrement(C, InstCall->getCXXThisVal()); } else { - if (Call.getNumArgs() >= 1) { - verifyIncrement(C, Call.getArgSVal(0)); - } + verifyIncrement(C, getIteratorArg(Call, 0, C.blockCount())); } } else if (isDecrementOperator(Func->getOverloadedOperator())) { // Check for out-of-range decrementions if (const auto *InstCall = dyn_cast(&Call)) { verifyDecrement(C, InstCall->getCXXThisVal()); } else { - if (Call.getNumArgs() >= 1) { - verifyDecrement(C, Call.getArgSVal(0)); - } + verifyDecrement(C, getIteratorArg(Call, 0, C.blockCount())); } } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { @@ -115,7 +111,8 @@ if (Call.getNumArgs() >= 2 && Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), - Call.getArgSVal(0), Call.getArgSVal(1)); + getIteratorArg(Call, 0, C.blockCount()), + Call.getArgSVal(1)); } } } else if (isDereferenceOperator(Func->getOverloadedOperator())) { @@ -123,18 +120,20 @@ if (const auto *InstCall = dyn_cast(&Call)) { verifyDereference(C, InstCall->getCXXThisVal()); } else { - verifyDereference(C, Call.getArgSVal(0)); + verifyDereference(C, getIteratorArg(Call, 0, C.blockCount())); } } } else { const AdvanceFn *Verifier = AdvanceFunctions.lookup(Call); if (Verifier) { + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + if (Call.getNumArgs() > 1) { - (this->**Verifier)(C, Call.getArgSVal(0), Call.getArgSVal(1)); + (this->**Verifier)(C, Arg0, Call.getArgSVal(1)); } else { auto &BVF = C.getSValBuilder().getBasicValueFactory(); (this->**Verifier)( - C, Call.getArgSVal(0), + C, Arg0, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); } } Index: clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp @@ -32,16 +32,12 @@ std::unique_ptr MismatchedBugType; - void verifyMatch(CheckerContext &C, const SVal &Iter, - const MemRegion *Cont) const; - void verifyMatch(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const; - void reportBug(const StringRef &Message, const SVal &Val1, - const SVal &Val2, CheckerContext &C, - ExplodedNode *ErrNode) const; - void reportBug(const StringRef &Message, const SVal &Val, - const MemRegion *Reg, CheckerContext &C, - ExplodedNode *ErrNode) const; + void verifyMatch(CheckerContext &C, SVal Iter, const MemRegion *Cont) const; + void verifyMatch(CheckerContext &C, SVal Iter1, SVal Iter2) const; + void reportBug(const StringRef &Message, SVal Val1, SVal Val2, + CheckerContext &C, ExplodedNode *ErrNode) const; + void reportBug(const StringRef &Message, SVal Val, const MemRegion *Reg, + CheckerContext &C, ExplodedNode *ErrNode) const; public: MismatchedIteratorChecker(); @@ -66,51 +62,55 @@ if (!Func) return; + // If the call has no arguments, there is nothing to check here + if (Call.getNumArgs() < 1) + return; + if (Func->isOverloadedOperator() && isComparisonOperator(Func->getOverloadedOperator())) { // Check for comparisons of iterators of different containers + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); if (const auto *InstCall = dyn_cast(&Call)) { - if (Call.getNumArgs() < 1) - return; - if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) || !isIteratorType(Call.getArgExpr(0)->getType())) return; - verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0)); + verifyMatch(C, InstCall->getCXXThisVal(), Arg0); } else { if (Call.getNumArgs() < 2) return; + SVal Arg1 = getIteratorArg(Call, 1, C.blockCount()); if (!isIteratorType(Call.getArgExpr(0)->getType()) || !isIteratorType(Call.getArgExpr(1)->getType())) return; - verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); + verifyMatch(C, Arg0, Arg1); } } else if (const auto *InstCall = dyn_cast(&Call)) { const auto *ContReg = InstCall->getCXXThisVal().getAsRegion(); if (!ContReg) return; + + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); // Check for erase, insert and emplace using iterator of another container if (isEraseCall(Func) || isEraseAfterCall(Func)) { - verifyMatch(C, Call.getArgSVal(0), - InstCall->getCXXThisVal().getAsRegion()); + verifyMatch(C, Arg0, InstCall->getCXXThisVal().getAsRegion()); if (Call.getNumArgs() == 2) { - verifyMatch(C, Call.getArgSVal(1), - InstCall->getCXXThisVal().getAsRegion()); + SVal Arg1 = getIteratorArg(Call, 1, C.blockCount()); + verifyMatch(C, Arg1, InstCall->getCXXThisVal().getAsRegion()); } } else if (isInsertCall(Func)) { - verifyMatch(C, Call.getArgSVal(0), - InstCall->getCXXThisVal().getAsRegion()); + verifyMatch(C, Arg0, InstCall->getCXXThisVal().getAsRegion()); if (Call.getNumArgs() == 3 && isIteratorType(Call.getArgExpr(1)->getType()) && isIteratorType(Call.getArgExpr(2)->getType())) { - verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2)); + SVal Arg1 = getIteratorArg(Call, 1, C.blockCount()); + SVal Arg2 = getIteratorArg(Call, 2, C.blockCount()); + verifyMatch(C, Arg1, Arg2); } } else if (isEmplaceCall(Func)) { - verifyMatch(C, Call.getArgSVal(0), - InstCall->getCXXThisVal().getAsRegion()); + verifyMatch(C, Arg0, InstCall->getCXXThisVal().getAsRegion()); } } else if (isa(&Call)) { // Check match of first-last iterator pair in a constructor of a container @@ -129,7 +129,9 @@ !isIteratorType(Call.getArgExpr(1)->getType())) return; - verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); + SVal Arg0 = getIteratorArg(Call, 0, C.blockCount()); + SVal Arg1 = getIteratorArg(Call, 1, C.blockCount()); + verifyMatch(C, Arg0, Arg1); } else { // The main purpose of iterators is to abstract away from different // containers and provide a (maybe limited) uniform access to them. @@ -167,7 +169,7 @@ if (!isIteratorType(TAType)) continue; - SVal LHS = UndefinedVal(); + Optional LHS = None; // For every template parameter which is an iterator type in the // instantiation look for all functions' parameters' type by it and @@ -179,10 +181,13 @@ if (!ParamType || ParamType->getReplacedParameter()->getDecl() != TPDecl) continue; - if (LHS.isUndef()) { - LHS = Call.getArgSVal(J); + + SVal ArgJ = getIteratorArg(Call, J, C.blockCount()); + + if (!LHS.hasValue()) { + LHS = ArgJ; } else { - verifyMatch(C, LHS, Call.getArgSVal(J)); + verifyMatch(C, *LHS, ArgJ); } } } @@ -200,7 +205,7 @@ verifyMatch(C, LVal, RVal); } -void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, +void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter, const MemRegion *Cont) const { // Verify match between a container and the container of an iterator Cont = Cont->getMostDerivedObjectRegion(); @@ -231,14 +236,13 @@ if (!N) { return; } - reportBug("Container accessed using foreign iterator argument.", - Iter, Cont, C, N); + reportBug("Container accessed using foreign iterator argument.", Iter, Cont, + C, N); } } -void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, - const SVal &Iter1, - const SVal &Iter2) const { +void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1, + SVal Iter2) const { // Verify match between the containers of two iterators auto State = C.getState(); const auto *Pos1 = getIteratorPosition(State, Iter1); @@ -275,10 +279,8 @@ } } -void MismatchedIteratorChecker::reportBug(const StringRef &Message, - const SVal &Val1, - const SVal &Val2, - CheckerContext &C, +void MismatchedIteratorChecker::reportBug(const StringRef &Message, SVal Val1, + SVal Val2, CheckerContext &C, ExplodedNode *ErrNode) const { auto R = std::make_unique(*MismatchedBugType, Message, ErrNode); @@ -287,8 +289,8 @@ C.emitReport(std::move(R)); } -void MismatchedIteratorChecker::reportBug(const StringRef &Message, - const SVal &Val, const MemRegion *Reg, +void MismatchedIteratorChecker::reportBug(const StringRef &Message, SVal Val, + const MemRegion *Reg, CheckerContext &C, ExplodedNode *ErrNode) const { auto R = std::make_unique(*MismatchedBugType, Message, Index: clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp +++ clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp @@ -24,12 +24,12 @@ namespace { class STLAlgorithmModeling : public Checker { - bool evalFind(CheckerContext &C, const CallExpr *CE) const; + void evalFind(CheckerContext &C, const CallExpr *CE, SVal Begin, + SVal End) const; - void Find(CheckerContext &C, const CallExpr *CE, unsigned paramNum) const; - - using FnCheck = bool (STLAlgorithmModeling::*)(CheckerContext &, - const CallExpr *) const; + using FnCheck = void (STLAlgorithmModeling::*)(CheckerContext &, + const CallExpr *, SVal Begin, + SVal End) const; const CallDescriptionMap Callbacks = { {{{"std", "find"}, 3}, &STLAlgorithmModeling::evalFind}, @@ -67,51 +67,45 @@ bool STLAlgorithmModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); - if (!CE) - return false; - - const FnCheck *Handler = Callbacks.lookup(Call); - if (!Handler) - return false; - - return (this->**Handler)(C, CE); -} - -bool STLAlgorithmModeling::evalFind(CheckerContext &C, - const CallExpr *CE) const { // std::find()-like functions either take their primary range in the first // two parameters, or if the first parameter is "execution policy" then in // the second and third. This means that the second parameter must always be // an iterator. - if (!isIteratorType(CE->getArg(1)->getType())) + if (Call.getNumArgs() < 2 || !isIteratorType(Call.getArgExpr(1)->getType())) return false; // If no "execution policy" parameter is used then the first argument is the - // beginning of the range. - if (isIteratorType(CE->getArg(0)->getType())) { - Find(C, CE, 0); - return true; + // beginning of the range. If "execution policy" parmeter is used then the + // third argument is the end of the range. + unsigned ArgNum = 0; + if (!isIteratorType(Call.getArgExpr(0)->getType())) { + if (Call.getNumArgs() < 3 || !isIteratorType(Call.getArgExpr(2)->getType())) + return false; + ArgNum = 1; } - // If "execution policy" parameter is used then the second argument is the - // beginning of the range. - if (isIteratorType(CE->getArg(2)->getType())) { - Find(C, CE, 1); - return true; - } + SVal ArgB = getIteratorArg(Call, ArgNum, C.blockCount()); + SVal ArgE = getIteratorArg(Call, ArgNum + 1, C.blockCount()); + + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; - return false; + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->**Handler)(C, CE, ArgB, ArgE); + return true; } -void STLAlgorithmModeling::Find(CheckerContext &C, const CallExpr *CE, - unsigned paramNum) const { +void STLAlgorithmModeling::evalFind(CheckerContext &C, const CallExpr *CE, + SVal Begin, SVal End) const { auto State = C.getState(); auto &SVB = C.getSValBuilder(); const auto *LCtx = C.getLocationContext(); SVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); - SVal Param = State->getSVal(CE->getArg(paramNum), LCtx); auto StateFound = State->BindExpr(CE, LCtx, RetVal); @@ -119,7 +113,7 @@ // assume that in case of successful search the position of the found element // is not ahead of it. // FIXME: Reverse iterators - const auto *Pos = getIteratorPosition(State, Param); + const auto *Pos = getIteratorPosition(State, Begin); if (Pos) { StateFound = createIteratorPosition(StateFound, RetVal, Pos->getContainer(), CE, LCtx, C.blockCount()); @@ -135,13 +129,11 @@ StateFound = StateFound->assume(GreaterOrEqual.castAs(), true); } - Param = State->getSVal(CE->getArg(paramNum + 1), LCtx); - // If we have an iterator position for the range-end argument then we can // assume that in case of successful search the position of the found element // is ahead of it. // FIXME: Reverse iterators - Pos = getIteratorPosition(State, Param); + Pos = getIteratorPosition(State, End); if (Pos) { StateFound = createIteratorPosition(StateFound, RetVal, Pos->getContainer(), CE, LCtx, C.blockCount()); @@ -160,7 +152,7 @@ C.addTransition(StateFound); if (AggressiveStdFindModeling) { - auto StateNotFound = State->BindExpr(CE, LCtx, Param); + auto StateNotFound = State->BindExpr(CE, LCtx, End); C.addTransition(StateNotFound); } } Index: clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -132,10 +132,11 @@ case ConstructionContext::SimpleConstructorInitializerKind: { const auto *ICC = cast(CC); const auto *Init = ICC->getCXXCtorInitializer(); - assert(Init->isAnyMemberInitializer()); const CXXMethodDecl *CurCtor = cast(LCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); + if (Init->isBaseInitializer() || Init->isDelegatingInitializer()) + return ThisVal; const ValueDecl *Field; SVal FieldVal; @@ -364,6 +365,8 @@ case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { const auto *ICC = cast(CC); + const auto *Init = ICC->getCXXCtorInitializer(); + assert(Init->isAnyMemberInitializer()); return addObjectUnderConstruction(State, ICC->getCXXCtorInitializer(), LCtx, V); } Index: clang/test/Analysis/Inputs/system-header-simulator-cxx.h =================================================================== --- clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -344,6 +344,8 @@ const T& front() const { return *begin(); } T& back() { return *(end() - 1); } const T& back() const { return *(end() - 1); } + + bool empty() const; }; template @@ -415,6 +417,8 @@ const T& front() const { return *begin(); } T& back() { return *--end(); } const T& back() const { return *--end(); } + + bool empty() const; }; template @@ -496,6 +500,8 @@ const T& front() const { return *begin(); } T& back() { return *(end() - 1); } const T& back() const { return *(end() - 1); } + + bool empty() const; }; template @@ -560,6 +566,8 @@ T& front() { return *begin(); } const T& front() const { return *begin(); } + + bool empty() const; }; template Index: clang/test/Analysis/container-modeling.cpp =================================================================== --- clang/test/Analysis/container-modeling.cpp +++ clang/test/Analysis/container-modeling.cpp @@ -16,6 +16,12 @@ void clang_analyzer_eval(bool); void clang_analyzer_warnIfReached(); +extern void __assert_fail (__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__ ((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail (#expr, __FILE__, __LINE__, __func__)) + void begin(const std::vector &V) { V.begin(); @@ -56,6 +62,40 @@ // expected-note@-1{{TRUE}} } +//////////////////////////////////////////////////////////////////////////////// +/// +/// C O N T A I N E R C A P A C I T Y +/// +//////////////////////////////////////////////////////////////////////////////// + +/// empty() + +void empty(const std::vector &V) { + for (auto n: V) {} + clang_analyzer_eval(clang_analyzer_container_begin(V) == + clang_analyzer_container_end(V)); + // expected-warning@-2{{TRUE}} expected-warning@-2{{FALSE}} + // expected-note@-3 {{TRUE}} expected-note@-3 {{FALSE}} +} + +void non_empty1(const std::vector &V) { + assert(!V.empty()); // expected-note{{'?' condition is true}} + for (auto n: V) {} + clang_analyzer_eval(clang_analyzer_container_begin(V) == + clang_analyzer_container_end(V)); + // expected-warning@-2{{FALSE}} + // expected-note@-3 {{FALSE}} +} + +void non_empty2(const std::vector &V) { + for (auto n: V) {} + assert(!V.empty()); // expected-note{{'?' condition is true}} + clang_analyzer_eval(clang_analyzer_container_begin(V) == + clang_analyzer_container_end(V)); + // expected-warning@-2{{FALSE}} + // expected-note@-3 {{FALSE}} +} + //////////////////////////////////////////////////////////////////////////////// /// /// C O N T A I N E R M O D I F I E R S Index: clang/test/Analysis/diagnostics/explicit-suppression.cpp =================================================================== --- clang/test/Analysis/diagnostics/explicit-suppression.cpp +++ clang/test/Analysis/diagnostics/explicit-suppression.cpp @@ -19,6 +19,6 @@ void testCopyNull(C *I, C *E) { std::copy(I, E, (C *)0); #ifndef SUPPRESSED - // expected-warning@../Inputs/system-header-simulator-cxx.h:709 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:717 {{Called C++ object pointer is null}} #endif } Index: clang/test/Analysis/iterator-modeling.cpp =================================================================== --- clang/test/Analysis/iterator-modeling.cpp +++ clang/test/Analysis/iterator-modeling.cpp @@ -2026,6 +2026,55 @@ ++i0; } +template +struct delegated_ctor_iterator { + delegated_ctor_iterator(const T&, int); + delegated_ctor_iterator(const T& t) : delegated_ctor_iterator(t, 0) {} + delegated_ctor_iterator operator++(); + delegated_ctor_iterator operator++(int); + T& operator*(); +}; + +template +struct container_with_delegated_ctor_iterator { + typedef delegated_ctor_iterator iterator; + iterator begin() const { return delegated_ctor_iterator(T()); } +}; + +void +test_delegated_ctor_iterator( + const container_with_delegated_ctor_iterator &c) { + auto i = c.begin(); // no-crash + clang_analyzer_denote(clang_analyzer_container_begin(c), "$c.begin()"); + clang_analyzer_express(clang_analyzer_iterator_position(i)); // expected-warning{{$c.begin()}} +} + +template +struct base_ctor_iterator { + base_ctor_iterator(const T&); + base_ctor_iterator operator++(); + base_ctor_iterator operator++(int); + T& operator*(); +}; + +template +struct derived_ctor_iterator: public base_ctor_iterator { + derived_ctor_iterator(const T& t) : base_ctor_iterator(t) {} +}; + +template +struct container_with_derived_ctor_iterator { + typedef derived_ctor_iterator iterator; + iterator begin() const { return derived_ctor_iterator(T()); } +}; + +void +test_derived_ctor_iterator(const container_with_derived_ctor_iterator &c) { + auto i = c.begin(); + clang_analyzer_denote(clang_analyzer_container_begin(c), "$c.begin()"); + clang_analyzer_express(clang_analyzer_iterator_position(i)); // expected-warning{{$c.begin()}} +} + void clang_analyzer_printState(); void print_state(std::vector &V) { Index: clang/test/Analysis/smart-ptr-text-output.cpp =================================================================== --- clang/test/Analysis/smart-ptr-text-output.cpp +++ clang/test/Analysis/smart-ptr-text-output.cpp @@ -80,7 +80,7 @@ void derefOnStdSwappedNullPtr() { std::unique_ptr P; // expected-note {{Default constructed smart pointer 'P' is null}} std::unique_ptr PNull; // expected-note {{Default constructed smart pointer 'PNull' is null}} - std::swap(P, PNull); // expected-note@Inputs/system-header-simulator-cxx.h:979 {{Swapped null smart pointer 'PNull' with smart pointer 'P'}} + std::swap(P, PNull); // expected-note@Inputs/system-header-simulator-cxx.h:987 {{Swapped null smart pointer 'PNull' with smart pointer 'P'}} // expected-note@-1 {{Calling 'swap'}} // expected-note@-2 {{Returning from 'swap'}} P->foo(); // expected-warning {{Dereference of null smart pointer 'P' [alpha.cplusplus.SmartPtr]}} Index: clang/unittests/StaticAnalyzer/TestReturnValueUnderConstruction.cpp =================================================================== --- clang/unittests/StaticAnalyzer/TestReturnValueUnderConstruction.cpp +++ clang/unittests/StaticAnalyzer/TestReturnValueUnderConstruction.cpp @@ -51,22 +51,65 @@ TEST(TestReturnValueUnderConstructionChecker, ReturnValueUnderConstructionChecker) { EXPECT_TRUE(runCheckerOnCode( - R"(class C { - public: - C(int nn): n(nn) {} - virtual ~C() {} - private: - int n; - }; - - C returnC(int m) { - C c(m); - return c; - } - - void foo() { - C c = returnC(1); - })")); + R"(class C { + public: + C(int nn): n(nn) {} + virtual ~C() {} + private: + int n; + }; + + C returnC(int m) { + C c(m); + return c; + } + + void foo() { + C c = returnC(1); + })")); + + EXPECT_TRUE(runCheckerOnCode( + R"(class C { + public: + C(int nn): n(nn) {} + explicit C(): C(0) {} + virtual ~C() {} + private: + int n; + }; + + C returnC() { + C c; + return c; + } + + void foo() { + C c = returnC(); + })")); + + EXPECT_TRUE(runCheckerOnCode( + R"(class C { + public: + C(int nn): n(nn) {} + virtual ~C() {} + private: + int n; + }; + + class D: public C { + public: + D(int nn): C(nn) {} + virtual ~D() {} + }; + + D returnD(int m) { + D d(m); + return d; + } + + void foo() { + D d = returnD(1); + })")); } } // namespace