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