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