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,
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
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(), 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
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, LCtx: C.getLocationContext(),
443 type: Type, 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 *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
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 =
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
541void 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
556void 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
573ProgramStateRef 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
587void 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
598void 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
628void 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
664void 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
675bool 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
712void 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
732bool 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
770bool 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
779bool 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
834void 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
911void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
912 auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
913 Checker->ModelSmartPtrDereference =
914 Mgr.getAnalyzerOptions().getCheckerBooleanOption(
915 C: Checker, OptionName: "ModelSmartPtrDereference");
916}
917
918bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
919 const LangOptions &LO = mgr.getLangOpts();
920 return LO.CPlusPlus;
921}
922