//===-- MismatchedIteratorChecker.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 mistakenly applying a foreign iterator on a container
// and for using iterators of two different containers in a context where
// iterators of the same container should be used.
//
//===----------------------------------------------------------------------===//
#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 MismatchedIteratorChecker
: public Checker<check::PreCall, check::PreStmt<BinaryOperator>> {
std::unique_ptr<BugType> MismatchedBugType;
void verifyMatch(CheckerContext &C, const SVal &Iter,
const MemRegion *Cont) const;
void verifyMatch(CheckerContext &C, const SVal &Iter1,
const SVal &Iter2) const;
void reportBug(const StringRef &Message, const SVal &Val1,
const SVal &Val2, CheckerContext &C,
ExplodedNode *ErrNode) const;
void reportBug(const StringRef &Message, const SVal &Val,
const MemRegion *Reg, CheckerContext &C,
ExplodedNode *ErrNode) const;
public:
MismatchedIteratorChecker();
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const;
};
} // namespace
MismatchedIteratorChecker::MismatchedIteratorChecker() {
MismatchedBugType.reset(
new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs",
/*SuppressOnSink=*/true));
}
void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
// Check for iterator mismatches
const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
if (!Func)
return;
if (Func->isOverloadedOperator() &&
isComparisonOperator(Func->getOverloadedOperator())) {
// Check for comparisons of iterators of different containers
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
if (Call.getNumArgs() < 1)
return;
if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) ||
!isIteratorType(Call.getArgExpr(0)->getType()))
return;
verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0));
} else {
if (Call.getNumArgs() < 2)
return;
if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
!isIteratorType(Call.getArgExpr(1)->getType()))
return;
verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
}
} else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
const auto *ContReg = InstCall->getCXXThisVal().getAsRegion();
if (!ContReg)
return;
// Check for erase, insert and emplace using iterator of another container
if (isEraseCall(Func) || isEraseAfterCall(Func)) {
verifyMatch(C, Call.getArgSVal(0),
InstCall->getCXXThisVal().getAsRegion());
if (Call.getNumArgs() == 2) {
verifyMatch(C, Call.getArgSVal(1),
InstCall->getCXXThisVal().getAsRegion());
}
} else if (isInsertCall(Func)) {
verifyMatch(C, Call.getArgSVal(0),
InstCall->getCXXThisVal().getAsRegion());
if (Call.getNumArgs() == 3 &&
isIteratorType(Call.getArgExpr(1)->getType()) &&
isIteratorType(Call.getArgExpr(2)->getType())) {
verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2));
}
} else if (isEmplaceCall(Func)) {
verifyMatch(C, Call.getArgSVal(0),
InstCall->getCXXThisVal().getAsRegion());
}
} else if (isa<CXXConstructorCall>(&Call)) {
// Check match of first-last iterator pair in a constructor of a container
if (Call.getNumArgs() < 2)
return;
const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
if (Ctr->getNumParams() < 2)
return;
if (Ctr->getParamDecl(0)->getName() != "first" ||
Ctr->getParamDecl(1)->getName() != "last")
return;
if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
!isIteratorType(Call.getArgExpr(1)->getType()))
return;
verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
} else {
// The main purpose of iterators is to abstract away from different
// containers and provide a (maybe limited) uniform access to them.
// This implies that any correctly written template function that
// works on multiple containers using iterators takes different
// template parameters for different containers. So we can safely
// assume that passing iterators of different containers as arguments
// whose type replaces the same template parameter is a bug.
//
// Example:
// template<typename I1, typename I2>
// void f(I1 first1, I1 last1, I2 first2, I2 last2);
//
// In this case the first two arguments to f() must be iterators must belong
// to the same container and the last to also to the same container but
// not necessarily to the same as the first two.
const auto *Templ = Func->getPrimaryTemplate();
if (!Templ)
return;
const auto *TParams = Templ->getTemplateParameters();
const auto *TArgs = Func->getTemplateSpecializationArgs();
// Iterate over all the template parameters
for (size_t I = 0; I < TParams->size(); ++I) {
const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I));
if (!TPDecl)
continue;
if (TPDecl->isParameterPack())
continue;
const auto TAType = TArgs->get(I).getAsType();
if (!isIteratorType(TAType))
continue;
SVal LHS = UndefinedVal();
// For every template parameter which is an iterator type in the
// instantiation look for all functions' parameters' type by it and
// check whether they belong to the same container
for (auto J = 0U; J < Func->getNumParams(); ++J) {
const auto *Param = Func->getParamDecl(J);
const auto *ParamType =
Param->getType()->getAs<SubstTemplateTypeParmType>();
if (!ParamType ||
ParamType->getReplacedParameter()->getDecl() != TPDecl)
continue;
if (LHS.isUndef()) {
LHS = Call.getArgSVal(J);
} else {
verifyMatch(C, LHS, Call.getArgSVal(J));
}
}
}
}
}
void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO,
CheckerContext &C) const {
if (!BO->isComparisonOp())
return;
ProgramStateRef State = C.getState();
SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext());
SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext());
verifyMatch(C, LVal, RVal);
}
void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter,
const MemRegion *Cont) const {
// Verify match between a container and the container of an iterator
Cont = Cont->getMostDerivedObjectRegion();
if (const auto *ContSym = Cont->getSymbolicBase()) {
if (isa<SymbolConjured>(ContSym->getSymbol()))
return;
}
auto State = C.getState();
const auto *Pos = getIteratorPosition(State, Iter);
if (!Pos)
return;
const auto *IterCont = Pos->getContainer();
// Skip symbolic regions based on conjured symbols. Two conjured symbols
// may or may not be the same. For example, the same function can return
// the same or a different container but we get different conjured symbols
// for each call. This may cause false positives so omit them from the check.
if (const auto *ContSym = IterCont->getSymbolicBase()) {
if (isa<SymbolConjured>(ContSym->getSymbol()))
return;
}
if (IterCont != Cont) {
auto *N = C.generateNonFatalErrorNode(State);
if (!N) {
return;
}
reportBug("Container accessed using foreign iterator argument.",
Iter, Cont, C, N);
}
}
void MismatchedIteratorChecker::verifyMatch(CheckerContext &C,
const SVal &Iter1,
const SVal &Iter2) const {
// Verify match between the containers of two iterators
auto State = C.getState();
const auto *Pos1 = getIteratorPosition(State, Iter1);
if (!Pos1)
return;
const auto *IterCont1 = Pos1->getContainer();
// Skip symbolic regions based on conjured symbols. Two conjured symbols
// may or may not be the same. For example, the same function can return
// the same or a different container but we get different conjured symbols
// for each call. This may cause false positives so omit them from the check.
if (const auto *ContSym = IterCont1->getSymbolicBase()) {
if (isa<SymbolConjured>(ContSym->getSymbol()))
return;
}
const auto *Pos2 = getIteratorPosition(State, Iter2);
if (!Pos2)
return;
const auto *IterCont2 = Pos2->getContainer();
if (const auto *ContSym = IterCont2->getSymbolicBase()) {
if (isa<SymbolConjured>(ContSym->getSymbol()))
return;
}
if (IterCont1 != IterCont2) {
auto *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
reportBug("Iterators of different containers used where the "
"same container is expected.", Iter1, Iter2, C, N);
}
}
void MismatchedIteratorChecker::reportBug(const StringRef &Message,
const SVal &Val1,
const SVal &Val2,
CheckerContext &C,
ExplodedNode *ErrNode) const {
auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message,
ErrNode);
R->markInteresting(Val1);
R->markInteresting(Val2);
C.emitReport(std::move(R));
}
void MismatchedIteratorChecker::reportBug(const StringRef &Message,
const SVal &Val, const MemRegion *Reg,
CheckerContext &C,
ExplodedNode *ErrNode) const {
auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message,
ErrNode);
R->markInteresting(Val);
R->markInteresting(Reg);
C.emitReport(std::move(R));
}
void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
mgr.registerChecker<MismatchedIteratorChecker>();
}
bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
return true;
}