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/Checker.h" |
24 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
25 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
26 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
27 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
28 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" |
29 | #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" |
30 | #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" |
31 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" |
32 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" |
33 | #include "llvm/ADT/STLExtras.h" |
34 | #include "llvm/Support/ErrorHandling.h" |
35 | #include <optional> |
36 | |
37 | using namespace clang; |
38 | using namespace ento; |
39 | |
40 | namespace { |
41 | |
42 | class SmartPtrModeling |
43 | : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges, |
44 | check::LiveSymbols> { |
45 | |
46 | bool isBoolConversionMethod(const CallEvent &Call) const; |
47 | |
48 | public: |
49 | // Whether the checker should model for null dereferences of smart pointers. |
50 | bool ModelSmartPtrDereference = false; |
51 | bool evalCall(const CallEvent &Call, CheckerContext &C) const; |
52 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
53 | ProgramStateRef |
54 | checkRegionChanges(ProgramStateRef State, |
55 | const InvalidatedSymbols *Invalidated, |
56 | ArrayRef<const MemRegion *> ExplicitRegions, |
57 | ArrayRef<const MemRegion *> Regions, |
58 | const LocationContext *LCtx, const CallEvent *Call) const; |
59 | void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, |
60 | const char *Sep) const override; |
61 | void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; |
62 | |
63 | private: |
64 | void handleReset(const CallEvent &Call, CheckerContext &C) const; |
65 | void handleRelease(const CallEvent &Call, CheckerContext &C) const; |
66 | void handleSwapMethod(const CallEvent &Call, CheckerContext &C) const; |
67 | void handleGet(const CallEvent &Call, CheckerContext &C) const; |
68 | bool handleAssignOp(const CallEvent &Call, CheckerContext &C) const; |
69 | bool handleMoveCtr(const CallEvent &Call, CheckerContext &C, |
70 | const MemRegion *ThisRegion) const; |
71 | bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion, |
72 | const MemRegion *OtherSmartPtrRegion, |
73 | const CallEvent &Call) const; |
74 | void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const; |
75 | bool handleComparisionOp(const CallEvent &Call, CheckerContext &C) const; |
76 | bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const; |
77 | bool handleSwap(ProgramStateRef State, SVal First, SVal Second, |
78 | CheckerContext &C) const; |
79 | std::pair<SVal, ProgramStateRef> retrieveOrConjureInnerPtrVal( |
80 | ProgramStateRef State, const MemRegion *ThisRegion, |
81 | ConstCFGElementRef Elem, QualType Type, CheckerContext &C) const; |
82 | |
83 | using SmartPtrMethodHandlerFn = |
84 | void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; |
85 | CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ |
86 | {{CDM::CXXMethod, {"reset" }}, &SmartPtrModeling::handleReset}, |
87 | {{CDM::CXXMethod, {"release" }}, &SmartPtrModeling::handleRelease}, |
88 | {{CDM::CXXMethod, {"swap" }, 1}, &SmartPtrModeling::handleSwapMethod}, |
89 | {{CDM::CXXMethod, {"get" }}, &SmartPtrModeling::handleGet}}; |
90 | const CallDescription StdSwapCall{CDM::SimpleFunc, {"std" , "swap" }, 2}; |
91 | const CallDescriptionSet MakeUniqueVariants{ |
92 | {CDM::SimpleFunc, {"std" , "make_unique" }}, |
93 | {CDM::SimpleFunc, {"std" , "make_unique_for_overwrite" }}}; |
94 | }; |
95 | } // end of anonymous namespace |
96 | |
97 | REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) |
98 | |
99 | // Checks if RD has name in Names and is in std namespace |
100 | static bool hasStdClassWithName(const CXXRecordDecl *RD, |
101 | ArrayRef<llvm::StringLiteral> Names) { |
102 | if (!RD || !RD->getDeclContext()->isStdNamespace()) |
103 | return false; |
104 | if (RD->getDeclName().isIdentifier()) |
105 | return llvm::is_contained(Range&: Names, Element: RD->getName()); |
106 | return false; |
107 | } |
108 | |
109 | constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr" , "unique_ptr" , |
110 | "weak_ptr" }; |
111 | |
112 | static bool isStdSmartPtr(const CXXRecordDecl *RD) { |
113 | return hasStdClassWithName(RD, Names: STD_PTR_NAMES); |
114 | } |
115 | |
116 | static bool isStdSmartPtr(const Expr *E) { |
117 | return isStdSmartPtr(RD: E->getType()->getAsCXXRecordDecl()); |
118 | } |
119 | |
120 | // Define the inter-checker API. |
121 | namespace clang { |
122 | namespace ento { |
123 | namespace smartptr { |
124 | bool isStdSmartPtrCall(const CallEvent &Call) { |
125 | const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: Call.getDecl()); |
126 | if (!MethodDecl || !MethodDecl->getParent()) |
127 | return false; |
128 | return isStdSmartPtr(RD: MethodDecl->getParent()); |
129 | } |
130 | |
131 | bool isStdSmartPtr(const CXXRecordDecl *RD) { |
132 | if (!RD || !RD->getDeclContext()->isStdNamespace()) |
133 | return false; |
134 | |
135 | if (RD->getDeclName().isIdentifier()) { |
136 | StringRef Name = RD->getName(); |
137 | return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr" ; |
138 | } |
139 | return false; |
140 | } |
141 | |
142 | bool isStdSmartPtr(const Expr *E) { |
143 | return isStdSmartPtr(RD: E->getType()->getAsCXXRecordDecl()); |
144 | } |
145 | |
146 | bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { |
147 | const auto *InnerPointVal = State->get<TrackedRegionMap>(key: ThisRegion); |
148 | return InnerPointVal && |
149 | !State->assume(Cond: InnerPointVal->castAs<DefinedOrUnknownSVal>(), Assumption: true); |
150 | } |
151 | } // namespace smartptr |
152 | } // namespace ento |
153 | } // namespace clang |
154 | |
155 | // If a region is removed all of the subregions need to be removed too. |
156 | static TrackedRegionMapTy |
157 | removeTrackedSubregions(TrackedRegionMapTy RegionMap, |
158 | TrackedRegionMapTy::Factory &RegionMapFactory, |
159 | const MemRegion *Region) { |
160 | if (!Region) |
161 | return RegionMap; |
162 | for (const auto &E : RegionMap) { |
163 | if (E.first->isSubRegionOf(R: Region)) |
164 | RegionMap = RegionMapFactory.remove(Old: RegionMap, K: E.first); |
165 | } |
166 | return RegionMap; |
167 | } |
168 | |
169 | static ProgramStateRef updateSwappedRegion(ProgramStateRef State, |
170 | const MemRegion *Region, |
171 | const SVal *RegionInnerPointerVal) { |
172 | if (RegionInnerPointerVal) { |
173 | State = State->set<TrackedRegionMap>(K: Region, E: *RegionInnerPointerVal); |
174 | } else { |
175 | State = State->remove<TrackedRegionMap>(K: Region); |
176 | } |
177 | return State; |
178 | } |
179 | |
180 | static QualType getInnerPointerType(CheckerContext C, const CXXRecordDecl *RD) { |
181 | if (!RD || !RD->isInStdNamespace()) |
182 | return {}; |
183 | |
184 | const auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: RD); |
185 | if (!TSD) |
186 | return {}; |
187 | |
188 | auto TemplateArgs = TSD->getTemplateArgs().asArray(); |
189 | if (TemplateArgs.empty()) |
190 | return {}; |
191 | auto InnerValueType = TemplateArgs[0].getAsType(); |
192 | return C.getASTContext().getPointerType(T: InnerValueType.getCanonicalType()); |
193 | } |
194 | |
195 | // This is for use with standalone-functions like std::make_unique, |
196 | // std::make_unique_for_overwrite, etc. It reads the template parameter and |
197 | // returns the pointer type corresponding to it, |
198 | static QualType getPointerTypeFromTemplateArg(const CallEvent &Call, |
199 | CheckerContext &C) { |
200 | const auto *FD = dyn_cast_or_null<FunctionDecl>(Val: Call.getDecl()); |
201 | if (!FD || !FD->getPrimaryTemplate()) |
202 | return {}; |
203 | const auto &TemplateArgs = FD->getTemplateSpecializationArgs()->asArray(); |
204 | if (TemplateArgs.size() == 0) |
205 | return {}; |
206 | auto ValueType = TemplateArgs[0].getAsType(); |
207 | return C.getASTContext().getPointerType(T: ValueType.getCanonicalType()); |
208 | } |
209 | |
210 | // Helper method to get the inner pointer type of specialized smart pointer |
211 | // Returns empty type if not found valid inner pointer type. |
212 | static QualType getInnerPointerType(const CallEvent &Call, CheckerContext &C) { |
213 | const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Val: Call.getDecl()); |
214 | if (!MethodDecl || !MethodDecl->getParent()) |
215 | return {}; |
216 | |
217 | const auto *RecordDecl = MethodDecl->getParent(); |
218 | return getInnerPointerType(C, RD: RecordDecl); |
219 | } |
220 | |
221 | // Helper method to pretty print region and avoid extra spacing. |
222 | static void checkAndPrettyPrintRegion(llvm::raw_ostream &OS, |
223 | const MemRegion *Region) { |
224 | if (Region->canPrintPretty()) { |
225 | OS << " " ; |
226 | Region->printPretty(os&: OS); |
227 | } |
228 | } |
229 | |
230 | bool SmartPtrModeling::isBoolConversionMethod(const CallEvent &Call) const { |
231 | // TODO: Update CallDescription to support anonymous calls? |
232 | // TODO: Handle other methods, such as .get() or .release(). |
233 | // But once we do, we'd need a visitor to explain null dereferences |
234 | // that are found via such modeling. |
235 | const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Val: Call.getDecl()); |
236 | return CD && CD->getConversionType()->isBooleanType(); |
237 | } |
238 | |
239 | constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream" }; |
240 | |
241 | static bool isStdBasicOstream(const Expr *E) { |
242 | const auto *RD = E->getType()->getAsCXXRecordDecl(); |
243 | return hasStdClassWithName(RD, Names: BASIC_OSTREAM_NAMES); |
244 | } |
245 | |
246 | static bool isStdFunctionCall(const CallEvent &Call) { |
247 | return Call.getDecl() && Call.getDecl()->getDeclContext()->isStdNamespace(); |
248 | } |
249 | |
250 | static bool isStdOstreamOperatorCall(const CallEvent &Call) { |
251 | if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call)) |
252 | return false; |
253 | const auto *FC = dyn_cast<SimpleFunctionCall>(Val: &Call); |
254 | if (!FC) |
255 | return false; |
256 | const FunctionDecl *FD = FC->getDecl(); |
257 | if (!FD->isOverloadedOperator()) |
258 | return false; |
259 | const OverloadedOperatorKind OOK = FD->getOverloadedOperator(); |
260 | if (OOK != clang::OO_LessLess) |
261 | return false; |
262 | return isStdSmartPtr(E: Call.getArgExpr(Index: 1)) && |
263 | isStdBasicOstream(E: Call.getArgExpr(Index: 0)); |
264 | } |
265 | |
266 | static bool isPotentiallyComparisionOpCall(const CallEvent &Call) { |
267 | if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call)) |
268 | return false; |
269 | return smartptr::isStdSmartPtr(E: Call.getArgExpr(Index: 0)) || |
270 | smartptr::isStdSmartPtr(E: Call.getArgExpr(Index: 1)); |
271 | } |
272 | |
273 | bool SmartPtrModeling::evalCall(const CallEvent &Call, |
274 | CheckerContext &C) const { |
275 | |
276 | ProgramStateRef State = C.getState(); |
277 | |
278 | // If any one of the arg is a unique_ptr, then |
279 | // we can try this function |
280 | if (ModelSmartPtrDereference && isPotentiallyComparisionOpCall(Call)) |
281 | if (handleComparisionOp(Call, C)) |
282 | return true; |
283 | |
284 | if (ModelSmartPtrDereference && isStdOstreamOperatorCall(Call)) |
285 | return handleOstreamOperator(Call, C); |
286 | |
287 | if (StdSwapCall.matches(Call)) { |
288 | // Check the first arg, if it is of std::unique_ptr type. |
289 | assert(Call.getNumArgs() == 2 && "std::swap should have two arguments" ); |
290 | const Expr *FirstArg = Call.getArgExpr(Index: 0); |
291 | if (!smartptr::isStdSmartPtr(RD: FirstArg->getType()->getAsCXXRecordDecl())) |
292 | return false; |
293 | return handleSwap(State, First: Call.getArgSVal(Index: 0), Second: Call.getArgSVal(Index: 1), C); |
294 | } |
295 | |
296 | if (MakeUniqueVariants.contains(Call)) { |
297 | if (!ModelSmartPtrDereference) |
298 | return false; |
299 | |
300 | const std::optional<SVal> ThisRegionOpt = |
301 | Call.getReturnValueUnderConstruction(); |
302 | if (!ThisRegionOpt) |
303 | return false; |
304 | |
305 | const auto PtrVal = C.getSValBuilder().getConjuredHeapSymbolVal( |
306 | elem: Call.getCFGElementRef(), LCtx: C.getLocationContext(), |
307 | type: getPointerTypeFromTemplateArg(Call, C), Count: C.blockCount()); |
308 | |
309 | const MemRegion *ThisRegion = ThisRegionOpt->getAsRegion(); |
310 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: PtrVal); |
311 | State = State->assume(Cond: PtrVal, Assumption: true); |
312 | |
313 | // TODO: ExprEngine should do this for us. |
314 | // For a bit more context: |
315 | // 1) Why do we need this? Since we are modelling a "function" |
316 | // that returns a constructed object we need to store this information in |
317 | // the program state. |
318 | // |
319 | // 2) Why does this work? |
320 | // `updateObjectsUnderConstruction` does exactly as it sounds. |
321 | // |
322 | // 3) How should it look like when moved to the Engine? |
323 | // It would be nice if we can just |
324 | // pretend we don't need to know about this - ie, completely automatic work. |
325 | // However, realistically speaking, I think we would need to "signal" the |
326 | // ExprEngine evalCall handler that we are constructing an object with this |
327 | // function call (constructors obviously construct, hence can be |
328 | // automatically deduced). |
329 | auto &Engine = State->getStateManager().getOwningEngine(); |
330 | State = Engine.updateObjectsUnderConstruction( |
331 | V: *ThisRegionOpt, E: nullptr, State, LCtx: C.getLocationContext(), |
332 | CC: Call.getConstructionContext(), CallOpts: {}); |
333 | |
334 | // We don't leave a note here since it is guaranteed the |
335 | // unique_ptr from this call is non-null (hence is safe to de-reference). |
336 | C.addTransition(State); |
337 | return true; |
338 | } |
339 | |
340 | if (!smartptr::isStdSmartPtrCall(Call)) |
341 | return false; |
342 | |
343 | if (isBoolConversionMethod(Call)) { |
344 | const MemRegion *ThisR = |
345 | cast<CXXInstanceCall>(Val: &Call)->getCXXThisVal().getAsRegion(); |
346 | |
347 | if (ModelSmartPtrDereference) { |
348 | // The check for the region is moved is duplicated in handleBoolOperation |
349 | // method. |
350 | // FIXME: Once we model std::move for smart pointers clean up this and use |
351 | // that modeling. |
352 | handleBoolConversion(Call, C); |
353 | return true; |
354 | } else { |
355 | if (!move::isMovedFrom(State, Region: ThisR)) { |
356 | // TODO: Model this case as well. At least, avoid invalidation of |
357 | // globals. |
358 | return false; |
359 | } |
360 | |
361 | // TODO: Add a note to bug reports describing this decision. |
362 | C.addTransition(State: State->BindExpr( |
363 | S: Call.getOriginExpr(), LCtx: C.getLocationContext(), |
364 | V: C.getSValBuilder().makeZeroVal(type: Call.getResultType()))); |
365 | |
366 | return true; |
367 | } |
368 | } |
369 | |
370 | if (!ModelSmartPtrDereference) |
371 | return false; |
372 | |
373 | if (const auto *CC = dyn_cast<CXXConstructorCall>(Val: &Call)) { |
374 | if (CC->getDecl()->isCopyConstructor()) |
375 | return false; |
376 | |
377 | const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion(); |
378 | if (!ThisRegion) |
379 | return false; |
380 | |
381 | QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType(); |
382 | |
383 | if (CC->getDecl()->isMoveConstructor()) |
384 | return handleMoveCtr(Call, C, ThisRegion); |
385 | |
386 | if (Call.getNumArgs() == 0) { |
387 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
388 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: NullVal); |
389 | |
390 | C.addTransition( |
391 | State, Tag: C.getNoteTag(Cb: [ThisRegion](PathSensitiveBugReport &BR, |
392 | llvm::raw_ostream &OS) { |
393 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
394 | !BR.isInteresting(R: ThisRegion)) |
395 | return; |
396 | OS << "Default constructed smart pointer" ; |
397 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
398 | OS << " is null" ; |
399 | })); |
400 | } else { |
401 | const auto *TrackingExpr = Call.getArgExpr(Index: 0); |
402 | assert(TrackingExpr->getType()->isPointerType() && |
403 | "Adding a non pointer value to TrackedRegionMap" ); |
404 | auto ArgVal = Call.getArgSVal(Index: 0); |
405 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: ArgVal); |
406 | |
407 | C.addTransition(State, Tag: C.getNoteTag(Cb: [ThisRegion, TrackingExpr, |
408 | ArgVal](PathSensitiveBugReport &BR, |
409 | llvm::raw_ostream &OS) { |
410 | if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || |
411 | !BR.isInteresting(R: ThisRegion)) |
412 | return; |
413 | bugreporter::trackExpressionValue(N: BR.getErrorNode(), E: TrackingExpr, R&: BR); |
414 | OS << "Smart pointer" ; |
415 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
416 | if (ArgVal.isZeroConstant()) |
417 | OS << " is constructed using a null value" ; |
418 | else |
419 | OS << " is constructed" ; |
420 | })); |
421 | } |
422 | return true; |
423 | } |
424 | |
425 | if (handleAssignOp(Call, C)) |
426 | return true; |
427 | |
428 | const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); |
429 | if (!Handler) |
430 | return false; |
431 | (this->**Handler)(Call, C); |
432 | |
433 | return C.isDifferent(); |
434 | } |
435 | |
436 | std::pair<SVal, ProgramStateRef> SmartPtrModeling::retrieveOrConjureInnerPtrVal( |
437 | ProgramStateRef State, const MemRegion *ThisRegion, ConstCFGElementRef Elem, |
438 | QualType Type, CheckerContext &C) const { |
439 | const auto *Ptr = State->get<TrackedRegionMap>(key: ThisRegion); |
440 | if (Ptr) |
441 | return {*Ptr, State}; |
442 | auto Val = C.getSValBuilder().conjureSymbolVal(elem: Elem, LCtx: C.getLocationContext(), |
443 | type: Type, visitCount: C.blockCount()); |
444 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: Val); |
445 | return {Val, State}; |
446 | } |
447 | |
448 | bool SmartPtrModeling::handleComparisionOp(const CallEvent &Call, |
449 | CheckerContext &C) const { |
450 | const auto *FC = dyn_cast<SimpleFunctionCall>(Val: &Call); |
451 | if (!FC) |
452 | return false; |
453 | const FunctionDecl *FD = FC->getDecl(); |
454 | if (!FD->isOverloadedOperator()) |
455 | return false; |
456 | const OverloadedOperatorKind OOK = FD->getOverloadedOperator(); |
457 | if (!(OOK == OO_EqualEqual || OOK == OO_ExclaimEqual || OOK == OO_Less || |
458 | OOK == OO_LessEqual || OOK == OO_Greater || OOK == OO_GreaterEqual || |
459 | OOK == OO_Spaceship)) |
460 | return false; |
461 | |
462 | // There are some special cases about which we can infer about |
463 | // the resulting answer. |
464 | // For reference, there is a discussion at https://reviews.llvm.org/D104616. |
465 | // Also, the cppreference page is good to look at |
466 | // https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_cmp. |
467 | |
468 | auto makeSValFor = [&C, this](ProgramStateRef State, const Expr *E, |
469 | ConstCFGElementRef Elem, |
470 | SVal S) -> std::pair<SVal, ProgramStateRef> { |
471 | if (S.isZeroConstant()) { |
472 | return {S, State}; |
473 | } |
474 | const MemRegion *Reg = S.getAsRegion(); |
475 | assert(Reg && |
476 | "this pointer of std::unique_ptr should be obtainable as MemRegion" ); |
477 | QualType Type = getInnerPointerType(C, RD: E->getType()->getAsCXXRecordDecl()); |
478 | return retrieveOrConjureInnerPtrVal(State, ThisRegion: Reg, Elem, Type, C); |
479 | }; |
480 | |
481 | SVal First = Call.getArgSVal(Index: 0); |
482 | SVal Second = Call.getArgSVal(Index: 1); |
483 | const auto *FirstExpr = Call.getArgExpr(Index: 0); |
484 | const auto *SecondExpr = Call.getArgExpr(Index: 1); |
485 | |
486 | const auto *ResultExpr = Call.getOriginExpr(); |
487 | const auto *LCtx = C.getLocationContext(); |
488 | auto &Bldr = C.getSValBuilder(); |
489 | ProgramStateRef State = C.getState(); |
490 | |
491 | SVal FirstPtrVal, SecondPtrVal; |
492 | std::tie(args&: FirstPtrVal, args&: State) = |
493 | makeSValFor(State, FirstExpr, Call.getCFGElementRef(), First); |
494 | std::tie(args&: SecondPtrVal, args&: State) = |
495 | makeSValFor(State, SecondExpr, Call.getCFGElementRef(), 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}, Elem: Call.getCFGElementRef(), |
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, Elem: Call.getCFGElementRef(), 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 | InnerPointerVal = C.getSValBuilder().conjureSymbolVal( |
855 | call: Call, type: InnerPointerType, visitCount: C.blockCount()); |
856 | State = State->set<TrackedRegionMap>(K: ThisRegion, E: InnerPointerVal); |
857 | } |
858 | |
859 | if (State->isNull(V: InnerPointerVal).isConstrainedTrue()) { |
860 | State = State->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
861 | V: C.getSValBuilder().makeTruthVal(b: false)); |
862 | |
863 | C.addTransition(State); |
864 | return; |
865 | } else if (State->isNonNull(V: InnerPointerVal).isConstrainedTrue()) { |
866 | State = State->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
867 | V: C.getSValBuilder().makeTruthVal(b: true)); |
868 | |
869 | C.addTransition(State); |
870 | return; |
871 | } else if (move::isMovedFrom(State, Region: ThisRegion)) { |
872 | C.addTransition( |
873 | State: State->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
874 | V: C.getSValBuilder().makeZeroVal(type: Call.getResultType()))); |
875 | return; |
876 | } else { |
877 | ProgramStateRef NotNullState, NullState; |
878 | std::tie(args&: NotNullState, args&: NullState) = |
879 | State->assume(Cond: InnerPointerVal.castAs<DefinedOrUnknownSVal>()); |
880 | |
881 | auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType); |
882 | // Explicitly tracking the region as null. |
883 | NullState = NullState->set<TrackedRegionMap>(K: ThisRegion, E: NullVal); |
884 | |
885 | NullState = NullState->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
886 | V: C.getSValBuilder().makeTruthVal(b: false)); |
887 | C.addTransition(State: NullState, Tag: C.getNoteTag( |
888 | Cb: [ThisRegion](PathSensitiveBugReport &BR, |
889 | llvm::raw_ostream &OS) { |
890 | OS << "Assuming smart pointer" ; |
891 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
892 | OS << " is null" ; |
893 | }, |
894 | /*IsPrunable=*/true)); |
895 | NotNullState = |
896 | NotNullState->BindExpr(S: CallExpr, LCtx: C.getLocationContext(), |
897 | V: C.getSValBuilder().makeTruthVal(b: true)); |
898 | C.addTransition( |
899 | State: NotNullState, |
900 | Tag: C.getNoteTag( |
901 | Cb: [ThisRegion](PathSensitiveBugReport &BR, llvm::raw_ostream &OS) { |
902 | OS << "Assuming smart pointer" ; |
903 | checkAndPrettyPrintRegion(OS, Region: ThisRegion); |
904 | OS << " is non-null" ; |
905 | }, |
906 | /*IsPrunable=*/true)); |
907 | return; |
908 | } |
909 | } |
910 | |
911 | void ento::registerSmartPtrModeling(CheckerManager &Mgr) { |
912 | auto *Checker = Mgr.registerChecker<SmartPtrModeling>(); |
913 | Checker->ModelSmartPtrDereference = |
914 | Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
915 | C: Checker, OptionName: "ModelSmartPtrDereference" ); |
916 | } |
917 | |
918 | bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { |
919 | const LangOptions &LO = mgr.getLangOpts(); |
920 | return LO.CPlusPlus; |
921 | } |
922 | |