xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp (revision a5609102117d2384fb73a14f37d24a0c844e3864)
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 bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const {
112   // TODO: Update CallDescription to support anonymous calls?
113   // TODO: Handle other methods, such as .get() or .release().
114   // But once we do, we'd need a visitor to explain null dereferences
115   // that are found via such modeling.
116   const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
117   return CD && CD->getConversionType()->isBooleanType();
118 }
119 
120 bool SmartPtrModeling::evalCall(const CallEvent &Call,
121                                 CheckerContext &C) const {
122 
123   if (!smartptr::isStdSmartPtrCall(Call))
124     return false;
125 
126   if (isNullAfterMoveMethod(Call)) {
127     ProgramStateRef State = C.getState();
128     const MemRegion *ThisR =
129         cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
130 
131     if (!move::isMovedFrom(State, ThisR)) {
132       // TODO: Model this case as well. At least, avoid invalidation of globals.
133       return false;
134     }
135 
136     // TODO: Add a note to bug reports describing this decision.
137     C.addTransition(
138         State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
139                         C.getSValBuilder().makeZeroVal(Call.getResultType())));
140     return true;
141   }
142 
143   if (!ModelSmartPtrDereference)
144     return false;
145 
146   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
147     if (CC->getDecl()->isCopyOrMoveConstructor())
148       return false;
149 
150     const MemRegion *ThisValRegion = CC->getCXXThisVal().getAsRegion();
151     if (!ThisValRegion)
152       return false;
153 
154     auto State = updateTrackedRegion(Call, C, ThisValRegion);
155     C.addTransition(State);
156     return true;
157   }
158 
159   const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
160   if (!Handler)
161     return false;
162   (this->**Handler)(Call, C);
163 
164   return C.isDifferent();
165 }
166 
167 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
168                                         CheckerContext &C) const {
169   ProgramStateRef State = C.getState();
170   // Clean up dead regions from the region map.
171   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
172   for (auto E : TrackedRegions) {
173     const MemRegion *Region = E.first;
174     bool IsRegDead = !SymReaper.isLiveRegion(Region);
175 
176     if (IsRegDead)
177       State = State->remove<TrackedRegionMap>(Region);
178   }
179   C.addTransition(State);
180 }
181 
182 ProgramStateRef SmartPtrModeling::checkRegionChanges(
183     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
184     ArrayRef<const MemRegion *> ExplicitRegions,
185     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
186     const CallEvent *Call) const {
187   TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
188   TrackedRegionMapTy::Factory &RegionMapFactory =
189       State->get_context<TrackedRegionMap>();
190   for (const auto *Region : Regions)
191     RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
192                                         Region->getBaseRegion());
193   return State->set<TrackedRegionMap>(RegionMap);
194 }
195 
196 void SmartPtrModeling::handleReset(const CallEvent &Call,
197                                    CheckerContext &C) const {
198   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
199   if (!IC)
200     return;
201 
202   const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion();
203   if (!ThisValRegion)
204     return;
205   auto State = updateTrackedRegion(Call, C, ThisValRegion);
206   C.addTransition(State);
207   // TODO: Make sure to ivalidate the the region in the Store if we don't have
208   // time to model all methods.
209 }
210 
211 void SmartPtrModeling::handleRelease(const CallEvent &Call,
212                                      CheckerContext &C) const {
213   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
214   if (!IC)
215     return;
216 
217   const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion();
218   if (!ThisValRegion)
219     return;
220 
221   auto State = updateTrackedRegion(Call, C, ThisValRegion);
222 
223   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisValRegion);
224   if (InnerPointVal) {
225     State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
226                             *InnerPointVal);
227   }
228   C.addTransition(State);
229   // TODO: Add support to enable MallocChecker to start tracking the raw
230   // pointer.
231 }
232 
233 void SmartPtrModeling::handleSwap(const CallEvent &Call,
234                                   CheckerContext &C) const {
235   // TODO: Add support to handle swap method.
236 }
237 
238 ProgramStateRef
239 SmartPtrModeling::updateTrackedRegion(const CallEvent &Call, CheckerContext &C,
240                                       const MemRegion *ThisValRegion) const {
241   // TODO: Refactor and clean up handling too many things.
242   ProgramStateRef State = C.getState();
243   auto NumArgs = Call.getNumArgs();
244 
245   if (NumArgs == 0) {
246     auto NullSVal = C.getSValBuilder().makeNull();
247     State = State->set<TrackedRegionMap>(ThisValRegion, NullSVal);
248   } else if (NumArgs == 1) {
249     auto ArgVal = Call.getArgSVal(0);
250     assert(Call.getArgExpr(0)->getType()->isPointerType() &&
251            "Adding a non pointer value to TrackedRegionMap");
252     State = State->set<TrackedRegionMap>(ThisValRegion, ArgVal);
253   }
254 
255   return State;
256 }
257 
258 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
259   auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
260   Checker->ModelSmartPtrDereference =
261       Mgr.getAnalyzerOptions().getCheckerBooleanOption(
262           Checker, "ModelSmartPtrDereference");
263 }
264 
265 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
266   const LangOptions &LO = mgr.getLangOpts();
267   return LO.CPlusPlus;
268 }
269