1 | // SmartPtrModeling.cpp - Model behavior of C++ smart pointers - 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 checker that models various aspects of |
10 | // C++ smart pointer behavior. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "Move.h" |
15 | #include "SmartPtr.h" |
16 | |
17 | #include "clang/AST/DeclCXX.h" |
18 | #include "clang/AST/DeclarationName.h" |
19 | #include "clang/AST/ExprCXX.h" |
20 | #include "clang/AST/Type.h" |
21 | #include "clang/Basic/LLVM.h" |
22 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
23 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
24 | #include "clang/StaticAnalyzer/Core/Checker.h" |
25 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
26 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
27 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
28 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
29 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" |
30 | #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" |
31 | #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" |
32 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" |
33 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" |
34 | #include "llvm/ADT/STLExtras.h" |
35 | #include "llvm/Support/ErrorHandling.h" |
36 | #include <optional> |
37 | #include <string> |
38 | |
39 | using namespace clang; |
40 | using namespace ento; |
41 | |
42 | namespace { |
43 | |
44 | class SmartPtrModeling |
45 | : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges, |
46 | check::LiveSymbols> { |
47 | |
48 | bool isBoolConversionMethod(const CallEvent &Call) const; |
49 | |
50 | public: |
51 | // Whether the checker should model for null dereferences of smart pointers. |
52 | bool ModelSmartPtrDereference = false; |
53 | bool evalCall(const CallEvent &Call, CheckerContext &C) const; |
54 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
55 | ProgramStateRef |
56 | checkRegionChanges(ProgramStateRef State, |
57 | const InvalidatedSymbols *Invalidated, |
58 | ArrayRef<const MemRegion *> ExplicitRegions, |
59 | ArrayRef<const MemRegion *> Regions, |
60 | const LocationContext *LCtx, const CallEvent *Call) const; |
61 | void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, |
62 | const char *Sep) const override; |
63 | void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; |
64 | |
65 | private: |
66 | void handleReset(const CallEvent &Call, CheckerContext &C) const; |
67 | void handleRelease(const CallEvent &Call, CheckerContext &C) const; |
68 | void handleSwapMethod(const CallEvent &Call, CheckerContext &C) const; |
69 | void handleGet(const CallEvent &Call, CheckerContext &C) const; |
70 | bool handleAssignOp(const CallEvent &Call, CheckerContext &C) const; |
71 | bool handleMoveCtr(const CallEvent &Call, CheckerContext &C, |
72 | const MemRegion *ThisRegion) const; |
73 | bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion, |
74 | const MemRegion *OtherSmartPtrRegion, |
75 | const CallEvent &Call) const; |
76 | void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const; |
77 | bool handleComparisionOp(const CallEvent &Call, CheckerContext &C) const; |
78 | bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const; |
79 | bool handleSwap(ProgramStateRef State, SVal First, SVal Second, |
80 | CheckerContext &C) const; |
81 | std::pair<SVal, ProgramStateRef> |
82 | retrieveOrConjureInnerPtrVal(ProgramStateRef State, |
83 | const MemRegion *ThisRegion, const Expr *E, |
84 | QualType Type, CheckerContext &C) const; |
85 | |
86 | using SmartPtrMethodHandlerFn = |
87 | void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; |
88 | CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ |
89 | {{CDM::CXXMethod, {"reset" }}, &SmartPtrModeling::handleReset}, |
90 | {{CDM::CXXMethod, {"release" }}, &SmartPtrModeling::handleRelease}, |
91 | {{CDM::CXXMethod, {"swap" }, 1}, &SmartPtrModeling::handleSwapMethod}, |
92 | {{CDM::CXXMethod, {"get" }}, &SmartPtrModeling::handleGet}}; |
93 | const CallDescription StdSwapCall{CDM::SimpleFunc, {"std" , "swap" }, 2}; |
94 | const CallDescriptionSet MakeUniqueVariants{ |
95 | {CDM::SimpleFunc, {"std" , "make_unique" }}, |
96 | {CDM::SimpleFunc, {"std" , "make_unique_for_overwrite" }}}; |
97 | }; |
98 | } // end of anonymous namespace |
99 | |
100 | REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) |
101 | |
102 | // Checks if RD has name in Names and is in std namespace |
103 | static bool hasStdClassWithName(const CXXRecordDecl *RD, |
104 | ArrayRef<llvm::StringLiteral> Names) { |
105 | if (!RD || !RD->getDeclContext()->isStdNamespace()) |
106 | return false; |
107 | if (RD->getDeclName().isIdentifier()) |
108 | return llvm::is_contained(Range&: Names, Element: RD->getName()); |
109 | return false; |
110 | } |
111 | |
112 | constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr" , "unique_ptr" , |
113 | "weak_ptr" }; |
114 | |
115 | static bool isStdSmartPtr(const CXXRecordDecl *RD) { |
116 | return hasStdClassWithName(RD, Names: STD_PTR_NAMES); |
117 | } |
118 | |
119 | static bool isStdSmartPtr(const Expr *E) { |
120 | return isStdSmartPtr(RD: E->getType()->getAsCXXRecordDecl()); |
121 | } |
122 | |
123 | // Define the inter-checker API. |
124 | namespace clang { |
125 | namespace ento { |
126 | namespace smartptr { |
127 | bool isStdSmartPtrCall(const CallEvent &Call) { |
128 | const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: Call.getDecl()); |
129 | if (!MethodDecl || !MethodDecl->getParent()) |
130 | return false; |
131 | return isStdSmartPtr(RD: MethodDecl->getParent()); |
132 | } |
133 | |
134 | bool isStdSmartPtr(const CXXRecordDecl *RD) { |
135 | if (!RD || !RD->getDeclContext()->isStdNamespace()) |
136 | return false; |
137 | |
138 | if (RD->getDeclName().isIdentifier()) { |
139 | StringRef Name = RD->getName(); |
140 | return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr" ; |
141 | } |
142 | return false; |
143 | } |
144 | |
145 | bool isStdSmartPtr(const Expr *E) { |
146 | return isStdSmartPtr(RD: E->getType()->getAsCXXRecordDecl()); |
147 | } |
148 | |
149 | bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { |
150 | const auto *InnerPointVal = State->get<TrackedRegionMap>(key: ThisRegion); |
151 | return InnerPointVal && |
152 | !State->assume(Cond: InnerPointVal->castAs<DefinedOrUnknownSVal>(), Assumption: true); |
153 | } |
154 | } // namespace smartptr |
155 | } // namespace ento |
156 | } // namespace clang |
157 | |
158 | // If a region is removed all of the subregions need to be removed too. |
159 | static TrackedRegionMapTy |
160 | removeTrackedSubregions(TrackedRegionMapTy RegionMap, |
161 | TrackedRegionMapTy::Factory &RegionMapFactory, |
162 | const MemRegion *Region) { |
163 | if (!Region) |
164 | return RegionMap; |
165 | for (const auto &E : RegionMap) { |
166 | if (E.first->isSubRegionOf(R: Region)) |
167 | RegionMap = RegionMapFactory.remove(Old: RegionMap, K: E.first); |
168 | } |
169 | return RegionMap; |
170 | } |
171 | |
172 | static ProgramStateRef updateSwappedRegion(ProgramStateRef State, |
173 | const MemRegion *Region, |
174 | const SVal *RegionInnerPointerVal) { |
175 | if (RegionInnerPointerVal) { |
176 | State = State->set<TrackedRegionMap>(K: Region, E: *RegionInnerPointerVal); |
177 | } else { |
178 | State = State->remove<TrackedRegionMap>(K: Region); |
179 | } |
180 | return State; |
181 | } |
182 | |
183 | static QualType getInnerPointerType(CheckerContext C, const CXXRecordDecl *RD) { |
184 | if (!RD || !RD->isInStdNamespace()) |
185 | return {}; |
186 | |
187 | const auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: RD); |
188 | if (!TSD) |
189 | return {}; |
190 | |
191 | auto TemplateArgs = TSD->getTemplateArgs().asArray(); |
192 | if (TemplateArgs.empty()) |
193 | return {}; |
194 | auto InnerValueType = TemplateArgs[0].getAsType(); |
195 | return C.getASTContext().getPointerType(T: InnerValueType.getCanonicalType()); |
196 | } |
197 | |
198 | // This is for use with standalone-functions like std::make_unique, |
199 | // std::make_unique_for_overwrite, etc. It reads the template parameter and |
200 | // returns the pointer type corresponding to it, |
201 | static QualType getPointerTypeFromTemplateArg(const CallEvent &Call, |
202 | CheckerContext &C) { |
203 | const auto *FD = dyn_cast_or_null<FunctionDecl>(Val: Call.getDecl()); |
204 | if (!FD || !FD->getPrimaryTemplate()) |
205 | return {}; |
206 | const auto &TemplateArgs = FD->getTemplateSpecializationArgs()->asArray(); |
207 | if (TemplateArgs.size() == 0) |
208 | return {}; |
209 | auto ValueType = TemplateArgs[0].getAsType(); |
210 | return C.getASTContext().getPointerType(T: ValueType.getCanonicalType()); |
211 | } |
212 | |
213 | // Helper method to get the inner pointer type of specialized smart pointer |
214 | // Returns empty type if not found valid inner pointer type. |
215 | static QualType getInnerPointerType(const CallEvent &Call, CheckerContext &C) { |
216 | const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: Call.getDecl()); |
217 | if (!MethodDecl || !MethodDecl->getParent()) |
218 | return {}; |
219 | |
220 | const auto *RecordDecl = MethodDecl->getParent(); |
221 | return getInnerPointerType(C, RD: RecordDecl); |
222 | } |
223 | |
224 | // Helper method to pretty print region and avoid extra spacing. |
225 | static void checkAndPrettyPrintRegion(llvm::raw_ostream &OS, |
226 | const MemRegion *Region) { |
227 | if (Region->canPrintPretty()) { |
228 | OS << " " ; |
229 | Region->printPretty(os&: OS); |
230 | } |
231 | } |
232 | |
233 | bool SmartPtrModeling::isBoolConversionMethod(const CallEvent &Call) const { |
234 | // TODO: Update CallDescription to support anonymous calls? |
235 | // TODO: Handle other methods, such as .get() or .release(). |
236 | // But once we do, we'd need a visitor to explain null dereferences |
237 | // that are found via such modeling. |
238 | const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Val: Call.getDecl()); |
239 | return CD && CD->getConversionType()->isBooleanType(); |
240 | } |
241 | |
242 | constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream" }; |
243 | |
244 | bool isStdBasicOstream(const Expr *E) { |
245 | const auto *RD = E->getType()->getAsCXXRecordDecl(); |
246 | return hasStdClassWithName(RD, Names: BASIC_OSTREAM_NAMES); |
247 | } |
248 | |
249 | static bool isStdFunctionCall(const CallEvent &Call) { |
250 | return Call.getDecl() && Call.getDecl()->getDeclContext()->isStdNamespace(); |
251 | } |
252 | |
253 | bool isStdOstreamOperatorCall(const CallEvent &Call) { |
254 | if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call)) |
255 | return false; |
256 | const auto *FC = dyn_cast<SimpleFunctionCall>(Val: &Call); |
257 | if (!FC) |
258 | return false; |
259 | const FunctionDecl *FD = FC->getDecl(); |
260 | if (!FD->isOverloadedOperator()) |
261 | return false; |
262 | const OverloadedOperatorKind OOK = FD->getOverloadedOperator(); |
263 | if (OOK != clang::OO_LessLess) |
264 | return false; |
265 | return isStdSmartPtr(E: Call.getArgExpr(Index: 1)) && |
266 | isStdBasicOstream(E: Call.getArgExpr(Index: 0)); |
267 | } |
268 | |
269 | static bool isPotentiallyComparisionOpCall(const CallEvent &Call) { |
270 | if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call)) |
271 | return false; |
272 | return smartptr::isStdSmartPtr(E: Call.getArgExpr(Index: 0)) || |
273 | smartptr::isStdSmartPtr(E: Call.getArgExpr(Index: 1)); |
274 | } |
275 | |
276 | bool SmartPtrModeling::evalCall(const CallEvent &Call, |
277 | CheckerContext &C) const { |
278 | |
279 | ProgramStateRef State = C.getState(); |
280 | |
281 | // If any one of the arg is a unique_ptr, then |
282 | // we can try this function |
283 | if (ModelSmartPtrDereference && isPotentiallyComparisionOpCall(Call)) |
284 | if (handleComparisionOp(Call, C)) |
285 | return true; |
286 | |
287 | if (ModelSmartPtrDereference && isStdOstreamOperatorCall(Call)) |
288 | return handleOstreamOperator(Call, C); |
289 | |
290 | if (StdSwapCall.matches(Call)) { |
291 | // Check the first arg, if it is of std::unique_ptr type. |
292 | assert(Call.getNumArgs() == 2 && "std::swap should have two arguments" ); |
293 | const Expr *FirstArg = Call.getArgExpr(Index: 0); |
294 | if (!smartptr::isStdSmartPtr(RD: FirstArg->getType()->getAsCXXRecordDecl())) |
295 | return false; |
296 | return handleSwap(State, First: Call.getArgSVal(Index: 0), Second: Call.getArgSVal(Index: 1), C); |
297 | } |
298 | |
299 | if (MakeUniqueVariants.contains(Call)) { |
300 | if (!ModelSmartPtrDereference) |
301 | return false; |
302 | |
303 | const std::optional<SVal> ThisRegionOpt = |
304 | Call.getReturnValueUnderConstruction(); |
305 | if (!ThisRegionOpt) |
306 | return false; |
307 | |
308 | const auto PtrVal = C.getSValBuilder().getConjuredHeapSymbolVal( |
309 | E: Call.getOriginExpr(), LCtx: C.getLocationContext(), |
310 | type: getPointerTypeFromTemplateArg(Call, C), Count: C.blockCount()); |
311 | |
312 | const MemRegion *ThisRegion = ThisRegionOpt->getAsRegion(); |
313 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: PtrVal); |
314 | State = State->assume(Cond: PtrVal, Assumption: true); |
315 | |
316 | // TODO: ExprEngine should do this for us. |
317 | // For a bit more context: |
318 | // 1) Why do we need this? Since we are modelling a "function" |
319 | // that returns a constructed object we need to store this information in |
320 | // the program state. |
321 | // |
322 | // 2) Why does this work? |
323 | // `updateObjectsUnderConstruction` does exactly as it sounds. |
324 | // |
325 | // 3) How should it look like when moved to the Engine? |
326 | // It would be nice if we can just |
327 | // pretend we don't need to know about this - ie, completely automatic work. |
328 | // However, realistically speaking, I think we would need to "signal" the |
329 | // ExprEngine evalCall handler that we are constructing an object with this |
330 | // function call (constructors obviously construct, hence can be |
331 | // automatically deduced). |
332 | auto &Engine = State->getStateManager().getOwningEngine(); |
333 | State = Engine.updateObjectsUnderConstruction( |
334 | V: *ThisRegionOpt, E: nullptr, State, LCtx: C.getLocationContext(), |
335 | CC: Call.getConstructionContext(), CallOpts: {}); |
336 | |
337 | // We don't leave a note here since it is guaranteed the |
338 | // unique_ptr from this call is non-null (hence is safe to de-reference). |
339 | C.addTransition(State); |
340 | return true; |
341 | } |
342 | |
343 | if (!smartptr::isStdSmartPtrCall(Call)) |
344 | return false; |
345 | |
346 | if (isBoolConversionMethod(Call)) { |
347 | const MemRegion *ThisR = |
348 | cast<CXXInstanceCall>(Val: &Call)->getCXXThisVal().getAsRegion(); |
349 | |
350 | if (ModelSmartPtrDereference) { |
351 | // The check for the region is moved is duplicated in handleBoolOperation |
352 | // method. |
353 | // FIXME: Once we model std::move for smart pointers clean up this and use |
354 | // that modeling. |
355 | handleBoolConversion(Call, C); |
356 | return true; |
357 | } else { |
358 | if (!move::isMovedFrom(State, Region: ThisR)) { |
359 | // TODO: Model this case as well. At least, avoid invalidation of |
360 | // globals. |
361 | return false; |
362 | } |
363 | |
364 | // TODO: Add a note to bug reports describing this decision. |
365 | C.addTransition(State: State->BindExpr( |
366 | S: Call.getOriginExpr(), LCtx: C.getLocationContext(), |
367 | V: C.getSValBuilder().makeZeroVal(type: Call.getResultType()))); |
368 | |
369 | return true; |
370 | } |
371 | } |
372 | |
373 | if (!ModelSmartPtrDereference) |
374 | return false; |
375 | |
376 | if (const auto *CC = dyn_cast<CXXConstructorCall>(Val: &Call)) { |
377 | if (CC->getDecl()->isCopyConstructor()) |
378 | return false; |
379 | |
380 | const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion(); |
381 | if (!ThisRegion) |
382 | return false; |
383 | |
384 | QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType(); |
385 | |
386 | if (CC->getDecl()->isMoveConstructor()) |
387 | return handleMoveCtr(Call, C, ThisRegion); |
388 | |
389 | if (Call.getNumArgs() == 0) { |
390 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
391 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: NullVal); |
392 | |
393 | C.addTransition( |
394 | State, Tag: C.getNoteTag(Cb: [ThisRegion](PathSensitiveBugReport &BR, |
395 | llvm::raw_ostream &OS) { |
396 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
397 | !BR.isInteresting(R: ThisRegion)) |
398 | return; |
399 | OS << "Default constructed smart pointer" ; |
400 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
401 | OS << " is null" ; |
402 | })); |
403 | } else { |
404 | const auto *TrackingExpr = Call.getArgExpr(Index: 0); |
405 | assert(TrackingExpr->getType()->isPointerType() && |
406 | "Adding a non pointer value to TrackedRegionMap" ); |
407 | auto ArgVal = Call.getArgSVal(Index: 0); |
408 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: ArgVal); |
409 | |
410 | C.addTransition(State, Tag: C.getNoteTag(Cb: [ThisRegion, TrackingExpr, |
411 | ArgVal](PathSensitiveBugReport &BR, |
412 | llvm::raw_ostream &OS) { |
413 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
414 | !BR.isInteresting(R: ThisRegion)) |
415 | return; |
416 | bugreporter::trackExpressionValue(N: BR.getErrorNode(), E: TrackingExpr, R&: BR); |
417 | OS << "Smart pointer" ; |
418 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
419 | if (ArgVal.isZeroConstant()) |
420 | OS << " is constructed using a null value" ; |
421 | else |
422 | OS << " is constructed" ; |
423 | })); |
424 | } |
425 | return true; |
426 | } |
427 | |
428 | if (handleAssignOp(Call, C)) |
429 | return true; |
430 | |
431 | const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); |
432 | if (!Handler) |
433 | return false; |
434 | (this->**Handler)(Call, C); |
435 | |
436 | return C.isDifferent(); |
437 | } |
438 | |
439 | std::pair<SVal, ProgramStateRef> SmartPtrModeling::retrieveOrConjureInnerPtrVal( |
440 | ProgramStateRef State, const MemRegion *ThisRegion, const Expr *E, |
441 | QualType Type, CheckerContext &C) const { |
442 | const auto *Ptr = State->get<TrackedRegionMap>(key: ThisRegion); |
443 | if (Ptr) |
444 | return {*Ptr, State}; |
445 | auto Val = C.getSValBuilder().conjureSymbolVal(stmt: E, LCtx: C.getLocationContext(), |
446 | type: Type, visitCount: C.blockCount()); |
447 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: Val); |
448 | return {Val, State}; |
449 | } |
450 | |
451 | bool SmartPtrModeling::handleComparisionOp(const CallEvent &Call, |
452 | CheckerContext &C) const { |
453 | const auto *FC = dyn_cast<SimpleFunctionCall>(Val: &Call); |
454 | if (!FC) |
455 | return false; |
456 | const FunctionDecl *FD = FC->getDecl(); |
457 | if (!FD->isOverloadedOperator()) |
458 | return false; |
459 | const OverloadedOperatorKind OOK = FD->getOverloadedOperator(); |
460 | if (!(OOK == OO_EqualEqual || OOK == OO_ExclaimEqual || OOK == OO_Less || |
461 | OOK == OO_LessEqual || OOK == OO_Greater || OOK == OO_GreaterEqual || |
462 | OOK == OO_Spaceship)) |
463 | return false; |
464 | |
465 | // There are some special cases about which we can infer about |
466 | // the resulting answer. |
467 | // For reference, there is a discussion at https://reviews.llvm.org/D104616. |
468 | // Also, the cppreference page is good to look at |
469 | // https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_cmp. |
470 | |
471 | auto makeSValFor = [&C, this](ProgramStateRef State, const Expr *E, |
472 | SVal S) -> std::pair<SVal, ProgramStateRef> { |
473 | if (S.isZeroConstant()) { |
474 | return {S, State}; |
475 | } |
476 | const MemRegion *Reg = S.getAsRegion(); |
477 | assert(Reg && |
478 | "this pointer of std::unique_ptr should be obtainable as MemRegion" ); |
479 | QualType Type = getInnerPointerType(C, RD: E->getType()->getAsCXXRecordDecl()); |
480 | return retrieveOrConjureInnerPtrVal(State, ThisRegion: Reg, E, Type, C); |
481 | }; |
482 | |
483 | SVal First = Call.getArgSVal(Index: 0); |
484 | SVal Second = Call.getArgSVal(Index: 1); |
485 | const auto *FirstExpr = Call.getArgExpr(Index: 0); |
486 | const auto *SecondExpr = Call.getArgExpr(Index: 1); |
487 | |
488 | const auto *ResultExpr = Call.getOriginExpr(); |
489 | const auto *LCtx = C.getLocationContext(); |
490 | auto &Bldr = C.getSValBuilder(); |
491 | ProgramStateRef State = C.getState(); |
492 | |
493 | SVal FirstPtrVal, SecondPtrVal; |
494 | std::tie(args&: FirstPtrVal, args&: State) = makeSValFor(State, FirstExpr, First); |
495 | std::tie(args&: SecondPtrVal, args&: State) = makeSValFor(State, SecondExpr, Second); |
496 | BinaryOperatorKind BOK = |
497 | operationKindFromOverloadedOperator(OOK, IsBinary: true).GetBinaryOpUnsafe(); |
498 | auto RetVal = Bldr.evalBinOp(state: State, op: BOK, lhs: FirstPtrVal, rhs: SecondPtrVal, |
499 | type: Call.getResultType()); |
500 | |
501 | if (OOK != OO_Spaceship) { |
502 | ProgramStateRef TrueState, FalseState; |
503 | std::tie(args&: TrueState, args&: FalseState) = |
504 | State->assume(Cond: *RetVal.getAs<DefinedOrUnknownSVal>()); |
505 | if (TrueState) |
506 | C.addTransition( |
507 | State: TrueState->BindExpr(S: ResultExpr, LCtx, V: Bldr.makeTruthVal(b: true))); |
508 | if (FalseState) |
509 | C.addTransition( |
510 | State: FalseState->BindExpr(S: ResultExpr, LCtx, V: Bldr.makeTruthVal(b: false))); |
511 | } else { |
512 | C.addTransition(State: State->BindExpr(S: ResultExpr, LCtx, V: RetVal)); |
513 | } |
514 | return true; |
515 | } |
516 | |
517 | bool SmartPtrModeling::handleOstreamOperator(const CallEvent &Call, |
518 | CheckerContext &C) const { |
519 | // operator<< does not modify the smart pointer. |
520 | // And we don't really have much of modelling of basic_ostream. |
521 | // So, we are better off: |
522 | // 1) Invalidating the mem-region of the ostream object at hand. |
523 | // 2) Setting the SVal of the basic_ostream as the return value. |
524 | // Not very satisfying, but it gets the job done, and is better |
525 | // than the default handling. :) |
526 | |
527 | ProgramStateRef State = C.getState(); |
528 | const auto StreamVal = Call.getArgSVal(Index: 0); |
529 | const MemRegion *StreamThisRegion = StreamVal.getAsRegion(); |
530 | if (!StreamThisRegion) |
531 | return false; |
532 | State = |
533 | State->invalidateRegions(Regions: {StreamThisRegion}, E: Call.getOriginExpr(), |
534 | BlockCount: C.blockCount(), LCtx: C.getLocationContext(), CausesPointerEscape: false); |
535 | State = |
536 | State->BindExpr(S: Call.getOriginExpr(), LCtx: C.getLocationContext(), V: StreamVal); |
537 | C.addTransition(State); |
538 | return true; |
539 | } |
540 | |
541 | void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, |
542 | CheckerContext &C) const { |
543 | ProgramStateRef State = C.getState(); |
544 | // Clean up dead regions from the region map. |
545 | TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); |
546 | for (auto E : TrackedRegions) { |
547 | const MemRegion *Region = E.first; |
548 | bool IsRegDead = !SymReaper.isLiveRegion(region: Region); |
549 | |
550 | if (IsRegDead) |
551 | State = State->remove<TrackedRegionMap>(K: Region); |
552 | } |
553 | C.addTransition(State); |
554 | } |
555 | |
556 | void SmartPtrModeling::printState(raw_ostream &Out, ProgramStateRef State, |
557 | const char *NL, const char *Sep) const { |
558 | TrackedRegionMapTy RS = State->get<TrackedRegionMap>(); |
559 | |
560 | if (!RS.isEmpty()) { |
561 | Out << Sep << "Smart ptr regions :" << NL; |
562 | for (auto I : RS) { |
563 | I.first->dumpToStream(os&: Out); |
564 | if (smartptr::isNullSmartPtr(State, ThisRegion: I.first)) |
565 | Out << ": Null" ; |
566 | else |
567 | Out << ": Non Null" ; |
568 | Out << NL; |
569 | } |
570 | } |
571 | } |
572 | |
573 | ProgramStateRef SmartPtrModeling::checkRegionChanges( |
574 | ProgramStateRef State, const InvalidatedSymbols *Invalidated, |
575 | ArrayRef<const MemRegion *> ExplicitRegions, |
576 | ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, |
577 | const CallEvent *Call) const { |
578 | TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>(); |
579 | TrackedRegionMapTy::Factory &RegionMapFactory = |
580 | State->get_context<TrackedRegionMap>(); |
581 | for (const auto *Region : Regions) |
582 | RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory, |
583 | Region: Region->getBaseRegion()); |
584 | return State->set<TrackedRegionMap>(RegionMap); |
585 | } |
586 | |
587 | void SmartPtrModeling::checkLiveSymbols(ProgramStateRef State, |
588 | SymbolReaper &SR) const { |
589 | // Marking tracked symbols alive |
590 | TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); |
591 | for (SVal Val : llvm::make_second_range(c&: TrackedRegions)) { |
592 | for (SymbolRef Sym : Val.symbols()) { |
593 | SR.markLive(sym: Sym); |
594 | } |
595 | } |
596 | } |
597 | |
598 | void SmartPtrModeling::handleReset(const CallEvent &Call, |
599 | CheckerContext &C) const { |
600 | ProgramStateRef State = C.getState(); |
601 | const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call); |
602 | if (!IC) |
603 | return; |
604 | |
605 | const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); |
606 | if (!ThisRegion) |
607 | return; |
608 | |
609 | assert(Call.getArgExpr(0)->getType()->isPointerType() && |
610 | "Adding a non pointer value to TrackedRegionMap" ); |
611 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: Call.getArgSVal(Index: 0)); |
612 | const auto *TrackingExpr = Call.getArgExpr(Index: 0); |
613 | C.addTransition( |
614 | State, Tag: C.getNoteTag(Cb: [ThisRegion, TrackingExpr](PathSensitiveBugReport &BR, |
615 | llvm::raw_ostream &OS) { |
616 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
617 | !BR.isInteresting(R: ThisRegion)) |
618 | return; |
619 | bugreporter::trackExpressionValue(N: BR.getErrorNode(), E: TrackingExpr, R&: BR); |
620 | OS << "Smart pointer" ; |
621 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
622 | OS << " reset using a null value" ; |
623 | })); |
624 | // TODO: Make sure to ivalidate the region in the Store if we don't have |
625 | // time to model all methods. |
626 | } |
627 | |
628 | void SmartPtrModeling::handleRelease(const CallEvent &Call, |
629 | CheckerContext &C) const { |
630 | ProgramStateRef State = C.getState(); |
631 | const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call); |
632 | if (!IC) |
633 | return; |
634 | |
635 | const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); |
636 | if (!ThisRegion) |
637 | return; |
638 | |
639 | const auto *InnerPointVal = State->get<TrackedRegionMap>(key: ThisRegion); |
640 | |
641 | if (InnerPointVal) { |
642 | State = State->BindExpr(S: Call.getOriginExpr(), LCtx: C.getLocationContext(), |
643 | V: *InnerPointVal); |
644 | } |
645 | |
646 | QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType(); |
647 | auto ValueToUpdate = C.getSValBuilder().makeNullWithType(type: ThisType); |
648 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: ValueToUpdate); |
649 | |
650 | C.addTransition(State, Tag: C.getNoteTag(Cb: [ThisRegion](PathSensitiveBugReport &BR, |
651 | llvm::raw_ostream &OS) { |
652 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
653 | !BR.isInteresting(R: ThisRegion)) |
654 | return; |
655 | |
656 | OS << "Smart pointer" ; |
657 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
658 | OS << " is released and set to null" ; |
659 | })); |
660 | // TODO: Add support to enable MallocChecker to start tracking the raw |
661 | // pointer. |
662 | } |
663 | |
664 | void SmartPtrModeling::handleSwapMethod(const CallEvent &Call, |
665 | CheckerContext &C) const { |
666 | // To model unique_ptr::swap() method. |
667 | const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call); |
668 | if (!IC) |
669 | return; |
670 | |
671 | auto State = C.getState(); |
672 | handleSwap(State, First: IC->getCXXThisVal(), Second: Call.getArgSVal(Index: 0), C); |
673 | } |
674 | |
675 | bool SmartPtrModeling::handleSwap(ProgramStateRef State, SVal First, |
676 | SVal Second, CheckerContext &C) const { |
677 | const MemRegion *FirstThisRegion = First.getAsRegion(); |
678 | if (!FirstThisRegion) |
679 | return false; |
680 | const MemRegion *SecondThisRegion = Second.getAsRegion(); |
681 | if (!SecondThisRegion) |
682 | return false; |
683 | |
684 | const auto *FirstInnerPtrVal = State->get<TrackedRegionMap>(key: FirstThisRegion); |
685 | const auto *SecondInnerPtrVal = |
686 | State->get<TrackedRegionMap>(key: SecondThisRegion); |
687 | |
688 | State = updateSwappedRegion(State, Region: FirstThisRegion, RegionInnerPointerVal: SecondInnerPtrVal); |
689 | State = updateSwappedRegion(State, Region: SecondThisRegion, RegionInnerPointerVal: FirstInnerPtrVal); |
690 | |
691 | C.addTransition(State, Tag: C.getNoteTag(Cb: [FirstThisRegion, SecondThisRegion]( |
692 | PathSensitiveBugReport &BR, |
693 | llvm::raw_ostream &OS) { |
694 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType()) |
695 | return; |
696 | if (BR.isInteresting(R: FirstThisRegion) && |
697 | !BR.isInteresting(R: SecondThisRegion)) { |
698 | BR.markInteresting(R: SecondThisRegion); |
699 | BR.markNotInteresting(R: FirstThisRegion); |
700 | } |
701 | if (BR.isInteresting(R: SecondThisRegion) && |
702 | !BR.isInteresting(R: FirstThisRegion)) { |
703 | BR.markInteresting(R: FirstThisRegion); |
704 | BR.markNotInteresting(R: SecondThisRegion); |
705 | } |
706 | // TODO: We need to emit some note here probably!! |
707 | })); |
708 | |
709 | return true; |
710 | } |
711 | |
712 | void SmartPtrModeling::handleGet(const CallEvent &Call, |
713 | CheckerContext &C) const { |
714 | ProgramStateRef State = C.getState(); |
715 | const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call); |
716 | if (!IC) |
717 | return; |
718 | |
719 | const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); |
720 | if (!ThisRegion) |
721 | return; |
722 | |
723 | SVal InnerPointerVal; |
724 | std::tie(args&: InnerPointerVal, args&: State) = retrieveOrConjureInnerPtrVal( |
725 | State, ThisRegion, E: Call.getOriginExpr(), Type: Call.getResultType(), C); |
726 | State = State->BindExpr(S: Call.getOriginExpr(), LCtx: C.getLocationContext(), |
727 | V: InnerPointerVal); |
728 | // TODO: Add NoteTag, for how the raw pointer got using 'get' method. |
729 | C.addTransition(State); |
730 | } |
731 | |
732 | bool SmartPtrModeling::handleAssignOp(const CallEvent &Call, |
733 | CheckerContext &C) const { |
734 | ProgramStateRef State = C.getState(); |
735 | const auto *OC = dyn_cast<CXXMemberOperatorCall>(Val: &Call); |
736 | if (!OC) |
737 | return false; |
738 | OverloadedOperatorKind OOK = OC->getOverloadedOperator(); |
739 | if (OOK != OO_Equal) |
740 | return false; |
741 | const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion(); |
742 | if (!ThisRegion) |
743 | return false; |
744 | |
745 | QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType(); |
746 | |
747 | const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(Index: 0).getAsRegion(); |
748 | // In case of 'nullptr' or '0' assigned |
749 | if (!OtherSmartPtrRegion) { |
750 | bool AssignedNull = Call.getArgSVal(Index: 0).isZeroConstant(); |
751 | if (!AssignedNull) |
752 | return false; |
753 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
754 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: NullVal); |
755 | C.addTransition(State, Tag: C.getNoteTag(Cb: [ThisRegion](PathSensitiveBugReport &BR, |
756 | llvm::raw_ostream &OS) { |
757 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
758 | !BR.isInteresting(R: ThisRegion)) |
759 | return; |
760 | OS << "Smart pointer" ; |
761 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
762 | OS << " is assigned to null" ; |
763 | })); |
764 | return true; |
765 | } |
766 | |
767 | return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call); |
768 | } |
769 | |
770 | bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C, |
771 | const MemRegion *ThisRegion) const { |
772 | const auto *OtherSmartPtrRegion = Call.getArgSVal(Index: 0).getAsRegion(); |
773 | if (!OtherSmartPtrRegion) |
774 | return false; |
775 | |
776 | return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call); |
777 | } |
778 | |
779 | bool SmartPtrModeling::updateMovedSmartPointers( |
780 | CheckerContext &C, const MemRegion *ThisRegion, |
781 | const MemRegion *OtherSmartPtrRegion, const CallEvent &Call) const { |
782 | ProgramStateRef State = C.getState(); |
783 | QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType(); |
784 | const auto *OtherInnerPtr = State->get<TrackedRegionMap>(key: OtherSmartPtrRegion); |
785 | if (OtherInnerPtr) { |
786 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: *OtherInnerPtr); |
787 | |
788 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
789 | State = State->set<TrackedRegionMap>(K: OtherSmartPtrRegion, E: NullVal); |
790 | bool IsArgValNull = OtherInnerPtr->isZeroConstant(); |
791 | |
792 | C.addTransition( |
793 | State, |
794 | Tag: C.getNoteTag(Cb: [ThisRegion, OtherSmartPtrRegion, IsArgValNull]( |
795 | PathSensitiveBugReport &BR, llvm::raw_ostream &OS) { |
796 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType()) |
797 | return; |
798 | if (BR.isInteresting(R: OtherSmartPtrRegion)) { |
799 | OS << "Smart pointer" ; |
800 | checkAndPrettyPrintRegion(OS, Region: OtherSmartPtrRegion); |
801 | OS << " is null after being moved to" ; |
802 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
803 | } |
804 | if (BR.isInteresting(R: ThisRegion) && IsArgValNull) { |
805 | OS << "A null pointer value is moved to" ; |
806 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
807 | BR.markInteresting(R: OtherSmartPtrRegion); |
808 | } |
809 | })); |
810 | return true; |
811 | } else { |
812 | // In case we dont know anything about value we are moving from |
813 | // remove the entry from map for which smart pointer got moved to. |
814 | // For unique_ptr<A>, Ty will be 'A*'. |
815 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
816 | State = State->remove<TrackedRegionMap>(K: ThisRegion); |
817 | State = State->set<TrackedRegionMap>(K: OtherSmartPtrRegion, E: NullVal); |
818 | C.addTransition(State, Tag: C.getNoteTag(Cb: [OtherSmartPtrRegion, |
819 | ThisRegion](PathSensitiveBugReport &BR, |
820 | llvm::raw_ostream &OS) { |
821 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
822 | !BR.isInteresting(R: OtherSmartPtrRegion)) |
823 | return; |
824 | OS << "Smart pointer" ; |
825 | checkAndPrettyPrintRegion(OS, Region: OtherSmartPtrRegion); |
826 | OS << " is null after; previous value moved to" ; |
827 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
828 | })); |
829 | return true; |
830 | } |
831 | return false; |
832 | } |
833 | |
834 | void SmartPtrModeling::handleBoolConversion(const CallEvent &Call, |
835 | CheckerContext &C) const { |
836 | // To model unique_ptr::operator bool |
837 | ProgramStateRef State = C.getState(); |
838 | const Expr *CallExpr = Call.getOriginExpr(); |
839 | const MemRegion *ThisRegion = |
840 | cast<CXXInstanceCall>(Val: &Call)->getCXXThisVal().getAsRegion(); |
841 | |
842 | QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType(); |
843 | |
844 | SVal InnerPointerVal; |
845 | if (const auto *InnerValPtr = State->get<TrackedRegionMap>(key: ThisRegion)) { |
846 | InnerPointerVal = *InnerValPtr; |
847 | } else { |
848 | // In case of inner pointer SVal is not available we create |
849 | // conjureSymbolVal for inner pointer value. |
850 | auto InnerPointerType = getInnerPointerType(Call, C); |
851 | if (InnerPointerType.isNull()) |
852 | return; |
853 | |
854 | const LocationContext *LC = C.getLocationContext(); |
855 | InnerPointerVal = C.getSValBuilder().conjureSymbolVal( |
856 | stmt: CallExpr, LCtx: LC, type: InnerPointerType, visitCount: C.blockCount()); |
857 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: InnerPointerVal); |
858 | } |
859 | |
860 | if (State->isNull(V: InnerPointerVal).isConstrainedTrue()) { |
861 | State = State->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
862 | V: C.getSValBuilder().makeTruthVal(b: false)); |
863 | |
864 | C.addTransition(State); |
865 | return; |
866 | } else if (State->isNonNull(V: InnerPointerVal).isConstrainedTrue()) { |
867 | State = State->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
868 | V: C.getSValBuilder().makeTruthVal(b: true)); |
869 | |
870 | C.addTransition(State); |
871 | return; |
872 | } else if (move::isMovedFrom(State, Region: ThisRegion)) { |
873 | C.addTransition( |
874 | State: State->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
875 | V: C.getSValBuilder().makeZeroVal(type: Call.getResultType()))); |
876 | return; |
877 | } else { |
878 | ProgramStateRef NotNullState, NullState; |
879 | std::tie(args&: NotNullState, args&: NullState) = |
880 | State->assume(Cond: InnerPointerVal.castAs<DefinedOrUnknownSVal>()); |
881 | |
882 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
883 | // Explicitly tracking the region as null. |
884 | NullState = NullState->set<TrackedRegionMap>(K: ThisRegion, E: NullVal); |
885 | |
886 | NullState = NullState->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
887 | V: C.getSValBuilder().makeTruthVal(b: false)); |
888 | C.addTransition(State: NullState, Tag: C.getNoteTag( |
889 | Cb: [ThisRegion](PathSensitiveBugReport &BR, |
890 | llvm::raw_ostream &OS) { |
891 | OS << "Assuming smart pointer" ; |
892 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
893 | OS << " is null" ; |
894 | }, |
895 | /*IsPrunable=*/true)); |
896 | NotNullState = |
897 | NotNullState->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
898 | V: C.getSValBuilder().makeTruthVal(b: true)); |
899 | C.addTransition( |
900 | State: NotNullState, |
901 | Tag: C.getNoteTag( |
902 | Cb: [ThisRegion](PathSensitiveBugReport &BR, llvm::raw_ostream &OS) { |
903 | OS << "Assuming smart pointer" ; |
904 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
905 | OS << " is non-null" ; |
906 | }, |
907 | /*IsPrunable=*/true)); |
908 | return; |
909 | } |
910 | } |
911 | |
912 | void ento::registerSmartPtrModeling(CheckerManager &Mgr) { |
913 | auto *Checker = Mgr.registerChecker<SmartPtrModeling>(); |
914 | Checker->ModelSmartPtrDereference = |
915 | Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
916 | C: Checker, OptionName: "ModelSmartPtrDereference" ); |
917 | } |
918 | |
919 | bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { |
920 | const LangOptions &LO = mgr.getLangOpts(); |
921 | return LO.CPlusPlus; |
922 | } |
923 | |