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