This commit explicitly specifies the matching mode (C library function, any non-method function, or C++ method) for the `CallDescription`s constructed in the iterator/container checkers. This change won't cause major functional changes, but isn't NFC because it ensures that e.g. call descriptions for a non-method function won't accidentally match a method that has the same name. Separate commits will perform (or have already performed) this change in other checkers. My goal is to ensure that the call description mode is always explicitly specified and eliminate (or strongly restrict) the vague "may be either a method or a simple function" mode that's the current default. I'm handling the iterator checkers in this separate commit because they're infamously complex; but I don't expect any trouble because this transition doesn't interact with the "central" logic of iterator handling.
852 lines
33 KiB
C++
852 lines
33 KiB
C++
//===-- IteratorModeling.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 modeling-checker for modeling STL iterator-like iterators.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 "clang/AST/DeclTemplate.h"
|
|
#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 "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
|
|
#include "Iterator.h"
|
|
|
|
#include <utility>
|
|
|
|
using namespace clang;
|
|
using namespace ento;
|
|
using namespace iterator;
|
|
|
|
namespace {
|
|
|
|
class IteratorModeling
|
|
: public Checker<check::PostCall, check::PostStmt<UnaryOperator>,
|
|
check::PostStmt<BinaryOperator>,
|
|
check::PostStmt<MaterializeTemporaryExpr>,
|
|
check::Bind, check::LiveSymbols, check::DeadSymbols> {
|
|
|
|
using AdvanceFn = void (IteratorModeling::*)(CheckerContext &, const Expr *,
|
|
SVal, SVal, SVal) const;
|
|
|
|
void handleOverloadedOperator(CheckerContext &C, const CallEvent &Call,
|
|
OverloadedOperatorKind Op) const;
|
|
void handleAdvanceLikeFunction(CheckerContext &C, const CallEvent &Call,
|
|
const Expr *OrigExpr,
|
|
const AdvanceFn *Handler) const;
|
|
|
|
void handleComparison(CheckerContext &C, const Expr *CE, SVal RetVal,
|
|
SVal LVal, SVal RVal, OverloadedOperatorKind Op) const;
|
|
void processComparison(CheckerContext &C, ProgramStateRef State,
|
|
SymbolRef Sym1, SymbolRef Sym2, SVal RetVal,
|
|
OverloadedOperatorKind Op) const;
|
|
void handleIncrement(CheckerContext &C, SVal RetVal, SVal Iter,
|
|
bool Postfix) const;
|
|
void handleDecrement(CheckerContext &C, SVal RetVal, SVal Iter,
|
|
bool Postfix) const;
|
|
void handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE,
|
|
OverloadedOperatorKind Op, SVal RetVal,
|
|
SVal Iterator, SVal Amount) const;
|
|
void handlePtrIncrOrDecr(CheckerContext &C, const Expr *Iterator,
|
|
OverloadedOperatorKind OK, SVal Offset) const;
|
|
void handleAdvance(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter,
|
|
SVal Amount) const;
|
|
void handlePrev(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter,
|
|
SVal Amount) const;
|
|
void handleNext(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter,
|
|
SVal Amount) const;
|
|
void assignToContainer(CheckerContext &C, const Expr *CE, SVal RetVal,
|
|
const MemRegion *Cont) const;
|
|
bool noChangeInAdvance(CheckerContext &C, SVal Iter, const Expr *CE) const;
|
|
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
|
|
const char *Sep) const override;
|
|
|
|
// std::advance, std::prev & std::next
|
|
CallDescriptionMap<AdvanceFn> AdvanceLikeFunctions = {
|
|
// template<class InputIt, class Distance>
|
|
// void advance(InputIt& it, Distance n);
|
|
{{CDM::SimpleFunc, {"std", "advance"}, 2},
|
|
&IteratorModeling::handleAdvance},
|
|
|
|
// template<class BidirIt>
|
|
// BidirIt prev(
|
|
// BidirIt it,
|
|
// typename std::iterator_traits<BidirIt>::difference_type n = 1);
|
|
{{CDM::SimpleFunc, {"std", "prev"}, 2}, &IteratorModeling::handlePrev},
|
|
|
|
// template<class ForwardIt>
|
|
// ForwardIt next(
|
|
// ForwardIt it,
|
|
// typename std::iterator_traits<ForwardIt>::difference_type n = 1);
|
|
{{CDM::SimpleFunc, {"std", "next"}, 2}, &IteratorModeling::handleNext},
|
|
};
|
|
|
|
public:
|
|
IteratorModeling() = default;
|
|
|
|
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
|
|
void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const;
|
|
void checkPostStmt(const UnaryOperator *UO, CheckerContext &C) const;
|
|
void checkPostStmt(const BinaryOperator *BO, 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;
|
|
};
|
|
|
|
bool isSimpleComparisonOperator(OverloadedOperatorKind OK);
|
|
bool isSimpleComparisonOperator(BinaryOperatorKind OK);
|
|
ProgramStateRef removeIteratorPosition(ProgramStateRef State, SVal Val);
|
|
ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1,
|
|
SymbolRef Sym2, bool Equal);
|
|
bool isBoundThroughLazyCompoundVal(const Environment &Env,
|
|
const MemRegion *Reg);
|
|
const ExplodedNode *findCallEnter(const ExplodedNode *Node, const Expr *Call);
|
|
|
|
} // namespace
|
|
|
|
void IteratorModeling::checkPostCall(const CallEvent &Call,
|
|
CheckerContext &C) const {
|
|
// Record new iterator positions and iterator position changes
|
|
const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
|
|
if (!Func)
|
|
return;
|
|
|
|
if (Func->isOverloadedOperator()) {
|
|
const auto Op = Func->getOverloadedOperator();
|
|
handleOverloadedOperator(C, Call, Op);
|
|
return;
|
|
}
|
|
|
|
const auto *OrigExpr = Call.getOriginExpr();
|
|
if (!OrigExpr)
|
|
return;
|
|
|
|
const AdvanceFn *Handler = AdvanceLikeFunctions.lookup(Call);
|
|
if (Handler) {
|
|
handleAdvanceLikeFunction(C, Call, OrigExpr, Handler);
|
|
return;
|
|
}
|
|
|
|
if (!isIteratorType(Call.getResultType()))
|
|
return;
|
|
|
|
auto State = C.getState();
|
|
|
|
// Already bound to container?
|
|
if (getIteratorPosition(State, Call.getReturnValue()))
|
|
return;
|
|
|
|
// Copy-like and move constructors
|
|
if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) {
|
|
if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) {
|
|
State = setIteratorPosition(State, Call.getReturnValue(), *Pos);
|
|
if (cast<CXXConstructorDecl>(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 of the
|
|
// same type as the return value 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()) &&
|
|
Call.getArgExpr(i)->getType().getNonReferenceType().getDesugaredType(
|
|
C.getASTContext()).getTypePtr() ==
|
|
Call.getResultType().getDesugaredType(C.getASTContext()).getTypePtr()) {
|
|
if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) {
|
|
assignToContainer(C, OrigExpr, Call.getReturnValue(),
|
|
Pos->getContainer());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::checkBind(SVal Loc, SVal Val, const Stmt *S,
|
|
CheckerContext &C) const {
|
|
auto State = C.getState();
|
|
const auto *Pos = getIteratorPosition(State, Val);
|
|
if (Pos) {
|
|
State = setIteratorPosition(State, Loc, *Pos);
|
|
C.addTransition(State);
|
|
} else {
|
|
const auto *OldPos = getIteratorPosition(State, Loc);
|
|
if (OldPos) {
|
|
State = removeIteratorPosition(State, Loc);
|
|
C.addTransition(State);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::checkPostStmt(const UnaryOperator *UO,
|
|
CheckerContext &C) const {
|
|
UnaryOperatorKind OK = UO->getOpcode();
|
|
if (!isIncrementOperator(OK) && !isDecrementOperator(OK))
|
|
return;
|
|
|
|
auto &SVB = C.getSValBuilder();
|
|
handlePtrIncrOrDecr(C, UO->getSubExpr(),
|
|
isIncrementOperator(OK) ? OO_Plus : OO_Minus,
|
|
SVB.makeArrayIndex(1));
|
|
}
|
|
|
|
void IteratorModeling::checkPostStmt(const BinaryOperator *BO,
|
|
CheckerContext &C) const {
|
|
const ProgramStateRef State = C.getState();
|
|
const BinaryOperatorKind OK = BO->getOpcode();
|
|
const Expr *const LHS = BO->getLHS();
|
|
const Expr *const RHS = BO->getRHS();
|
|
const SVal LVal = State->getSVal(LHS, C.getLocationContext());
|
|
const SVal RVal = State->getSVal(RHS, C.getLocationContext());
|
|
|
|
if (isSimpleComparisonOperator(BO->getOpcode())) {
|
|
SVal Result = State->getSVal(BO, C.getLocationContext());
|
|
handleComparison(C, BO, Result, LVal, RVal,
|
|
BinaryOperator::getOverloadedOperator(OK));
|
|
} else if (isRandomIncrOrDecrOperator(OK)) {
|
|
// In case of operator+ the iterator can be either on the LHS (eg.: it + 1),
|
|
// or on the RHS (eg.: 1 + it). Both cases are modeled.
|
|
const bool IsIterOnLHS = BO->getLHS()->getType()->isPointerType();
|
|
const Expr *const &IterExpr = IsIterOnLHS ? LHS : RHS;
|
|
const Expr *const &AmountExpr = IsIterOnLHS ? RHS : LHS;
|
|
|
|
// The non-iterator side must have an integral or enumeration type.
|
|
if (!AmountExpr->getType()->isIntegralOrEnumerationType())
|
|
return;
|
|
SVal AmountVal = IsIterOnLHS ? RVal : LVal;
|
|
handlePtrIncrOrDecr(C, IterExpr, BinaryOperator::getOverloadedOperator(OK),
|
|
AmountVal);
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::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->getSubExpr()));
|
|
if (!Pos)
|
|
return;
|
|
State = setIteratorPosition(State, C.getSVal(MTE), *Pos);
|
|
C.addTransition(State);
|
|
}
|
|
|
|
void IteratorModeling::checkLiveSymbols(ProgramStateRef State,
|
|
SymbolReaper &SR) const {
|
|
// Keep symbolic expressions of iterator positions alive
|
|
auto RegionMap = State->get<IteratorRegionMap>();
|
|
for (const IteratorPosition &Pos : llvm::make_second_range(RegionMap)) {
|
|
for (SymbolRef Sym : Pos.getOffset()->symbols())
|
|
if (isa<SymbolData>(Sym))
|
|
SR.markLive(Sym);
|
|
}
|
|
|
|
auto SymbolMap = State->get<IteratorSymbolMap>();
|
|
for (const IteratorPosition &Pos : llvm::make_second_range(SymbolMap)) {
|
|
for (SymbolRef Sym : Pos.getOffset()->symbols())
|
|
if (isa<SymbolData>(Sym))
|
|
SR.markLive(Sym);
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::checkDeadSymbols(SymbolReaper &SR,
|
|
CheckerContext &C) const {
|
|
// Cleanup
|
|
auto State = C.getState();
|
|
|
|
auto RegionMap = State->get<IteratorRegionMap>();
|
|
for (const auto &Reg : RegionMap) {
|
|
if (!SR.isLiveRegion(Reg.first)) {
|
|
// The region behind the `LazyCompoundVal` is often cleaned up before
|
|
// the `LazyCompoundVal` itself. If there are iterator positions keyed
|
|
// by these regions their cleanup must be deferred.
|
|
if (!isBoundThroughLazyCompoundVal(State->getEnvironment(), Reg.first)) {
|
|
State = State->remove<IteratorRegionMap>(Reg.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto SymbolMap = State->get<IteratorSymbolMap>();
|
|
for (const auto &Sym : SymbolMap) {
|
|
if (!SR.isLive(Sym.first)) {
|
|
State = State->remove<IteratorSymbolMap>(Sym.first);
|
|
}
|
|
}
|
|
|
|
C.addTransition(State);
|
|
}
|
|
|
|
void
|
|
IteratorModeling::handleOverloadedOperator(CheckerContext &C,
|
|
const CallEvent &Call,
|
|
OverloadedOperatorKind Op) const {
|
|
if (isSimpleComparisonOperator(Op)) {
|
|
const auto *OrigExpr = Call.getOriginExpr();
|
|
if (!OrigExpr)
|
|
return;
|
|
|
|
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
|
|
handleComparison(C, OrigExpr, Call.getReturnValue(),
|
|
InstCall->getCXXThisVal(), Call.getArgSVal(0), Op);
|
|
return;
|
|
}
|
|
|
|
handleComparison(C, OrigExpr, Call.getReturnValue(), Call.getArgSVal(0),
|
|
Call.getArgSVal(1), Op);
|
|
return;
|
|
} else if (isRandomIncrOrDecrOperator(Op)) {
|
|
const auto *OrigExpr = Call.getOriginExpr();
|
|
if (!OrigExpr)
|
|
return;
|
|
|
|
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
|
|
if (Call.getNumArgs() >= 1 &&
|
|
Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) {
|
|
handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(),
|
|
InstCall->getCXXThisVal(), Call.getArgSVal(0));
|
|
return;
|
|
}
|
|
} else if (Call.getNumArgs() >= 2) {
|
|
const Expr *FirstArg = Call.getArgExpr(0);
|
|
const Expr *SecondArg = Call.getArgExpr(1);
|
|
const QualType FirstType = FirstArg->getType();
|
|
const QualType SecondType = SecondArg->getType();
|
|
|
|
if (FirstType->isIntegralOrEnumerationType() ||
|
|
SecondType->isIntegralOrEnumerationType()) {
|
|
// In case of operator+ the iterator can be either on the LHS (eg.:
|
|
// it + 1), or on the RHS (eg.: 1 + it). Both cases are modeled.
|
|
const bool IsIterFirst = FirstType->isStructureOrClassType();
|
|
const SVal FirstArg = Call.getArgSVal(0);
|
|
const SVal SecondArg = Call.getArgSVal(1);
|
|
SVal Iterator = IsIterFirst ? FirstArg : SecondArg;
|
|
SVal Amount = IsIterFirst ? SecondArg : FirstArg;
|
|
|
|
handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(),
|
|
Iterator, Amount);
|
|
return;
|
|
}
|
|
}
|
|
} else if (isIncrementOperator(Op)) {
|
|
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
|
|
handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
|
|
Call.getNumArgs());
|
|
return;
|
|
}
|
|
|
|
handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0),
|
|
Call.getNumArgs());
|
|
return;
|
|
} else if (isDecrementOperator(Op)) {
|
|
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
|
|
handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
|
|
Call.getNumArgs());
|
|
return;
|
|
}
|
|
|
|
handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0),
|
|
Call.getNumArgs());
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
IteratorModeling::handleAdvanceLikeFunction(CheckerContext &C,
|
|
const CallEvent &Call,
|
|
const Expr *OrigExpr,
|
|
const AdvanceFn *Handler) const {
|
|
if (!C.wasInlined) {
|
|
(this->**Handler)(C, OrigExpr, Call.getReturnValue(),
|
|
Call.getArgSVal(0), Call.getArgSVal(1));
|
|
return;
|
|
}
|
|
|
|
// If std::advance() was inlined, but a non-standard function it calls inside
|
|
// was not, then we have to model it explicitly
|
|
const auto *IdInfo = cast<FunctionDecl>(Call.getDecl())->getIdentifier();
|
|
if (IdInfo) {
|
|
if (IdInfo->getName() == "advance") {
|
|
if (noChangeInAdvance(C, Call.getArgSVal(0), OrigExpr)) {
|
|
(this->**Handler)(C, OrigExpr, Call.getReturnValue(),
|
|
Call.getArgSVal(0), Call.getArgSVal(1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE,
|
|
SVal RetVal, SVal LVal, 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();
|
|
const auto *LPos = getIteratorPosition(State, LVal);
|
|
const auto *RPos = getIteratorPosition(State, RVal);
|
|
const MemRegion *Cont = nullptr;
|
|
if (LPos) {
|
|
Cont = LPos->getContainer();
|
|
} else if (RPos) {
|
|
Cont = RPos->getContainer();
|
|
}
|
|
if (!Cont)
|
|
return;
|
|
|
|
// At least one of the iterators has recorded positions. If one of them does
|
|
// not then create a new symbol for the offset.
|
|
SymbolRef Sym;
|
|
if (!LPos || !RPos) {
|
|
auto &SymMgr = C.getSymbolManager();
|
|
Sym = SymMgr.conjureSymbol(CE, C.getLocationContext(),
|
|
C.getASTContext().LongTy, C.blockCount());
|
|
State = assumeNoOverflow(State, Sym, 4);
|
|
}
|
|
|
|
if (!LPos) {
|
|
State = setIteratorPosition(State, LVal,
|
|
IteratorPosition::getPosition(Cont, Sym));
|
|
LPos = getIteratorPosition(State, LVal);
|
|
} else if (!RPos) {
|
|
State = setIteratorPosition(State, RVal,
|
|
IteratorPosition::getPosition(Cont, Sym));
|
|
RPos = getIteratorPosition(State, RVal);
|
|
}
|
|
|
|
// If the value for which we just tried to set a new iterator position is
|
|
// an `SVal`for which no iterator position can be set then the setting was
|
|
// unsuccessful. We cannot handle the comparison in this case.
|
|
if (!LPos || !RPos)
|
|
return;
|
|
|
|
// We cannot make assumptions on `UnknownVal`. Let us conjure a symbol
|
|
// instead.
|
|
if (RetVal.isUnknown()) {
|
|
auto &SymMgr = C.getSymbolManager();
|
|
auto *LCtx = C.getLocationContext();
|
|
RetVal = nonloc::SymbolVal(SymMgr.conjureSymbol(
|
|
CE, LCtx, C.getASTContext().BoolTy, C.blockCount()));
|
|
State = State->BindExpr(CE, LCtx, RetVal);
|
|
}
|
|
|
|
processComparison(C, State, LPos->getOffset(), RPos->getOffset(), RetVal, Op);
|
|
}
|
|
|
|
void IteratorModeling::processComparison(CheckerContext &C,
|
|
ProgramStateRef State, SymbolRef Sym1,
|
|
SymbolRef Sym2, SVal RetVal,
|
|
OverloadedOperatorKind Op) const {
|
|
if (const auto TruthVal = RetVal.getAs<nonloc::ConcreteInt>()) {
|
|
if ((State = relateSymbols(State, Sym1, Sym2,
|
|
(Op == OO_EqualEqual) ==
|
|
(TruthVal->getValue() != 0)))) {
|
|
C.addTransition(State);
|
|
} else {
|
|
C.generateSink(State, C.getPredecessor());
|
|
}
|
|
return;
|
|
}
|
|
|
|
const auto ConditionVal = RetVal.getAs<DefinedSVal>();
|
|
if (!ConditionVal)
|
|
return;
|
|
|
|
if (auto StateTrue = relateSymbols(State, Sym1, Sym2, Op == OO_EqualEqual)) {
|
|
StateTrue = StateTrue->assume(*ConditionVal, true);
|
|
C.addTransition(StateTrue);
|
|
}
|
|
|
|
if (auto StateFalse = relateSymbols(State, Sym1, Sym2, Op != OO_EqualEqual)) {
|
|
StateFalse = StateFalse->assume(*ConditionVal, false);
|
|
C.addTransition(StateFalse);
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::handleIncrement(CheckerContext &C, SVal RetVal,
|
|
SVal Iter, bool Postfix) const {
|
|
// Increment the symbolic expressions which represents the position of the
|
|
// iterator
|
|
auto State = C.getState();
|
|
auto &BVF = C.getSymbolManager().getBasicVals();
|
|
|
|
const auto *Pos = getIteratorPosition(State, Iter);
|
|
if (!Pos)
|
|
return;
|
|
|
|
auto NewState =
|
|
advancePosition(State, Iter, OO_Plus,
|
|
nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
|
|
assert(NewState &&
|
|
"Advancing position by concrete int should always be successful");
|
|
|
|
const auto *NewPos = getIteratorPosition(NewState, Iter);
|
|
assert(NewPos &&
|
|
"Iterator should have position after successful advancement");
|
|
|
|
State = setIteratorPosition(State, Iter, *NewPos);
|
|
State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos);
|
|
C.addTransition(State);
|
|
}
|
|
|
|
void IteratorModeling::handleDecrement(CheckerContext &C, SVal RetVal,
|
|
SVal Iter, bool Postfix) const {
|
|
// Decrement the symbolic expressions which represents the position of the
|
|
// iterator
|
|
auto State = C.getState();
|
|
auto &BVF = C.getSymbolManager().getBasicVals();
|
|
|
|
const auto *Pos = getIteratorPosition(State, Iter);
|
|
if (!Pos)
|
|
return;
|
|
|
|
auto NewState =
|
|
advancePosition(State, Iter, OO_Minus,
|
|
nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
|
|
assert(NewState &&
|
|
"Advancing position by concrete int should always be successful");
|
|
|
|
const auto *NewPos = getIteratorPosition(NewState, Iter);
|
|
assert(NewPos &&
|
|
"Iterator should have position after successful advancement");
|
|
|
|
State = setIteratorPosition(State, Iter, *NewPos);
|
|
State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos);
|
|
C.addTransition(State);
|
|
}
|
|
|
|
void IteratorModeling::handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE,
|
|
OverloadedOperatorKind Op,
|
|
SVal RetVal, SVal Iterator,
|
|
SVal Amount) const {
|
|
// Increment or decrement the symbolic expressions which represents the
|
|
// position of the iterator
|
|
auto State = C.getState();
|
|
|
|
const auto *Pos = getIteratorPosition(State, Iterator);
|
|
if (!Pos)
|
|
return;
|
|
|
|
const auto *Value = &Amount;
|
|
SVal Val;
|
|
if (auto LocAmount = Amount.getAs<Loc>()) {
|
|
Val = State->getRawSVal(*LocAmount);
|
|
Value = &Val;
|
|
}
|
|
|
|
const auto &TgtVal =
|
|
(Op == OO_PlusEqual || Op == OO_MinusEqual) ? Iterator : RetVal;
|
|
|
|
// `AdvancedState` is a state where the position of `LHS` is advanced. We
|
|
// only need this state to retrieve the new position, but we do not want
|
|
// to change the position of `LHS` (in every case).
|
|
auto AdvancedState = advancePosition(State, Iterator, Op, *Value);
|
|
if (AdvancedState) {
|
|
const auto *NewPos = getIteratorPosition(AdvancedState, Iterator);
|
|
assert(NewPos &&
|
|
"Iterator should have position after successful advancement");
|
|
|
|
State = setIteratorPosition(State, TgtVal, *NewPos);
|
|
C.addTransition(State);
|
|
} else {
|
|
assignToContainer(C, CE, TgtVal, Pos->getContainer());
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::handlePtrIncrOrDecr(CheckerContext &C,
|
|
const Expr *Iterator,
|
|
OverloadedOperatorKind OK,
|
|
SVal Offset) const {
|
|
if (!isa<DefinedSVal>(Offset))
|
|
return;
|
|
|
|
QualType PtrType = Iterator->getType();
|
|
if (!PtrType->isPointerType())
|
|
return;
|
|
QualType ElementType = PtrType->getPointeeType();
|
|
|
|
ProgramStateRef State = C.getState();
|
|
SVal OldVal = State->getSVal(Iterator, C.getLocationContext());
|
|
|
|
const IteratorPosition *OldPos = getIteratorPosition(State, OldVal);
|
|
if (!OldPos)
|
|
return;
|
|
|
|
SVal NewVal;
|
|
if (OK == OO_Plus || OK == OO_PlusEqual) {
|
|
NewVal = State->getLValue(ElementType, Offset, OldVal);
|
|
} else {
|
|
auto &SVB = C.getSValBuilder();
|
|
SVal NegatedOffset = SVB.evalMinus(Offset.castAs<NonLoc>());
|
|
NewVal = State->getLValue(ElementType, NegatedOffset, OldVal);
|
|
}
|
|
|
|
// `AdvancedState` is a state where the position of `Old` is advanced. We
|
|
// only need this state to retrieve the new position, but we do not want
|
|
// ever to change the position of `OldVal`.
|
|
auto AdvancedState = advancePosition(State, OldVal, OK, Offset);
|
|
if (AdvancedState) {
|
|
const IteratorPosition *NewPos = getIteratorPosition(AdvancedState, OldVal);
|
|
assert(NewPos &&
|
|
"Iterator should have position after successful advancement");
|
|
|
|
ProgramStateRef NewState = setIteratorPosition(State, NewVal, *NewPos);
|
|
C.addTransition(NewState);
|
|
} else {
|
|
assignToContainer(C, Iterator, NewVal, OldPos->getContainer());
|
|
}
|
|
}
|
|
|
|
void IteratorModeling::handleAdvance(CheckerContext &C, const Expr *CE,
|
|
SVal RetVal, SVal Iter,
|
|
SVal Amount) const {
|
|
handleRandomIncrOrDecr(C, CE, OO_PlusEqual, RetVal, Iter, Amount);
|
|
}
|
|
|
|
void IteratorModeling::handlePrev(CheckerContext &C, const Expr *CE,
|
|
SVal RetVal, SVal Iter, SVal Amount) const {
|
|
handleRandomIncrOrDecr(C, CE, OO_Minus, RetVal, Iter, Amount);
|
|
}
|
|
|
|
void IteratorModeling::handleNext(CheckerContext &C, const Expr *CE,
|
|
SVal RetVal, SVal Iter, SVal Amount) const {
|
|
handleRandomIncrOrDecr(C, CE, OO_Plus, RetVal, Iter, Amount);
|
|
}
|
|
|
|
void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE,
|
|
SVal RetVal,
|
|
const MemRegion *Cont) const {
|
|
Cont = Cont->getMostDerivedObjectRegion();
|
|
|
|
auto State = C.getState();
|
|
const auto *LCtx = C.getLocationContext();
|
|
State = createIteratorPosition(State, RetVal, Cont, CE, LCtx, C.blockCount());
|
|
|
|
C.addTransition(State);
|
|
}
|
|
|
|
bool IteratorModeling::noChangeInAdvance(CheckerContext &C, SVal Iter,
|
|
const Expr *CE) const {
|
|
// Compare the iterator position before and after the call. (To be called
|
|
// from `checkPostCall()`.)
|
|
const auto StateAfter = C.getState();
|
|
|
|
const auto *PosAfter = getIteratorPosition(StateAfter, Iter);
|
|
// If we have no position after the call of `std::advance`, then we are not
|
|
// interested. (Modeling of an inlined `std::advance()` should not remove the
|
|
// position in any case.)
|
|
if (!PosAfter)
|
|
return false;
|
|
|
|
const ExplodedNode *N = findCallEnter(C.getPredecessor(), CE);
|
|
assert(N && "Any call should have a `CallEnter` node.");
|
|
|
|
const auto StateBefore = N->getState();
|
|
const auto *PosBefore = getIteratorPosition(StateBefore, Iter);
|
|
// FIXME: `std::advance()` should not create a new iterator position but
|
|
// change existing ones. However, in case of iterators implemented as
|
|
// pointers the handling of parameters in `std::advance()`-like
|
|
// functions is still incomplete which may result in cases where
|
|
// the new position is assigned to the wrong pointer. This causes
|
|
// crash if we use an assertion here.
|
|
if (!PosBefore)
|
|
return false;
|
|
|
|
return PosBefore->getOffset() == PosAfter->getOffset();
|
|
}
|
|
|
|
void IteratorModeling::printState(raw_ostream &Out, ProgramStateRef State,
|
|
const char *NL, const char *Sep) const {
|
|
auto SymbolMap = State->get<IteratorSymbolMap>();
|
|
auto RegionMap = State->get<IteratorRegionMap>();
|
|
// Use a counter to add newlines before every line except the first one.
|
|
unsigned Count = 0;
|
|
|
|
if (!SymbolMap.isEmpty() || !RegionMap.isEmpty()) {
|
|
Out << Sep << "Iterator Positions :" << NL;
|
|
for (const auto &Sym : SymbolMap) {
|
|
if (Count++)
|
|
Out << NL;
|
|
|
|
Sym.first->dumpToStream(Out);
|
|
Out << " : ";
|
|
const auto Pos = Sym.second;
|
|
Out << (Pos.isValid() ? "Valid" : "Invalid") << " ; Container == ";
|
|
Pos.getContainer()->dumpToStream(Out);
|
|
Out<<" ; Offset == ";
|
|
Pos.getOffset()->dumpToStream(Out);
|
|
}
|
|
|
|
for (const auto &Reg : RegionMap) {
|
|
if (Count++)
|
|
Out << NL;
|
|
|
|
Reg.first->dumpToStream(Out);
|
|
Out << " : ";
|
|
const auto Pos = Reg.second;
|
|
Out << (Pos.isValid() ? "Valid" : "Invalid") << " ; Container == ";
|
|
Pos.getContainer()->dumpToStream(Out);
|
|
Out<<" ; Offset == ";
|
|
Pos.getOffset()->dumpToStream(Out);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool isSimpleComparisonOperator(OverloadedOperatorKind OK) {
|
|
return OK == OO_EqualEqual || OK == OO_ExclaimEqual;
|
|
}
|
|
|
|
bool isSimpleComparisonOperator(BinaryOperatorKind OK) {
|
|
return OK == BO_EQ || OK == BO_NE;
|
|
}
|
|
|
|
ProgramStateRef removeIteratorPosition(ProgramStateRef State, SVal Val) {
|
|
if (auto Reg = Val.getAsRegion()) {
|
|
Reg = Reg->getMostDerivedObjectRegion();
|
|
return State->remove<IteratorRegionMap>(Reg);
|
|
} else if (const auto Sym = Val.getAsSymbol()) {
|
|
return State->remove<IteratorSymbolMap>(Sym);
|
|
} else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) {
|
|
return State->remove<IteratorRegionMap>(LCVal->getRegion());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1,
|
|
SymbolRef Sym2, 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(Sym1),
|
|
nonloc::SymbolVal(Sym2), SVB.getConditionType());
|
|
|
|
assert(isa<DefinedSVal>(comparison) &&
|
|
"Symbol comparison must be a `DefinedSVal`");
|
|
|
|
auto NewState = State->assume(comparison.castAs<DefinedSVal>(), Equal);
|
|
if (!NewState)
|
|
return nullptr;
|
|
|
|
if (const auto CompSym = comparison.getAsSymbol()) {
|
|
assert(isa<SymIntExpr>(CompSym) &&
|
|
"Symbol comparison must be a `SymIntExpr`");
|
|
assert(BinaryOperator::isComparisonOp(
|
|
cast<SymIntExpr>(CompSym)->getOpcode()) &&
|
|
"Symbol comparison must be a comparison");
|
|
return assumeNoOverflow(NewState, cast<SymIntExpr>(CompSym)->getLHS(), 2);
|
|
}
|
|
|
|
return NewState;
|
|
}
|
|
|
|
bool isBoundThroughLazyCompoundVal(const Environment &Env,
|
|
const MemRegion *Reg) {
|
|
for (const auto &Binding : Env) {
|
|
if (const auto LCVal = Binding.second.getAs<nonloc::LazyCompoundVal>()) {
|
|
if (LCVal->getRegion() == Reg)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const ExplodedNode *findCallEnter(const ExplodedNode *Node, const Expr *Call) {
|
|
while (Node) {
|
|
ProgramPoint PP = Node->getLocation();
|
|
if (auto Enter = PP.getAs<CallEnter>()) {
|
|
if (Enter->getCallExpr() == Call)
|
|
break;
|
|
}
|
|
|
|
Node = Node->getFirstPred();
|
|
}
|
|
|
|
return Node;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void ento::registerIteratorModeling(CheckerManager &mgr) {
|
|
mgr.registerChecker<IteratorModeling>();
|
|
}
|
|
|
|
bool ento::shouldRegisterIteratorModeling(const CheckerManager &mgr) {
|
|
return true;
|
|
}
|