1ec727ea7Spatrick //==- CheckPlacementNew.cpp - Check for placement new operation --*- C++ -*-==//
2ec727ea7Spatrick //
3ec727ea7Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4ec727ea7Spatrick // See https://llvm.org/LICENSE.txt for license information.
5ec727ea7Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ec727ea7Spatrick //
7ec727ea7Spatrick //===----------------------------------------------------------------------===//
8ec727ea7Spatrick //
9ec727ea7Spatrick // This file defines a check for misuse of the default placement new operator.
10ec727ea7Spatrick //
11ec727ea7Spatrick //===----------------------------------------------------------------------===//
12ec727ea7Spatrick
13e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
16*a9ac8606Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
17e5dd7070Spatrick #include "llvm/Support/FormatVariadic.h"
18e5dd7070Spatrick
19e5dd7070Spatrick using namespace clang;
20e5dd7070Spatrick using namespace ento;
21e5dd7070Spatrick
22e5dd7070Spatrick namespace {
23e5dd7070Spatrick class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> {
24e5dd7070Spatrick public:
25e5dd7070Spatrick void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const;
26e5dd7070Spatrick
27e5dd7070Spatrick private:
28ec727ea7Spatrick bool checkPlaceCapacityIsSufficient(const CXXNewExpr *NE,
29ec727ea7Spatrick CheckerContext &C) const;
30ec727ea7Spatrick
31ec727ea7Spatrick bool checkPlaceIsAlignedProperly(const CXXNewExpr *NE,
32ec727ea7Spatrick CheckerContext &C) const;
33ec727ea7Spatrick
34e5dd7070Spatrick // Returns the size of the target in a placement new expression.
35e5dd7070Spatrick // E.g. in "new (&s) long" it returns the size of `long`.
36ec727ea7Spatrick SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, CheckerContext &C,
37ec727ea7Spatrick bool &IsArray) const;
38e5dd7070Spatrick // Returns the size of the place in a placement new expression.
39e5dd7070Spatrick // E.g. in "new (&s) long" it returns the size of `s`.
40ec727ea7Spatrick SVal getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const;
41ec727ea7Spatrick
42ec727ea7Spatrick void emitBadAlignReport(const Expr *P, CheckerContext &C,
43ec727ea7Spatrick unsigned AllocatedTAlign,
44ec727ea7Spatrick unsigned StorageTAlign) const;
45ec727ea7Spatrick unsigned getStorageAlign(CheckerContext &C, const ValueDecl *VD) const;
46ec727ea7Spatrick
47ec727ea7Spatrick void checkElementRegionAlign(const ElementRegion *R, CheckerContext &C,
48ec727ea7Spatrick const Expr *P, unsigned AllocatedTAlign) const;
49ec727ea7Spatrick
50ec727ea7Spatrick void checkFieldRegionAlign(const FieldRegion *R, CheckerContext &C,
51ec727ea7Spatrick const Expr *P, unsigned AllocatedTAlign) const;
52ec727ea7Spatrick
53ec727ea7Spatrick bool isVarRegionAlignedProperly(const VarRegion *R, CheckerContext &C,
54ec727ea7Spatrick const Expr *P,
55ec727ea7Spatrick unsigned AllocatedTAlign) const;
56ec727ea7Spatrick
57ec727ea7Spatrick BugType SBT{this, "Insufficient storage for placement new",
58ec727ea7Spatrick categories::MemoryError};
59ec727ea7Spatrick BugType ABT{this, "Bad align storage for placement new",
60e5dd7070Spatrick categories::MemoryError};
61e5dd7070Spatrick };
62e5dd7070Spatrick } // namespace
63e5dd7070Spatrick
getExtentSizeOfPlace(const CXXNewExpr * NE,CheckerContext & C) const64ec727ea7Spatrick SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE,
65e5dd7070Spatrick CheckerContext &C) const {
66ec727ea7Spatrick const Expr *Place = NE->getPlacementArg(0);
67*a9ac8606Spatrick return getDynamicExtentWithOffset(C.getState(), C.getSVal(Place));
68e5dd7070Spatrick }
69e5dd7070Spatrick
getExtentSizeOfNewTarget(const CXXNewExpr * NE,CheckerContext & C,bool & IsArray) const70e5dd7070Spatrick SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE,
71ec727ea7Spatrick CheckerContext &C,
72ec727ea7Spatrick bool &IsArray) const {
73ec727ea7Spatrick ProgramStateRef State = C.getState();
74e5dd7070Spatrick SValBuilder &SvalBuilder = C.getSValBuilder();
75e5dd7070Spatrick QualType ElementType = NE->getAllocatedType();
76e5dd7070Spatrick ASTContext &AstContext = C.getASTContext();
77e5dd7070Spatrick CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType);
78ec727ea7Spatrick IsArray = false;
79e5dd7070Spatrick if (NE->isArray()) {
80ec727ea7Spatrick IsArray = true;
81e5dd7070Spatrick const Expr *SizeExpr = *NE->getArraySize();
82e5dd7070Spatrick SVal ElementCount = C.getSVal(SizeExpr);
83e5dd7070Spatrick if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) {
84e5dd7070Spatrick // size in Bytes = ElementCountNL * TypeSize
85e5dd7070Spatrick return SvalBuilder.evalBinOp(
86e5dd7070Spatrick State, BO_Mul, *ElementCountNL,
87e5dd7070Spatrick SvalBuilder.makeArrayIndex(TypeSize.getQuantity()),
88e5dd7070Spatrick SvalBuilder.getArrayIndexType());
89e5dd7070Spatrick }
90e5dd7070Spatrick } else {
91e5dd7070Spatrick // Create a concrete int whose size in bits and signedness is equal to
92e5dd7070Spatrick // ArrayIndexType.
93e5dd7070Spatrick llvm::APInt I(AstContext.getTypeSizeInChars(SvalBuilder.getArrayIndexType())
94e5dd7070Spatrick .getQuantity() *
95e5dd7070Spatrick C.getASTContext().getCharWidth(),
96e5dd7070Spatrick TypeSize.getQuantity());
97e5dd7070Spatrick return SvalBuilder.makeArrayIndex(I.getZExtValue());
98e5dd7070Spatrick }
99e5dd7070Spatrick return UnknownVal();
100e5dd7070Spatrick }
101e5dd7070Spatrick
checkPlaceCapacityIsSufficient(const CXXNewExpr * NE,CheckerContext & C) const102ec727ea7Spatrick bool PlacementNewChecker::checkPlaceCapacityIsSufficient(
103ec727ea7Spatrick const CXXNewExpr *NE, CheckerContext &C) const {
104ec727ea7Spatrick bool IsArrayTypeAllocated;
105ec727ea7Spatrick SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArrayTypeAllocated);
106ec727ea7Spatrick SVal SizeOfPlace = getExtentSizeOfPlace(NE, C);
107ec727ea7Spatrick const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>();
108ec727ea7Spatrick if (!SizeOfTargetCI)
109ec727ea7Spatrick return true;
110ec727ea7Spatrick const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>();
111ec727ea7Spatrick if (!SizeOfPlaceCI)
112ec727ea7Spatrick return true;
113ec727ea7Spatrick
114ec727ea7Spatrick if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) ||
115ec727ea7Spatrick (IsArrayTypeAllocated &&
116ec727ea7Spatrick SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) {
117ec727ea7Spatrick if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
118ec727ea7Spatrick std::string Msg;
119ec727ea7Spatrick // TODO: use clang constant
120ec727ea7Spatrick if (IsArrayTypeAllocated &&
121ec727ea7Spatrick SizeOfPlaceCI->getValue() > SizeOfTargetCI->getValue())
122ec727ea7Spatrick Msg = std::string(llvm::formatv(
123ec727ea7Spatrick "{0} bytes is possibly not enough for array allocation which "
124ec727ea7Spatrick "requires {1} bytes. Current overhead requires the size of {2} "
125ec727ea7Spatrick "bytes",
126ec727ea7Spatrick SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue(),
127ec727ea7Spatrick SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue()));
128ec727ea7Spatrick else if (IsArrayTypeAllocated &&
129ec727ea7Spatrick SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue())
130ec727ea7Spatrick Msg = std::string(llvm::formatv(
131ec727ea7Spatrick "Storage provided to placement new is only {0} bytes, "
132ec727ea7Spatrick "whereas the allocated array type requires more space for "
133ec727ea7Spatrick "internal needs",
134ec727ea7Spatrick SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
135ec727ea7Spatrick else
136ec727ea7Spatrick Msg = std::string(llvm::formatv(
137ec727ea7Spatrick "Storage provided to placement new is only {0} bytes, "
138ec727ea7Spatrick "whereas the allocated type requires {1} bytes",
139ec727ea7Spatrick SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
140ec727ea7Spatrick
141ec727ea7Spatrick auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N);
142ec727ea7Spatrick bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R);
143ec727ea7Spatrick C.emitReport(std::move(R));
144ec727ea7Spatrick
145ec727ea7Spatrick return false;
146ec727ea7Spatrick }
147ec727ea7Spatrick }
148ec727ea7Spatrick
149ec727ea7Spatrick return true;
150ec727ea7Spatrick }
151ec727ea7Spatrick
emitBadAlignReport(const Expr * P,CheckerContext & C,unsigned AllocatedTAlign,unsigned StorageTAlign) const152ec727ea7Spatrick void PlacementNewChecker::emitBadAlignReport(const Expr *P, CheckerContext &C,
153ec727ea7Spatrick unsigned AllocatedTAlign,
154ec727ea7Spatrick unsigned StorageTAlign) const {
155ec727ea7Spatrick ProgramStateRef State = C.getState();
156ec727ea7Spatrick if (ExplodedNode *N = C.generateErrorNode(State)) {
157ec727ea7Spatrick std::string Msg(llvm::formatv("Storage type is aligned to {0} bytes but "
158ec727ea7Spatrick "allocated type is aligned to {1} bytes",
159ec727ea7Spatrick StorageTAlign, AllocatedTAlign));
160ec727ea7Spatrick
161ec727ea7Spatrick auto R = std::make_unique<PathSensitiveBugReport>(ABT, Msg, N);
162ec727ea7Spatrick bugreporter::trackExpressionValue(N, P, *R);
163ec727ea7Spatrick C.emitReport(std::move(R));
164ec727ea7Spatrick }
165ec727ea7Spatrick }
166ec727ea7Spatrick
getStorageAlign(CheckerContext & C,const ValueDecl * VD) const167ec727ea7Spatrick unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C,
168ec727ea7Spatrick const ValueDecl *VD) const {
169ec727ea7Spatrick unsigned StorageTAlign = C.getASTContext().getTypeAlign(VD->getType());
170ec727ea7Spatrick if (unsigned SpecifiedAlignment = VD->getMaxAlignment())
171ec727ea7Spatrick StorageTAlign = SpecifiedAlignment;
172ec727ea7Spatrick
173ec727ea7Spatrick return StorageTAlign / C.getASTContext().getCharWidth();
174ec727ea7Spatrick }
175ec727ea7Spatrick
checkElementRegionAlign(const ElementRegion * R,CheckerContext & C,const Expr * P,unsigned AllocatedTAlign) const176ec727ea7Spatrick void PlacementNewChecker::checkElementRegionAlign(
177ec727ea7Spatrick const ElementRegion *R, CheckerContext &C, const Expr *P,
178ec727ea7Spatrick unsigned AllocatedTAlign) const {
179ec727ea7Spatrick auto IsBaseRegionAlignedProperly = [this, R, &C, P,
180ec727ea7Spatrick AllocatedTAlign]() -> bool {
181ec727ea7Spatrick // Unwind nested ElementRegion`s to get the type.
182ec727ea7Spatrick const MemRegion *SuperRegion = R;
183ec727ea7Spatrick while (true) {
184ec727ea7Spatrick if (SuperRegion->getKind() == MemRegion::ElementRegionKind) {
185ec727ea7Spatrick SuperRegion = cast<SubRegion>(SuperRegion)->getSuperRegion();
186ec727ea7Spatrick continue;
187ec727ea7Spatrick }
188ec727ea7Spatrick
189ec727ea7Spatrick break;
190ec727ea7Spatrick }
191ec727ea7Spatrick
192ec727ea7Spatrick const DeclRegion *TheElementDeclRegion = SuperRegion->getAs<DeclRegion>();
193ec727ea7Spatrick if (!TheElementDeclRegion)
194ec727ea7Spatrick return false;
195ec727ea7Spatrick
196ec727ea7Spatrick const DeclRegion *BaseDeclRegion = R->getBaseRegion()->getAs<DeclRegion>();
197ec727ea7Spatrick if (!BaseDeclRegion)
198ec727ea7Spatrick return false;
199ec727ea7Spatrick
200ec727ea7Spatrick unsigned BaseRegionAlign = 0;
201ec727ea7Spatrick // We must use alignment TheElementDeclRegion if it has its own alignment
202ec727ea7Spatrick // specifier
203ec727ea7Spatrick if (TheElementDeclRegion->getDecl()->getMaxAlignment())
204ec727ea7Spatrick BaseRegionAlign = getStorageAlign(C, TheElementDeclRegion->getDecl());
205ec727ea7Spatrick else
206ec727ea7Spatrick BaseRegionAlign = getStorageAlign(C, BaseDeclRegion->getDecl());
207ec727ea7Spatrick
208ec727ea7Spatrick if (AllocatedTAlign > BaseRegionAlign) {
209ec727ea7Spatrick emitBadAlignReport(P, C, AllocatedTAlign, BaseRegionAlign);
210ec727ea7Spatrick return false;
211ec727ea7Spatrick }
212ec727ea7Spatrick
213ec727ea7Spatrick return true;
214ec727ea7Spatrick };
215ec727ea7Spatrick
216ec727ea7Spatrick auto CheckElementRegionOffset = [this, R, &C, P, AllocatedTAlign]() -> void {
217ec727ea7Spatrick RegionOffset TheOffsetRegion = R->getAsOffset();
218ec727ea7Spatrick if (TheOffsetRegion.hasSymbolicOffset())
219ec727ea7Spatrick return;
220ec727ea7Spatrick
221ec727ea7Spatrick unsigned Offset =
222ec727ea7Spatrick TheOffsetRegion.getOffset() / C.getASTContext().getCharWidth();
223ec727ea7Spatrick unsigned AddressAlign = Offset % AllocatedTAlign;
224ec727ea7Spatrick if (AddressAlign != 0) {
225ec727ea7Spatrick emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign);
226ec727ea7Spatrick return;
227ec727ea7Spatrick }
228ec727ea7Spatrick };
229ec727ea7Spatrick
230ec727ea7Spatrick if (IsBaseRegionAlignedProperly()) {
231ec727ea7Spatrick CheckElementRegionOffset();
232ec727ea7Spatrick }
233ec727ea7Spatrick }
234ec727ea7Spatrick
checkFieldRegionAlign(const FieldRegion * R,CheckerContext & C,const Expr * P,unsigned AllocatedTAlign) const235ec727ea7Spatrick void PlacementNewChecker::checkFieldRegionAlign(
236ec727ea7Spatrick const FieldRegion *R, CheckerContext &C, const Expr *P,
237ec727ea7Spatrick unsigned AllocatedTAlign) const {
238ec727ea7Spatrick const MemRegion *BaseRegion = R->getBaseRegion();
239ec727ea7Spatrick if (!BaseRegion)
240ec727ea7Spatrick return;
241ec727ea7Spatrick
242ec727ea7Spatrick if (const VarRegion *TheVarRegion = BaseRegion->getAs<VarRegion>()) {
243ec727ea7Spatrick if (isVarRegionAlignedProperly(TheVarRegion, C, P, AllocatedTAlign)) {
244ec727ea7Spatrick // We've checked type align but, unless FieldRegion
245ec727ea7Spatrick // offset is zero, we also need to check its own
246ec727ea7Spatrick // align.
247ec727ea7Spatrick RegionOffset Offset = R->getAsOffset();
248ec727ea7Spatrick if (Offset.hasSymbolicOffset())
249ec727ea7Spatrick return;
250ec727ea7Spatrick
251ec727ea7Spatrick int64_t OffsetValue =
252ec727ea7Spatrick Offset.getOffset() / C.getASTContext().getCharWidth();
253ec727ea7Spatrick unsigned AddressAlign = OffsetValue % AllocatedTAlign;
254ec727ea7Spatrick if (AddressAlign != 0)
255ec727ea7Spatrick emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign);
256ec727ea7Spatrick }
257ec727ea7Spatrick }
258ec727ea7Spatrick }
259ec727ea7Spatrick
isVarRegionAlignedProperly(const VarRegion * R,CheckerContext & C,const Expr * P,unsigned AllocatedTAlign) const260ec727ea7Spatrick bool PlacementNewChecker::isVarRegionAlignedProperly(
261ec727ea7Spatrick const VarRegion *R, CheckerContext &C, const Expr *P,
262ec727ea7Spatrick unsigned AllocatedTAlign) const {
263ec727ea7Spatrick const VarDecl *TheVarDecl = R->getDecl();
264ec727ea7Spatrick unsigned StorageTAlign = getStorageAlign(C, TheVarDecl);
265ec727ea7Spatrick if (AllocatedTAlign > StorageTAlign) {
266ec727ea7Spatrick emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign);
267ec727ea7Spatrick
268ec727ea7Spatrick return false;
269ec727ea7Spatrick }
270ec727ea7Spatrick
271ec727ea7Spatrick return true;
272ec727ea7Spatrick }
273ec727ea7Spatrick
checkPlaceIsAlignedProperly(const CXXNewExpr * NE,CheckerContext & C) const274ec727ea7Spatrick bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE,
275ec727ea7Spatrick CheckerContext &C) const {
276ec727ea7Spatrick const Expr *Place = NE->getPlacementArg(0);
277ec727ea7Spatrick
278ec727ea7Spatrick QualType AllocatedT = NE->getAllocatedType();
279ec727ea7Spatrick unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(AllocatedT) /
280ec727ea7Spatrick C.getASTContext().getCharWidth();
281ec727ea7Spatrick
282ec727ea7Spatrick SVal PlaceVal = C.getSVal(Place);
283ec727ea7Spatrick if (const MemRegion *MRegion = PlaceVal.getAsRegion()) {
284ec727ea7Spatrick if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>())
285ec727ea7Spatrick checkElementRegionAlign(TheElementRegion, C, Place, AllocatedTAlign);
286ec727ea7Spatrick else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>())
287ec727ea7Spatrick checkFieldRegionAlign(TheFieldRegion, C, Place, AllocatedTAlign);
288ec727ea7Spatrick else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>())
289ec727ea7Spatrick isVarRegionAlignedProperly(TheVarRegion, C, Place, AllocatedTAlign);
290ec727ea7Spatrick }
291ec727ea7Spatrick
292ec727ea7Spatrick return true;
293ec727ea7Spatrick }
294ec727ea7Spatrick
checkPreStmt(const CXXNewExpr * NE,CheckerContext & C) const295e5dd7070Spatrick void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE,
296e5dd7070Spatrick CheckerContext &C) const {
297e5dd7070Spatrick // Check only the default placement new.
298e5dd7070Spatrick if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator())
299e5dd7070Spatrick return;
300ec727ea7Spatrick
301e5dd7070Spatrick if (NE->getNumPlacementArgs() == 0)
302e5dd7070Spatrick return;
303e5dd7070Spatrick
304ec727ea7Spatrick if (!checkPlaceCapacityIsSufficient(NE, C))
305e5dd7070Spatrick return;
306e5dd7070Spatrick
307ec727ea7Spatrick checkPlaceIsAlignedProperly(NE, C);
308e5dd7070Spatrick }
309e5dd7070Spatrick
registerPlacementNewChecker(CheckerManager & mgr)310e5dd7070Spatrick void ento::registerPlacementNewChecker(CheckerManager &mgr) {
311e5dd7070Spatrick mgr.registerChecker<PlacementNewChecker>();
312e5dd7070Spatrick }
313e5dd7070Spatrick
shouldRegisterPlacementNewChecker(const CheckerManager & mgr)314ec727ea7Spatrick bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) {
315e5dd7070Spatrick return true;
316e5dd7070Spatrick }
317