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 void handleGet(const CallEvent &Call, CheckerContext &C) const; 60 61 using SmartPtrMethodHandlerFn = 62 void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; 63 CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ 64 {{"reset"}, &SmartPtrModeling::handleReset}, 65 {{"release"}, &SmartPtrModeling::handleRelease}, 66 {{"swap", 1}, &SmartPtrModeling::handleSwap}, 67 {{"get"}, &SmartPtrModeling::handleGet}}; 68 }; 69 } // end of anonymous namespace 70 71 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) 72 73 // Define the inter-checker API. 74 namespace clang { 75 namespace ento { 76 namespace smartptr { 77 bool isStdSmartPtrCall(const CallEvent &Call) { 78 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); 79 if (!MethodDecl || !MethodDecl->getParent()) 80 return false; 81 82 const auto *RecordDecl = MethodDecl->getParent(); 83 if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace()) 84 return false; 85 86 if (RecordDecl->getDeclName().isIdentifier()) { 87 StringRef Name = RecordDecl->getName(); 88 return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr"; 89 } 90 return false; 91 } 92 93 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { 94 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); 95 return InnerPointVal && InnerPointVal->isZeroConstant(); 96 } 97 } // namespace smartptr 98 } // namespace ento 99 } // namespace clang 100 101 // If a region is removed all of the subregions need to be removed too. 102 static TrackedRegionMapTy 103 removeTrackedSubregions(TrackedRegionMapTy RegionMap, 104 TrackedRegionMapTy::Factory &RegionMapFactory, 105 const MemRegion *Region) { 106 if (!Region) 107 return RegionMap; 108 for (const auto &E : RegionMap) { 109 if (E.first->isSubRegionOf(Region)) 110 RegionMap = RegionMapFactory.remove(RegionMap, E.first); 111 } 112 return RegionMap; 113 } 114 115 static ProgramStateRef updateSwappedRegion(ProgramStateRef State, 116 const MemRegion *Region, 117 const SVal *RegionInnerPointerVal) { 118 if (RegionInnerPointerVal) { 119 State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal); 120 } else { 121 State = State->remove<TrackedRegionMap>(Region); 122 } 123 return State; 124 } 125 126 bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { 127 // TODO: Update CallDescription to support anonymous calls? 128 // TODO: Handle other methods, such as .get() or .release(). 129 // But once we do, we'd need a visitor to explain null dereferences 130 // that are found via such modeling. 131 const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl()); 132 return CD && CD->getConversionType()->isBooleanType(); 133 } 134 135 bool SmartPtrModeling::evalCall(const CallEvent &Call, 136 CheckerContext &C) const { 137 138 ProgramStateRef State = C.getState(); 139 if (!smartptr::isStdSmartPtrCall(Call)) 140 return false; 141 142 if (isNullAfterMoveMethod(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 const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); 210 if (!Handler) 211 return false; 212 (this->**Handler)(Call, C); 213 214 return C.isDifferent(); 215 } 216 217 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, 218 CheckerContext &C) const { 219 ProgramStateRef State = C.getState(); 220 // Clean up dead regions from the region map. 221 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); 222 for (auto E : TrackedRegions) { 223 const MemRegion *Region = E.first; 224 bool IsRegDead = !SymReaper.isLiveRegion(Region); 225 226 if (IsRegDead) 227 State = State->remove<TrackedRegionMap>(Region); 228 } 229 C.addTransition(State); 230 } 231 232 ProgramStateRef SmartPtrModeling::checkRegionChanges( 233 ProgramStateRef State, const InvalidatedSymbols *Invalidated, 234 ArrayRef<const MemRegion *> ExplicitRegions, 235 ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, 236 const CallEvent *Call) const { 237 TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>(); 238 TrackedRegionMapTy::Factory &RegionMapFactory = 239 State->get_context<TrackedRegionMap>(); 240 for (const auto *Region : Regions) 241 RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory, 242 Region->getBaseRegion()); 243 return State->set<TrackedRegionMap>(RegionMap); 244 } 245 246 void SmartPtrModeling::handleReset(const CallEvent &Call, 247 CheckerContext &C) const { 248 ProgramStateRef State = C.getState(); 249 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 250 if (!IC) 251 return; 252 253 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 254 if (!ThisRegion) 255 return; 256 257 assert(Call.getArgExpr(0)->getType()->isPointerType() && 258 "Adding a non pointer value to TrackedRegionMap"); 259 State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0)); 260 const auto *TrackingExpr = Call.getArgExpr(0); 261 C.addTransition( 262 State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR, 263 llvm::raw_ostream &OS) { 264 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 265 !BR.isInteresting(ThisRegion)) 266 return; 267 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR); 268 OS << "Smart pointer "; 269 ThisRegion->printPretty(OS); 270 OS << " reset using a null value"; 271 })); 272 // TODO: Make sure to ivalidate the region in the Store if we don't have 273 // time to model all methods. 274 } 275 276 void SmartPtrModeling::handleRelease(const CallEvent &Call, 277 CheckerContext &C) const { 278 ProgramStateRef State = C.getState(); 279 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 280 if (!IC) 281 return; 282 283 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 284 if (!ThisRegion) 285 return; 286 287 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); 288 289 if (InnerPointVal) { 290 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 291 *InnerPointVal); 292 } 293 294 auto ValueToUpdate = C.getSValBuilder().makeNull(); 295 State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate); 296 297 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, 298 llvm::raw_ostream &OS) { 299 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 300 !BR.isInteresting(ThisRegion)) 301 return; 302 303 OS << "Smart pointer "; 304 ThisRegion->printPretty(OS); 305 OS << " is released and set to null"; 306 })); 307 // TODO: Add support to enable MallocChecker to start tracking the raw 308 // pointer. 309 } 310 311 void SmartPtrModeling::handleSwap(const CallEvent &Call, 312 CheckerContext &C) const { 313 // To model unique_ptr::swap() method. 314 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 315 if (!IC) 316 return; 317 318 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 319 if (!ThisRegion) 320 return; 321 322 const auto *ArgRegion = Call.getArgSVal(0).getAsRegion(); 323 if (!ArgRegion) 324 return; 325 326 auto State = C.getState(); 327 const auto *ThisRegionInnerPointerVal = 328 State->get<TrackedRegionMap>(ThisRegion); 329 const auto *ArgRegionInnerPointerVal = 330 State->get<TrackedRegionMap>(ArgRegion); 331 332 // Swap the tracked region values. 333 State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal); 334 State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal); 335 336 C.addTransition( 337 State, C.getNoteTag([ThisRegion, ArgRegion](PathSensitiveBugReport &BR, 338 llvm::raw_ostream &OS) { 339 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 340 !BR.isInteresting(ThisRegion)) 341 return; 342 BR.markInteresting(ArgRegion); 343 OS << "Swapped null smart pointer "; 344 ArgRegion->printPretty(OS); 345 OS << " with smart pointer "; 346 ThisRegion->printPretty(OS); 347 })); 348 } 349 350 void SmartPtrModeling::handleGet(const CallEvent &Call, 351 CheckerContext &C) const { 352 ProgramStateRef State = C.getState(); 353 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 354 if (!IC) 355 return; 356 357 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 358 if (!ThisRegion) 359 return; 360 361 SVal InnerPointerVal; 362 if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) { 363 InnerPointerVal = *InnerValPtr; 364 } else { 365 const auto *CallExpr = Call.getOriginExpr(); 366 InnerPointerVal = C.getSValBuilder().conjureSymbolVal( 367 CallExpr, C.getLocationContext(), Call.getResultType(), C.blockCount()); 368 State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal); 369 } 370 371 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 372 InnerPointerVal); 373 // TODO: Add NoteTag, for how the raw pointer got using 'get' method. 374 C.addTransition(State); 375 } 376 377 void ento::registerSmartPtrModeling(CheckerManager &Mgr) { 378 auto *Checker = Mgr.registerChecker<SmartPtrModeling>(); 379 Checker->ModelSmartPtrDereference = 380 Mgr.getAnalyzerOptions().getCheckerBooleanOption( 381 Checker, "ModelSmartPtrDereference"); 382 } 383 384 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { 385 const LangOptions &LO = mgr.getLangOpts(); 386 return LO.CPlusPlus; 387 } 388