diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp')
| -rw-r--r-- | contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp | 316 | 
1 files changed, 316 insertions, 0 deletions
| diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp new file mode 100644 index 000000000000..99e11a15c08d --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp @@ -0,0 +1,316 @@ +//==- CheckPlacementNew.cpp - Check for placement new operation --*- 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 file defines a check for misuse of the default placement new operator. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace clang; +using namespace ento; + +namespace { +class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> { +public: +  void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const; + +private: +  bool checkPlaceCapacityIsSufficient(const CXXNewExpr *NE, +                                      CheckerContext &C) const; + +  bool checkPlaceIsAlignedProperly(const CXXNewExpr *NE, +                                   CheckerContext &C) const; + +  // Returns the size of the target in a placement new expression. +  // E.g. in "new (&s) long" it returns the size of `long`. +  SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, CheckerContext &C, +                                bool &IsArray) const; +  // Returns the size of the place in a placement new expression. +  // E.g. in "new (&s) long" it returns the size of `s`. +  SVal getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const; + +  void emitBadAlignReport(const Expr *P, CheckerContext &C, +                          unsigned AllocatedTAlign, +                          unsigned StorageTAlign) const; +  unsigned getStorageAlign(CheckerContext &C, const ValueDecl *VD) const; + +  void checkElementRegionAlign(const ElementRegion *R, CheckerContext &C, +                               const Expr *P, unsigned AllocatedTAlign) const; + +  void checkFieldRegionAlign(const FieldRegion *R, CheckerContext &C, +                             const Expr *P, unsigned AllocatedTAlign) const; + +  bool isVarRegionAlignedProperly(const VarRegion *R, CheckerContext &C, +                                  const Expr *P, +                                  unsigned AllocatedTAlign) const; + +  BugType SBT{this, "Insufficient storage for placement new", +              categories::MemoryError}; +  BugType ABT{this, "Bad align storage for placement new", +              categories::MemoryError}; +}; +} // namespace + +SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE, +                                               CheckerContext &C) const { +  const Expr *Place = NE->getPlacementArg(0); +  return getDynamicExtentWithOffset(C.getState(), C.getSVal(Place)); +} + +SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, +                                                   CheckerContext &C, +                                                   bool &IsArray) const { +  ProgramStateRef State = C.getState(); +  SValBuilder &SvalBuilder = C.getSValBuilder(); +  QualType ElementType = NE->getAllocatedType(); +  ASTContext &AstContext = C.getASTContext(); +  CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType); +  IsArray = false; +  if (NE->isArray()) { +    IsArray = true; +    const Expr *SizeExpr = *NE->getArraySize(); +    SVal ElementCount = C.getSVal(SizeExpr); +    if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) { +      // size in Bytes = ElementCountNL * TypeSize +      return SvalBuilder.evalBinOp( +          State, BO_Mul, *ElementCountNL, +          SvalBuilder.makeArrayIndex(TypeSize.getQuantity()), +          SvalBuilder.getArrayIndexType()); +    } +  } else { +    // Create a concrete int whose size in bits and signedness is equal to +    // ArrayIndexType. +    llvm::APInt I(AstContext.getTypeSizeInChars(SvalBuilder.getArrayIndexType()) +                          .getQuantity() * +                      C.getASTContext().getCharWidth(), +                  TypeSize.getQuantity()); +    return SvalBuilder.makeArrayIndex(I.getZExtValue()); +  } +  return UnknownVal(); +} + +bool PlacementNewChecker::checkPlaceCapacityIsSufficient( +    const CXXNewExpr *NE, CheckerContext &C) const { +  bool IsArrayTypeAllocated; +  SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArrayTypeAllocated); +  SVal SizeOfPlace = getExtentSizeOfPlace(NE, C); +  const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>(); +  if (!SizeOfTargetCI) +    return true; +  const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>(); +  if (!SizeOfPlaceCI) +    return true; + +  if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) || +      (IsArrayTypeAllocated && +       SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) { +    if (ExplodedNode *N = C.generateErrorNode(C.getState())) { +      std::string Msg; +      // TODO: use clang constant +      if (IsArrayTypeAllocated && +          SizeOfPlaceCI->getValue() > SizeOfTargetCI->getValue()) +        Msg = std::string(llvm::formatv( +            "{0} bytes is possibly not enough for array allocation which " +            "requires {1} bytes. Current overhead requires the size of {2} " +            "bytes", +            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue(), +            SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue())); +      else if (IsArrayTypeAllocated && +               SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue()) +        Msg = std::string(llvm::formatv( +            "Storage provided to placement new is only {0} bytes, " +            "whereas the allocated array type requires more space for " +            "internal needs", +            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue())); +      else +        Msg = std::string(llvm::formatv( +            "Storage provided to placement new is only {0} bytes, " +            "whereas the allocated type requires {1} bytes", +            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue())); + +      auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N); +      bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R); +      C.emitReport(std::move(R)); + +      return false; +    } +  } + +  return true; +} + +void PlacementNewChecker::emitBadAlignReport(const Expr *P, CheckerContext &C, +                                             unsigned AllocatedTAlign, +                                             unsigned StorageTAlign) const { +  ProgramStateRef State = C.getState(); +  if (ExplodedNode *N = C.generateErrorNode(State)) { +    std::string Msg(llvm::formatv("Storage type is aligned to {0} bytes but " +                                  "allocated type is aligned to {1} bytes", +                                  StorageTAlign, AllocatedTAlign)); + +    auto R = std::make_unique<PathSensitiveBugReport>(ABT, Msg, N); +    bugreporter::trackExpressionValue(N, P, *R); +    C.emitReport(std::move(R)); +  } +} + +unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C, +                                              const ValueDecl *VD) const { +  unsigned StorageTAlign = C.getASTContext().getTypeAlign(VD->getType()); +  if (unsigned SpecifiedAlignment = VD->getMaxAlignment()) +    StorageTAlign = SpecifiedAlignment; + +  return StorageTAlign / C.getASTContext().getCharWidth(); +} + +void PlacementNewChecker::checkElementRegionAlign( +    const ElementRegion *R, CheckerContext &C, const Expr *P, +    unsigned AllocatedTAlign) const { +  auto IsBaseRegionAlignedProperly = [this, R, &C, P, +                                      AllocatedTAlign]() -> bool { +    // Unwind nested ElementRegion`s to get the type. +    const MemRegion *SuperRegion = R; +    while (true) { +      if (SuperRegion->getKind() == MemRegion::ElementRegionKind) { +        SuperRegion = cast<SubRegion>(SuperRegion)->getSuperRegion(); +        continue; +      } + +      break; +    } + +    const DeclRegion *TheElementDeclRegion = SuperRegion->getAs<DeclRegion>(); +    if (!TheElementDeclRegion) +      return false; + +    const DeclRegion *BaseDeclRegion = R->getBaseRegion()->getAs<DeclRegion>(); +    if (!BaseDeclRegion) +      return false; + +    unsigned BaseRegionAlign = 0; +    // We must use alignment TheElementDeclRegion if it has its own alignment +    // specifier +    if (TheElementDeclRegion->getDecl()->getMaxAlignment()) +      BaseRegionAlign = getStorageAlign(C, TheElementDeclRegion->getDecl()); +    else +      BaseRegionAlign = getStorageAlign(C, BaseDeclRegion->getDecl()); + +    if (AllocatedTAlign > BaseRegionAlign) { +      emitBadAlignReport(P, C, AllocatedTAlign, BaseRegionAlign); +      return false; +    } + +    return true; +  }; + +  auto CheckElementRegionOffset = [this, R, &C, P, AllocatedTAlign]() -> void { +    RegionOffset TheOffsetRegion = R->getAsOffset(); +    if (TheOffsetRegion.hasSymbolicOffset()) +      return; + +    unsigned Offset = +        TheOffsetRegion.getOffset() / C.getASTContext().getCharWidth(); +    unsigned AddressAlign = Offset % AllocatedTAlign; +    if (AddressAlign != 0) { +      emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign); +      return; +    } +  }; + +  if (IsBaseRegionAlignedProperly()) { +    CheckElementRegionOffset(); +  } +} + +void PlacementNewChecker::checkFieldRegionAlign( +    const FieldRegion *R, CheckerContext &C, const Expr *P, +    unsigned AllocatedTAlign) const { +  const MemRegion *BaseRegion = R->getBaseRegion(); +  if (!BaseRegion) +    return; + +  if (const VarRegion *TheVarRegion = BaseRegion->getAs<VarRegion>()) { +    if (isVarRegionAlignedProperly(TheVarRegion, C, P, AllocatedTAlign)) { +      // We've checked type align but, unless FieldRegion +      // offset is zero, we also need to check its own +      // align. +      RegionOffset Offset = R->getAsOffset(); +      if (Offset.hasSymbolicOffset()) +        return; + +      int64_t OffsetValue = +          Offset.getOffset() / C.getASTContext().getCharWidth(); +      unsigned AddressAlign = OffsetValue % AllocatedTAlign; +      if (AddressAlign != 0) +        emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign); +    } +  } +} + +bool PlacementNewChecker::isVarRegionAlignedProperly( +    const VarRegion *R, CheckerContext &C, const Expr *P, +    unsigned AllocatedTAlign) const { +  const VarDecl *TheVarDecl = R->getDecl(); +  unsigned StorageTAlign = getStorageAlign(C, TheVarDecl); +  if (AllocatedTAlign > StorageTAlign) { +    emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign); + +    return false; +  } + +  return true; +} + +bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE, +                                                      CheckerContext &C) const { +  const Expr *Place = NE->getPlacementArg(0); + +  QualType AllocatedT = NE->getAllocatedType(); +  unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(AllocatedT) / +                             C.getASTContext().getCharWidth(); + +  SVal PlaceVal = C.getSVal(Place); +  if (const MemRegion *MRegion = PlaceVal.getAsRegion()) { +    if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>()) +      checkElementRegionAlign(TheElementRegion, C, Place, AllocatedTAlign); +    else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>()) +      checkFieldRegionAlign(TheFieldRegion, C, Place, AllocatedTAlign); +    else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>()) +      isVarRegionAlignedProperly(TheVarRegion, C, Place, AllocatedTAlign); +  } + +  return true; +} + +void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE, +                                       CheckerContext &C) const { +  // Check only the default placement new. +  if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator()) +    return; + +  if (NE->getNumPlacementArgs() == 0) +    return; + +  if (!checkPlaceCapacityIsSufficient(NE, C)) +    return; + +  checkPlaceIsAlignedProperly(NE, C); +} + +void ento::registerPlacementNewChecker(CheckerManager &mgr) { +  mgr.registerChecker<PlacementNewChecker>(); +} + +bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) { +  return true; +} | 
