//===-- IteratorChecker.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. // //===----------------------------------------------------------------------===// // // In the code, iterator can be represented as a: // * type-I: typedef-ed pointer. Operations over such iterator, such as // comparisons or increments, are modeled straightforwardly by the // analyzer. // * type-II: structure with its method bodies available. Operations over such // iterator are inlined by the analyzer, and results of modeling // these operations are exposing implementation details of the // iterators, which is not necessarily helping. // * type-III: completely opaque structure. Operations over such iterator are // modeled conservatively, producing conjured symbols everywhere. // // To handle all these types in a common way we introduce a structure called // IteratorPosition which is an abstraction of the position the iterator // represents using symbolic expressions. The checker handles all the // operations on this structure. // // Additionally, depending on the circumstances, operators of types II and III // can be represented as: // * type-IIa, type-IIIa: conjured structure symbols - when returned by value // from conservatively evaluated methods such as // `.begin()`. // * type-IIb, type-IIIb: memory regions of iterator-typed objects, such as // variables or temporaries, when the iterator object is // currently treated as an lvalue. // * type-IIc, type-IIIc: compound values of iterator-typed objects, when the // iterator object is treated as an rvalue taken of a // particular lvalue, eg. a copy of "type-a" iterator // object, or an iterator that existed before the // analysis has started. // // To handle any of these three different representations stored in an SVal we // use setter and getters functions which separate the three cases. To store // them we use a pointer union of symbol and memory region. // // The checker works the following way: We record the begin and the // past-end iterator for all containers whenever their `.begin()` and `.end()` // are called. Since the Constraint Manager cannot handle such SVals we need // to take over its role. We post-check equality and non-equality comparisons // and record that the two sides are equal if we are in the 'equal' branch // (true-branch for `==` and false-branch for `!=`). // // In case of type-I or type-II iterators we get a concrete integer as a result // of the comparison (1 or 0) but in case of type-III we only get a Symbol. In // this latter case we record the symbol and reload it in evalAssume() and do // the propagation there. We also handle (maybe double) negated comparisons // which are represented in the form of (x == 0 or x != 0) where x is the // comparison itself. // // Since `SimpleConstraintManager` cannot handle complex symbolic expressions // we only use expressions of the format S, S+n or S-n for iterator positions // where S is a conjured symbol and n is an unsigned concrete integer. When // making an assumption e.g. `S1 + n == S2 + m` we store `S1 - S2 == m - n` as // a constraint which we later retrieve when doing an actual comparison. #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" using namespace clang; using namespace ento; namespace { // Abstract position of an iterator. This helps to handle all three kinds // of operators in a common way by using a symbolic position. struct IteratorPosition { private: // Container the iterator belongs to const MemRegion *Cont; // Abstract offset const SymbolRef Offset; IteratorPosition(const MemRegion *C, SymbolRef Of) : Cont(C), Offset(Of) {} public: const MemRegion *getContainer() const { return Cont; } SymbolRef getOffset() const { return Offset; } static IteratorPosition getPosition(const MemRegion *C, SymbolRef Of) { return IteratorPosition(C, Of); } IteratorPosition setTo(SymbolRef NewOf) const { return IteratorPosition(Cont, NewOf); } bool operator==(const IteratorPosition &X) const { return Cont == X.Cont && Offset == X.Offset; } bool operator!=(const IteratorPosition &X) const { return Cont != X.Cont || Offset != X.Offset; } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(Cont); ID.Add(Offset); } }; typedef llvm::PointerUnion RegionOrSymbol; // Structure to record the symbolic begin and end position of a container struct ContainerData { private: const SymbolRef Begin, End; ContainerData(SymbolRef B, SymbolRef E) : Begin(B), End(E) {} public: static ContainerData fromBegin(SymbolRef B) { return ContainerData(B, nullptr); } static ContainerData fromEnd(SymbolRef E) { return ContainerData(nullptr, E); } SymbolRef getBegin() const { return Begin; } SymbolRef getEnd() const { return End; } ContainerData newBegin(SymbolRef B) const { return ContainerData(B, End); } ContainerData newEnd(SymbolRef E) const { return ContainerData(Begin, E); } bool operator==(const ContainerData &X) const { return Begin == X.Begin && End == X.End; } bool operator!=(const ContainerData &X) const { return Begin != X.Begin || End != X.End; } void Profile(llvm::FoldingSetNodeID &ID) const { ID.Add(Begin); ID.Add(End); } }; // Structure fo recording iterator comparisons. We needed to retrieve the // original comparison expression in assumptions. 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 IteratorChecker : public Checker, check::PostStmt, check::LiveSymbols, check::DeadSymbols, eval::Assume> { std::unique_ptr OutOfRangeBugType; void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const; void verifyDereference(CheckerContext &C, const SVal &Val) 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 handleRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, const SVal &RetVal, const SVal &LHS, const SVal &RHS) const; void handleBegin(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const; void handleEnd(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const; void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, const MemRegion *Cont) const; void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, const SVal &RetVal, const SVal &LHS, const SVal &RHS) const; void reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const; public: IteratorChecker(); enum CheckKind { CK_IteratorRangeChecker, CK_NumCheckKinds }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; CheckName CheckNames[CK_NumCheckKinds]; 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 checkPostStmt(const MaterializeTemporaryExpr *MTE, CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, bool Assumption) const; }; } // namespace REGISTER_MAP_WITH_PROGRAMSTATE(IteratorSymbolMap, SymbolRef, IteratorPosition) REGISTER_MAP_WITH_PROGRAMSTATE(IteratorRegionMap, const MemRegion *, IteratorPosition) REGISTER_MAP_WITH_PROGRAMSTATE(ContainerMap, const MemRegion *, ContainerData) REGISTER_MAP_WITH_PROGRAMSTATE(IteratorComparisonMap, const SymExpr *, IteratorComparison) namespace { bool isIteratorType(const QualType &Type); bool isIterator(const CXXRecordDecl *CRD); bool isBeginCall(const FunctionDecl *Func); bool isEndCall(const FunctionDecl *Func); bool isSimpleComparisonOperator(OverloadedOperatorKind OK); bool isDereferenceOperator(OverloadedOperatorKind OK); bool isIncrementOperator(OverloadedOperatorKind OK); bool isDecrementOperator(OverloadedOperatorKind OK); bool isRandomIncrOrDecrOperator(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); SymbolRef getContainerBegin(ProgramStateRef State, const MemRegion *Cont); SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont); ProgramStateRef createContainerBegin(ProgramStateRef State, const MemRegion *Cont, const SymbolRef Sym); ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, const SymbolRef Sym); const IteratorPosition *getIteratorPosition(ProgramStateRef State, const SVal &Val); const IteratorPosition *getIteratorPosition(ProgramStateRef State, RegionOrSymbol RegOrSym); ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, const IteratorPosition &Pos); ProgramStateRef setIteratorPosition(ProgramStateRef State, RegionOrSymbol RegOrSym, const IteratorPosition &Pos); ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val); ProgramStateRef adjustIteratorPosition(ProgramStateRef State, RegionOrSymbol RegOrSym, const IteratorPosition &Pos, bool Equal); ProgramStateRef relateIteratorPositions(ProgramStateRef State, const IteratorPosition &Pos1, const IteratorPosition &Pos2, bool Equal); const ContainerData *getContainerData(ProgramStateRef State, const MemRegion *Cont); ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, const ContainerData &CData); bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos); bool isZero(ProgramStateRef State, const NonLoc &Val); } // namespace IteratorChecker::IteratorChecker() { OutOfRangeBugType.reset( new BugType(this, "Iterator out of range", "Misuse of STL APIs")); OutOfRangeBugType->setSuppressOnSink(true); } void IteratorChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { // Check for out of range access const auto *Func = dyn_cast_or_null(Call.getDecl()); if (!Func) return; if (Func->isOverloadedOperator()) { if (ChecksEnabled[CK_IteratorRangeChecker] && isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { // Check for out-of-range incrementions and decrementions if (Call.getNumArgs() >= 1) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); } } else { if (Call.getNumArgs() >= 2) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1)); } } } else if (ChecksEnabled[CK_IteratorRangeChecker] && isDereferenceOperator(Func->getOverloadedOperator())) { // Check for dereference of out-of-range iterators if (const auto *InstCall = dyn_cast(&Call)) { verifyDereference(C, InstCall->getCXXThisVal()); } else { verifyDereference(C, Call.getArgSVal(0)); } } } } void IteratorChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { // Record new iterator positions and iterator position changes const auto *Func = dyn_cast_or_null(Call.getDecl()); if (!Func) return; if (Func->isOverloadedOperator()) { const auto Op = Func->getOverloadedOperator(); if (isSimpleComparisonOperator(Op)) { if (const auto *InstCall = dyn_cast(&Call)) { handleComparison(C, Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0), Op); } else { handleComparison(C, Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1), Op); } } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { if (Call.getNumArgs() >= 1) { handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); } } else { if (Call.getNumArgs() >= 2) { handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1)); } } } else if (isIncrementOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getNumArgs()); } else { handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0), Call.getNumArgs()); } } else if (isDecrementOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getNumArgs()); } else { handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0), Call.getNumArgs()); } } } else { const auto *OrigExpr = Call.getOriginExpr(); if (!OrigExpr) return; if (!isIteratorType(Call.getResultType())) return; auto State = C.getState(); // Already bound to container? if (getIteratorPosition(State, Call.getReturnValue())) return; if (const auto *InstCall = dyn_cast(&Call)) { if (isBeginCall(Func)) { handleBegin(C, OrigExpr, Call.getReturnValue(), InstCall->getCXXThisVal()); return; } if (isEndCall(Func)) { handleEnd(C, OrigExpr, Call.getReturnValue(), InstCall->getCXXThisVal()); 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); if (cast(Func)->isMoveConstructor()) { State = removeIteratorPosition(State, Call.getArgSVal(0)); } C.addTransition(State); return; } } // Assumption: if return value is an iterator which is not yet bound to a // container, then look for the first iterator argument, and // bind the return value to the same container. This approach // works for STL algorithms. // FIXME: Add a more conservative mode for (unsigned i = 0; i < Call.getNumArgs(); ++i) { if (isIteratorType(Call.getArgExpr(i)->getType())) { if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) { assignToContainer(C, OrigExpr, Call.getReturnValue(), Pos->getContainer()); return; } } } } } void IteratorChecker::checkPreStmt(const CXXOperatorCallExpr *COCE, CheckerContext &C) const { const auto *ThisExpr = COCE->getArg(0); auto State = C.getState(); const auto *LCtx = C.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); // FIXME: This solution is unreliable. It may happen that another checker // subscribes to the pre-statement check of `CXXOperatorCallExpr` // and adds a transition before us. The proper fix is to make the // CFG provide a `ConstructionContext` for the `CXXOperatorCallExpr`, // which would turn the corresponding `CFGStmt` element into a // `CFGCXXRecordTypedCall` element, which will allow `ExprEngine` to // foresee that the `begin()`/`end()` call constructs the object // directly in the temporary region that `CXXOperatorCallExpr` takes // as its implicit object argument. const auto *Pos = getIteratorPosition(OldState, OldThis); if (!Pos) return; State = setIteratorPosition(State, CurrentThis, *Pos); C.addTransition(State); } } void IteratorChecker::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->GetTemporaryExpr())); if (!Pos) return; State = setIteratorPosition(State, C.getSVal(MTE), *Pos); C.addTransition(State); } void IteratorChecker::checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const { // Keep symbolic expressions of iterator positions, container begins and ends // alive auto RegionMap = State->get(); 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)) SR.markLive(*i); } auto SymbolMap = State->get(); for (const auto Sym : SymbolMap) { const auto Offset = Sym.second.getOffset(); for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) if (isa(*i)) SR.markLive(*i); } auto ContMap = State->get(); for (const auto Cont : ContMap) { const auto CData = Cont.second; if (CData.getBegin()) { SR.markLive(CData.getBegin()); } if (CData.getEnd()) { SR.markLive(CData.getEnd()); } } } void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { // Cleanup 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.isLive(Sym.first)) { State = State->remove(Sym.first); } } auto ContMap = State->get(); for (const auto Cont : ContMap) { if (!SR.isLiveRegion(Cont.first)) { State = State->remove(Cont.first); } } auto ComparisonMap = State->get(); for (const auto Comp : ComparisonMap) { if (!SR.isLive(Comp.first)) { State = State->remove(Comp.first); } } } ProgramStateRef IteratorChecker::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); } void IteratorChecker::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 IteratorChecker::verifyDereference(CheckerContext &C, const SVal &Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && isOutOfRange(State, *Pos)) { // If I do not put a tag here, some range tests will fail static CheckerProgramPointTag Tag("IteratorRangeChecker", "IteratorOutOfRange"); auto *N = C.generateNonFatalErrorNode(State, &Tag); if (!N) return; reportOutOfRangeBug("Iterator accessed outside of its range.", Val, C, N); } } void IteratorChecker::handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, bool Postfix) const { // Increment the symbolic expressions which represents the position of the // iterator auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Iter); if (Pos) { auto &SymMgr = C.getSymbolManager(); auto &BVF = SymMgr.getBasicVals(); auto &SVB = C.getSValBuilder(); const auto OldOffset = Pos->getOffset(); auto NewOffset = SVB.evalBinOp(State, BO_Add, nonloc::SymbolVal(OldOffset), nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), SymMgr.getType(OldOffset)).getAsSymbol(); auto NewPos = Pos->setTo(NewOffset); State = setIteratorPosition(State, Iter, NewPos); State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); C.addTransition(State); } } void IteratorChecker::handleDecrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, bool Postfix) const { // Decrement the symbolic expressions which represents the position of the // iterator auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Iter); if (Pos) { auto &SymMgr = C.getSymbolManager(); auto &BVF = SymMgr.getBasicVals(); auto &SVB = C.getSValBuilder(); const auto OldOffset = Pos->getOffset(); auto NewOffset = SVB.evalBinOp(State, BO_Sub, nonloc::SymbolVal(OldOffset), nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), SymMgr.getType(OldOffset)).getAsSymbol(); auto NewPos = Pos->setTo(NewOffset); State = setIteratorPosition(State, Iter, NewPos); State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); C.addTransition(State); } } // This function tells the analyzer's engine that symbols produced by our // checker, most notably iterator positions, are relatively small. // A distance between items in the container should not be very large. // By assuming that it is within around 1/8 of the address space, // we can help the analyzer perform operations on these symbols // without being afraid of integer overflows. // FIXME: Should we provide it as an API, so that all checkers could use it? static ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, long Scale) { SValBuilder &SVB = State->getStateManager().getSValBuilder(); BasicValueFactory &BV = SVB.getBasicValueFactory(); QualType T = Sym->getType(); assert(T->isSignedIntegerOrEnumerationType()); APSIntType AT = BV.getAPSIntType(T); ProgramStateRef NewState = State; llvm::APSInt Max = AT.getMaxValue() / AT.getValue(Scale); SVal IsCappedFromAbove = SVB.evalBinOpNN(State, BO_LE, nonloc::SymbolVal(Sym), nonloc::ConcreteInt(Max), SVB.getConditionType()); if (auto DV = IsCappedFromAbove.getAs()) { NewState = NewState->assume(*DV, true); if (!NewState) return State; } llvm::APSInt Min = -Max; SVal IsCappedFromBelow = SVB.evalBinOpNN(State, BO_GE, nonloc::SymbolVal(Sym), nonloc::ConcreteInt(Min), SVB.getConditionType()); if (auto DV = IsCappedFromBelow.getAs()) { NewState = NewState->assume(*DV, true); if (!NewState) return State; } return NewState; } void IteratorChecker::handleRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, const SVal &RetVal, const SVal &LHS, const SVal &RHS) const { // Increment or decrement the symbolic expressions which represents the // position of the iterator auto State = C.getState(); const auto *Pos = getIteratorPosition(State, LHS); if (!Pos) return; const auto *value = &RHS; if (auto loc = RHS.getAs()) { const auto val = State->getRawSVal(*loc); value = &val; } auto &SymMgr = C.getSymbolManager(); auto &SVB = C.getSValBuilder(); auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; const auto OldOffset = Pos->getOffset(); SymbolRef NewOffset; if (const auto intValue = value->getAs()) { // For concrete integers we can calculate the new position NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset), *intValue, SymMgr.getType(OldOffset)).getAsSymbol(); } else { // For other symbols create a new symbol to keep expressions simple const auto &LCtx = C.getLocationContext(); NewOffset = SymMgr.conjureSymbol(nullptr, LCtx, SymMgr.getType(OldOffset), C.blockCount()); State = assumeNoOverflow(State, NewOffset, 4); } auto NewPos = Pos->setTo(NewOffset); auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal; State = setIteratorPosition(State, TgtVal, NewPos); C.addTransition(State); } void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, const SVal &RetVal, const SVal &LHS, const SVal &RHS) const { auto State = C.getState(); // If the iterator is initially inside its range, then the operation is valid const auto *Pos = getIteratorPosition(State, LHS); if (!Pos || !isOutOfRange(State, *Pos)) return; auto value = RHS; if (auto loc = RHS.getAs()) { value = State->getRawSVal(*loc); } // Incremention or decremention by 0 is never bug if (isZero(State, value.castAs())) return; auto &SymMgr = C.getSymbolManager(); auto &SVB = C.getSValBuilder(); auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; const auto OldOffset = Pos->getOffset(); const auto intValue = value.getAs(); if (!intValue) return; auto NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset), *intValue, SymMgr.getType(OldOffset)).getAsSymbol(); auto NewPos = Pos->setTo(NewOffset); // If out of range, the only valid operation is to step into the range if (isOutOfRange(State, NewPos)) { auto *N = C.generateNonFatalErrorNode(State); if (!N) return; reportOutOfRangeBug("Iterator accessed past its end.", LHS, C, N); } } void IteratorChecker::handleBegin(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; while (const auto *CBOR = ContReg->getAs()) { ContReg = CBOR->getSuperRegion(); } // 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) { auto &SymMgr = C.getSymbolManager(); BeginSym = SymMgr.conjureSymbol(CE, C.getLocationContext(), C.getASTContext().LongTy, C.blockCount()); State = assumeNoOverflow(State, BeginSym, 4); State = createContainerBegin(State, ContReg, BeginSym); } State = setIteratorPosition(State, RetVal, IteratorPosition::getPosition(ContReg, BeginSym)); C.addTransition(State); } void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; while (const auto *CBOR = ContReg->getAs()) { ContReg = CBOR->getSuperRegion(); } // If the container already has an end symbol then use it. Otherwise first // create a new one. auto State = C.getState(); auto EndSym = getContainerEnd(State, ContReg); if (!EndSym) { auto &SymMgr = C.getSymbolManager(); EndSym = SymMgr.conjureSymbol(CE, C.getLocationContext(), C.getASTContext().LongTy, C.blockCount()); State = assumeNoOverflow(State, EndSym, 4); State = createContainerEnd(State, ContReg, EndSym); } State = setIteratorPosition(State, RetVal, IteratorPosition::getPosition(ContReg, EndSym)); C.addTransition(State); } void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, const MemRegion *Cont) const { while (const auto *CBOR = Cont->getAs()) { Cont = CBOR->getSuperRegion(); } auto State = C.getState(); auto &SymMgr = C.getSymbolManager(); auto Sym = SymMgr.conjureSymbol(CE, C.getLocationContext(), C.getASTContext().LongTy, C.blockCount()); State = assumeNoOverflow(State, Sym, 4); State = setIteratorPosition(State, RetVal, IteratorPosition::getPosition(Cont, Sym)); C.addTransition(State); } void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const { auto R = llvm::make_unique(*OutOfRangeBugType, Message, ErrNode); R->markInteresting(Val); C.emitReport(std::move(R)); } namespace { bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc); bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, BinaryOperator::Opcode Opc); 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 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"); } bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { return OK == OO_EqualEqual || OK == OO_ExclaimEqual; } bool isDereferenceOperator(OverloadedOperatorKind OK) { return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || OK == OO_Subscript; } bool isIncrementOperator(OverloadedOperatorKind OK) { return OK == OO_PlusPlus; } bool isDecrementOperator(OverloadedOperatorKind OK) { return OK == OO_MinusMinus; } bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK) { return OK == OO_Plus || OK == OO_PlusEqual || OK == OO_Minus || OK == OO_MinusEqual; } 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_or_null(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) { State = relateIteratorPositions(State, *LPos, *RPos, Equal); } 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); } SymbolRef getContainerBegin(ProgramStateRef State, const MemRegion *Cont) { const auto *CDataPtr = getContainerData(State, Cont); if (!CDataPtr) return nullptr; return CDataPtr->getBegin(); } SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont) { const auto *CDataPtr = getContainerData(State, Cont); if (!CDataPtr) return nullptr; return CDataPtr->getEnd(); } ProgramStateRef createContainerBegin(ProgramStateRef State, const MemRegion *Cont, const SymbolRef Sym) { // Only create if it does not exist const auto *CDataPtr = getContainerData(State, Cont); if (CDataPtr) { if (CDataPtr->getBegin()) { return State; } const auto CData = CDataPtr->newBegin(Sym); return setContainerData(State, Cont, CData); } const auto CData = ContainerData::fromBegin(Sym); return setContainerData(State, Cont, CData); } ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, const SymbolRef Sym) { // Only create if it does not exist const auto *CDataPtr = getContainerData(State, Cont); if (CDataPtr) { if (CDataPtr->getEnd()) { return State; } const auto CData = CDataPtr->newEnd(Sym); return setContainerData(State, Cont, CData); } const auto CData = ContainerData::fromEnd(Sym); return setContainerData(State, Cont, CData); } const ContainerData *getContainerData(ProgramStateRef State, const MemRegion *Cont) { return State->get(Cont); } ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, const ContainerData &CData) { return State->set(Cont, CData); } 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, const 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, const IteratorPosition &Pos) { if (RegOrSym.is()) { return State->set(RegOrSym.get(), Pos); } else if (RegOrSym.is()) { return State->set(RegOrSym.get(), Pos); } return nullptr; } ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { if (const auto Reg = Val.getAsRegion()) { 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 adjustIteratorPosition(ProgramStateRef State, RegionOrSymbol RegOrSym, const IteratorPosition &Pos, bool Equal) { if (Equal) { return setIteratorPosition(State, RegOrSym, Pos); } else { return State; } } ProgramStateRef relateIteratorPositions(ProgramStateRef State, const IteratorPosition &Pos1, const IteratorPosition &Pos2, 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(Pos1.getOffset()), nonloc::SymbolVal(Pos2.getOffset()), SVB.getConditionType()); assert(comparison.getAs() && "Symbol comparison must be a `DefinedSVal`"); auto NewState = State->assume(comparison.castAs(), Equal); 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 isZero(ProgramStateRef State, const NonLoc &Val) { auto &BVF = State->getBasicVals(); return compare(State, Val, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0))), BO_EQ); } bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { const auto *Cont = Pos.getContainer(); const auto *CData = getContainerData(State, Cont); if (!CData) return false; // Out of range means less than the begin symbol or greater or equal to the // end symbol. const auto Beg = CData->getBegin(); if (Beg) { if (isLess(State, Pos.getOffset(), Beg)) { return true; } } const auto End = CData->getEnd(); if (End) { if (isGreaterOrEqual(State, Pos.getOffset(), End)) { return true; } } return false; } bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { return compare(State, Sym1, Sym2, BO_LT); } bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { return compare(State, Sym1, Sym2, BO_GE); } bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc) { return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc); } bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, BinaryOperator::Opcode Opc) { auto &SVB = State->getStateManager().getSValBuilder(); const auto comparison = SVB.evalBinOp(State, Opc, NL1, NL2, SVB.getConditionType()); assert(comparison.getAs() && "Symbol comparison must be a `DefinedSVal`"); return !State->assume(comparison.castAs(), false); } } // namespace #define REGISTER_CHECKER(name) \ void ento::register##name(CheckerManager &Mgr) { \ auto *checker = Mgr.registerChecker(); \ checker->ChecksEnabled[IteratorChecker::CK_##name] = true; \ checker->CheckNames[IteratorChecker::CK_##name] = \ Mgr.getCurrentCheckName(); \ } REGISTER_CHECKER(IteratorRangeChecker)