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