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; +} |