xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp (revision 1b743a9efa0884ed3a48ebea97b6ef6cb7d73164)
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/ExprCXX.h"
19 #include "clang/AST/Type.h"
20 #include "clang/Basic/LLVM.h"
21 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
22 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
23 #include "clang/StaticAnalyzer/Core/Checker.h"
24 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
25 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
26 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
27 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
28 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
29 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
30 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
31 #include <string>
32 
33 using namespace clang;
34 using namespace ento;
35 
36 namespace {
37 class SmartPtrModeling
38     : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges> {
39 
40   bool isAssignOpMethod(const CallEvent &Call) const;
41 
42 public:
43   // Whether the checker should model for null dereferences of smart pointers.
44   DefaultBool ModelSmartPtrDereference;
45   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
46   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
47   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
48   ProgramStateRef
49   checkRegionChanges(ProgramStateRef State,
50                      const InvalidatedSymbols *Invalidated,
51                      ArrayRef<const MemRegion *> ExplicitRegions,
52                      ArrayRef<const MemRegion *> Regions,
53                      const LocationContext *LCtx, const CallEvent *Call) const;
54 
55 private:
56   void handleReset(const CallEvent &Call, CheckerContext &C) const;
57   void handleRelease(const CallEvent &Call, CheckerContext &C) const;
58   void handleSwap(const CallEvent &Call, CheckerContext &C) const;
59   void handleGet(const CallEvent &Call, CheckerContext &C) const;
60   bool handleAssignOp(const CallEvent &Call, CheckerContext &C) const;
61   bool handleMoveCtr(const CallEvent &Call, CheckerContext &C,
62                      const MemRegion *ThisRegion) const;
63   bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion,
64                                 const MemRegion *OtherSmartPtrRegion) const;
65 
66   using SmartPtrMethodHandlerFn =
67       void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
68   CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
69       {{"reset"}, &SmartPtrModeling::handleReset},
70       {{"release"}, &SmartPtrModeling::handleRelease},
71       {{"swap", 1}, &SmartPtrModeling::handleSwap},
72       {{"get"}, &SmartPtrModeling::handleGet}};
73 };
74 } // end of anonymous namespace
75 
76 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
77 
78 // Define the inter-checker API.
79 namespace clang {
80 namespace ento {
81 namespace smartptr {
82 bool isStdSmartPtrCall(const CallEvent &Call) {
83   const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
84   if (!MethodDecl || !MethodDecl->getParent())
85     return false;
86 
87   const auto *RecordDecl = MethodDecl->getParent();
88   if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace())
89     return false;
90 
91   if (RecordDecl->getDeclName().isIdentifier()) {
92     StringRef Name = RecordDecl->getName();
93     return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr";
94   }
95   return false;
96 }
97 
98 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
99   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
100   return InnerPointVal && InnerPointVal->isZeroConstant();
101 }
102 } // namespace smartptr
103 } // namespace ento
104 } // namespace clang
105 
106 // If a region is removed all of the subregions need to be removed too.
107 static TrackedRegionMapTy
108 removeTrackedSubregions(TrackedRegionMapTy RegionMap,
109                         TrackedRegionMapTy::Factory &RegionMapFactory,
110                         const MemRegion *Region) {
111   if (!Region)
112     return RegionMap;
113   for (const auto &E : RegionMap) {
114     if (E.first->isSubRegionOf(Region))
115       RegionMap = RegionMapFactory.remove(RegionMap, E.first);
116   }
117   return RegionMap;
118 }
119 
120 static ProgramStateRef updateSwappedRegion(ProgramStateRef State,
121                                            const MemRegion *Region,
122                                            const SVal *RegionInnerPointerVal) {
123   if (RegionInnerPointerVal) {
124     State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal);
125   } else {
126     State = State->remove<TrackedRegionMap>(Region);
127   }
128   return State;
129 }
130 
131 bool SmartPtrModeling::isAssignOpMethod(const CallEvent &Call) const {
132   // TODO: Update CallDescription to support anonymous calls?
133   // TODO: Handle other methods, such as .get() or .release().
134   // But once we do, we'd need a visitor to explain null dereferences
135   // that are found via such modeling.
136   const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
137   return CD && CD->getConversionType()->isBooleanType();
138 }
139 
140 bool SmartPtrModeling::evalCall(const CallEvent &Call,
141                                 CheckerContext &C) const {
142   ProgramStateRef State = C.getState();
143   if (!smartptr::isStdSmartPtrCall(Call))
144     return false;
145 
146   if (isAssignOpMethod(Call)) {
147     const MemRegion *ThisR =
148         cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
149 
150     if (!move::isMovedFrom(State, ThisR)) {
151       // TODO: Model this case as well. At least, avoid invalidation of
152       // globals.
153       return false;
154     }
155 
156     // TODO: Add a note to bug reports describing this decision.
157     C.addTransition(
158         State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
159                         C.getSValBuilder().makeZeroVal(Call.getResultType())));
160     return true;
161   }
162 
163   if (!ModelSmartPtrDereference)
164     return false;
165 
166   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
167     if (CC->getDecl()->isCopyConstructor())
168       return false;
169 
170     const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion();
171     if (!ThisRegion)
172       return false;
173 
174     if (CC->getDecl()->isMoveConstructor())
175       return handleMoveCtr(Call, C, ThisRegion);
176 
177     if (Call.getNumArgs() == 0) {
178       auto NullVal = C.getSValBuilder().makeNull();
179       State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
180 
181       C.addTransition(
182           State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
183                                            llvm::raw_ostream &OS) {
184             if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
185                 !BR.isInteresting(ThisRegion))
186               return;
187             OS << "Default constructed smart pointer ";
188             ThisRegion->printPretty(OS);
189             OS << " is null";
190           }));
191     } else {
192       const auto *TrackingExpr = Call.getArgExpr(0);
193       assert(TrackingExpr->getType()->isPointerType() &&
194              "Adding a non pointer value to TrackedRegionMap");
195       auto ArgVal = Call.getArgSVal(0);
196       State = State->set<TrackedRegionMap>(ThisRegion, ArgVal);
197 
198       C.addTransition(State, C.getNoteTag([ThisRegion, TrackingExpr,
199                                            ArgVal](PathSensitiveBugReport &BR,
200                                                    llvm::raw_ostream &OS) {
201         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
202             !BR.isInteresting(ThisRegion))
203           return;
204         bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
205         OS << "Smart pointer ";
206         ThisRegion->printPretty(OS);
207         if (ArgVal.isZeroConstant())
208           OS << " is constructed using a null value";
209         else
210           OS << " is constructed";
211       }));
212     }
213     return true;
214   }
215 
216   if (handleAssignOp(Call, C))
217     return true;
218 
219   const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
220   if (!Handler)
221     return false;
222   (this->**Handler)(Call, C);
223 
224   return C.isDifferent();
225 }
226 
227 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
228                                         CheckerContext &C) const {
229   ProgramStateRef State = C.getState();
230   // Clean up dead regions from the region map.
231   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
232   for (auto E : TrackedRegions) {
233     const MemRegion *Region = E.first;
234     bool IsRegDead = !SymReaper.isLiveRegion(Region);
235 
236     if (IsRegDead)
237       State = State->remove<TrackedRegionMap>(Region);
238   }
239   C.addTransition(State);
240 }
241 
242 ProgramStateRef SmartPtrModeling::checkRegionChanges(
243     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
244     ArrayRef<const MemRegion *> ExplicitRegions,
245     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
246     const CallEvent *Call) const {
247   TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
248   TrackedRegionMapTy::Factory &RegionMapFactory =
249       State->get_context<TrackedRegionMap>();
250   for (const auto *Region : Regions)
251     RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
252                                         Region->getBaseRegion());
253   return State->set<TrackedRegionMap>(RegionMap);
254 }
255 
256 void SmartPtrModeling::handleReset(const CallEvent &Call,
257                                    CheckerContext &C) const {
258   ProgramStateRef State = C.getState();
259   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
260   if (!IC)
261     return;
262 
263   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
264   if (!ThisRegion)
265     return;
266 
267   assert(Call.getArgExpr(0)->getType()->isPointerType() &&
268          "Adding a non pointer value to TrackedRegionMap");
269   State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0));
270   const auto *TrackingExpr = Call.getArgExpr(0);
271   C.addTransition(
272       State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR,
273                                                      llvm::raw_ostream &OS) {
274         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
275             !BR.isInteresting(ThisRegion))
276           return;
277         bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
278         OS << "Smart pointer ";
279         ThisRegion->printPretty(OS);
280         OS << " reset using a null value";
281       }));
282   // TODO: Make sure to ivalidate the region in the Store if we don't have
283   // time to model all methods.
284 }
285 
286 void SmartPtrModeling::handleRelease(const CallEvent &Call,
287                                      CheckerContext &C) const {
288   ProgramStateRef State = C.getState();
289   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
290   if (!IC)
291     return;
292 
293   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
294   if (!ThisRegion)
295     return;
296 
297   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
298 
299   if (InnerPointVal) {
300     State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
301                             *InnerPointVal);
302   }
303 
304   auto ValueToUpdate = C.getSValBuilder().makeNull();
305   State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate);
306 
307   C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
308                                                    llvm::raw_ostream &OS) {
309     if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
310         !BR.isInteresting(ThisRegion))
311       return;
312 
313     OS << "Smart pointer ";
314     ThisRegion->printPretty(OS);
315     OS << " is released and set to null";
316   }));
317   // TODO: Add support to enable MallocChecker to start tracking the raw
318   // pointer.
319 }
320 
321 void SmartPtrModeling::handleSwap(const CallEvent &Call,
322                                   CheckerContext &C) const {
323   // To model unique_ptr::swap() method.
324   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
325   if (!IC)
326     return;
327 
328   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
329   if (!ThisRegion)
330     return;
331 
332   const auto *ArgRegion = Call.getArgSVal(0).getAsRegion();
333   if (!ArgRegion)
334     return;
335 
336   auto State = C.getState();
337   const auto *ThisRegionInnerPointerVal =
338       State->get<TrackedRegionMap>(ThisRegion);
339   const auto *ArgRegionInnerPointerVal =
340       State->get<TrackedRegionMap>(ArgRegion);
341 
342   // Swap the tracked region values.
343   State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal);
344   State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal);
345 
346   C.addTransition(
347       State, C.getNoteTag([ThisRegion, ArgRegion](PathSensitiveBugReport &BR,
348                                                   llvm::raw_ostream &OS) {
349         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
350             !BR.isInteresting(ThisRegion))
351           return;
352         BR.markInteresting(ArgRegion);
353         OS << "Swapped null smart pointer ";
354         ArgRegion->printPretty(OS);
355         OS << " with smart pointer ";
356         ThisRegion->printPretty(OS);
357       }));
358 }
359 
360 void SmartPtrModeling::handleGet(const CallEvent &Call,
361                                  CheckerContext &C) const {
362   ProgramStateRef State = C.getState();
363   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
364   if (!IC)
365     return;
366 
367   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
368   if (!ThisRegion)
369     return;
370 
371   SVal InnerPointerVal;
372   if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) {
373     InnerPointerVal = *InnerValPtr;
374   } else {
375     const auto *CallExpr = Call.getOriginExpr();
376     InnerPointerVal = C.getSValBuilder().conjureSymbolVal(
377         CallExpr, C.getLocationContext(), Call.getResultType(), C.blockCount());
378     State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal);
379   }
380 
381   State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
382                           InnerPointerVal);
383   // TODO: Add NoteTag, for how the raw pointer got using 'get' method.
384   C.addTransition(State);
385 }
386 
387 bool SmartPtrModeling::handleAssignOp(const CallEvent &Call,
388                                       CheckerContext &C) const {
389   ProgramStateRef State = C.getState();
390   const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call);
391   if (!OC)
392     return false;
393   OverloadedOperatorKind OOK = OC->getOverloadedOperator();
394   if (OOK != OO_Equal)
395     return false;
396   const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion();
397   if (!ThisRegion)
398     return false;
399 
400   const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(0).getAsRegion();
401   // In case of 'nullptr' or '0' assigned
402   if (!OtherSmartPtrRegion) {
403     bool AssignedNull = Call.getArgSVal(0).isZeroConstant();
404     if (!AssignedNull)
405       return false;
406     auto NullVal = C.getSValBuilder().makeNull();
407     State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
408     C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
409                                                      llvm::raw_ostream &OS) {
410       if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
411           !BR.isInteresting(ThisRegion))
412         return;
413       OS << "Smart pointer ";
414       ThisRegion->printPretty(OS);
415       OS << " is assigned to null";
416     }));
417     return true;
418   }
419 
420   return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion);
421 }
422 
423 bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C,
424                                      const MemRegion *ThisRegion) const {
425   const auto *OtherSmartPtrRegion = Call.getArgSVal(0).getAsRegion();
426   if (!OtherSmartPtrRegion)
427     return false;
428 
429   return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion);
430 }
431 
432 bool SmartPtrModeling::updateMovedSmartPointers(
433     CheckerContext &C, const MemRegion *ThisRegion,
434     const MemRegion *OtherSmartPtrRegion) const {
435   ProgramStateRef State = C.getState();
436   const auto *OtherInnerPtr = State->get<TrackedRegionMap>(OtherSmartPtrRegion);
437   if (OtherInnerPtr) {
438     State = State->set<TrackedRegionMap>(ThisRegion, *OtherInnerPtr);
439     auto NullVal = C.getSValBuilder().makeNull();
440     State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
441     bool IsArgValNull = OtherInnerPtr->isZeroConstant();
442 
443     C.addTransition(
444         State,
445         C.getNoteTag([ThisRegion, OtherSmartPtrRegion, IsArgValNull](
446                          PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
447           if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
448             return;
449           if (BR.isInteresting(OtherSmartPtrRegion)) {
450             OS << "Smart pointer ";
451             OtherSmartPtrRegion->printPretty(OS);
452             OS << " is null after being moved to ";
453             ThisRegion->printPretty(OS);
454           }
455           if (BR.isInteresting(ThisRegion) && IsArgValNull) {
456             OS << "A null pointer value is moved to ";
457             ThisRegion->printPretty(OS);
458             BR.markInteresting(OtherSmartPtrRegion);
459           }
460         }));
461     return true;
462   } else {
463     // In case we dont know anything about value we are moving from
464     // remove the entry from map for which smart pointer got moved to.
465     auto NullVal = C.getSValBuilder().makeNull();
466     State = State->remove<TrackedRegionMap>(ThisRegion);
467     State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
468     C.addTransition(State, C.getNoteTag([OtherSmartPtrRegion,
469                                          ThisRegion](PathSensitiveBugReport &BR,
470                                                      llvm::raw_ostream &OS) {
471       if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
472           !BR.isInteresting(OtherSmartPtrRegion))
473         return;
474       OS << "Smart pointer ";
475       OtherSmartPtrRegion->printPretty(OS);
476       OS << " is null after; previous value moved to ";
477       ThisRegion->printPretty(OS);
478     }));
479     return true;
480   }
481   return false;
482 }
483 
484 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
485   auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
486   Checker->ModelSmartPtrDereference =
487       Mgr.getAnalyzerOptions().getCheckerBooleanOption(
488           Checker, "ModelSmartPtrDereference");
489 }
490 
491 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
492   const LangOptions &LO = mgr.getLangOpts();
493   return LO.CPlusPlus;
494 }
495