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 | |
64 | SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE, |
65 | CheckerContext &C) const { |
66 | const Expr *Place = NE->getPlacementArg(I: 0); |
67 | return getDynamicExtentWithOffset(State: C.getState(), BufV: C.getSVal(S: Place)); |
68 | } |
69 | |
70 | 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(T: ElementType); |
78 | IsArray = false; |
79 | if (NE->isArray()) { |
80 | IsArray = true; |
81 | const Expr *SizeExpr = *NE->getArraySize(); |
82 | SVal ElementCount = C.getSVal(S: SizeExpr); |
83 | if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) { |
84 | // size in Bytes = ElementCountNL * TypeSize |
85 | return SvalBuilder.evalBinOp( |
86 | state: State, op: BO_Mul, lhs: *ElementCountNL, |
87 | rhs: SvalBuilder.makeArrayIndex(idx: TypeSize.getQuantity()), |
88 | type: 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(T: SvalBuilder.getArrayIndexType()) |
94 | .getQuantity() * |
95 | C.getASTContext().getCharWidth(), |
96 | TypeSize.getQuantity()); |
97 | return SvalBuilder.makeArrayIndex(idx: I.getZExtValue()); |
98 | } |
99 | return UnknownVal(); |
100 | } |
101 | |
102 | bool PlacementNewChecker::checkPlaceCapacityIsSufficient( |
103 | const CXXNewExpr *NE, CheckerContext &C) const { |
104 | bool IsArrayTypeAllocated; |
105 | SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArray&: 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(State: 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 | Fmt: "{0} bytes is possibly not enough for array allocation which " |
124 | "requires {1} bytes. Current overhead requires the size of {2} " |
125 | "bytes" , |
126 | Vals: SizeOfPlaceCI->getValue(), Vals: SizeOfTargetCI->getValue(), |
127 | Vals: SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue())); |
128 | else if (IsArrayTypeAllocated && |
129 | SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue()) |
130 | Msg = std::string(llvm::formatv( |
131 | Fmt: "Storage provided to placement new is only {0} bytes, " |
132 | "whereas the allocated array type requires more space for " |
133 | "internal needs" , |
134 | Vals: SizeOfPlaceCI->getValue(), Vals: SizeOfTargetCI->getValue())); |
135 | else |
136 | Msg = std::string(llvm::formatv( |
137 | Fmt: "Storage provided to placement new is only {0} bytes, " |
138 | "whereas the allocated type requires {1} bytes" , |
139 | Vals: SizeOfPlaceCI->getValue(), Vals: SizeOfTargetCI->getValue())); |
140 | |
141 | auto R = std::make_unique<PathSensitiveBugReport>(args: SBT, args&: Msg, args&: N); |
142 | bugreporter::trackExpressionValue(N, E: NE->getPlacementArg(I: 0), R&: *R); |
143 | C.emitReport(R: std::move(R)); |
144 | |
145 | return false; |
146 | } |
147 | } |
148 | |
149 | return true; |
150 | } |
151 | |
152 | 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(Fmt: "Storage type is aligned to {0} bytes but " |
158 | "allocated type is aligned to {1} bytes" , |
159 | Vals&: StorageTAlign, Vals&: AllocatedTAlign)); |
160 | |
161 | auto R = std::make_unique<PathSensitiveBugReport>(args: ABT, args&: Msg, args&: N); |
162 | bugreporter::trackExpressionValue(N, E: P, R&: *R); |
163 | C.emitReport(R: std::move(R)); |
164 | } |
165 | } |
166 | |
167 | unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C, |
168 | const ValueDecl *VD) const { |
169 | unsigned StorageTAlign = C.getASTContext().getTypeAlign(T: VD->getType()); |
170 | if (unsigned SpecifiedAlignment = VD->getMaxAlignment()) |
171 | StorageTAlign = SpecifiedAlignment; |
172 | |
173 | return StorageTAlign / C.getASTContext().getCharWidth(); |
174 | } |
175 | |
176 | 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>(Val: 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, VD: TheElementDeclRegion->getDecl()); |
205 | else |
206 | BaseRegionAlign = getStorageAlign(C, VD: BaseDeclRegion->getDecl()); |
207 | |
208 | if (AllocatedTAlign > BaseRegionAlign) { |
209 | emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign: 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, StorageTAlign: AddressAlign); |
226 | return; |
227 | } |
228 | }; |
229 | |
230 | if (IsBaseRegionAlignedProperly()) { |
231 | CheckElementRegionOffset(); |
232 | } |
233 | } |
234 | |
235 | 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(R: 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, StorageTAlign: AddressAlign); |
256 | } |
257 | } |
258 | } |
259 | |
260 | 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, VD: TheVarDecl); |
265 | if (AllocatedTAlign > StorageTAlign) { |
266 | emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign); |
267 | |
268 | return false; |
269 | } |
270 | |
271 | return true; |
272 | } |
273 | |
274 | bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE, |
275 | CheckerContext &C) const { |
276 | const Expr *Place = NE->getPlacementArg(I: 0); |
277 | |
278 | QualType AllocatedT = NE->getAllocatedType(); |
279 | unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(T: AllocatedT) / |
280 | C.getASTContext().getCharWidth(); |
281 | |
282 | SVal PlaceVal = C.getSVal(S: Place); |
283 | if (const MemRegion *MRegion = PlaceVal.getAsRegion()) { |
284 | if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>()) |
285 | checkElementRegionAlign(R: TheElementRegion, C, P: Place, AllocatedTAlign); |
286 | else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>()) |
287 | checkFieldRegionAlign(R: TheFieldRegion, C, P: Place, AllocatedTAlign); |
288 | else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>()) |
289 | isVarRegionAlignedProperly(R: TheVarRegion, C, P: Place, AllocatedTAlign); |
290 | } |
291 | |
292 | return true; |
293 | } |
294 | |
295 | 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 | |
310 | void ento::registerPlacementNewChecker(CheckerManager &mgr) { |
311 | mgr.registerChecker<PlacementNewChecker>(); |
312 | } |
313 | |
314 | bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) { |
315 | return true; |
316 | } |
317 | |