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 isNullAfterMoveMethod(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 
61   using SmartPtrMethodHandlerFn =
62       void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
63   CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
64       {{"reset"}, &SmartPtrModeling::handleReset},
65       {{"release"}, &SmartPtrModeling::handleRelease},
66       {{"swap", 1}, &SmartPtrModeling::handleSwap},
67       {{"get"}, &SmartPtrModeling::handleGet}};
68 };
69 } // end of anonymous namespace
70 
71 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
72 
73 // Define the inter-checker API.
74 namespace clang {
75 namespace ento {
76 namespace smartptr {
77 bool isStdSmartPtrCall(const CallEvent &Call) {
78   const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
79   if (!MethodDecl || !MethodDecl->getParent())
80     return false;
81 
82   const auto *RecordDecl = MethodDecl->getParent();
83   if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace())
84     return false;
85 
86   if (RecordDecl->getDeclName().isIdentifier()) {
87     StringRef Name = RecordDecl->getName();
88     return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr";
89   }
90   return false;
91 }
92 
93 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
94   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
95   return InnerPointVal && InnerPointVal->isZeroConstant();
96 }
97 } // namespace smartptr
98 } // namespace ento
99 } // namespace clang
100 
101 // If a region is removed all of the subregions need to be removed too.
102 static TrackedRegionMapTy
103 removeTrackedSubregions(TrackedRegionMapTy RegionMap,
104                         TrackedRegionMapTy::Factory &RegionMapFactory,
105                         const MemRegion *Region) {
106   if (!Region)
107     return RegionMap;
108   for (const auto &E : RegionMap) {
109     if (E.first->isSubRegionOf(Region))
110       RegionMap = RegionMapFactory.remove(RegionMap, E.first);
111   }
112   return RegionMap;
113 }
114 
115 static ProgramStateRef updateSwappedRegion(ProgramStateRef State,
116                                            const MemRegion *Region,
117                                            const SVal *RegionInnerPointerVal) {
118   if (RegionInnerPointerVal) {
119     State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal);
120   } else {
121     State = State->remove<TrackedRegionMap>(Region);
122   }
123   return State;
124 }
125 
126 bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const {
127   // TODO: Update CallDescription to support anonymous calls?
128   // TODO: Handle other methods, such as .get() or .release().
129   // But once we do, we'd need a visitor to explain null dereferences
130   // that are found via such modeling.
131   const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
132   return CD && CD->getConversionType()->isBooleanType();
133 }
134 
135 bool SmartPtrModeling::evalCall(const CallEvent &Call,
136                                 CheckerContext &C) const {
137 
138   ProgramStateRef State = C.getState();
139   if (!smartptr::isStdSmartPtrCall(Call))
140     return false;
141 
142   if (isNullAfterMoveMethod(Call)) {
143     const MemRegion *ThisR =
144         cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
145 
146     if (!move::isMovedFrom(State, ThisR)) {
147       // TODO: Model this case as well. At least, avoid invalidation of
148       // globals.
149       return false;
150     }
151 
152     // TODO: Add a note to bug reports describing this decision.
153     C.addTransition(
154         State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
155                         C.getSValBuilder().makeZeroVal(Call.getResultType())));
156     return true;
157   }
158 
159   if (!ModelSmartPtrDereference)
160     return false;
161 
162   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
163     if (CC->getDecl()->isCopyOrMoveConstructor())
164       return false;
165 
166     const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion();
167     if (!ThisRegion)
168       return false;
169 
170     if (Call.getNumArgs() == 0) {
171       auto NullVal = C.getSValBuilder().makeNull();
172       State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
173 
174       C.addTransition(
175           State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
176                                            llvm::raw_ostream &OS) {
177             if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
178                 !BR.isInteresting(ThisRegion))
179               return;
180             OS << "Default constructed smart pointer ";
181             ThisRegion->printPretty(OS);
182             OS << " is null";
183           }));
184     } else {
185       const auto *TrackingExpr = Call.getArgExpr(0);
186       assert(TrackingExpr->getType()->isPointerType() &&
187              "Adding a non pointer value to TrackedRegionMap");
188       auto ArgVal = Call.getArgSVal(0);
189       State = State->set<TrackedRegionMap>(ThisRegion, ArgVal);
190 
191       C.addTransition(State, C.getNoteTag([ThisRegion, TrackingExpr,
192                                            ArgVal](PathSensitiveBugReport &BR,
193                                                    llvm::raw_ostream &OS) {
194         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
195             !BR.isInteresting(ThisRegion))
196           return;
197         bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
198         OS << "Smart pointer ";
199         ThisRegion->printPretty(OS);
200         if (ArgVal.isZeroConstant())
201           OS << " is constructed using a null value";
202         else
203           OS << " is constructed";
204       }));
205     }
206     return true;
207   }
208 
209   const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
210   if (!Handler)
211     return false;
212   (this->**Handler)(Call, C);
213 
214   return C.isDifferent();
215 }
216 
217 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
218                                         CheckerContext &C) const {
219   ProgramStateRef State = C.getState();
220   // Clean up dead regions from the region map.
221   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
222   for (auto E : TrackedRegions) {
223     const MemRegion *Region = E.first;
224     bool IsRegDead = !SymReaper.isLiveRegion(Region);
225 
226     if (IsRegDead)
227       State = State->remove<TrackedRegionMap>(Region);
228   }
229   C.addTransition(State);
230 }
231 
232 ProgramStateRef SmartPtrModeling::checkRegionChanges(
233     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
234     ArrayRef<const MemRegion *> ExplicitRegions,
235     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
236     const CallEvent *Call) const {
237   TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
238   TrackedRegionMapTy::Factory &RegionMapFactory =
239       State->get_context<TrackedRegionMap>();
240   for (const auto *Region : Regions)
241     RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
242                                         Region->getBaseRegion());
243   return State->set<TrackedRegionMap>(RegionMap);
244 }
245 
246 void SmartPtrModeling::handleReset(const CallEvent &Call,
247                                    CheckerContext &C) const {
248   ProgramStateRef State = C.getState();
249   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
250   if (!IC)
251     return;
252 
253   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
254   if (!ThisRegion)
255     return;
256 
257   assert(Call.getArgExpr(0)->getType()->isPointerType() &&
258          "Adding a non pointer value to TrackedRegionMap");
259   State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0));
260   const auto *TrackingExpr = Call.getArgExpr(0);
261   C.addTransition(
262       State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR,
263                                                      llvm::raw_ostream &OS) {
264         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
265             !BR.isInteresting(ThisRegion))
266           return;
267         bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
268         OS << "Smart pointer ";
269         ThisRegion->printPretty(OS);
270         OS << " reset using a null value";
271       }));
272   // TODO: Make sure to ivalidate the region in the Store if we don't have
273   // time to model all methods.
274 }
275 
276 void SmartPtrModeling::handleRelease(const CallEvent &Call,
277                                      CheckerContext &C) const {
278   ProgramStateRef State = C.getState();
279   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
280   if (!IC)
281     return;
282 
283   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
284   if (!ThisRegion)
285     return;
286 
287   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
288 
289   if (InnerPointVal) {
290     State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
291                             *InnerPointVal);
292   }
293 
294   auto ValueToUpdate = C.getSValBuilder().makeNull();
295   State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate);
296 
297   C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
298                                                    llvm::raw_ostream &OS) {
299     if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
300         !BR.isInteresting(ThisRegion))
301       return;
302 
303     OS << "Smart pointer ";
304     ThisRegion->printPretty(OS);
305     OS << " is released and set to null";
306   }));
307   // TODO: Add support to enable MallocChecker to start tracking the raw
308   // pointer.
309 }
310 
311 void SmartPtrModeling::handleSwap(const CallEvent &Call,
312                                   CheckerContext &C) const {
313   // To model unique_ptr::swap() method.
314   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
315   if (!IC)
316     return;
317 
318   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
319   if (!ThisRegion)
320     return;
321 
322   const auto *ArgRegion = Call.getArgSVal(0).getAsRegion();
323   if (!ArgRegion)
324     return;
325 
326   auto State = C.getState();
327   const auto *ThisRegionInnerPointerVal =
328       State->get<TrackedRegionMap>(ThisRegion);
329   const auto *ArgRegionInnerPointerVal =
330       State->get<TrackedRegionMap>(ArgRegion);
331 
332   // Swap the tracked region values.
333   State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal);
334   State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal);
335 
336   C.addTransition(
337       State, C.getNoteTag([ThisRegion, ArgRegion](PathSensitiveBugReport &BR,
338                                                   llvm::raw_ostream &OS) {
339         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
340             !BR.isInteresting(ThisRegion))
341           return;
342         BR.markInteresting(ArgRegion);
343         OS << "Swapped null smart pointer ";
344         ArgRegion->printPretty(OS);
345         OS << " with smart pointer ";
346         ThisRegion->printPretty(OS);
347       }));
348 }
349 
350 void SmartPtrModeling::handleGet(const CallEvent &Call,
351                                  CheckerContext &C) const {
352   ProgramStateRef State = C.getState();
353   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
354   if (!IC)
355     return;
356 
357   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
358   if (!ThisRegion)
359     return;
360 
361   SVal InnerPointerVal;
362   if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) {
363     InnerPointerVal = *InnerValPtr;
364   } else {
365     const auto *CallExpr = Call.getOriginExpr();
366     InnerPointerVal = C.getSValBuilder().conjureSymbolVal(
367         CallExpr, C.getLocationContext(), Call.getResultType(), C.blockCount());
368     State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal);
369   }
370 
371   State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
372                           InnerPointerVal);
373   // TODO: Add NoteTag, for how the raw pointer got using 'get' method.
374   C.addTransition(State);
375 }
376 
377 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
378   auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
379   Checker->ModelSmartPtrDereference =
380       Mgr.getAnalyzerOptions().getCheckerBooleanOption(
381           Checker, "ModelSmartPtrDereference");
382 }
383 
384 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
385   const LangOptions &LO = mgr.getLangOpts();
386   return LO.CPlusPlus;
387 }
388