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 isNullAfterMoveMethod(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 60 using SmartPtrMethodHandlerFn = 61 void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; 62 CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ 63 {{"reset"}, &SmartPtrModeling::handleReset}, 64 {{"release"}, &SmartPtrModeling::handleRelease}, 65 {{"swap", 1}, &SmartPtrModeling::handleSwap}}; 66 }; 67 } // end of anonymous namespace 68 69 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) 70 71 // Define the inter-checker API. 72 namespace clang { 73 namespace ento { 74 namespace smartptr { 75 bool isStdSmartPtrCall(const CallEvent &Call) { 76 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); 77 if (!MethodDecl || !MethodDecl->getParent()) 78 return false; 79 80 const auto *RecordDecl = MethodDecl->getParent(); 81 if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace()) 82 return false; 83 84 if (RecordDecl->getDeclName().isIdentifier()) { 85 StringRef Name = RecordDecl->getName(); 86 return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr"; 87 } 88 return false; 89 } 90 91 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { 92 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); 93 return InnerPointVal && InnerPointVal->isZeroConstant(); 94 } 95 } // namespace smartptr 96 } // namespace ento 97 } // namespace clang 98 99 // If a region is removed all of the subregions need to be removed too. 100 static TrackedRegionMapTy 101 removeTrackedSubregions(TrackedRegionMapTy RegionMap, 102 TrackedRegionMapTy::Factory &RegionMapFactory, 103 const MemRegion *Region) { 104 if (!Region) 105 return RegionMap; 106 for (const auto &E : RegionMap) { 107 if (E.first->isSubRegionOf(Region)) 108 RegionMap = RegionMapFactory.remove(RegionMap, E.first); 109 } 110 return RegionMap; 111 } 112 113 static ProgramStateRef updateSwappedRegion(ProgramStateRef State, 114 const MemRegion *Region, 115 const SVal *RegionInnerPointerVal) { 116 if (RegionInnerPointerVal) { 117 State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal); 118 } else { 119 State = State->remove<TrackedRegionMap>(Region); 120 } 121 return State; 122 } 123 124 bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { 125 // TODO: Update CallDescription to support anonymous calls? 126 // TODO: Handle other methods, such as .get() or .release(). 127 // But once we do, we'd need a visitor to explain null dereferences 128 // that are found via such modeling. 129 const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl()); 130 return CD && CD->getConversionType()->isBooleanType(); 131 } 132 133 bool SmartPtrModeling::evalCall(const CallEvent &Call, 134 CheckerContext &C) const { 135 136 ProgramStateRef State = C.getState(); 137 if (!smartptr::isStdSmartPtrCall(Call)) 138 return false; 139 140 if (isNullAfterMoveMethod(Call)) { 141 const MemRegion *ThisR = 142 cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion(); 143 144 if (!move::isMovedFrom(State, ThisR)) { 145 // TODO: Model this case as well. At least, avoid invalidation of 146 // globals. 147 return false; 148 } 149 150 // TODO: Add a note to bug reports describing this decision. 151 C.addTransition( 152 State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 153 C.getSValBuilder().makeZeroVal(Call.getResultType()))); 154 return true; 155 } 156 157 if (!ModelSmartPtrDereference) 158 return false; 159 160 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { 161 if (CC->getDecl()->isCopyOrMoveConstructor()) 162 return false; 163 164 const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion(); 165 if (!ThisRegion) 166 return false; 167 168 if (Call.getNumArgs() == 0) { 169 auto NullVal = C.getSValBuilder().makeNull(); 170 State = State->set<TrackedRegionMap>(ThisRegion, NullVal); 171 172 C.addTransition( 173 State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, 174 llvm::raw_ostream &OS) { 175 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 176 !BR.isInteresting(ThisRegion)) 177 return; 178 OS << "Default constructed smart pointer "; 179 ThisRegion->printPretty(OS); 180 OS << " is null"; 181 })); 182 } else { 183 const auto *TrackingExpr = Call.getArgExpr(0); 184 assert(TrackingExpr->getType()->isPointerType() && 185 "Adding a non pointer value to TrackedRegionMap"); 186 auto ArgVal = Call.getArgSVal(0); 187 State = State->set<TrackedRegionMap>(ThisRegion, ArgVal); 188 189 C.addTransition(State, C.getNoteTag([ThisRegion, TrackingExpr, 190 ArgVal](PathSensitiveBugReport &BR, 191 llvm::raw_ostream &OS) { 192 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 193 !BR.isInteresting(ThisRegion)) 194 return; 195 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR); 196 OS << "Smart pointer "; 197 ThisRegion->printPretty(OS); 198 if (ArgVal.isZeroConstant()) 199 OS << " is constructed using a null value"; 200 else 201 OS << " is constructed"; 202 })); 203 } 204 return true; 205 } 206 207 const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); 208 if (!Handler) 209 return false; 210 (this->**Handler)(Call, C); 211 212 return C.isDifferent(); 213 } 214 215 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, 216 CheckerContext &C) const { 217 ProgramStateRef State = C.getState(); 218 // Clean up dead regions from the region map. 219 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); 220 for (auto E : TrackedRegions) { 221 const MemRegion *Region = E.first; 222 bool IsRegDead = !SymReaper.isLiveRegion(Region); 223 224 if (IsRegDead) 225 State = State->remove<TrackedRegionMap>(Region); 226 } 227 C.addTransition(State); 228 } 229 230 ProgramStateRef SmartPtrModeling::checkRegionChanges( 231 ProgramStateRef State, const InvalidatedSymbols *Invalidated, 232 ArrayRef<const MemRegion *> ExplicitRegions, 233 ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, 234 const CallEvent *Call) const { 235 TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>(); 236 TrackedRegionMapTy::Factory &RegionMapFactory = 237 State->get_context<TrackedRegionMap>(); 238 for (const auto *Region : Regions) 239 RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory, 240 Region->getBaseRegion()); 241 return State->set<TrackedRegionMap>(RegionMap); 242 } 243 244 void SmartPtrModeling::handleReset(const CallEvent &Call, 245 CheckerContext &C) const { 246 ProgramStateRef State = C.getState(); 247 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 248 if (!IC) 249 return; 250 251 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 252 if (!ThisRegion) 253 return; 254 255 assert(Call.getArgExpr(0)->getType()->isPointerType() && 256 "Adding a non pointer value to TrackedRegionMap"); 257 State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0)); 258 const auto *TrackingExpr = Call.getArgExpr(0); 259 C.addTransition( 260 State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR, 261 llvm::raw_ostream &OS) { 262 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 263 !BR.isInteresting(ThisRegion)) 264 return; 265 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR); 266 OS << "Smart pointer "; 267 ThisRegion->printPretty(OS); 268 OS << " reset using a null value"; 269 })); 270 // TODO: Make sure to ivalidate the region in the Store if we don't have 271 // time to model all methods. 272 } 273 274 void SmartPtrModeling::handleRelease(const CallEvent &Call, 275 CheckerContext &C) const { 276 ProgramStateRef State = C.getState(); 277 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 278 if (!IC) 279 return; 280 281 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 282 if (!ThisRegion) 283 return; 284 285 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); 286 287 if (InnerPointVal) { 288 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 289 *InnerPointVal); 290 } 291 292 auto ValueToUpdate = C.getSValBuilder().makeNull(); 293 State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate); 294 295 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, 296 llvm::raw_ostream &OS) { 297 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 298 !BR.isInteresting(ThisRegion)) 299 return; 300 301 OS << "Smart pointer "; 302 ThisRegion->printPretty(OS); 303 OS << " is released and set to null"; 304 })); 305 // TODO: Add support to enable MallocChecker to start tracking the raw 306 // pointer. 307 } 308 309 void SmartPtrModeling::handleSwap(const CallEvent &Call, 310 CheckerContext &C) const { 311 // To model unique_ptr::swap() method. 312 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 313 if (!IC) 314 return; 315 316 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 317 if (!ThisRegion) 318 return; 319 320 const auto *ArgRegion = Call.getArgSVal(0).getAsRegion(); 321 if (!ArgRegion) 322 return; 323 324 auto State = C.getState(); 325 const auto *ThisRegionInnerPointerVal = 326 State->get<TrackedRegionMap>(ThisRegion); 327 const auto *ArgRegionInnerPointerVal = 328 State->get<TrackedRegionMap>(ArgRegion); 329 330 // Swap the tracked region values. 331 State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal); 332 State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal); 333 334 C.addTransition( 335 State, C.getNoteTag([ThisRegion, ArgRegion](PathSensitiveBugReport &BR, 336 llvm::raw_ostream &OS) { 337 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 338 !BR.isInteresting(ThisRegion)) 339 return; 340 BR.markInteresting(ArgRegion); 341 OS << "Swapped null smart pointer "; 342 ArgRegion->printPretty(OS); 343 OS << " with smart pointer "; 344 ThisRegion->printPretty(OS); 345 })); 346 } 347 348 void ento::registerSmartPtrModeling(CheckerManager &Mgr) { 349 auto *Checker = Mgr.registerChecker<SmartPtrModeling>(); 350 Checker->ModelSmartPtrDereference = 351 Mgr.getAnalyzerOptions().getCheckerBooleanOption( 352 Checker, "ModelSmartPtrDereference"); 353 } 354 355 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { 356 const LangOptions &LO = mgr.getLangOpts(); 357 return LO.CPlusPlus; 358 } 359