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