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
37using namespace clang;
38using namespace ento;
39
40namespace {
41
42class SmartPtrModeling
43 : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges,
44 check::LiveSymbols> {
45
46 bool isBoolConversionMethod(const CallEvent &Call) const;
47
48public:
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, const StackFrame *SF,
58 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
63private:
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
97REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
98
99// Checks if RD has name in Names and is in std namespace
100static 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
109constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr", "unique_ptr",
110 "weak_ptr"};
111
112static bool isStdSmartPtr(const CXXRecordDecl *RD) {
113 return hasStdClassWithName(RD, Names: STD_PTR_NAMES);
114}
115
116static bool isStdSmartPtr(const Expr *E) {
117 return isStdSmartPtr(RD: E->getType()->getAsCXXRecordDecl());
118}
119
120// Define the inter-checker API.
121namespace clang {
122namespace ento {
123namespace smartptr {
124bool 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
131bool 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
142bool isStdSmartPtr(const Expr *E) {
143 return isStdSmartPtr(RD: E->getType()->getAsCXXRecordDecl());
144}
145
146bool 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.
156static TrackedRegionMapTy
157removeTrackedSubregions(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
169static 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
180static 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,
198static 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.
212static 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.
222static void checkAndPrettyPrintRegion(llvm::raw_ostream &OS,
223 const MemRegion *Region) {
224 if (Region->canPrintPretty()) {
225 OS << " ";
226 Region->printPretty(os&: OS);
227 }
228}
229
230bool 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
239constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream"};
240
241static bool isStdBasicOstream(const Expr *E) {
242 const auto *RD = E->getType()->getAsCXXRecordDecl();
243 return hasStdClassWithName(RD, Names: BASIC_OSTREAM_NAMES);
244}
245
246static bool isStdFunctionCall(const CallEvent &Call) {
247 return Call.getDecl() && Call.getDecl()->getDeclContext()->isStdNamespace();
248}
249
250static 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
266static 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
273bool 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(), SF: C.getStackFrame(),
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, SF: C.getStackFrame(),
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 E: Call.getOriginExpr(), SF: C.getStackFrame(),
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
436std::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, SF: C.getStackFrame(), type: Type,
443 visitCount: C.blockCount());
444 State = State->set<TrackedRegionMap>(K: ThisRegion, E: Val);
445 return {Val, State};
446}
447
448bool 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 *SF = C.getStackFrame();
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(E: ResultExpr, SF, V: Bldr.makeTruthVal(b: true)));
508 if (FalseState)
509 C.addTransition(
510 State: FalseState->BindExpr(E: ResultExpr, SF, V: Bldr.makeTruthVal(b: false)));
511 } else {
512 C.addTransition(State: State->BindExpr(E: ResultExpr, SF, V: RetVal));
513 }
514 return true;
515}
516
517bool 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 = State->invalidateRegions(Regions: {StreamThisRegion}, Elem: Call.getCFGElementRef(),
533 BlockCount: C.blockCount(), SF: C.getStackFrame(), CausesPointerEscape: false);
534 State = State->BindExpr(E: Call.getOriginExpr(), SF: C.getStackFrame(), V: StreamVal);
535 C.addTransition(State);
536 return true;
537}
538
539void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
540 CheckerContext &C) const {
541 ProgramStateRef State = C.getState();
542 // Clean up dead regions from the region map.
543 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
544 for (auto E : TrackedRegions) {
545 const MemRegion *Region = E.first;
546 bool IsRegDead = !SymReaper.isLiveRegion(region: Region);
547
548 if (IsRegDead)
549 State = State->remove<TrackedRegionMap>(K: Region);
550 }
551 C.addTransition(State);
552}
553
554void SmartPtrModeling::printState(raw_ostream &Out, ProgramStateRef State,
555 const char *NL, const char *Sep) const {
556 TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
557
558 if (!RS.isEmpty()) {
559 Out << Sep << "Smart ptr regions :" << NL;
560 for (auto I : RS) {
561 I.first->dumpToStream(os&: Out);
562 if (smartptr::isNullSmartPtr(State, ThisRegion: I.first))
563 Out << ": Null";
564 else
565 Out << ": Non Null";
566 Out << NL;
567 }
568 }
569}
570
571ProgramStateRef SmartPtrModeling::checkRegionChanges(
572 ProgramStateRef State, const InvalidatedSymbols *Invalidated,
573 ArrayRef<const MemRegion *> ExplicitRegions,
574 ArrayRef<const MemRegion *> Regions, const StackFrame *SF,
575 const CallEvent *Call) const {
576 TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
577 TrackedRegionMapTy::Factory &RegionMapFactory =
578 State->get_context<TrackedRegionMap>();
579 for (const auto *Region : Regions)
580 RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
581 Region: Region->getBaseRegion());
582 return State->set<TrackedRegionMap>(RegionMap);
583}
584
585void SmartPtrModeling::checkLiveSymbols(ProgramStateRef State,
586 SymbolReaper &SR) const {
587 // Marking tracked symbols alive
588 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
589 for (SVal Val : llvm::make_second_range(c&: TrackedRegions)) {
590 for (SymbolRef Sym : Val.symbols()) {
591 SR.markLive(sym: Sym);
592 }
593 }
594}
595
596void SmartPtrModeling::handleReset(const CallEvent &Call,
597 CheckerContext &C) const {
598 ProgramStateRef State = C.getState();
599 const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call);
600 if (!IC)
601 return;
602
603 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
604 if (!ThisRegion)
605 return;
606
607 assert(Call.getArgExpr(0)->getType()->isPointerType() &&
608 "Adding a non pointer value to TrackedRegionMap");
609 State = State->set<TrackedRegionMap>(K: ThisRegion, E: Call.getArgSVal(Index: 0));
610 const auto *TrackingExpr = Call.getArgExpr(Index: 0);
611 C.addTransition(
612 State, Tag: C.getNoteTag(Cb: [ThisRegion, TrackingExpr](PathSensitiveBugReport &BR,
613 llvm::raw_ostream &OS) {
614 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
615 !BR.isInteresting(R: ThisRegion))
616 return;
617 bugreporter::trackExpressionValue(N: BR.getErrorNode(), E: TrackingExpr, R&: BR);
618 OS << "Smart pointer";
619 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
620 OS << " reset using a null value";
621 }));
622 // TODO: Make sure to ivalidate the region in the Store if we don't have
623 // time to model all methods.
624}
625
626void SmartPtrModeling::handleRelease(const CallEvent &Call,
627 CheckerContext &C) const {
628 ProgramStateRef State = C.getState();
629 const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call);
630 if (!IC)
631 return;
632
633 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
634 if (!ThisRegion)
635 return;
636
637 const auto *InnerPointVal = State->get<TrackedRegionMap>(key: ThisRegion);
638
639 if (InnerPointVal) {
640 State = State->BindExpr(E: Call.getOriginExpr(), SF: C.getStackFrame(),
641 V: *InnerPointVal);
642 }
643
644 QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType();
645 auto ValueToUpdate = C.getSValBuilder().makeNullWithType(type: ThisType);
646 State = State->set<TrackedRegionMap>(K: ThisRegion, E: ValueToUpdate);
647
648 C.addTransition(State, Tag: C.getNoteTag(Cb: [ThisRegion](PathSensitiveBugReport &BR,
649 llvm::raw_ostream &OS) {
650 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
651 !BR.isInteresting(R: ThisRegion))
652 return;
653
654 OS << "Smart pointer";
655 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
656 OS << " is released and set to null";
657 }));
658 // TODO: Add support to enable MallocChecker to start tracking the raw
659 // pointer.
660}
661
662void SmartPtrModeling::handleSwapMethod(const CallEvent &Call,
663 CheckerContext &C) const {
664 // To model unique_ptr::swap() method.
665 const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call);
666 if (!IC)
667 return;
668
669 auto State = C.getState();
670 handleSwap(State, First: IC->getCXXThisVal(), Second: Call.getArgSVal(Index: 0), C);
671}
672
673bool SmartPtrModeling::handleSwap(ProgramStateRef State, SVal First,
674 SVal Second, CheckerContext &C) const {
675 const MemRegion *FirstThisRegion = First.getAsRegion();
676 if (!FirstThisRegion)
677 return false;
678 const MemRegion *SecondThisRegion = Second.getAsRegion();
679 if (!SecondThisRegion)
680 return false;
681
682 const auto *FirstInnerPtrVal = State->get<TrackedRegionMap>(key: FirstThisRegion);
683 const auto *SecondInnerPtrVal =
684 State->get<TrackedRegionMap>(key: SecondThisRegion);
685
686 State = updateSwappedRegion(State, Region: FirstThisRegion, RegionInnerPointerVal: SecondInnerPtrVal);
687 State = updateSwappedRegion(State, Region: SecondThisRegion, RegionInnerPointerVal: FirstInnerPtrVal);
688
689 C.addTransition(State, Tag: C.getNoteTag(Cb: [FirstThisRegion, SecondThisRegion](
690 PathSensitiveBugReport &BR,
691 llvm::raw_ostream &OS) {
692 if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
693 return;
694 if (BR.isInteresting(R: FirstThisRegion) &&
695 !BR.isInteresting(R: SecondThisRegion)) {
696 BR.markInteresting(R: SecondThisRegion);
697 BR.markNotInteresting(R: FirstThisRegion);
698 }
699 if (BR.isInteresting(R: SecondThisRegion) &&
700 !BR.isInteresting(R: FirstThisRegion)) {
701 BR.markInteresting(R: FirstThisRegion);
702 BR.markNotInteresting(R: SecondThisRegion);
703 }
704 // TODO: We need to emit some note here probably!!
705 }));
706
707 return true;
708}
709
710void SmartPtrModeling::handleGet(const CallEvent &Call,
711 CheckerContext &C) const {
712 ProgramStateRef State = C.getState();
713 const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call);
714 if (!IC)
715 return;
716
717 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
718 if (!ThisRegion)
719 return;
720
721 SVal InnerPointerVal;
722 std::tie(args&: InnerPointerVal, args&: State) = retrieveOrConjureInnerPtrVal(
723 State, ThisRegion, Elem: Call.getCFGElementRef(), Type: Call.getResultType(), C);
724 State =
725 State->BindExpr(E: Call.getOriginExpr(), SF: C.getStackFrame(), V: InnerPointerVal);
726 // TODO: Add NoteTag, for how the raw pointer got using 'get' method.
727 C.addTransition(State);
728}
729
730bool SmartPtrModeling::handleAssignOp(const CallEvent &Call,
731 CheckerContext &C) const {
732 ProgramStateRef State = C.getState();
733 const auto *OC = dyn_cast<CXXMemberOperatorCall>(Val: &Call);
734 if (!OC)
735 return false;
736 OverloadedOperatorKind OOK = OC->getOverloadedOperator();
737 if (OOK != OO_Equal)
738 return false;
739 const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion();
740 if (!ThisRegion)
741 return false;
742
743 QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType();
744
745 const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(Index: 0).getAsRegion();
746 // In case of 'nullptr' or '0' assigned
747 if (!OtherSmartPtrRegion) {
748 bool AssignedNull = Call.getArgSVal(Index: 0).isZeroConstant();
749 if (!AssignedNull)
750 return false;
751 auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType);
752 State = State->set<TrackedRegionMap>(K: ThisRegion, E: NullVal);
753 C.addTransition(State, Tag: C.getNoteTag(Cb: [ThisRegion](PathSensitiveBugReport &BR,
754 llvm::raw_ostream &OS) {
755 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
756 !BR.isInteresting(R: ThisRegion))
757 return;
758 OS << "Smart pointer";
759 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
760 OS << " is assigned to null";
761 }));
762 return true;
763 }
764
765 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call);
766}
767
768bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C,
769 const MemRegion *ThisRegion) const {
770 const auto *OtherSmartPtrRegion = Call.getArgSVal(Index: 0).getAsRegion();
771 if (!OtherSmartPtrRegion)
772 return false;
773
774 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call);
775}
776
777bool SmartPtrModeling::updateMovedSmartPointers(
778 CheckerContext &C, const MemRegion *ThisRegion,
779 const MemRegion *OtherSmartPtrRegion, const CallEvent &Call) const {
780 ProgramStateRef State = C.getState();
781 QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType();
782 const auto *OtherInnerPtr = State->get<TrackedRegionMap>(key: OtherSmartPtrRegion);
783 if (OtherInnerPtr) {
784 State = State->set<TrackedRegionMap>(K: ThisRegion, E: *OtherInnerPtr);
785
786 auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType);
787 State = State->set<TrackedRegionMap>(K: OtherSmartPtrRegion, E: NullVal);
788 bool IsArgValNull = OtherInnerPtr->isZeroConstant();
789
790 C.addTransition(
791 State,
792 Tag: C.getNoteTag(Cb: [ThisRegion, OtherSmartPtrRegion, IsArgValNull](
793 PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
794 if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
795 return;
796 if (BR.isInteresting(R: OtherSmartPtrRegion)) {
797 OS << "Smart pointer";
798 checkAndPrettyPrintRegion(OS, Region: OtherSmartPtrRegion);
799 OS << " is null after being moved to";
800 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
801 }
802 if (BR.isInteresting(R: ThisRegion) && IsArgValNull) {
803 OS << "A null pointer value is moved to";
804 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
805 BR.markInteresting(R: OtherSmartPtrRegion);
806 }
807 }));
808 return true;
809 } else {
810 // In case we dont know anything about value we are moving from
811 // remove the entry from map for which smart pointer got moved to.
812 // For unique_ptr<A>, Ty will be 'A*'.
813 auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType);
814 State = State->remove<TrackedRegionMap>(K: ThisRegion);
815 State = State->set<TrackedRegionMap>(K: OtherSmartPtrRegion, E: NullVal);
816 C.addTransition(State, Tag: C.getNoteTag(Cb: [OtherSmartPtrRegion,
817 ThisRegion](PathSensitiveBugReport &BR,
818 llvm::raw_ostream &OS) {
819 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
820 !BR.isInteresting(R: OtherSmartPtrRegion))
821 return;
822 OS << "Smart pointer";
823 checkAndPrettyPrintRegion(OS, Region: OtherSmartPtrRegion);
824 OS << " is null after; previous value moved to";
825 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
826 }));
827 return true;
828 }
829 return false;
830}
831
832void SmartPtrModeling::handleBoolConversion(const CallEvent &Call,
833 CheckerContext &C) const {
834 // To model unique_ptr::operator bool
835 ProgramStateRef State = C.getState();
836 const Expr *CallExpr = Call.getOriginExpr();
837 const MemRegion *ThisRegion =
838 cast<CXXInstanceCall>(Val: &Call)->getCXXThisVal().getAsRegion();
839
840 QualType ThisType = cast<CXXMethodDecl>(Val: Call.getDecl())->getThisType();
841
842 SVal InnerPointerVal;
843 if (const auto *InnerValPtr = State->get<TrackedRegionMap>(key: ThisRegion)) {
844 InnerPointerVal = *InnerValPtr;
845 } else {
846 // In case of inner pointer SVal is not available we create
847 // conjureSymbolVal for inner pointer value.
848 auto InnerPointerType = getInnerPointerType(Call, C);
849 if (InnerPointerType.isNull())
850 return;
851
852 InnerPointerVal = C.getSValBuilder().conjureSymbolVal(
853 call: Call, type: InnerPointerType, visitCount: C.blockCount());
854 State = State->set<TrackedRegionMap>(K: ThisRegion, E: InnerPointerVal);
855 }
856
857 if (State->isNull(V: InnerPointerVal).isConstrainedTrue()) {
858 State = State->BindExpr(E: CallExpr, SF: C.getStackFrame(),
859 V: C.getSValBuilder().makeTruthVal(b: false));
860
861 C.addTransition(State);
862 return;
863 } else if (State->isNonNull(V: InnerPointerVal).isConstrainedTrue()) {
864 State = State->BindExpr(E: CallExpr, SF: C.getStackFrame(),
865 V: C.getSValBuilder().makeTruthVal(b: true));
866
867 C.addTransition(State);
868 return;
869 } else if (move::isMovedFrom(State, Region: ThisRegion)) {
870 C.addTransition(
871 State: State->BindExpr(E: CallExpr, SF: C.getStackFrame(),
872 V: C.getSValBuilder().makeZeroVal(type: Call.getResultType())));
873 return;
874 } else {
875 ProgramStateRef NotNullState, NullState;
876 std::tie(args&: NotNullState, args&: NullState) =
877 State->assume(Cond: InnerPointerVal.castAs<DefinedOrUnknownSVal>());
878
879 auto NullVal = C.getSValBuilder().makeNullWithType(type: ThisType);
880 // Explicitly tracking the region as null.
881 NullState = NullState->set<TrackedRegionMap>(K: ThisRegion, E: NullVal);
882
883 NullState = NullState->BindExpr(E: CallExpr, SF: C.getStackFrame(),
884 V: C.getSValBuilder().makeTruthVal(b: false));
885 C.addTransition(State: NullState, Tag: C.getNoteTag(
886 Cb: [ThisRegion](PathSensitiveBugReport &BR,
887 llvm::raw_ostream &OS) {
888 OS << "Assuming smart pointer";
889 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
890 OS << " is null";
891 },
892 /*IsPrunable=*/true));
893 NotNullState = NotNullState->BindExpr(
894 E: CallExpr, SF: C.getStackFrame(), V: C.getSValBuilder().makeTruthVal(b: true));
895 C.addTransition(
896 State: NotNullState,
897 Tag: C.getNoteTag(
898 Cb: [ThisRegion](PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
899 OS << "Assuming smart pointer";
900 checkAndPrettyPrintRegion(OS, Region: ThisRegion);
901 OS << " is non-null";
902 },
903 /*IsPrunable=*/true));
904 return;
905 }
906}
907
908void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
909 auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
910 Checker->ModelSmartPtrDereference =
911 Mgr.getAnalyzerOptions().getCheckerBooleanOption(
912 C: Checker, OptionName: "ModelSmartPtrDereference");
913}
914
915bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
916 const LangOptions &LO = mgr.getLangOpts();
917 return LO.CPlusPlus;
918}
919