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 : public Checker<eval::Call, check::DeadSymbols> {
34 
35   bool isNullAfterMoveMethod(const CallEvent &Call) const;
36 
37 public:
38   // Whether the checker should model for null dereferences of smart pointers.
39   DefaultBool ModelSmartPtrDereference;
40   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
41   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
42   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
43 
44 private:
45   ProgramStateRef updateTrackedRegion(const CallEvent &Call, CheckerContext &C,
46                                       const MemRegion *ThisValRegion) const;
47   void handleReset(const CallEvent &Call, CheckerContext &C) const;
48   void handleRelease(const CallEvent &Call, CheckerContext &C) const;
49   void handleSwap(const CallEvent &Call, CheckerContext &C) const;
50 
51   using SmartPtrMethodHandlerFn =
52       void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
53   CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
54       {{"reset"}, &SmartPtrModeling::handleReset},
55       {{"release"}, &SmartPtrModeling::handleRelease},
56       {{"swap", 1}, &SmartPtrModeling::handleSwap}};
57 };
58 } // end of anonymous namespace
59 
60 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
61 
62 // Define the inter-checker API.
63 namespace clang {
64 namespace ento {
65 namespace smartptr {
66 bool isStdSmartPtrCall(const CallEvent &Call) {
67   const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
68   if (!MethodDecl || !MethodDecl->getParent())
69     return false;
70 
71   const auto *RecordDecl = MethodDecl->getParent();
72   if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace())
73     return false;
74 
75   if (RecordDecl->getDeclName().isIdentifier()) {
76     return smartptr::StdSmartPtrs.count(RecordDecl->getName().lower());
77   }
78   return false;
79 }
80 
81 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
82   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
83   return InnerPointVal && InnerPointVal->isZeroConstant();
84 }
85 } // namespace smartptr
86 } // namespace ento
87 } // namespace clang
88 
89 bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const {
90   // TODO: Update CallDescription to support anonymous calls?
91   // TODO: Handle other methods, such as .get() or .release().
92   // But once we do, we'd need a visitor to explain null dereferences
93   // that are found via such modeling.
94   const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
95   return CD && CD->getConversionType()->isBooleanType();
96 }
97 
98 bool SmartPtrModeling::evalCall(const CallEvent &Call,
99                                 CheckerContext &C) const {
100 
101   if (!smartptr::isStdSmartPtrCall(Call))
102     return false;
103 
104   if (isNullAfterMoveMethod(Call)) {
105     ProgramStateRef State = C.getState();
106     const MemRegion *ThisR =
107         cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
108 
109     if (!move::isMovedFrom(State, ThisR)) {
110       // TODO: Model this case as well. At least, avoid invalidation of globals.
111       return false;
112     }
113 
114     // TODO: Add a note to bug reports describing this decision.
115     C.addTransition(
116         State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
117                         C.getSValBuilder().makeZeroVal(Call.getResultType())));
118     return true;
119   }
120 
121   if (!ModelSmartPtrDereference)
122     return false;
123 
124   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
125     if (CC->getDecl()->isCopyOrMoveConstructor())
126       return false;
127 
128     const MemRegion *ThisValRegion = CC->getCXXThisVal().getAsRegion();
129     if (!ThisValRegion)
130       return false;
131 
132     auto State = updateTrackedRegion(Call, C, ThisValRegion);
133     C.addTransition(State);
134     return true;
135   }
136 
137   const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
138   if (!Handler)
139     return false;
140   (this->**Handler)(Call, C);
141 
142   return C.isDifferent();
143 }
144 
145 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
146                                         CheckerContext &C) const {
147   ProgramStateRef State = C.getState();
148   // Clean up dead regions from the region map.
149   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
150   for (auto E : TrackedRegions) {
151     const MemRegion *Region = E.first;
152     bool IsRegDead = !SymReaper.isLiveRegion(Region);
153 
154     if (IsRegDead)
155       State = State->remove<TrackedRegionMap>(Region);
156   }
157   C.addTransition(State);
158 }
159 
160 void SmartPtrModeling::handleReset(const CallEvent &Call,
161                                    CheckerContext &C) const {
162   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
163   if (!IC)
164     return;
165 
166   const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion();
167   if (!ThisValRegion)
168     return;
169   auto State = updateTrackedRegion(Call, C, ThisValRegion);
170   C.addTransition(State);
171   // TODO: Make sure to ivalidate the the region in the Store if we don't have
172   // time to model all methods.
173 }
174 
175 void SmartPtrModeling::handleRelease(const CallEvent &Call,
176                                      CheckerContext &C) const {
177   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
178   if (!IC)
179     return;
180 
181   const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion();
182   if (!ThisValRegion)
183     return;
184 
185   auto State = updateTrackedRegion(Call, C, ThisValRegion);
186 
187   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisValRegion);
188   if (InnerPointVal) {
189     State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
190                             *InnerPointVal);
191   }
192   C.addTransition(State);
193   // TODO: Add support to enable MallocChecker to start tracking the raw
194   // pointer.
195 }
196 
197 void SmartPtrModeling::handleSwap(const CallEvent &Call,
198                                   CheckerContext &C) const {
199   // TODO: Add support to handle swap method.
200 }
201 
202 ProgramStateRef
203 SmartPtrModeling::updateTrackedRegion(const CallEvent &Call, CheckerContext &C,
204                                       const MemRegion *ThisValRegion) const {
205   // TODO: Refactor and clean up handling too many things.
206   ProgramStateRef State = C.getState();
207   auto NumArgs = Call.getNumArgs();
208 
209   if (NumArgs == 0) {
210     auto NullSVal = C.getSValBuilder().makeNull();
211     State = State->set<TrackedRegionMap>(ThisValRegion, NullSVal);
212   } else if (NumArgs == 1) {
213     auto ArgVal = Call.getArgSVal(0);
214     assert(Call.getArgExpr(0)->getType()->isPointerType() &&
215            "Adding a non pointer value to TrackedRegionMap");
216     State = State->set<TrackedRegionMap>(ThisValRegion, ArgVal);
217   }
218 
219   return State;
220 }
221 
222 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
223   auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
224   Checker->ModelSmartPtrDereference =
225       Mgr.getAnalyzerOptions().getCheckerBooleanOption(
226           Checker, "ModelSmartPtrDereference");
227 }
228 
229 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
230   const LangOptions &LO = mgr.getLangOpts();
231   return LO.CPlusPlus;
232 }
233