forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			349 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			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 LangOptions &LO) {
 | 
						|
  return true;
 | 
						|
}
 |