llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp

349 lines
12 KiB
C++

//=== PointerArithChecker.cpp - Pointer arithmetic checker -----*- 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
//
//===----------------------------------------------------------------------===//
//
// This files defines PointerArithChecker, a builtin checker that checks for
// pointer arithmetic on locations other than array elements.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace ento;
namespace {
enum class AllocKind {
SingleObject,
Array,
Unknown,
Reinterpreted // Single object interpreted as an array.
};
} // end namespace
namespace llvm {
template <> struct FoldingSetTrait<AllocKind> {
static inline void Profile(AllocKind X, FoldingSetNodeID &ID) {
ID.AddInteger(static_cast<int>(X));
}
};
} // end namespace llvm
namespace {
class PointerArithChecker
: public Checker<
check::PreStmt<BinaryOperator>, check::PreStmt<UnaryOperator>,
check::PreStmt<ArraySubscriptExpr>, check::PreStmt<CastExpr>,
check::PostStmt<CastExpr>, check::PostStmt<CXXNewExpr>,
check::PostStmt<CallExpr>, check::DeadSymbols> {
AllocKind getKindOfNewOp(const CXXNewExpr *NE, const FunctionDecl *FD) const;
const MemRegion *getArrayRegion(const MemRegion *Region, bool &Polymorphic,
AllocKind &AKind, CheckerContext &C) const;
const MemRegion *getPointedRegion(const MemRegion *Region,
CheckerContext &C) const;
void reportPointerArithMisuse(const Expr *E, CheckerContext &C,
bool PointedNeeded = false) const;
void initAllocIdentifiers(ASTContext &C) const;
mutable std::unique_ptr<BuiltinBug> BT_pointerArith;
mutable std::unique_ptr<BuiltinBug> BT_polyArray;
mutable llvm::SmallSet<IdentifierInfo *, 8> AllocFunctions;
public:
void checkPreStmt(const UnaryOperator *UOp, CheckerContext &C) const;
void checkPreStmt(const BinaryOperator *BOp, CheckerContext &C) const;
void checkPreStmt(const ArraySubscriptExpr *SubExpr, CheckerContext &C) const;
void checkPreStmt(const CastExpr *CE, CheckerContext &C) const;
void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;
void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const;
void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
};
} // end namespace
REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, const MemRegion *, AllocKind)
void PointerArithChecker::checkDeadSymbols(SymbolReaper &SR,
CheckerContext &C) const {
// TODO: intentional leak. Some information is garbage collected too early,
// see http://reviews.llvm.org/D14203 for further information.
/*ProgramStateRef State = C.getState();
RegionStateTy RegionStates = State->get<RegionState>();
for (RegionStateTy::iterator I = RegionStates.begin(), E = RegionStates.end();
I != E; ++I) {
if (!SR.isLiveRegion(I->first))
State = State->remove<RegionState>(I->first);
}
C.addTransition(State);*/
}
AllocKind PointerArithChecker::getKindOfNewOp(const CXXNewExpr *NE,
const FunctionDecl *FD) const {
// This checker try not to assume anything about placement and overloaded
// new to avoid false positives.
if (isa<CXXMethodDecl>(FD))
return AllocKind::Unknown;
if (FD->getNumParams() != 1 || FD->isVariadic())
return AllocKind::Unknown;
if (NE->isArray())
return AllocKind::Array;
return AllocKind::SingleObject;
}
const MemRegion *
PointerArithChecker::getPointedRegion(const MemRegion *Region,
CheckerContext &C) const {
assert(Region);
ProgramStateRef State = C.getState();
SVal S = State->getSVal(Region);
return S.getAsRegion();
}
/// Checks whether a region is the part of an array.
/// In case there is a derived to base cast above the array element, the
/// Polymorphic output value is set to true. AKind output value is set to the
/// allocation kind of the inspected region.
const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region,
bool &Polymorphic,
AllocKind &AKind,
CheckerContext &C) const {
assert(Region);
while (const auto *BaseRegion = dyn_cast<CXXBaseObjectRegion>(Region)) {
Region = BaseRegion->getSuperRegion();
Polymorphic = true;
}
if (const auto *ElemRegion = dyn_cast<ElementRegion>(Region)) {
Region = ElemRegion->getSuperRegion();
}
ProgramStateRef State = C.getState();
if (const AllocKind *Kind = State->get<RegionState>(Region)) {
AKind = *Kind;
if (*Kind == AllocKind::Array)
return Region;
else
return nullptr;
}
// When the region is symbolic and we do not have any information about it,
// assume that this is an array to avoid false positives.
if (isa<SymbolicRegion>(Region))
return Region;
// No AllocKind stored and not symbolic, assume that it points to a single
// object.
return nullptr;
}
void PointerArithChecker::reportPointerArithMisuse(const Expr *E,
CheckerContext &C,
bool PointedNeeded) const {
SourceRange SR = E->getSourceRange();
if (SR.isInvalid())
return;
ProgramStateRef State = C.getState();
const MemRegion *Region = C.getSVal(E).getAsRegion();
if (!Region)
return;
if (PointedNeeded)
Region = getPointedRegion(Region, C);
if (!Region)
return;
bool IsPolymorphic = false;
AllocKind Kind = AllocKind::Unknown;
if (const MemRegion *ArrayRegion =
getArrayRegion(Region, IsPolymorphic, Kind, C)) {
if (!IsPolymorphic)
return;
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT_polyArray)
BT_polyArray.reset(new BuiltinBug(
this, "Dangerous pointer arithmetic",
"Pointer arithmetic on a pointer to base class is dangerous "
"because derived and base class may have different size."));
auto R = std::make_unique<PathSensitiveBugReport>(
*BT_polyArray, BT_polyArray->getDescription(), N);
R->addRange(E->getSourceRange());
R->markInteresting(ArrayRegion);
C.emitReport(std::move(R));
}
return;
}
if (Kind == AllocKind::Reinterpreted)
return;
// We might not have enough information about symbolic regions.
if (Kind != AllocKind::SingleObject &&
Region->getKind() == MemRegion::Kind::SymbolicRegionKind)
return;
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT_pointerArith)
BT_pointerArith.reset(new BuiltinBug(this, "Dangerous pointer arithmetic",
"Pointer arithmetic on non-array "
"variables relies on memory layout, "
"which is dangerous."));
auto R = std::make_unique<PathSensitiveBugReport>(
*BT_pointerArith, BT_pointerArith->getDescription(), N);
R->addRange(SR);
R->markInteresting(Region);
C.emitReport(std::move(R));
}
}
void PointerArithChecker::initAllocIdentifiers(ASTContext &C) const {
if (!AllocFunctions.empty())
return;
AllocFunctions.insert(&C.Idents.get("alloca"));
AllocFunctions.insert(&C.Idents.get("malloc"));
AllocFunctions.insert(&C.Idents.get("realloc"));
AllocFunctions.insert(&C.Idents.get("calloc"));
AllocFunctions.insert(&C.Idents.get("valloc"));
}
void PointerArithChecker::checkPostStmt(const CallExpr *CE,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const FunctionDecl *FD = C.getCalleeDecl(CE);
if (!FD)
return;
IdentifierInfo *FunI = FD->getIdentifier();
initAllocIdentifiers(C.getASTContext());
if (AllocFunctions.count(FunI) == 0)
return;
SVal SV = C.getSVal(CE);
const MemRegion *Region = SV.getAsRegion();
if (!Region)
return;
// Assume that C allocation functions allocate arrays to avoid false
// positives.
// TODO: Add heuristics to distinguish alloc calls that allocates single
// objecs.
State = State->set<RegionState>(Region, AllocKind::Array);
C.addTransition(State);
}
void PointerArithChecker::checkPostStmt(const CXXNewExpr *NE,
CheckerContext &C) const {
const FunctionDecl *FD = NE->getOperatorNew();
if (!FD)
return;
AllocKind Kind = getKindOfNewOp(NE, FD);
ProgramStateRef State = C.getState();
SVal AllocedVal = C.getSVal(NE);
const MemRegion *Region = AllocedVal.getAsRegion();
if (!Region)
return;
State = State->set<RegionState>(Region, Kind);
C.addTransition(State);
}
void PointerArithChecker::checkPostStmt(const CastExpr *CE,
CheckerContext &C) const {
if (CE->getCastKind() != CastKind::CK_BitCast)
return;
const Expr *CastedExpr = CE->getSubExpr();
ProgramStateRef State = C.getState();
SVal CastedVal = C.getSVal(CastedExpr);
const MemRegion *Region = CastedVal.getAsRegion();
if (!Region)
return;
// Suppress reinterpret casted hits.
State = State->set<RegionState>(Region, AllocKind::Reinterpreted);
C.addTransition(State);
}
void PointerArithChecker::checkPreStmt(const CastExpr *CE,
CheckerContext &C) const {
if (CE->getCastKind() != CastKind::CK_ArrayToPointerDecay)
return;
const Expr *CastedExpr = CE->getSubExpr();
ProgramStateRef State = C.getState();
SVal CastedVal = C.getSVal(CastedExpr);
const MemRegion *Region = CastedVal.getAsRegion();
if (!Region)
return;
if (const AllocKind *Kind = State->get<RegionState>(Region)) {
if (*Kind == AllocKind::Array || *Kind == AllocKind::Reinterpreted)
return;
}
State = State->set<RegionState>(Region, AllocKind::Array);
C.addTransition(State);
}
void PointerArithChecker::checkPreStmt(const UnaryOperator *UOp,
CheckerContext &C) const {
if (!UOp->isIncrementDecrementOp() || !UOp->getType()->isPointerType())
return;
reportPointerArithMisuse(UOp->getSubExpr(), C, true);
}
void PointerArithChecker::checkPreStmt(const ArraySubscriptExpr *SubsExpr,
CheckerContext &C) const {
SVal Idx = C.getSVal(SubsExpr->getIdx());
// Indexing with 0 is OK.
if (Idx.isZeroConstant())
return;
// Indexing vector-type expressions is also OK.
if (SubsExpr->getBase()->getType()->isVectorType())
return;
reportPointerArithMisuse(SubsExpr->getBase(), C);
}
void PointerArithChecker::checkPreStmt(const BinaryOperator *BOp,
CheckerContext &C) const {
BinaryOperatorKind OpKind = BOp->getOpcode();
if (!BOp->isAdditiveOp() && OpKind != BO_AddAssign && OpKind != BO_SubAssign)
return;
const Expr *Lhs = BOp->getLHS();
const Expr *Rhs = BOp->getRHS();
ProgramStateRef State = C.getState();
if (Rhs->getType()->isIntegerType() && Lhs->getType()->isPointerType()) {
SVal RHSVal = C.getSVal(Rhs);
if (State->isNull(RHSVal).isConstrainedTrue())
return;
reportPointerArithMisuse(Lhs, C, !BOp->isAdditiveOp());
}
// The int += ptr; case is not valid C++.
if (Lhs->getType()->isIntegerType() && Rhs->getType()->isPointerType()) {
SVal LHSVal = C.getSVal(Lhs);
if (State->isNull(LHSVal).isConstrainedTrue())
return;
reportPointerArithMisuse(Rhs, C);
}
}
void ento::registerPointerArithChecker(CheckerManager &mgr) {
mgr.registerChecker<PointerArithChecker>();
}
bool ento::shouldRegisterPointerArithChecker(const CheckerManager &mgr) {
return true;
}