xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp (revision fe6060f10f634930ff71b7c50291ddc610da2475)
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/DynamicExtent.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 
getExtentSizeOfPlace(const CXXNewExpr * NE,CheckerContext & C) const64 SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE,
65                                                CheckerContext &C) const {
66   const Expr *Place = NE->getPlacementArg(0);
67   return getDynamicExtentWithOffset(C.getState(), C.getSVal(Place));
68 }
69 
getExtentSizeOfNewTarget(const CXXNewExpr * NE,CheckerContext & C,bool & IsArray) const70 SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE,
71                                                    CheckerContext &C,
72                                                    bool &IsArray) const {
73   ProgramStateRef State = C.getState();
74   SValBuilder &SvalBuilder = C.getSValBuilder();
75   QualType ElementType = NE->getAllocatedType();
76   ASTContext &AstContext = C.getASTContext();
77   CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType);
78   IsArray = false;
79   if (NE->isArray()) {
80     IsArray = true;
81     const Expr *SizeExpr = *NE->getArraySize();
82     SVal ElementCount = C.getSVal(SizeExpr);
83     if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) {
84       // size in Bytes = ElementCountNL * TypeSize
85       return SvalBuilder.evalBinOp(
86           State, BO_Mul, *ElementCountNL,
87           SvalBuilder.makeArrayIndex(TypeSize.getQuantity()),
88           SvalBuilder.getArrayIndexType());
89     }
90   } else {
91     // Create a concrete int whose size in bits and signedness is equal to
92     // ArrayIndexType.
93     llvm::APInt I(AstContext.getTypeSizeInChars(SvalBuilder.getArrayIndexType())
94                           .getQuantity() *
95                       C.getASTContext().getCharWidth(),
96                   TypeSize.getQuantity());
97     return SvalBuilder.makeArrayIndex(I.getZExtValue());
98   }
99   return UnknownVal();
100 }
101 
checkPlaceCapacityIsSufficient(const CXXNewExpr * NE,CheckerContext & C) const102 bool PlacementNewChecker::checkPlaceCapacityIsSufficient(
103     const CXXNewExpr *NE, CheckerContext &C) const {
104   bool IsArrayTypeAllocated;
105   SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArrayTypeAllocated);
106   SVal SizeOfPlace = getExtentSizeOfPlace(NE, C);
107   const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>();
108   if (!SizeOfTargetCI)
109     return true;
110   const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>();
111   if (!SizeOfPlaceCI)
112     return true;
113 
114   if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) ||
115       (IsArrayTypeAllocated &&
116        SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) {
117     if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
118       std::string Msg;
119       // TODO: use clang constant
120       if (IsArrayTypeAllocated &&
121           SizeOfPlaceCI->getValue() > SizeOfTargetCI->getValue())
122         Msg = std::string(llvm::formatv(
123             "{0} bytes is possibly not enough for array allocation which "
124             "requires {1} bytes. Current overhead requires the size of {2} "
125             "bytes",
126             SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue(),
127             SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue()));
128       else if (IsArrayTypeAllocated &&
129                SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue())
130         Msg = std::string(llvm::formatv(
131             "Storage provided to placement new is only {0} bytes, "
132             "whereas the allocated array type requires more space for "
133             "internal needs",
134             SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
135       else
136         Msg = std::string(llvm::formatv(
137             "Storage provided to placement new is only {0} bytes, "
138             "whereas the allocated type requires {1} bytes",
139             SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
140 
141       auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N);
142       bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R);
143       C.emitReport(std::move(R));
144 
145       return false;
146     }
147   }
148 
149   return true;
150 }
151 
emitBadAlignReport(const Expr * P,CheckerContext & C,unsigned AllocatedTAlign,unsigned StorageTAlign) const152 void PlacementNewChecker::emitBadAlignReport(const Expr *P, CheckerContext &C,
153                                              unsigned AllocatedTAlign,
154                                              unsigned StorageTAlign) const {
155   ProgramStateRef State = C.getState();
156   if (ExplodedNode *N = C.generateErrorNode(State)) {
157     std::string Msg(llvm::formatv("Storage type is aligned to {0} bytes but "
158                                   "allocated type is aligned to {1} bytes",
159                                   StorageTAlign, AllocatedTAlign));
160 
161     auto R = std::make_unique<PathSensitiveBugReport>(ABT, Msg, N);
162     bugreporter::trackExpressionValue(N, P, *R);
163     C.emitReport(std::move(R));
164   }
165 }
166 
getStorageAlign(CheckerContext & C,const ValueDecl * VD) const167 unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C,
168                                               const ValueDecl *VD) const {
169   unsigned StorageTAlign = C.getASTContext().getTypeAlign(VD->getType());
170   if (unsigned SpecifiedAlignment = VD->getMaxAlignment())
171     StorageTAlign = SpecifiedAlignment;
172 
173   return StorageTAlign / C.getASTContext().getCharWidth();
174 }
175 
checkElementRegionAlign(const ElementRegion * R,CheckerContext & C,const Expr * P,unsigned AllocatedTAlign) const176 void PlacementNewChecker::checkElementRegionAlign(
177     const ElementRegion *R, CheckerContext &C, const Expr *P,
178     unsigned AllocatedTAlign) const {
179   auto IsBaseRegionAlignedProperly = [this, R, &C, P,
180                                       AllocatedTAlign]() -> bool {
181     // Unwind nested ElementRegion`s to get the type.
182     const MemRegion *SuperRegion = R;
183     while (true) {
184       if (SuperRegion->getKind() == MemRegion::ElementRegionKind) {
185         SuperRegion = cast<SubRegion>(SuperRegion)->getSuperRegion();
186         continue;
187       }
188 
189       break;
190     }
191 
192     const DeclRegion *TheElementDeclRegion = SuperRegion->getAs<DeclRegion>();
193     if (!TheElementDeclRegion)
194       return false;
195 
196     const DeclRegion *BaseDeclRegion = R->getBaseRegion()->getAs<DeclRegion>();
197     if (!BaseDeclRegion)
198       return false;
199 
200     unsigned BaseRegionAlign = 0;
201     // We must use alignment TheElementDeclRegion if it has its own alignment
202     // specifier
203     if (TheElementDeclRegion->getDecl()->getMaxAlignment())
204       BaseRegionAlign = getStorageAlign(C, TheElementDeclRegion->getDecl());
205     else
206       BaseRegionAlign = getStorageAlign(C, BaseDeclRegion->getDecl());
207 
208     if (AllocatedTAlign > BaseRegionAlign) {
209       emitBadAlignReport(P, C, AllocatedTAlign, BaseRegionAlign);
210       return false;
211     }
212 
213     return true;
214   };
215 
216   auto CheckElementRegionOffset = [this, R, &C, P, AllocatedTAlign]() -> void {
217     RegionOffset TheOffsetRegion = R->getAsOffset();
218     if (TheOffsetRegion.hasSymbolicOffset())
219       return;
220 
221     unsigned Offset =
222         TheOffsetRegion.getOffset() / C.getASTContext().getCharWidth();
223     unsigned AddressAlign = Offset % AllocatedTAlign;
224     if (AddressAlign != 0) {
225       emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign);
226       return;
227     }
228   };
229 
230   if (IsBaseRegionAlignedProperly()) {
231     CheckElementRegionOffset();
232   }
233 }
234 
checkFieldRegionAlign(const FieldRegion * R,CheckerContext & C,const Expr * P,unsigned AllocatedTAlign) const235 void PlacementNewChecker::checkFieldRegionAlign(
236     const FieldRegion *R, CheckerContext &C, const Expr *P,
237     unsigned AllocatedTAlign) const {
238   const MemRegion *BaseRegion = R->getBaseRegion();
239   if (!BaseRegion)
240     return;
241 
242   if (const VarRegion *TheVarRegion = BaseRegion->getAs<VarRegion>()) {
243     if (isVarRegionAlignedProperly(TheVarRegion, C, P, AllocatedTAlign)) {
244       // We've checked type align but, unless FieldRegion
245       // offset is zero, we also need to check its own
246       // align.
247       RegionOffset Offset = R->getAsOffset();
248       if (Offset.hasSymbolicOffset())
249         return;
250 
251       int64_t OffsetValue =
252           Offset.getOffset() / C.getASTContext().getCharWidth();
253       unsigned AddressAlign = OffsetValue % AllocatedTAlign;
254       if (AddressAlign != 0)
255         emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign);
256     }
257   }
258 }
259 
isVarRegionAlignedProperly(const VarRegion * R,CheckerContext & C,const Expr * P,unsigned AllocatedTAlign) const260 bool PlacementNewChecker::isVarRegionAlignedProperly(
261     const VarRegion *R, CheckerContext &C, const Expr *P,
262     unsigned AllocatedTAlign) const {
263   const VarDecl *TheVarDecl = R->getDecl();
264   unsigned StorageTAlign = getStorageAlign(C, TheVarDecl);
265   if (AllocatedTAlign > StorageTAlign) {
266     emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign);
267 
268     return false;
269   }
270 
271   return true;
272 }
273 
checkPlaceIsAlignedProperly(const CXXNewExpr * NE,CheckerContext & C) const274 bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE,
275                                                       CheckerContext &C) const {
276   const Expr *Place = NE->getPlacementArg(0);
277 
278   QualType AllocatedT = NE->getAllocatedType();
279   unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(AllocatedT) /
280                              C.getASTContext().getCharWidth();
281 
282   SVal PlaceVal = C.getSVal(Place);
283   if (const MemRegion *MRegion = PlaceVal.getAsRegion()) {
284     if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>())
285       checkElementRegionAlign(TheElementRegion, C, Place, AllocatedTAlign);
286     else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>())
287       checkFieldRegionAlign(TheFieldRegion, C, Place, AllocatedTAlign);
288     else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>())
289       isVarRegionAlignedProperly(TheVarRegion, C, Place, AllocatedTAlign);
290   }
291 
292   return true;
293 }
294 
checkPreStmt(const CXXNewExpr * NE,CheckerContext & C) const295 void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE,
296                                        CheckerContext &C) const {
297   // Check only the default placement new.
298   if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator())
299     return;
300 
301   if (NE->getNumPlacementArgs() == 0)
302     return;
303 
304   if (!checkPlaceCapacityIsSufficient(NE, C))
305     return;
306 
307   checkPlaceIsAlignedProperly(NE, C);
308 }
309 
registerPlacementNewChecker(CheckerManager & mgr)310 void ento::registerPlacementNewChecker(CheckerManager &mgr) {
311   mgr.registerChecker<PlacementNewChecker>();
312 }
313 
shouldRegisterPlacementNewChecker(const CheckerManager & mgr)314 bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) {
315   return true;
316 }
317