summaryrefslogtreecommitdiff
path: root/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2020-07-26 19:36:28 +0000
committerDimitry Andric <dim@FreeBSD.org>2020-07-26 19:36:28 +0000
commitcfca06d7963fa0909f90483b42a6d7d194d01e08 (patch)
tree209fb2a2d68f8f277793fc8df46c753d31bc853b /clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
parent706b4fc47bbc608932d3b491ae19a3b9cde9497b (diff)
Notes
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp')
-rw-r--r--clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp299
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;
}