317 lines
12 KiB
C++
317 lines
12 KiB
C++
//==- 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;
|
|
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 getDynamicSizeWithOffset(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;
|
|
}
|