diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp')
-rw-r--r-- | contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp new file mode 100644 index 000000000000..4dd2f700a2a0 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp @@ -0,0 +1,372 @@ +//===-- 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/CallDescription.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>> { + + const BugType OutOfRangeBugType{this, "Iterator out of range", + "Misuse of STL APIs"}; + + 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(StringRef Message, SVal Val, CheckerContext &C, + ExplodedNode *ErrNode) const; + +public: + 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; + + // FIXME: these three functions are also listed in IteratorModeling.cpp, + // perhaps unify their handling? + CallDescriptionMap<AdvanceFn> AdvanceFunctions = { + {{CDM::SimpleFunc, {"std", "advance"}, 2}, + &IteratorRangeChecker::verifyAdvance}, + {{CDM::SimpleFunc, {"std", "prev"}, 2}, + &IteratorRangeChecker::verifyPrev}, + {{CDM::SimpleFunc, {"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, NonLoc Val); + +} // namespace + +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.isUnknownOrUndef() || !isa<NonLoc>(Value)) + 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(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, 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; +} |