| //===-- IteratorRangeChecker.cpp ----------------------------------*- C++ -*--// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Defines a checker for dereference of the past-the-end iterator and |
| // out-of-range increments and decrements. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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 "Iterator.h" |
| |
| using namespace clang; |
| using namespace ento; |
| using namespace iterator; |
| |
| namespace { |
| |
| class IteratorRangeChecker |
| : public Checker<check::PreCall, check::PreStmt<UnaryOperator>, |
| check::PreStmt<BinaryOperator>, |
| check::PreStmt<ArraySubscriptExpr>, |
| check::PreStmt<MemberExpr>> { |
| |
| std::unique_ptr<BugType> OutOfRangeBugType; |
| |
| void verifyDereference(CheckerContext &C, SVal Val) const; |
| void verifyIncrement(CheckerContext &C, SVal Iter) const; |
| void verifyDecrement(CheckerContext &C, SVal Iter) const; |
| void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, |
| SVal LHS, SVal RHS) const; |
| void verifyAdvance(CheckerContext &C, SVal LHS, SVal RHS) const; |
| void verifyPrev(CheckerContext &C, SVal LHS, SVal RHS) const; |
| void verifyNext(CheckerContext &C, SVal LHS, SVal RHS) const; |
| void reportBug(const StringRef &Message, SVal Val, CheckerContext &C, |
| ExplodedNode *ErrNode) const; |
| |
| public: |
| IteratorRangeChecker(); |
| |
| void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
| void checkPreStmt(const UnaryOperator *UO, CheckerContext &C) const; |
| void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; |
| void checkPreStmt(const ArraySubscriptExpr *ASE, CheckerContext &C) const; |
| void checkPreStmt(const MemberExpr *ME, CheckerContext &C) const; |
| |
| using AdvanceFn = void (IteratorRangeChecker::*)(CheckerContext &, SVal, |
| SVal) const; |
| |
| CallDescriptionMap<AdvanceFn> AdvanceFunctions = { |
| {{{"std", "advance"}, 2}, &IteratorRangeChecker::verifyAdvance}, |
| {{{"std", "prev"}, 2}, &IteratorRangeChecker::verifyPrev}, |
| {{{"std", "next"}, 2}, &IteratorRangeChecker::verifyNext}, |
| }; |
| }; |
| |
| bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); |
| bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos); |
| bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); |
| bool isZero(ProgramStateRef State, const NonLoc &Val); |
| |
| } //namespace |
| |
| IteratorRangeChecker::IteratorRangeChecker() { |
| OutOfRangeBugType.reset( |
| new BugType(this, "Iterator out of range", "Misuse of STL APIs")); |
| } |
| |
| void IteratorRangeChecker::checkPreCall(const CallEvent &Call, |
| CheckerContext &C) const { |
| // Check for out of range access |
| const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); |
| if (!Func) |
| return; |
| |
| if (Func->isOverloadedOperator()) { |
| if (isIncrementOperator(Func->getOverloadedOperator())) { |
| // Check for out-of-range incrementions |
| if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { |
| verifyIncrement(C, InstCall->getCXXThisVal()); |
| } else { |
| if (Call.getNumArgs() >= 1) { |
| verifyIncrement(C, Call.getArgSVal(0)); |
| } |
| } |
| } else if (isDecrementOperator(Func->getOverloadedOperator())) { |
| // Check for out-of-range decrementions |
| if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { |
| verifyDecrement(C, InstCall->getCXXThisVal()); |
| } else { |
| if (Call.getNumArgs() >= 1) { |
| verifyDecrement(C, Call.getArgSVal(0)); |
| } |
| } |
| } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { |
| if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { |
| // Check for out-of-range incrementions and decrementions |
| if (Call.getNumArgs() >= 1 && |
| Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { |
| verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), |
| InstCall->getCXXThisVal(), |
| Call.getArgSVal(0)); |
| } |
| } else { |
| if (Call.getNumArgs() >= 2 && |
| Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { |
| verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), |
| Call.getArgSVal(0), Call.getArgSVal(1)); |
| } |
| } |
| } else if (isDereferenceOperator(Func->getOverloadedOperator())) { |
| // Check for dereference of out-of-range iterators |
| if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { |
| verifyDereference(C, InstCall->getCXXThisVal()); |
| } else { |
| verifyDereference(C, Call.getArgSVal(0)); |
| } |
| } |
| } else { |
| const AdvanceFn *Verifier = AdvanceFunctions.lookup(Call); |
| if (Verifier) { |
| if (Call.getNumArgs() > 1) { |
| (this->**Verifier)(C, Call.getArgSVal(0), Call.getArgSVal(1)); |
| } else { |
| auto &BVF = C.getSValBuilder().getBasicValueFactory(); |
| (this->**Verifier)( |
| C, Call.getArgSVal(0), |
| nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); |
| } |
| } |
| } |
| } |
| |
| void IteratorRangeChecker::checkPreStmt(const UnaryOperator *UO, |
| CheckerContext &C) const { |
| if (isa<CXXThisExpr>(UO->getSubExpr())) |
| return; |
| |
| ProgramStateRef State = C.getState(); |
| UnaryOperatorKind OK = UO->getOpcode(); |
| SVal SubVal = State->getSVal(UO->getSubExpr(), C.getLocationContext()); |
| |
| if (isDereferenceOperator(OK)) { |
| verifyDereference(C, SubVal); |
| } else if (isIncrementOperator(OK)) { |
| verifyIncrement(C, SubVal); |
| } else if (isDecrementOperator(OK)) { |
| verifyDecrement(C, SubVal); |
| } |
| } |
| |
| void IteratorRangeChecker::checkPreStmt(const BinaryOperator *BO, |
| CheckerContext &C) const { |
| ProgramStateRef State = C.getState(); |
| BinaryOperatorKind OK = BO->getOpcode(); |
| SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext()); |
| |
| if (isDereferenceOperator(OK)) { |
| verifyDereference(C, LVal); |
| } else if (isRandomIncrOrDecrOperator(OK)) { |
| SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext()); |
| if (!BO->getRHS()->getType()->isIntegralOrEnumerationType()) |
| return; |
| verifyRandomIncrOrDecr(C, BinaryOperator::getOverloadedOperator(OK), LVal, |
| RVal); |
| } |
| } |
| |
| void IteratorRangeChecker::checkPreStmt(const ArraySubscriptExpr *ASE, |
| CheckerContext &C) const { |
| ProgramStateRef State = C.getState(); |
| SVal LVal = State->getSVal(ASE->getLHS(), C.getLocationContext()); |
| verifyDereference(C, LVal); |
| } |
| |
| void IteratorRangeChecker::checkPreStmt(const MemberExpr *ME, |
| CheckerContext &C) const { |
| if (!ME->isArrow() || ME->isImplicitAccess()) |
| return; |
| |
| ProgramStateRef State = C.getState(); |
| SVal BaseVal = State->getSVal(ME->getBase(), C.getLocationContext()); |
| verifyDereference(C, BaseVal); |
| } |
| |
| void IteratorRangeChecker::verifyDereference(CheckerContext &C, |
| SVal Val) const { |
| auto State = C.getState(); |
| const auto *Pos = getIteratorPosition(State, Val); |
| if (Pos && isPastTheEnd(State, *Pos)) { |
| auto *N = C.generateErrorNode(State); |
| if (!N) |
| return; |
| reportBug("Past-the-end iterator dereferenced.", Val, C, N); |
| return; |
| } |
| } |
| |
| void IteratorRangeChecker::verifyIncrement(CheckerContext &C, SVal Iter) const { |
| auto &BVF = C.getSValBuilder().getBasicValueFactory(); |
| verifyRandomIncrOrDecr(C, OO_Plus, Iter, |
| nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); |
| } |
| |
| void IteratorRangeChecker::verifyDecrement(CheckerContext &C, SVal Iter) const { |
| auto &BVF = C.getSValBuilder().getBasicValueFactory(); |
| verifyRandomIncrOrDecr(C, OO_Minus, Iter, |
| nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); |
| } |
| |
| void IteratorRangeChecker::verifyRandomIncrOrDecr(CheckerContext &C, |
| OverloadedOperatorKind Op, |
| SVal LHS, SVal RHS) const { |
| auto State = C.getState(); |
| |
| auto Value = RHS; |
| if (auto ValAsLoc = RHS.getAs<Loc>()) { |
| Value = State->getRawSVal(*ValAsLoc); |
| } |
| |
| if (Value.isUnknown()) |
| return; |
| |
| // Incremention or decremention by 0 is never a bug. |
| if (isZero(State, Value.castAs<NonLoc>())) |
| return; |
| |
| // The result may be the past-end iterator of the container, but any other |
| // out of range position is undefined behaviour |
| auto StateAfter = advancePosition(State, LHS, Op, Value); |
| if (!StateAfter) |
| return; |
| |
| const auto *PosAfter = getIteratorPosition(StateAfter, LHS); |
| assert(PosAfter && |
| "Iterator should have position after successful advancement"); |
| if (isAheadOfRange(State, *PosAfter)) { |
| auto *N = C.generateErrorNode(State); |
| if (!N) |
| return; |
| reportBug("Iterator decremented ahead of its valid range.", LHS, |
| C, N); |
| } |
| if (isBehindPastTheEnd(State, *PosAfter)) { |
| auto *N = C.generateErrorNode(State); |
| if (!N) |
| return; |
| reportBug("Iterator incremented behind the past-the-end " |
| "iterator.", LHS, C, N); |
| } |
| } |
| |
| void IteratorRangeChecker::verifyAdvance(CheckerContext &C, SVal LHS, |
| SVal RHS) const { |
| verifyRandomIncrOrDecr(C, OO_PlusEqual, LHS, RHS); |
| } |
| |
| void IteratorRangeChecker::verifyPrev(CheckerContext &C, SVal LHS, |
| SVal RHS) const { |
| verifyRandomIncrOrDecr(C, OO_Minus, LHS, RHS); |
| } |
| |
| void IteratorRangeChecker::verifyNext(CheckerContext &C, SVal LHS, |
| SVal RHS) const { |
| verifyRandomIncrOrDecr(C, OO_Plus, LHS, RHS); |
| } |
| |
| void IteratorRangeChecker::reportBug(const StringRef &Message, SVal Val, |
| CheckerContext &C, |
| ExplodedNode *ErrNode) const { |
| auto R = std::make_unique<PathSensitiveBugReport>(*OutOfRangeBugType, Message, |
| ErrNode); |
| |
| const auto *Pos = getIteratorPosition(C.getState(), Val); |
| assert(Pos && "Iterator without known position cannot be out-of-range."); |
| |
| R->markInteresting(Val); |
| R->markInteresting(Pos->getContainer()); |
| C.emitReport(std::move(R)); |
| } |
| |
| namespace { |
| |
| bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); |
| bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); |
| bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); |
| |
| 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 isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { |
| const auto *Cont = Pos.getContainer(); |
| const auto *CData = getContainerData(State, Cont); |
| if (!CData) |
| return false; |
| |
| const auto End = CData->getEnd(); |
| if (End) { |
| if (isEqual(State, Pos.getOffset(), End)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos) { |
| const auto *Cont = Pos.getContainer(); |
| const auto *CData = getContainerData(State, Cont); |
| if (!CData) |
| return false; |
| |
| const auto Beg = CData->getBegin(); |
| if (Beg) { |
| if (isLess(State, Pos.getOffset(), Beg)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { |
| const auto *Cont = Pos.getContainer(); |
| const auto *CData = getContainerData(State, Cont); |
| if (!CData) |
| return false; |
| |
| const auto End = CData->getEnd(); |
| if (End) { |
| if (isGreater(State, Pos.getOffset(), End)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { |
| return compare(State, Sym1, Sym2, BO_LT); |
| } |
| |
| bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { |
| return compare(State, Sym1, Sym2, BO_GT); |
| } |
| |
| bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { |
| return compare(State, Sym1, Sym2, BO_EQ); |
| } |
| |
| } // namespace |
| |
| void ento::registerIteratorRangeChecker(CheckerManager &mgr) { |
| mgr.registerChecker<IteratorRangeChecker>(); |
| } |
| |
| bool ento::shouldRegisterIteratorRangeChecker(const CheckerManager &mgr) { |
| return true; |
| } |