diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2020-07-26 19:36:28 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2020-07-26 19:36:28 +0000 |
commit | cfca06d7963fa0909f90483b42a6d7d194d01e08 (patch) | |
tree | 209fb2a2d68f8f277793fc8df46c753d31bc853b /clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp | |
parent | 706b4fc47bbc608932d3b491ae19a3b9cde9497b (diff) |
Notes
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp | 299 |
1 files changed, 247 insertions, 52 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp index 48fee4a0ffb7..dc9cd717be9e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp @@ -1,6 +1,19 @@ +//==- 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/DynamicSize.h" #include "llvm/Support/FormatVariadic.h" using namespace clang; @@ -12,51 +25,59 @@ 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, ProgramStateRef State, - CheckerContext &C) const; + 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 Expr *NE, ProgramStateRef State, - CheckerContext &C) const; - BugType BT{this, "Insufficient storage for placement new", - categories::MemoryError}; + 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 Expr *Place, - ProgramStateRef State, +SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const { - const MemRegion *MRegion = C.getSVal(Place).getAsRegion(); - if (!MRegion) - return UnknownVal(); - RegionOffset Offset = MRegion->getAsOffset(); - if (Offset.hasSymbolicOffset()) - return UnknownVal(); - const MemRegion *BaseRegion = MRegion->getBaseRegion(); - if (!BaseRegion) - return UnknownVal(); - - SValBuilder &SvalBuilder = C.getSValBuilder(); - NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( - Offset.getOffset() / C.getASTContext().getCharWidth()); - DefinedOrUnknownSVal ExtentInBytes = - BaseRegion->castAs<SubRegion>()->getExtent(SvalBuilder); - - return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, - ExtentInBytes, OffsetInBytes, - SvalBuilder.getArrayIndexType()); + const Expr *Place = NE->getPlacementArg(0); + return getDynamicSizeWithOffset(C.getState(), C.getSVal(Place)); } SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, - ProgramStateRef State, - CheckerContext &C) const { + 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>()) { @@ -78,44 +99,218 @@ SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, return UnknownVal(); } -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; - - ProgramStateRef State = C.getState(); - SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, State, C); - const Expr *Place = NE->getPlacementArg(0); - SVal SizeOfPlace = getExtentSizeOfPlace(Place, State, C); +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; + return true; const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>(); if (!SizeOfPlaceCI) - return; + return true; - if (SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) { - if (ExplodedNode *N = C.generateErrorNode(State)) { - std::string Msg = - llvm::formatv("Storage provided to placement new is only {0} bytes, " - "whereas the allocated type requires {1} bytes", - SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()); + 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>(BT, Msg, N); - bugreporter::trackExpressionValue(N, Place, *R); + 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 LangOptions &LO) { +bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) { return true; } |