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