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