Aethezz 635e6d7653
[analyzer] Fix FP for cplusplus.placement new #149240 (#150161)
Fix false positive where warnings were asserted for placement new even
when no additional space is requested

The PlacementNewChecker incorrectly triggered warnings when the storage
provided matched or exceeded the allocated type size, causing false
positives. Now the warning triggers only when the provided storage is
strictly less than the required size.

Add test cases covering exact size, undersize, and oversize scenarios to
validate the fix.

Fixes #149240
2025-07-30 15:14:06 +02:00

297 lines
11 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/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())) {
if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
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());
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;
}