You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
308 lines
11 KiB
308 lines
11 KiB
4 months ago
|
//===-- 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;
|
||
|
}
|