Index: lib/StaticAnalyzer/Checkers/IteratorChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -182,6 +182,8 @@ void handleComparison(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const; + void handleEmpty(CheckerContext &C, const Expr *CE, const SVal &Cont, + const SVal &RetVal) const; void processComparison(CheckerContext &C, ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, const SVal &RetVal, OverloadedOperatorKind Op) const; @@ -272,6 +274,7 @@ bool isIteratorType(const QualType &Type); bool isIterator(const CXXRecordDecl *CRD); +bool isContainerType(const QualType &Type); bool isComparisonOperator(OverloadedOperatorKind OK); bool isBeginCall(const FunctionDecl *Func); bool isEndCall(const FunctionDecl *Func); @@ -279,6 +282,7 @@ bool isClearCall(const FunctionDecl *Func); bool isPushBackCall(const FunctionDecl *Func); bool isEmplaceBackCall(const FunctionDecl *Func); +bool isEmptyCall(const FunctionDecl *Func); bool isPopBackCall(const FunctionDecl *Func); bool isPushFrontCall(const FunctionDecl *Func); bool isEmplaceFrontCall(const FunctionDecl *Func); @@ -691,6 +695,17 @@ if (!OrigExpr) return; + if (const auto *InstCall = dyn_cast(&Call)) { + if (isContainerType(InstCall->getCXXThisExpr()->getType())) { + if (isEmptyCall(Func)) { + handleEmpty(C, OrigExpr, InstCall->getCXXThisVal(), + Call.getReturnValue()); + } + } + } + + + if (!isIteratorType(Call.getResultType())) return; @@ -888,6 +903,33 @@ processComparison(C, State, LPos->getOffset(), RPos->getOffset(), RetVal, Op); } +void IteratorChecker::handleEmpty(CheckerContext &C, const Expr *CE, + const SVal &Cont, const 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 BeginSym = getContainerBegin(State, ContReg); + if (!BeginSym) { + State = createContainerBegin(State, ContReg, CE, C.getASTContext().LongTy, + C.getLocationContext(), C.blockCount()); + BeginSym = getContainerBegin(State, ContReg); + } + auto EndSym = getContainerEnd(State, ContReg); + if (!EndSym) { + State = createContainerEnd(State, ContReg, CE, C.getASTContext().LongTy, + C.getLocationContext(), C.blockCount()); + EndSym = getContainerEnd(State, ContReg); + } + + processComparison(C, State, BeginSym, EndSym, RetVal, OO_EqualEqual); +} + void IteratorChecker::processComparison(CheckerContext &C, ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, const SVal &RetVal, @@ -1622,6 +1664,7 @@ namespace { +bool isContainer(const CXXRecordDecl *CRD); bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); @@ -1686,6 +1729,39 @@ HasPostIncrOp && HasDerefOp; } +bool isContainerType(const QualType &Type) { + if (isIteratorType(Type)) + return false; + + const auto *CRD = Type.getNonReferenceType() + ->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); + return isContainer(CRD); +} + +bool isContainer(const CXXRecordDecl *CRD) { + if (!CRD) + return false; + + for (const auto *Decl : CRD->decls()) { + const auto *TD = dyn_cast(Decl); + if (!TD) + continue; + + const auto *Type = TD->getTypeForDecl(); + if (!Type) + continue; + + const auto *CRD = Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); + if(!CRD) + continue; + + if (isIterator(CRD)) + return true; + } + + return false; +} + bool isComparisonOperator(OverloadedOperatorKind OK) { return OK == OO_EqualEqual || OK == OO_ExclaimEqual || OK == OO_Less || OK == OO_LessEqual || OK == OO_Greater || OK == OO_GreaterEqual; @@ -1827,6 +1903,15 @@ return IdInfo->getName() == "erase_after"; } +bool isEmptyCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() > 0) + return false; + return IdInfo->getName() == "empty"; +} + bool isAssignmentOperator(OverloadedOperatorKind OK) { return OK == OO_Equal; } bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { Index: test/Analysis/Inputs/system-header-simulator-cxx.h =================================================================== --- test/Analysis/Inputs/system-header-simulator-cxx.h +++ test/Analysis/Inputs/system-header-simulator-cxx.h @@ -316,6 +316,8 @@ const T& front() const { return *begin(); } T& back() { return *(end() - 1); } const T& back() const { return *(end() - 1); } + + bool empty() const; }; template @@ -386,6 +388,8 @@ const T& front() const { return *begin(); } T& back() { return *--end(); } const T& back() const { return *--end(); } + + bool empty() const; }; template @@ -466,6 +470,8 @@ const T& front() const { return *begin(); } T& back() { return *(end() - 1); } const T& back() const { return *(end() - 1); } + + bool empty() const; }; template @@ -529,6 +535,8 @@ T& front() { return *begin(); } const T& front() const { return *begin(); } + + bool empty() const; }; template Index: test/Analysis/diagnostics/explicit-suppression.cpp =================================================================== --- test/Analysis/diagnostics/explicit-suppression.cpp +++ 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:677 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:685 {{Called C++ object pointer is null}} #endif } Index: test/Analysis/iterator-range.cpp =================================================================== --- test/Analysis/iterator-range.cpp +++ test/Analysis/iterator-range.cpp @@ -5,6 +5,12 @@ 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 simple_good_end(const std::vector &v) { auto i = v.end(); if (i != v.end()) { @@ -236,3 +242,32 @@ *i0; // no-warning } } + +void empty(const std::vector &V) { + for (auto n: V) {} + *V.begin(); // expected-warning{{Past-the-end iterator dereferenced}} +} + +void non_empty1(const std::vector &V) { + assert(!V.empty()); + for (auto n: V) {} + *V.begin(); // no-warning +} + +void non_empty2(const std::vector &V) { + for (auto n: V) {} + assert(!V.empty()); + *V.begin(); // no-warning +} + +bool is_empty_V() { + std::vector V; + bool e = V.empty(); + return e; +} + +void deferred_emptiness_check() { + bool b = is_empty_V(); + if (b) { + } +}