xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp (revision 76c0577763505ea3db1017a9aab579c1c2f135d0)
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/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
21 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
22 #include "clang/StaticAnalyzer/Core/Checker.h"
23 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
24 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
25 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
26 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
27 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
28 
29 using namespace clang;
30 using namespace ento;
31 
32 namespace {
33 class SmartPtrModeling
34     : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges> {
35 
36   bool isNullAfterMoveMethod(const CallEvent &Call) const;
37 
38 public:
39   // Whether the checker should model for null dereferences of smart pointers.
40   DefaultBool ModelSmartPtrDereference;
41   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
42   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
43   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
44   ProgramStateRef
45   checkRegionChanges(ProgramStateRef State,
46                      const InvalidatedSymbols *Invalidated,
47                      ArrayRef<const MemRegion *> ExplicitRegions,
48                      ArrayRef<const MemRegion *> Regions,
49                      const LocationContext *LCtx, const CallEvent *Call) const;
50 
51 private:
52   ProgramStateRef updateTrackedRegion(const CallEvent &Call, CheckerContext &C,
53                                       const MemRegion *ThisValRegion) const;
54   void handleReset(const CallEvent &Call, CheckerContext &C) const;
55   void handleRelease(const CallEvent &Call, CheckerContext &C) const;
56   void handleSwap(const CallEvent &Call, CheckerContext &C) const;
57 
58   using SmartPtrMethodHandlerFn =
59       void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
60   CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
61       {{"reset"}, &SmartPtrModeling::handleReset},
62       {{"release"}, &SmartPtrModeling::handleRelease},
63       {{"swap", 1}, &SmartPtrModeling::handleSwap}};
64 };
65 } // end of anonymous namespace
66 
67 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
68 
69 // Define the inter-checker API.
70 namespace clang {
71 namespace ento {
72 namespace smartptr {
73 bool isStdSmartPtrCall(const CallEvent &Call) {
74   const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
75   if (!MethodDecl || !MethodDecl->getParent())
76     return false;
77 
78   const auto *RecordDecl = MethodDecl->getParent();
79   if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace())
80     return false;
81 
82   if (RecordDecl->getDeclName().isIdentifier()) {
83     StringRef Name = RecordDecl->getName();
84     return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr";
85   }
86   return false;
87 }
88 
89 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
90   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
91   return InnerPointVal && InnerPointVal->isZeroConstant();
92 }
93 } // namespace smartptr
94 } // namespace ento
95 } // namespace clang
96 
97 // If a region is removed all of the subregions need to be removed too.
98 static TrackedRegionMapTy
99 removeTrackedSubregions(TrackedRegionMapTy RegionMap,
100                         TrackedRegionMapTy::Factory &RegionMapFactory,
101                         const MemRegion *Region) {
102   if (!Region)
103     return RegionMap;
104   for (const auto &E : RegionMap) {
105     if (E.first->isSubRegionOf(Region))
106       RegionMap = RegionMapFactory.remove(RegionMap, E.first);
107   }
108   return RegionMap;
109 }
110 
111 static ProgramStateRef updateSwappedRegion(ProgramStateRef State,
112                                            const MemRegion *Region,
113                                            const SVal *RegionInnerPointerVal) {
114   if (RegionInnerPointerVal) {
115     State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal);
116   } else {
117     State = State->remove<TrackedRegionMap>(Region);
118   }
119   return State;
120 }
121 
122 bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const {
123   // TODO: Update CallDescription to support anonymous calls?
124   // TODO: Handle other methods, such as .get() or .release().
125   // But once we do, we'd need a visitor to explain null dereferences
126   // that are found via such modeling.
127   const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
128   return CD && CD->getConversionType()->isBooleanType();
129 }
130 
131 bool SmartPtrModeling::evalCall(const CallEvent &Call,
132                                 CheckerContext &C) const {
133 
134   if (!smartptr::isStdSmartPtrCall(Call))
135     return false;
136 
137   if (isNullAfterMoveMethod(Call)) {
138     ProgramStateRef State = C.getState();
139     const MemRegion *ThisR =
140         cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
141 
142     if (!move::isMovedFrom(State, ThisR)) {
143       // TODO: Model this case as well. At least, avoid invalidation of
144       // globals.
145       return false;
146     }
147 
148     // TODO: Add a note to bug reports describing this decision.
149     C.addTransition(
150         State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
151                         C.getSValBuilder().makeZeroVal(Call.getResultType())));
152     return true;
153   }
154 
155   if (!ModelSmartPtrDereference)
156     return false;
157 
158   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
159     if (CC->getDecl()->isCopyOrMoveConstructor())
160       return false;
161 
162     const MemRegion *ThisValRegion = CC->getCXXThisVal().getAsRegion();
163     if (!ThisValRegion)
164       return false;
165 
166     auto State = updateTrackedRegion(Call, C, ThisValRegion);
167     C.addTransition(State);
168     return true;
169   }
170 
171   const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
172   if (!Handler)
173     return false;
174   (this->**Handler)(Call, C);
175 
176   return C.isDifferent();
177 }
178 
179 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
180                                         CheckerContext &C) const {
181   ProgramStateRef State = C.getState();
182   // Clean up dead regions from the region map.
183   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
184   for (auto E : TrackedRegions) {
185     const MemRegion *Region = E.first;
186     bool IsRegDead = !SymReaper.isLiveRegion(Region);
187 
188     if (IsRegDead)
189       State = State->remove<TrackedRegionMap>(Region);
190   }
191   C.addTransition(State);
192 }
193 
194 ProgramStateRef SmartPtrModeling::checkRegionChanges(
195     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
196     ArrayRef<const MemRegion *> ExplicitRegions,
197     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
198     const CallEvent *Call) const {
199   TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
200   TrackedRegionMapTy::Factory &RegionMapFactory =
201       State->get_context<TrackedRegionMap>();
202   for (const auto *Region : Regions)
203     RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
204                                         Region->getBaseRegion());
205   return State->set<TrackedRegionMap>(RegionMap);
206 }
207 
208 void SmartPtrModeling::handleReset(const CallEvent &Call,
209                                    CheckerContext &C) const {
210   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
211   if (!IC)
212     return;
213 
214   const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion();
215   if (!ThisValRegion)
216     return;
217   auto State = updateTrackedRegion(Call, C, ThisValRegion);
218   C.addTransition(State);
219   // TODO: Make sure to ivalidate the region in the Store if we don't have
220   // time to model all methods.
221 }
222 
223 void SmartPtrModeling::handleRelease(const CallEvent &Call,
224                                      CheckerContext &C) const {
225   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
226   if (!IC)
227     return;
228 
229   const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion();
230   if (!ThisValRegion)
231     return;
232 
233   auto State = updateTrackedRegion(Call, C, ThisValRegion);
234 
235   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisValRegion);
236   if (InnerPointVal) {
237     State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
238                             *InnerPointVal);
239   }
240   C.addTransition(State);
241   // TODO: Add support to enable MallocChecker to start tracking the raw
242   // pointer.
243 }
244 
245 void SmartPtrModeling::handleSwap(const CallEvent &Call,
246                                   CheckerContext &C) const {
247   // To model unique_ptr::swap() method.
248   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
249   if (!IC)
250     return;
251 
252   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
253   if (!ThisRegion)
254     return;
255 
256   const auto *ArgRegion = Call.getArgSVal(0).getAsRegion();
257   if (!ArgRegion)
258     return;
259 
260   auto State = C.getState();
261   const auto *ThisRegionInnerPointerVal =
262       State->get<TrackedRegionMap>(ThisRegion);
263   const auto *ArgRegionInnerPointerVal =
264       State->get<TrackedRegionMap>(ArgRegion);
265 
266   // Swap the tracked region values.
267   State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal);
268   State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal);
269 
270   C.addTransition(State);
271 }
272 
273 ProgramStateRef
274 SmartPtrModeling::updateTrackedRegion(const CallEvent &Call, CheckerContext &C,
275                                       const MemRegion *ThisValRegion) const {
276   // TODO: Refactor and clean up handling too many things.
277   ProgramStateRef State = C.getState();
278   auto NumArgs = Call.getNumArgs();
279 
280   if (NumArgs == 0) {
281     auto NullSVal = C.getSValBuilder().makeNull();
282     State = State->set<TrackedRegionMap>(ThisValRegion, NullSVal);
283   } else if (NumArgs == 1) {
284     auto ArgVal = Call.getArgSVal(0);
285     assert(Call.getArgExpr(0)->getType()->isPointerType() &&
286            "Adding a non pointer value to TrackedRegionMap");
287     State = State->set<TrackedRegionMap>(ThisValRegion, ArgVal);
288   }
289 
290   return State;
291 }
292 
293 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
294   auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
295   Checker->ModelSmartPtrDereference =
296       Mgr.getAnalyzerOptions().getCheckerBooleanOption(
297           Checker, "ModelSmartPtrDereference");
298 }
299 
300 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
301   const LangOptions &LO = mgr.getLangOpts();
302   return LO.CPlusPlus;
303 }
304