xref: /openbsd-src/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp (revision a9ac8606c53d55cee9c3a39778b249c51df111ef)
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