Skip to content

Commit

Permalink
[analyzer] Improve PlacementNewChecker
Browse files Browse the repository at this point in the history
Summary:
1. Added insufficient storage check for arrays
2. Added align support check

Based on https://reviews.llvm.org/D76229

Reviewers: aaron.ballman, lebedev.ri, NoQ, martong

Reviewed By: martong

Subscribers: xazax.hun, baloghadamsoftware, szepet, rnkovacs, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, Charusso, ASDenysPetrov, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D76996

Patch by Karasev Nikita!
  • Loading branch information
Gabor Marton committed May 14, 2020
1 parent 5f3f45d commit 7c37684
Show file tree
Hide file tree
Showing 2 changed files with 540 additions and 32 deletions.
266 changes: 234 additions & 32 deletions clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
Expand Up @@ -25,22 +25,47 @@ class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> {
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 {
ProgramStateRef State = C.getState();
const Expr *Place = NE->getPlacementArg(0);

const MemRegion *MRegion = C.getSVal(Place).getAsRegion();
if (!MRegion)
return UnknownVal();
Expand All @@ -63,13 +88,16 @@ SVal PlacementNewChecker::getExtentSizeOfPlace(const Expr *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>()) {
Expand All @@ -91,38 +119,212 @@ 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 = std::string(
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) {
Expand Down

0 comments on commit 7c37684

Please sign in to comment.