From: Gabor Horvath Date: Mon, 9 Jan 2017 09:52:32 +0000 (+0000) Subject: [analyzer] Add checker for iterators dereferenced beyond their range. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0560207d98587df0cf66e990efa7a610cae7dcd8;p=clang [analyzer] Add checker for iterators dereferenced beyond their range. Patch by: Adam Balogh! Differential Revision: https://reviews.llvm.org/D25660 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@291430 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/StaticAnalyzer/Checkers/Checkers.td b/include/clang/StaticAnalyzer/Checkers/Checkers.td index 2e58debf80..6957891049 100644 --- a/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -278,6 +278,14 @@ def VirtualCallChecker : Checker<"VirtualCall">, } // end: "optin.cplusplus" +let ParentPackage = CplusplusAlpha in { + +def IteratorPastEndChecker : Checker<"IteratorPastEnd">, + HelpText<"Check iterators used past end">, + DescFile<"IteratorPastEndChecker.cpp">; + +} // end: "alpha.cplusplus" + //===----------------------------------------------------------------------===// // Valist checkers. diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 41415f0376..05505ec386 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -39,6 +39,7 @@ add_clang_library(clangStaticAnalyzerCheckers GenericTaintChecker.cpp GTestChecker.cpp IdenticalExprChecker.cpp + IteratorPastEndChecker.cpp IvarInvalidationChecker.cpp LLVMConventionsChecker.cpp LocalizationChecker.cpp diff --git a/lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp new file mode 100644 index 0000000000..531054aa78 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp @@ -0,0 +1,842 @@ +//===-- IteratorPastEndChecker.cpp --------------------------------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for using iterators outside their range (past end). Usage +// means here dereferencing, incrementing etc. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +#include + +using namespace clang; +using namespace ento; + +namespace { +struct IteratorPosition { +private: + enum Kind { InRange, OutofRange } K; + IteratorPosition(Kind InK) : K(InK) {} + +public: + bool isInRange() const { return K == InRange; } + bool isOutofRange() const { return K == OutofRange; } + + static IteratorPosition getInRange() { return IteratorPosition(InRange); } + static IteratorPosition getOutofRange() { + return IteratorPosition(OutofRange); + } + + bool operator==(const IteratorPosition &X) const { return K == X.K; } + bool operator!=(const IteratorPosition &X) const { return K != X.K; } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } +}; + +typedef llvm::PointerUnion RegionOrSymbol; + +struct IteratorComparison { +private: + RegionOrSymbol Left, Right; + bool Equality; + +public: + IteratorComparison(RegionOrSymbol L, RegionOrSymbol R, bool Eq) + : Left(L), Right(R), Equality(Eq) {} + + RegionOrSymbol getLeft() const { return Left; } + RegionOrSymbol getRight() const { return Right; } + bool isEquality() const { return Equality; } + bool operator==(const IteratorComparison &X) const { + return Left == X.Left && Right == X.Right && Equality == X.Equality; + } + bool operator!=(const IteratorComparison &X) const { + return Left != X.Left || Right != X.Right || Equality != X.Equality; + } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Equality); } +}; + +class IteratorPastEndChecker + : public Checker< + check::PreCall, check::PostCall, check::PreStmt, + check::PostStmt, check::PostStmt, + check::PostStmt, check::BeginFunction, + check::DeadSymbols, eval::Assume, eval::Call> { + mutable IdentifierInfo *II_find = nullptr, + *II_find_end = nullptr, *II_find_first_of = nullptr, + *II_find_if = nullptr, *II_find_if_not = nullptr, + *II_lower_bound = nullptr, *II_upper_bound = nullptr, + *II_search = nullptr, *II_search_n = nullptr; + + std::unique_ptr PastEndBugType; + + void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal, + const SVal &RVal, OverloadedOperatorKind Op) const; + void handleAccess(CheckerContext &C, const SVal &Val) const; + void handleDecrement(CheckerContext &C, const SVal &Val) const; + void handleEnd(CheckerContext &C, const SVal &RetVal) const; + + bool evalFind(CheckerContext &C, const CallExpr *CE) const; + bool evalFindEnd(CheckerContext &C, const CallExpr *CE) const; + bool evalFindFirstOf(CheckerContext &C, const CallExpr *CE) const; + bool evalFindIf(CheckerContext &C, const CallExpr *CE) const; + bool evalFindIfNot(CheckerContext &C, const CallExpr *CE) const; + bool evalLowerBound(CheckerContext &C, const CallExpr *CE) const; + bool evalUpperBound(CheckerContext &C, const CallExpr *CE) const; + bool evalSearch(CheckerContext &C, const CallExpr *CE) const; + bool evalSearchN(CheckerContext &C, const CallExpr *CE) const; + void Find(CheckerContext &C, const CallExpr *CE) const; + + void reportPastEndBug(const StringRef &Message, const SVal &Val, + CheckerContext &C, ExplodedNode *ErrNode) const; + void initIdentifiers(ASTContext &Ctx) const; + +public: + IteratorPastEndChecker(); + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreStmt(const CXXOperatorCallExpr *COCE, CheckerContext &C) const; + void checkBeginFunction(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 checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const; + bool evalCall(const CallExpr *CE, CheckerContext &C) const; +}; +} + +REGISTER_MAP_WITH_PROGRAMSTATE(IteratorSymbolMap, SymbolRef, IteratorPosition) +REGISTER_MAP_WITH_PROGRAMSTATE(IteratorRegionMap, const MemRegion *, + IteratorPosition) + +REGISTER_MAP_WITH_PROGRAMSTATE(IteratorComparisonMap, const SymExpr *, + IteratorComparison) + +#define INIT_ID(Id) \ + if (!II_##Id) \ + II_##Id = &Ctx.Idents.get(#Id) + +namespace { + +bool isIteratorType(const QualType &Type); +bool isIterator(const CXXRecordDecl *CRD); +bool isEndCall(const FunctionDecl *Func); +bool isSimpleComparisonOperator(OverloadedOperatorKind OK); +bool isAccessOperator(OverloadedOperatorKind OK); +bool isDecrementOperator(OverloadedOperatorKind OK); +BinaryOperator::Opcode getOpcode(const SymExpr *SE); +const RegionOrSymbol getRegionOrSymbol(const SVal &Val); +const ProgramStateRef processComparison(ProgramStateRef State, + RegionOrSymbol LVal, + RegionOrSymbol RVal, bool Equal); +const ProgramStateRef saveComparison(ProgramStateRef State, + const SymExpr *Condition, const SVal &LVal, + const SVal &RVal, bool Eq); +const IteratorComparison *loadComparison(ProgramStateRef State, + const SymExpr *Condition); +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + const SVal &Val); +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym); +ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, + IteratorPosition Pos); +ProgramStateRef setIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + IteratorPosition Pos); +ProgramStateRef adjustIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + IteratorPosition Pos, bool Equal); +bool contradictingIteratorPositions(IteratorPosition Pos1, + IteratorPosition Pos2, bool Equal); +} + +IteratorPastEndChecker::IteratorPastEndChecker() { + PastEndBugType.reset( + new BugType(this, "Iterator Past End", "Misuse of STL APIs")); + PastEndBugType->setSuppressOnSink(true); +} + +void IteratorPastEndChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + // Check for access past end + const auto *Func = Call.getDecl()->getAsFunction(); + if (!Func) + return; + if (Func->isOverloadedOperator()) { + if (isAccessOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast(&Call)) { + handleAccess(C, InstCall->getCXXThisVal()); + } else { + handleAccess(C, Call.getArgSVal(0)); + } + } + } +} + +void IteratorPastEndChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + // Record end() iterators, iterator decrementation and comparison + const auto *Func = Call.getDecl()->getAsFunction(); + if (!Func) + return; + if (Func->isOverloadedOperator()) { + const auto Op = Func->getOverloadedOperator(); + if (isSimpleComparisonOperator(Op)) { + if (Func->isCXXInstanceMember()) { + const auto &InstCall = static_cast(Call); + handleComparison(C, InstCall.getReturnValue(), InstCall.getCXXThisVal(), + InstCall.getArgSVal(0), Op); + } else { + handleComparison(C, Call.getReturnValue(), Call.getArgSVal(0), + Call.getArgSVal(1), Op); + } + } else if (isDecrementOperator(Func->getOverloadedOperator())) { + if (Func->isCXXInstanceMember()) { + const auto &InstCall = static_cast(Call); + handleDecrement(C, InstCall.getCXXThisVal()); + } else { + handleDecrement(C, Call.getArgSVal(0)); + } + } + } else if (Func->isCXXInstanceMember()) { + if (!isEndCall(Func)) + return; + if (!isIteratorType(Call.getResultType())) + return; + handleEnd(C, Call.getReturnValue()); + } +} + +void IteratorPastEndChecker::checkPreStmt(const CXXOperatorCallExpr *COCE, + CheckerContext &C) const { + const auto *ThisExpr = COCE->getArg(0); + + auto State = C.getState(); + const auto *LCtx = C.getPredecessor()->getLocationContext(); + + const auto CurrentThis = State->getSVal(ThisExpr, LCtx); + if (const auto *Reg = CurrentThis.getAsRegion()) { + if (!Reg->getAs()) + return; + const auto OldState = C.getPredecessor()->getFirstPred()->getState(); + const auto OldThis = OldState->getSVal(ThisExpr, LCtx); + const auto *Pos = getIteratorPosition(OldState, OldThis); + if (!Pos) + return; + State = setIteratorPosition(State, CurrentThis, *Pos); + C.addTransition(State); + } +} + +void IteratorPastEndChecker::checkBeginFunction(CheckerContext &C) const { + // Copy state of iterator arguments to iterator parameters + auto State = C.getState(); + const auto *LCtx = C.getLocationContext(); + + const auto *Site = cast(LCtx)->getCallSite(); + if (!Site) + return; + + const auto *FD = dyn_cast(LCtx->getDecl()); + if (!FD) + return; + + const auto *CE = dyn_cast(Site); + if (!CE) + return; + + bool Change = false; + int idx = 0; + for (const auto P : FD->parameters()) { + auto Param = State->getLValue(P, LCtx); + auto Arg = State->getSVal(CE->getArg(idx++), LCtx->getParent()); + const auto *Pos = getIteratorPosition(State, Arg); + if (!Pos) + continue; + State = setIteratorPosition(State, Param, *Pos); + Change = true; + } + if (Change) { + C.addTransition(State); + } +} + +void IteratorPastEndChecker::checkPostStmt(const CXXConstructExpr *CCE, + CheckerContext &C) const { + // Transfer iterator state in case of copy or move by constructor + const auto *ctr = CCE->getConstructor(); + if (!ctr->isCopyOrMoveConstructor()) + return; + const auto *RHSExpr = CCE->getArg(0); + + auto State = C.getState(); + const auto *LCtx = C.getLocationContext(); + + const auto RetVal = State->getSVal(CCE, LCtx); + + const auto RHSVal = State->getSVal(RHSExpr, LCtx); + const auto *RHSPos = getIteratorPosition(State, RHSVal); + if (!RHSPos) + return; + State = setIteratorPosition(State, RetVal, *RHSPos); + C.addTransition(State); +} + +void IteratorPastEndChecker::checkPostStmt(const DeclStmt *DS, + CheckerContext &C) const { + // Transfer iterator state to new variable declaration + for (const auto *D : DS->decls()) { + const auto *VD = dyn_cast(D); + if (!VD || !VD->hasInit()) + continue; + + auto State = C.getState(); + const auto *LCtx = C.getPredecessor()->getLocationContext(); + const auto *Pos = + getIteratorPosition(State, State->getSVal(VD->getInit(), LCtx)); + if (!Pos) + continue; + State = setIteratorPosition(State, State->getLValue(VD, LCtx), *Pos); + C.addTransition(State); + } +} + +void IteratorPastEndChecker::checkPostStmt(const MaterializeTemporaryExpr *MTE, + CheckerContext &C) const { + /* Transfer iterator state for to temporary objects */ + auto State = C.getState(); + const auto *LCtx = C.getPredecessor()->getLocationContext(); + const auto *Pos = + getIteratorPosition(State, State->getSVal(MTE->GetTemporaryExpr(), LCtx)); + if (!Pos) + return; + State = setIteratorPosition(State, State->getSVal(MTE, LCtx), *Pos); + C.addTransition(State); +} + +void IteratorPastEndChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + auto State = C.getState(); + + auto RegionMap = State->get(); + for (const auto Reg : RegionMap) { + if (!SR.isLiveRegion(Reg.first)) { + State = State->remove(Reg.first); + } + } + + auto SymbolMap = State->get(); + for (const auto Sym : SymbolMap) { + if (SR.isDead(Sym.first)) { + State = State->remove(Sym.first); + } + } + + auto ComparisonMap = State->get(); + for (const auto Comp : ComparisonMap) { + if (SR.isDead(Comp.first)) { + State = State->remove(Comp.first); + } + } +} + +ProgramStateRef IteratorPastEndChecker::evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + // Load recorded comparison and transfer iterator state between sides + // according to comparison operator and assumption + const auto *SE = Cond.getAsSymExpr(); + if (!SE) + return State; + + auto Opc = getOpcode(SE); + if (Opc != BO_EQ && Opc != BO_NE) + return State; + + bool Negated = false; + const auto *Comp = loadComparison(State, SE); + if (!Comp) { + // Try negated comparison, which is a SymExpr to 0 integer comparison + const auto *SIE = dyn_cast(SE); + if (!SIE) + return State; + + if (SIE->getRHS() != 0) + return State; + + SE = SIE->getLHS(); + Negated = SIE->getOpcode() == BO_EQ; // Equal to zero means negation + Opc = getOpcode(SE); + if (Opc != BO_EQ && Opc != BO_NE) + return State; + + Comp = loadComparison(State, SE); + if (!Comp) + return State; + } + + return processComparison(State, Comp->getLeft(), Comp->getRight(), + (Comp->isEquality() == Assumption) != Negated); +} + +// FIXME: Evaluation of these STL calls should be moved to StdCLibraryFunctions +// checker (see patch r284960) or another similar checker for C++ STL +// functions (e.g. StdCXXLibraryFunctions or StdCppLibraryFunctions). +bool IteratorPastEndChecker::evalCall(const CallExpr *CE, + CheckerContext &C) const { + const FunctionDecl *FD = C.getCalleeDecl(CE); + if (!FD) + return false; + + ASTContext &Ctx = C.getASTContext(); + initIdentifiers(Ctx); + + if (FD->getKind() == Decl::Function) { + if (FD->isInStdNamespace()) { + if (FD->getIdentifier() == II_find) { + return evalFind(C, CE); + } else if (FD->getIdentifier() == II_find_end) { + return evalFindEnd(C, CE); + } else if (FD->getIdentifier() == II_find_first_of) { + return evalFindFirstOf(C, CE); + } else if (FD->getIdentifier() == II_find_if) { + return evalFindIf(C, CE); + } else if (FD->getIdentifier() == II_find_if) { + return evalFindIf(C, CE); + } else if (FD->getIdentifier() == II_find_if_not) { + return evalFindIfNot(C, CE); + } else if (FD->getIdentifier() == II_upper_bound) { + return evalUpperBound(C, CE); + } else if (FD->getIdentifier() == II_lower_bound) { + return evalLowerBound(C, CE); + } else if (FD->getIdentifier() == II_search) { + return evalSearch(C, CE); + } else if (FD->getIdentifier() == II_search_n) { + return evalSearchN(C, CE); + } + } + } + + return false; +} + +void IteratorPastEndChecker::handleComparison(CheckerContext &C, + const SVal &RetVal, + const SVal &LVal, + const 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(); + if (const auto *Condition = RetVal.getAsSymbolicExpression()) { + const auto *LPos = getIteratorPosition(State, LVal); + const auto *RPos = getIteratorPosition(State, RVal); + if (!LPos && !RPos) + return; + State = saveComparison(State, Condition, LVal, RVal, Op == OO_EqualEqual); + C.addTransition(State); + } else if (const auto TruthVal = RetVal.getAs()) { + if ((State = processComparison( + State, getRegionOrSymbol(LVal), getRegionOrSymbol(RVal), + (Op == OO_EqualEqual) == (TruthVal->getValue() != 0)))) { + C.addTransition(State); + } else { + C.generateSink(State, C.getPredecessor()); + } + } +} + +void IteratorPastEndChecker::handleAccess(CheckerContext &C, + const SVal &Val) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos && Pos->isOutofRange()) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) { + return; + } + reportPastEndBug("Iterator accessed past its end.", Val, C, N); + } +} + +void IteratorPastEndChecker::handleDecrement(CheckerContext &C, + const SVal &Val) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos && Pos->isOutofRange()) { + State = setIteratorPosition(State, Val, IteratorPosition::getInRange()); + // FIXME: We could also check for iterators ahead of their beginnig in the + // future, but currently we do not care for such errors. We also + // assume that the iterator is not past its end by more then one + // position. + C.addTransition(State); + } +} + +void IteratorPastEndChecker::handleEnd(CheckerContext &C, + const SVal &RetVal) const { + auto State = C.getState(); + State = setIteratorPosition(State, RetVal, IteratorPosition::getOutofRange()); + C.addTransition(State); +} + +bool IteratorPastEndChecker::evalFind(CheckerContext &C, + const CallExpr *CE) const { + if (CE->getNumArgs() == 3 && isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalFindEnd(CheckerContext &C, + const CallExpr *CE) const { + if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && + isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType()) && + isIteratorType(CE->getArg(2)->getType()) && + isIteratorType(CE->getArg(3)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalFindFirstOf(CheckerContext &C, + const CallExpr *CE) const { + if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && + isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType()) && + isIteratorType(CE->getArg(2)->getType()) && + isIteratorType(CE->getArg(3)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalFindIf(CheckerContext &C, + const CallExpr *CE) const { + if (CE->getNumArgs() == 3 && isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalFindIfNot(CheckerContext &C, + const CallExpr *CE) const { + if (CE->getNumArgs() == 3 && isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalLowerBound(CheckerContext &C, + const CallExpr *CE) const { + if ((CE->getNumArgs() == 3 || CE->getNumArgs() == 4) && + isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalUpperBound(CheckerContext &C, + const CallExpr *CE) const { + if ((CE->getNumArgs() == 3 || CE->getNumArgs() == 4) && + isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalSearch(CheckerContext &C, + const CallExpr *CE) const { + if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && + isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType()) && + isIteratorType(CE->getArg(2)->getType()) && + isIteratorType(CE->getArg(3)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +bool IteratorPastEndChecker::evalSearchN(CheckerContext &C, + const CallExpr *CE) const { + if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && + isIteratorType(CE->getArg(0)->getType()) && + isIteratorType(CE->getArg(1)->getType())) { + Find(C, CE); + return true; + } + return false; +} + +void IteratorPastEndChecker::Find(CheckerContext &C, const CallExpr *CE) const { + auto state = C.getState(); + auto &svalBuilder = C.getSValBuilder(); + const auto *LCtx = C.getLocationContext(); + + auto RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + auto SecondParam = state->getSVal(CE->getArg(1), LCtx); + + auto stateFound = state->BindExpr(CE, LCtx, RetVal); + auto stateNotFound = state->BindExpr(CE, LCtx, SecondParam); + + C.addTransition(stateFound); + C.addTransition(stateNotFound); +} + +void IteratorPastEndChecker::reportPastEndBug(const StringRef &Message, + const SVal &Val, + CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = llvm::make_unique(*PastEndBugType, Message, ErrNode); + R->markInteresting(Val); + C.emitReport(std::move(R)); +} + +void IteratorPastEndChecker::initIdentifiers(ASTContext &Ctx) const { + INIT_ID(find); + INIT_ID(find_end); + INIT_ID(find_first_of); + INIT_ID(find_if); + INIT_ID(find_if_not); + INIT_ID(lower_bound); + INIT_ID(upper_bound); + INIT_ID(search); + INIT_ID(search_n); +} + +namespace { + +bool isIteratorType(const QualType &Type) { + if (Type->isPointerType()) + return true; + + const auto *CRD = Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); + return isIterator(CRD); +} + +bool isIterator(const CXXRecordDecl *CRD) { + if (!CRD) + return false; + + const auto Name = CRD->getName(); + if (!(Name.endswith_lower("iterator") || Name.endswith_lower("iter") || + 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; + } + } + + return HasCopyCtor && HasCopyAssign && HasDtor && HasPreIncrOp && + HasPostIncrOp && HasDerefOp; +} + +bool isEndCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + return IdInfo->getName().endswith_lower("end"); +} + +bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { + return OK == OO_EqualEqual || OK == OO_ExclaimEqual; +} + +bool isAccessOperator(OverloadedOperatorKind OK) { + return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || + OK == OO_Plus || OK == OO_PlusEqual || OK == OO_PlusPlus || + OK == OO_Subscript; +} + +bool isDecrementOperator(OverloadedOperatorKind OK) { + return OK == OO_MinusEqual || OK == OO_MinusMinus; +} + +BinaryOperator::Opcode getOpcode(const SymExpr *SE) { + if (const auto *BSE = dyn_cast(SE)) { + return BSE->getOpcode(); + } else if (const auto *SC = dyn_cast(SE)) { + const auto *COE = dyn_cast(SC->getStmt()); + if (!COE) + return BO_Comma; // Extremal value, neither EQ nor NE + if (COE->getOperator() == OO_EqualEqual) { + return BO_EQ; + } else if (COE->getOperator() == OO_ExclaimEqual) { + return BO_NE; + } + return BO_Comma; // Extremal value, neither EQ nor NE + } + return BO_Comma; // Extremal value, neither EQ nor NE +} + +const RegionOrSymbol getRegionOrSymbol(const SVal &Val) { + if (const auto Reg = Val.getAsRegion()) { + return Reg; + } else if (const auto Sym = Val.getAsSymbol()) { + return Sym; + } else if (const auto LCVal = Val.getAs()) { + return LCVal->getRegion(); + } + return RegionOrSymbol(); +} + +const ProgramStateRef processComparison(ProgramStateRef State, + RegionOrSymbol LVal, + RegionOrSymbol RVal, bool Equal) { + const auto *LPos = getIteratorPosition(State, LVal); + const auto *RPos = getIteratorPosition(State, RVal); + if (LPos && !RPos) { + State = adjustIteratorPosition(State, RVal, *LPos, Equal); + } else if (!LPos && RPos) { + State = adjustIteratorPosition(State, LVal, *RPos, Equal); + } else if (LPos && RPos) { + if (contradictingIteratorPositions(*LPos, *RPos, Equal)) { + return nullptr; + } + } + return State; +} + +const ProgramStateRef saveComparison(ProgramStateRef State, + const SymExpr *Condition, const SVal &LVal, + const SVal &RVal, bool Eq) { + const auto Left = getRegionOrSymbol(LVal); + const auto Right = getRegionOrSymbol(RVal); + if (!Left || !Right) + return State; + return State->set(Condition, + IteratorComparison(Left, Right, Eq)); +} + +const IteratorComparison *loadComparison(ProgramStateRef State, + const SymExpr *Condition) { + return State->get(Condition); +} + +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + const SVal &Val) { + if (const auto Reg = Val.getAsRegion()) { + 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; +} + +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym) { + if (RegOrSym.is()) { + return State->get(RegOrSym.get()); + } else if (RegOrSym.is()) { + return State->get(RegOrSym.get()); + } + return nullptr; +} + +ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, + IteratorPosition Pos) { + if (const auto Reg = Val.getAsRegion()) { + 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; +} + +ProgramStateRef setIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + IteratorPosition Pos) { + if (RegOrSym.is()) { + return State->set(RegOrSym.get(), + Pos); + } else if (RegOrSym.is()) { + return State->set(RegOrSym.get(), Pos); + } + return nullptr; +} + +ProgramStateRef adjustIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + IteratorPosition Pos, bool Equal) { + + if ((Pos.isInRange() && Equal) || (Pos.isOutofRange() && !Equal)) { + return setIteratorPosition(State, RegOrSym, IteratorPosition::getInRange()); + } else if (Pos.isOutofRange() && Equal) { + return setIteratorPosition(State, RegOrSym, + IteratorPosition::getOutofRange()); + } else { + return State; + } +} + +bool contradictingIteratorPositions(IteratorPosition Pos1, + IteratorPosition Pos2, bool Equal) { + return ((Pos1 != Pos2) && Equal) || + ((Pos1.isOutofRange() && Pos2.isOutofRange()) && !Equal); +} +} + +void ento::registerIteratorPastEndChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp index 707168b4de..7e7e329dc4 100644 --- a/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1248,7 +1248,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Expr::MaterializeTemporaryExprClass: { Bldr.takeNodes(Pred); const MaterializeTemporaryExpr *MTE = cast(S); - CreateCXXTemporaryObject(MTE, Pred, Dst); + ExplodedNodeSet dstPrevisit; + getCheckerManager().runCheckersForPreStmt(dstPrevisit, Pred, MTE, *this); + ExplodedNodeSet dstExpr; + for (ExplodedNodeSet::iterator i = dstPrevisit.begin(), + e = dstPrevisit.end(); i != e ; ++i) { + CreateCXXTemporaryObject(MTE, *i, dstExpr); + } + getCheckerManager().runCheckersForPostStmt(Dst, dstExpr, MTE, *this); Bldr.addNodes(Dst); break; } diff --git a/test/Analysis/Inputs/system-header-simulator-cxx.h b/test/Analysis/Inputs/system-header-simulator-cxx.h index 04f1000dbc..005e7f57af 100644 --- a/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -10,6 +10,29 @@ typedef unsigned char uint8_t; typedef __typeof__(sizeof(int)) size_t; void *memmove(void *s1, const void *s2, size_t n); +template struct __iterator { + typedef __iterator iterator; + typedef __iterator const_iterator; + + __iterator(const Ptr p) : ptr(p) {} + + __iterator operator++() { return *this; } + __iterator operator++(int) { return *this; } + __iterator operator--() { return *this; } + __iterator operator--(int) { return *this; } + Ref operator*() const { return *ptr; } + Ptr operator->() const { return *ptr; } + + bool operator==(const iterator &rhs) const { return ptr == rhs.ptr; } + bool operator==(const const_iterator &rhs) const { return ptr == rhs.ptr; } + + bool operator!=(const iterator &rhs) const { return ptr != rhs.ptr; } + bool operator!=(const const_iterator &rhs) const { return ptr != rhs.ptr; } + +private: + Ptr ptr; +}; + namespace std { template struct pair { @@ -27,6 +50,9 @@ namespace std { template class vector { + typedef __iterator iterator; + typedef __iterator const_iterator; + T *_start; T *_finish; T *_end_of_storage; @@ -49,11 +75,10 @@ namespace std { return _start[n]; } - T *begin() { return _start; } - const T *begin() const { return _start; } - - T *end() { return _finish; } - const T *end() const { return _finish; } + iterator begin() { return iterator(_start); } + const_iterator begin() const { return const_iterator(_start); } + iterator end() { return iterator(_finish); } + const_iterator end() const { return const_iterator(_finish); } }; class exception { @@ -223,6 +248,35 @@ namespace std { return __copy_backward(II, IE, OI); } + template + InputIterator find(InputIterator first, InputIterator last, const T &val); + template + ForwardIterator1 find_end(ForwardIterator1 first1, ForwardIterator1 last1, + ForwardIterator2 first2, ForwardIterator2 last2); + template + ForwardIterator1 find_first_of(ForwardIterator1 first1, + ForwardIterator1 last1, + ForwardIterator2 first2, + ForwardIterator2 last2); + template + InputIterator find_if(InputIterator first, InputIterator last, + UnaryPredicate pred); + template + InputIterator find_if_not(InputIterator first, InputIterator last, + UnaryPredicate pred); + template + InputIterator lower_bound(InputIterator first, InputIterator last, + const T &val); + template + InputIterator upper_bound(InputIterator first, InputIterator last, + const T &val); + template + ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1, + ForwardIterator2 first2, ForwardIterator2 last2); + template + ForwardIterator1 search_n(ForwardIterator1 first1, ForwardIterator1 last1, + ForwardIterator2 first2, ForwardIterator2 last2); + struct input_iterator_tag { }; struct output_iterator_tag { }; struct forward_iterator_tag : public input_iterator_tag { }; diff --git a/test/Analysis/diagnostics/explicit-suppression.cpp b/test/Analysis/diagnostics/explicit-suppression.cpp index 67a47d02b6..d36def20f2 100644 --- a/test/Analysis/diagnostics/explicit-suppression.cpp +++ b/test/Analysis/diagnostics/explicit-suppression.cpp @@ -18,6 +18,6 @@ class C { void testCopyNull(C *I, C *E) { std::copy(I, E, (C *)0); #ifndef SUPPRESSED - // expected-warning@../Inputs/system-header-simulator-cxx.h:166 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:191 {{Called C++ object pointer is null}} #endif } diff --git a/test/Analysis/inlining/stl.cpp b/test/Analysis/inlining/stl.cpp index 95ac3f8172..d89a109830 100644 --- a/test/Analysis/inlining/stl.cpp +++ b/test/Analysis/inlining/stl.cpp @@ -6,8 +6,7 @@ void clang_analyzer_eval(bool); void testVector(std::vector &nums) { - if (nums.begin()) return; - if (nums.end()) return; + if (nums.begin() != nums.end()) return; clang_analyzer_eval(nums.size() == 0); #if INLINE diff --git a/test/Analysis/iterator-past-end.cpp b/test/Analysis/iterator-past-end.cpp new file mode 100644 index 0000000000..4d9ed0cf98 --- /dev/null +++ b/test/Analysis/iterator-past-end.cpp @@ -0,0 +1,205 @@ +// RUN: %clang_cc1 -std=c++11 -analyze -analyzer-checker=core,cplusplus,alpha.cplusplus.IteratorPastEnd -analyzer-eagerly-assume -analyzer-config c++-container-inlining=false %s -verify +// RUN: %clang_cc1 -std=c++11 -analyze -analyzer-checker=core,cplusplus,alpha.cplusplus.IteratorPastEnd -analyzer-eagerly-assume -analyzer-config c++-container-inlining=true -DINLINE=1 %s -verify + +#include "Inputs/system-header-simulator-cxx.h" + +void simple_good(const std::vector &v) { + auto i = v.end(); + if (i != v.end()) + *i; // no-warning +} + +void simple_good_negated(const std::vector &v) { + auto i = v.end(); + if (!(i == v.end())) + *i; // no-warning +} + +void simple_bad(const std::vector &v) { + auto i = v.end(); + *i; // expected-warning{{Iterator accessed past its end}} +} + +void copy(const std::vector &v) { + auto i1 = v.end(); + auto i2 = i1; + *i2; // expected-warning{{Iterator accessed past its end}} +} + +void decrease(const std::vector &v) { + auto i = v.end(); + --i; + *i; // no-warning +} + +void copy_and_decrease1(const std::vector &v) { + auto i1 = v.end(); + auto i2 = i1; + --i1; + *i1; // no-warning +} + +void copy_and_decrease2(const std::vector &v) { + auto i1 = v.end(); + auto i2 = i1; + --i1; + *i2; // expected-warning{{Iterator accessed past its end}} +} + +void copy_and_increase1(const std::vector &v) { + auto i1 = v.begin(); + auto i2 = i1; + ++i1; + if (i1 == v.end()) + *i2; // no-warning +} + +void copy_and_increase2(const std::vector &v) { + auto i1 = v.begin(); + auto i2 = i1; + ++i1; + if (i2 == v.end()) + *i2; // expected-warning{{Iterator accessed past its end}} +} + +void good_find(std::vector &vec, int e) { + auto first = std::find(vec.begin(), vec.end(), e); + if (vec.end() != first) + *first; // no-warning +} + +void bad_find(std::vector &vec, int e) { + auto first = std::find(vec.begin(), vec.end(), e); + *first; // expected-warning{{Iterator accessed past its end}} +} + +void good_find_end(std::vector &vec, std::vector &seq) { + auto last = std::find_end(vec.begin(), vec.end(), seq.begin(), seq.end()); + if (vec.end() != last) + *last; // no-warning +} + +void bad_find_end(std::vector &vec, std::vector &seq) { + auto last = std::find_end(vec.begin(), vec.end(), seq.begin(), seq.end()); + *last; // expected-warning{{Iterator accessed past its end}} +} + +void good_find_first_of(std::vector &vec, std::vector &seq) { + auto first = + std::find_first_of(vec.begin(), vec.end(), seq.begin(), seq.end()); + if (vec.end() != first) + *first; // no-warning +} + +void bad_find_first_of(std::vector &vec, std::vector &seq) { + auto first = std::find_end(vec.begin(), vec.end(), seq.begin(), seq.end()); + *first; // expected-warning{{Iterator accessed past its end}} +} + +bool odd(int i) { return i % 2; } + +void good_find_if(std::vector &vec) { + auto first = std::find_if(vec.begin(), vec.end(), odd); + if (vec.end() != first) + *first; // no-warning +} + +void bad_find_if(std::vector &vec, int e) { + auto first = std::find_if(vec.begin(), vec.end(), odd); + *first; // expected-warning{{Iterator accessed past its end}} +} + +void good_find_if_not(std::vector &vec) { + auto first = std::find_if_not(vec.begin(), vec.end(), odd); + if (vec.end() != first) + *first; // no-warning +} + +void bad_find_if_not(std::vector &vec, int e) { + auto first = std::find_if_not(vec.begin(), vec.end(), odd); + *first; // expected-warning{{Iterator accessed past its end}} +} + +void good_lower_bound(std::vector &vec, int e) { + auto first = std::lower_bound(vec.begin(), vec.end(), e); + if (vec.end() != first) + *first; // no-warning +} + +void bad_lower_bound(std::vector &vec, int e) { + auto first = std::lower_bound(vec.begin(), vec.end(), e); + *first; // expected-warning{{Iterator accessed past its end}} +} + +void good_upper_bound(std::vector &vec, int e) { + auto last = std::lower_bound(vec.begin(), vec.end(), e); + if (vec.end() != last) + *last; // no-warning +} + +void bad_upper_bound(std::vector &vec, int e) { + auto last = std::lower_bound(vec.begin(), vec.end(), e); + *last; // expected-warning{{Iterator accessed past its end}} +} + +void good_search(std::vector &vec, std::vector &seq) { + auto first = std::search(vec.begin(), vec.end(), seq.begin(), seq.end()); + if (vec.end() != first) + *first; // no-warning +} + +void bad_search(std::vector &vec, std::vector &seq) { + auto first = std::search(vec.begin(), vec.end(), seq.begin(), seq.end()); + *first; // expected-warning{{Iterator accessed past its end}} +} + +void good_search_n(std::vector &vec, std::vector &seq) { + auto nth = std::search_n(vec.begin(), vec.end(), seq.begin(), seq.end()); + if (vec.end() != nth) + *nth; // no-warning +} + +void bad_search_n(std::vector &vec, std::vector &seq) { + auto nth = std::search_n(vec.begin(), vec.end(), seq.begin(), seq.end()); + *nth; // expected-warning{{Iterator accessed past its end}} +} + +template +InputIterator nonStdFind(InputIterator first, InputIterator last, + const T &val) { + for (auto i = first; i != last; ++i) { + if (*i == val) { + return i; + } + } + return last; +} + +void good_non_std_find(std::vector &vec, int e) { + auto first = nonStdFind(vec.begin(), vec.end(), e); + if (vec.end() != first) + *first; // no-warning +} + +void bad_non_std_find(std::vector &vec, int e) { + auto first = nonStdFind(vec.begin(), vec.end(), e); + *first; // expected-warning{{Iterator accessed past its end}} +} + +void tricky(std::vector &vec, int e) { + const auto first = vec.begin(); + const auto comp1 = (first != vec.end()), comp2 = (first == vec.end()); + if (comp1) + *first; +} + +void loop(std::vector &vec, int e) { + auto start = vec.begin(); + while (true) { + auto item = std::find(start, vec.end(), e); + if (item == vec.end()) + break; + *item; // no-warning + start = ++item; // no-warning + } +}