xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp (revision 20676cab1168c2c60982af85f42725955cbcd7b5)
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 isAssignOpMethod(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   void handleGet(const CallEvent &Call, CheckerContext &C) const;
60   bool handleAssignOp(const CallEvent &Call, CheckerContext &C) const;
61 
62   using SmartPtrMethodHandlerFn =
63       void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
64   CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
65       {{"reset"}, &SmartPtrModeling::handleReset},
66       {{"release"}, &SmartPtrModeling::handleRelease},
67       {{"swap", 1}, &SmartPtrModeling::handleSwap},
68       {{"get"}, &SmartPtrModeling::handleGet}};
69 };
70 } // end of anonymous namespace
71 
72 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
73 
74 // Define the inter-checker API.
75 namespace clang {
76 namespace ento {
77 namespace smartptr {
78 bool isStdSmartPtrCall(const CallEvent &Call) {
79   const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
80   if (!MethodDecl || !MethodDecl->getParent())
81     return false;
82 
83   const auto *RecordDecl = MethodDecl->getParent();
84   if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace())
85     return false;
86 
87   if (RecordDecl->getDeclName().isIdentifier()) {
88     StringRef Name = RecordDecl->getName();
89     return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr";
90   }
91   return false;
92 }
93 
94 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
95   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
96   return InnerPointVal && InnerPointVal->isZeroConstant();
97 }
98 } // namespace smartptr
99 } // namespace ento
100 } // namespace clang
101 
102 // If a region is removed all of the subregions need to be removed too.
103 static TrackedRegionMapTy
104 removeTrackedSubregions(TrackedRegionMapTy RegionMap,
105                         TrackedRegionMapTy::Factory &RegionMapFactory,
106                         const MemRegion *Region) {
107   if (!Region)
108     return RegionMap;
109   for (const auto &E : RegionMap) {
110     if (E.first->isSubRegionOf(Region))
111       RegionMap = RegionMapFactory.remove(RegionMap, E.first);
112   }
113   return RegionMap;
114 }
115 
116 static ProgramStateRef updateSwappedRegion(ProgramStateRef State,
117                                            const MemRegion *Region,
118                                            const SVal *RegionInnerPointerVal) {
119   if (RegionInnerPointerVal) {
120     State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal);
121   } else {
122     State = State->remove<TrackedRegionMap>(Region);
123   }
124   return State;
125 }
126 
127 bool SmartPtrModeling::isAssignOpMethod(const CallEvent &Call) const {
128   // TODO: Update CallDescription to support anonymous calls?
129   // TODO: Handle other methods, such as .get() or .release().
130   // But once we do, we'd need a visitor to explain null dereferences
131   // that are found via such modeling.
132   const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
133   return CD && CD->getConversionType()->isBooleanType();
134 }
135 
136 bool SmartPtrModeling::evalCall(const CallEvent &Call,
137                                 CheckerContext &C) const {
138   ProgramStateRef State = C.getState();
139   if (!smartptr::isStdSmartPtrCall(Call))
140     return false;
141 
142   if (isAssignOpMethod(Call)) {
143     const MemRegion *ThisR =
144         cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
145 
146     if (!move::isMovedFrom(State, ThisR)) {
147       // TODO: Model this case as well. At least, avoid invalidation of
148       // globals.
149       return false;
150     }
151 
152     // TODO: Add a note to bug reports describing this decision.
153     C.addTransition(
154         State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
155                         C.getSValBuilder().makeZeroVal(Call.getResultType())));
156     return true;
157   }
158 
159   if (!ModelSmartPtrDereference)
160     return false;
161 
162   if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
163     if (CC->getDecl()->isCopyOrMoveConstructor())
164       return false;
165 
166     const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion();
167     if (!ThisRegion)
168       return false;
169 
170     if (Call.getNumArgs() == 0) {
171       auto NullVal = C.getSValBuilder().makeNull();
172       State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
173 
174       C.addTransition(
175           State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
176                                            llvm::raw_ostream &OS) {
177             if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
178                 !BR.isInteresting(ThisRegion))
179               return;
180             OS << "Default constructed smart pointer ";
181             ThisRegion->printPretty(OS);
182             OS << " is null";
183           }));
184     } else {
185       const auto *TrackingExpr = Call.getArgExpr(0);
186       assert(TrackingExpr->getType()->isPointerType() &&
187              "Adding a non pointer value to TrackedRegionMap");
188       auto ArgVal = Call.getArgSVal(0);
189       State = State->set<TrackedRegionMap>(ThisRegion, ArgVal);
190 
191       C.addTransition(State, C.getNoteTag([ThisRegion, TrackingExpr,
192                                            ArgVal](PathSensitiveBugReport &BR,
193                                                    llvm::raw_ostream &OS) {
194         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
195             !BR.isInteresting(ThisRegion))
196           return;
197         bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
198         OS << "Smart pointer ";
199         ThisRegion->printPretty(OS);
200         if (ArgVal.isZeroConstant())
201           OS << " is constructed using a null value";
202         else
203           OS << " is constructed";
204       }));
205     }
206     return true;
207   }
208 
209   if (handleAssignOp(Call, C))
210     return true;
211 
212   const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
213   if (!Handler)
214     return false;
215   (this->**Handler)(Call, C);
216 
217   return C.isDifferent();
218 }
219 
220 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
221                                         CheckerContext &C) const {
222   ProgramStateRef State = C.getState();
223   // Clean up dead regions from the region map.
224   TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
225   for (auto E : TrackedRegions) {
226     const MemRegion *Region = E.first;
227     bool IsRegDead = !SymReaper.isLiveRegion(Region);
228 
229     if (IsRegDead)
230       State = State->remove<TrackedRegionMap>(Region);
231   }
232   C.addTransition(State);
233 }
234 
235 ProgramStateRef SmartPtrModeling::checkRegionChanges(
236     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
237     ArrayRef<const MemRegion *> ExplicitRegions,
238     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
239     const CallEvent *Call) const {
240   TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
241   TrackedRegionMapTy::Factory &RegionMapFactory =
242       State->get_context<TrackedRegionMap>();
243   for (const auto *Region : Regions)
244     RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
245                                         Region->getBaseRegion());
246   return State->set<TrackedRegionMap>(RegionMap);
247 }
248 
249 void SmartPtrModeling::handleReset(const CallEvent &Call,
250                                    CheckerContext &C) const {
251   ProgramStateRef State = C.getState();
252   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
253   if (!IC)
254     return;
255 
256   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
257   if (!ThisRegion)
258     return;
259 
260   assert(Call.getArgExpr(0)->getType()->isPointerType() &&
261          "Adding a non pointer value to TrackedRegionMap");
262   State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0));
263   const auto *TrackingExpr = Call.getArgExpr(0);
264   C.addTransition(
265       State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR,
266                                                      llvm::raw_ostream &OS) {
267         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
268             !BR.isInteresting(ThisRegion))
269           return;
270         bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
271         OS << "Smart pointer ";
272         ThisRegion->printPretty(OS);
273         OS << " reset using a null value";
274       }));
275   // TODO: Make sure to ivalidate the region in the Store if we don't have
276   // time to model all methods.
277 }
278 
279 void SmartPtrModeling::handleRelease(const CallEvent &Call,
280                                      CheckerContext &C) const {
281   ProgramStateRef State = C.getState();
282   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
283   if (!IC)
284     return;
285 
286   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
287   if (!ThisRegion)
288     return;
289 
290   const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
291 
292   if (InnerPointVal) {
293     State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
294                             *InnerPointVal);
295   }
296 
297   auto ValueToUpdate = C.getSValBuilder().makeNull();
298   State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate);
299 
300   C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
301                                                    llvm::raw_ostream &OS) {
302     if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
303         !BR.isInteresting(ThisRegion))
304       return;
305 
306     OS << "Smart pointer ";
307     ThisRegion->printPretty(OS);
308     OS << " is released and set to null";
309   }));
310   // TODO: Add support to enable MallocChecker to start tracking the raw
311   // pointer.
312 }
313 
314 void SmartPtrModeling::handleSwap(const CallEvent &Call,
315                                   CheckerContext &C) const {
316   // To model unique_ptr::swap() method.
317   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
318   if (!IC)
319     return;
320 
321   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
322   if (!ThisRegion)
323     return;
324 
325   const auto *ArgRegion = Call.getArgSVal(0).getAsRegion();
326   if (!ArgRegion)
327     return;
328 
329   auto State = C.getState();
330   const auto *ThisRegionInnerPointerVal =
331       State->get<TrackedRegionMap>(ThisRegion);
332   const auto *ArgRegionInnerPointerVal =
333       State->get<TrackedRegionMap>(ArgRegion);
334 
335   // Swap the tracked region values.
336   State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal);
337   State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal);
338 
339   C.addTransition(
340       State, C.getNoteTag([ThisRegion, ArgRegion](PathSensitiveBugReport &BR,
341                                                   llvm::raw_ostream &OS) {
342         if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
343             !BR.isInteresting(ThisRegion))
344           return;
345         BR.markInteresting(ArgRegion);
346         OS << "Swapped null smart pointer ";
347         ArgRegion->printPretty(OS);
348         OS << " with smart pointer ";
349         ThisRegion->printPretty(OS);
350       }));
351 }
352 
353 void SmartPtrModeling::handleGet(const CallEvent &Call,
354                                  CheckerContext &C) const {
355   ProgramStateRef State = C.getState();
356   const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
357   if (!IC)
358     return;
359 
360   const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
361   if (!ThisRegion)
362     return;
363 
364   SVal InnerPointerVal;
365   if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) {
366     InnerPointerVal = *InnerValPtr;
367   } else {
368     const auto *CallExpr = Call.getOriginExpr();
369     InnerPointerVal = C.getSValBuilder().conjureSymbolVal(
370         CallExpr, C.getLocationContext(), Call.getResultType(), C.blockCount());
371     State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal);
372   }
373 
374   State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
375                           InnerPointerVal);
376   // TODO: Add NoteTag, for how the raw pointer got using 'get' method.
377   C.addTransition(State);
378 }
379 
380 bool SmartPtrModeling::handleAssignOp(const CallEvent &Call,
381                                       CheckerContext &C) const {
382   ProgramStateRef State = C.getState();
383   const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call);
384   if (!OC)
385     return false;
386   OverloadedOperatorKind OOK = OC->getOverloadedOperator();
387   if (OOK != OO_Equal)
388     return false;
389   const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion();
390   if (!ThisRegion)
391     return false;
392 
393   const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(0).getAsRegion();
394   // In case of 'nullptr' or '0' assigned
395   if (!OtherSmartPtrRegion) {
396     bool AssignedNull = Call.getArgSVal(0).isZeroConstant();
397     if (!AssignedNull)
398       return false;
399     auto NullVal = C.getSValBuilder().makeNull();
400     State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
401     C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
402                                                      llvm::raw_ostream &OS) {
403       if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
404           !BR.isInteresting(ThisRegion))
405         return;
406       OS << "Smart pointer ";
407       ThisRegion->printPretty(OS);
408       OS << " is assigned to null";
409     }));
410     return true;
411   }
412 
413   const auto *OtherInnerPtr = State->get<TrackedRegionMap>(OtherSmartPtrRegion);
414   if (OtherInnerPtr) {
415     State = State->set<TrackedRegionMap>(ThisRegion, *OtherInnerPtr);
416     auto NullVal = C.getSValBuilder().makeNull();
417     State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
418     bool IsArgValNull = OtherInnerPtr->isZeroConstant();
419 
420     C.addTransition(
421         State,
422         C.getNoteTag([ThisRegion, OtherSmartPtrRegion, IsArgValNull](
423                          PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
424           if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
425             return;
426           if (BR.isInteresting(OtherSmartPtrRegion)) {
427             OS << "Smart pointer ";
428             OtherSmartPtrRegion->printPretty(OS);
429             OS << " is null after being moved to ";
430             ThisRegion->printPretty(OS);
431           }
432           if (BR.isInteresting(ThisRegion) && IsArgValNull) {
433             OS << "Null pointer value move-assigned to ";
434             ThisRegion->printPretty(OS);
435             BR.markInteresting(OtherSmartPtrRegion);
436           }
437         }));
438     return true;
439   } else {
440     // In case we dont know anything about value we are moving from
441     // remove the entry from map for which smart pointer got moved to.
442     auto NullVal = C.getSValBuilder().makeNull();
443     State = State->remove<TrackedRegionMap>(ThisRegion);
444     State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
445     C.addTransition(State, C.getNoteTag([OtherSmartPtrRegion,
446                                          ThisRegion](PathSensitiveBugReport &BR,
447                                                      llvm::raw_ostream &OS) {
448       if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
449           !BR.isInteresting(OtherSmartPtrRegion))
450         return;
451       OS << "Smart pointer ";
452       OtherSmartPtrRegion->printPretty(OS);
453       OS << " is null after; previous value moved to ";
454       ThisRegion->printPretty(OS);
455     }));
456     return true;
457   }
458   return false;
459 }
460 
461 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
462   auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
463   Checker->ModelSmartPtrDereference =
464       Mgr.getAnalyzerOptions().getCheckerBooleanOption(
465           Checker, "ModelSmartPtrDereference");
466 }
467 
468 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
469   const LangOptions &LO = mgr.getLangOpts();
470   return LO.CPlusPlus;
471 }
472