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 bool handleMoveCtr(const CallEvent &Call, CheckerContext &C, 62 const MemRegion *ThisRegion) const; 63 bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion, 64 const MemRegion *OtherSmartPtrRegion) const; 65 66 using SmartPtrMethodHandlerFn = 67 void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; 68 CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ 69 {{"reset"}, &SmartPtrModeling::handleReset}, 70 {{"release"}, &SmartPtrModeling::handleRelease}, 71 {{"swap", 1}, &SmartPtrModeling::handleSwap}, 72 {{"get"}, &SmartPtrModeling::handleGet}}; 73 }; 74 } // end of anonymous namespace 75 76 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) 77 78 // Define the inter-checker API. 79 namespace clang { 80 namespace ento { 81 namespace smartptr { 82 bool isStdSmartPtrCall(const CallEvent &Call) { 83 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); 84 if (!MethodDecl || !MethodDecl->getParent()) 85 return false; 86 87 const auto *RecordDecl = MethodDecl->getParent(); 88 if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace()) 89 return false; 90 91 if (RecordDecl->getDeclName().isIdentifier()) { 92 StringRef Name = RecordDecl->getName(); 93 return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr"; 94 } 95 return false; 96 } 97 98 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { 99 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); 100 return InnerPointVal && InnerPointVal->isZeroConstant(); 101 } 102 } // namespace smartptr 103 } // namespace ento 104 } // namespace clang 105 106 // If a region is removed all of the subregions need to be removed too. 107 static TrackedRegionMapTy 108 removeTrackedSubregions(TrackedRegionMapTy RegionMap, 109 TrackedRegionMapTy::Factory &RegionMapFactory, 110 const MemRegion *Region) { 111 if (!Region) 112 return RegionMap; 113 for (const auto &E : RegionMap) { 114 if (E.first->isSubRegionOf(Region)) 115 RegionMap = RegionMapFactory.remove(RegionMap, E.first); 116 } 117 return RegionMap; 118 } 119 120 static ProgramStateRef updateSwappedRegion(ProgramStateRef State, 121 const MemRegion *Region, 122 const SVal *RegionInnerPointerVal) { 123 if (RegionInnerPointerVal) { 124 State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal); 125 } else { 126 State = State->remove<TrackedRegionMap>(Region); 127 } 128 return State; 129 } 130 131 bool SmartPtrModeling::isAssignOpMethod(const CallEvent &Call) const { 132 // TODO: Update CallDescription to support anonymous calls? 133 // TODO: Handle other methods, such as .get() or .release(). 134 // But once we do, we'd need a visitor to explain null dereferences 135 // that are found via such modeling. 136 const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl()); 137 return CD && CD->getConversionType()->isBooleanType(); 138 } 139 140 bool SmartPtrModeling::evalCall(const CallEvent &Call, 141 CheckerContext &C) const { 142 ProgramStateRef State = C.getState(); 143 if (!smartptr::isStdSmartPtrCall(Call)) 144 return false; 145 146 if (isAssignOpMethod(Call)) { 147 const MemRegion *ThisR = 148 cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion(); 149 150 if (!move::isMovedFrom(State, ThisR)) { 151 // TODO: Model this case as well. At least, avoid invalidation of 152 // globals. 153 return false; 154 } 155 156 // TODO: Add a note to bug reports describing this decision. 157 C.addTransition( 158 State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 159 C.getSValBuilder().makeZeroVal(Call.getResultType()))); 160 return true; 161 } 162 163 if (!ModelSmartPtrDereference) 164 return false; 165 166 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { 167 if (CC->getDecl()->isCopyConstructor()) 168 return false; 169 170 const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion(); 171 if (!ThisRegion) 172 return false; 173 174 if (CC->getDecl()->isMoveConstructor()) 175 return handleMoveCtr(Call, C, ThisRegion); 176 177 if (Call.getNumArgs() == 0) { 178 auto NullVal = C.getSValBuilder().makeNull(); 179 State = State->set<TrackedRegionMap>(ThisRegion, NullVal); 180 181 C.addTransition( 182 State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, 183 llvm::raw_ostream &OS) { 184 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 185 !BR.isInteresting(ThisRegion)) 186 return; 187 OS << "Default constructed smart pointer "; 188 ThisRegion->printPretty(OS); 189 OS << " is null"; 190 })); 191 } else { 192 const auto *TrackingExpr = Call.getArgExpr(0); 193 assert(TrackingExpr->getType()->isPointerType() && 194 "Adding a non pointer value to TrackedRegionMap"); 195 auto ArgVal = Call.getArgSVal(0); 196 State = State->set<TrackedRegionMap>(ThisRegion, ArgVal); 197 198 C.addTransition(State, C.getNoteTag([ThisRegion, TrackingExpr, 199 ArgVal](PathSensitiveBugReport &BR, 200 llvm::raw_ostream &OS) { 201 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 202 !BR.isInteresting(ThisRegion)) 203 return; 204 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR); 205 OS << "Smart pointer "; 206 ThisRegion->printPretty(OS); 207 if (ArgVal.isZeroConstant()) 208 OS << " is constructed using a null value"; 209 else 210 OS << " is constructed"; 211 })); 212 } 213 return true; 214 } 215 216 if (handleAssignOp(Call, C)) 217 return true; 218 219 const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); 220 if (!Handler) 221 return false; 222 (this->**Handler)(Call, C); 223 224 return C.isDifferent(); 225 } 226 227 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, 228 CheckerContext &C) const { 229 ProgramStateRef State = C.getState(); 230 // Clean up dead regions from the region map. 231 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); 232 for (auto E : TrackedRegions) { 233 const MemRegion *Region = E.first; 234 bool IsRegDead = !SymReaper.isLiveRegion(Region); 235 236 if (IsRegDead) 237 State = State->remove<TrackedRegionMap>(Region); 238 } 239 C.addTransition(State); 240 } 241 242 ProgramStateRef SmartPtrModeling::checkRegionChanges( 243 ProgramStateRef State, const InvalidatedSymbols *Invalidated, 244 ArrayRef<const MemRegion *> ExplicitRegions, 245 ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, 246 const CallEvent *Call) const { 247 TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>(); 248 TrackedRegionMapTy::Factory &RegionMapFactory = 249 State->get_context<TrackedRegionMap>(); 250 for (const auto *Region : Regions) 251 RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory, 252 Region->getBaseRegion()); 253 return State->set<TrackedRegionMap>(RegionMap); 254 } 255 256 void SmartPtrModeling::handleReset(const CallEvent &Call, 257 CheckerContext &C) const { 258 ProgramStateRef State = C.getState(); 259 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 260 if (!IC) 261 return; 262 263 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 264 if (!ThisRegion) 265 return; 266 267 assert(Call.getArgExpr(0)->getType()->isPointerType() && 268 "Adding a non pointer value to TrackedRegionMap"); 269 State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0)); 270 const auto *TrackingExpr = Call.getArgExpr(0); 271 C.addTransition( 272 State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR, 273 llvm::raw_ostream &OS) { 274 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 275 !BR.isInteresting(ThisRegion)) 276 return; 277 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR); 278 OS << "Smart pointer "; 279 ThisRegion->printPretty(OS); 280 OS << " reset using a null value"; 281 })); 282 // TODO: Make sure to ivalidate the region in the Store if we don't have 283 // time to model all methods. 284 } 285 286 void SmartPtrModeling::handleRelease(const CallEvent &Call, 287 CheckerContext &C) const { 288 ProgramStateRef State = C.getState(); 289 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 290 if (!IC) 291 return; 292 293 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 294 if (!ThisRegion) 295 return; 296 297 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); 298 299 if (InnerPointVal) { 300 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 301 *InnerPointVal); 302 } 303 304 auto ValueToUpdate = C.getSValBuilder().makeNull(); 305 State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate); 306 307 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, 308 llvm::raw_ostream &OS) { 309 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 310 !BR.isInteresting(ThisRegion)) 311 return; 312 313 OS << "Smart pointer "; 314 ThisRegion->printPretty(OS); 315 OS << " is released and set to null"; 316 })); 317 // TODO: Add support to enable MallocChecker to start tracking the raw 318 // pointer. 319 } 320 321 void SmartPtrModeling::handleSwap(const CallEvent &Call, 322 CheckerContext &C) const { 323 // To model unique_ptr::swap() method. 324 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 325 if (!IC) 326 return; 327 328 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 329 if (!ThisRegion) 330 return; 331 332 const auto *ArgRegion = Call.getArgSVal(0).getAsRegion(); 333 if (!ArgRegion) 334 return; 335 336 auto State = C.getState(); 337 const auto *ThisRegionInnerPointerVal = 338 State->get<TrackedRegionMap>(ThisRegion); 339 const auto *ArgRegionInnerPointerVal = 340 State->get<TrackedRegionMap>(ArgRegion); 341 342 // Swap the tracked region values. 343 State = updateSwappedRegion(State, ThisRegion, ArgRegionInnerPointerVal); 344 State = updateSwappedRegion(State, ArgRegion, ThisRegionInnerPointerVal); 345 346 C.addTransition( 347 State, C.getNoteTag([ThisRegion, ArgRegion](PathSensitiveBugReport &BR, 348 llvm::raw_ostream &OS) { 349 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 350 !BR.isInteresting(ThisRegion)) 351 return; 352 BR.markInteresting(ArgRegion); 353 OS << "Swapped null smart pointer "; 354 ArgRegion->printPretty(OS); 355 OS << " with smart pointer "; 356 ThisRegion->printPretty(OS); 357 })); 358 } 359 360 void SmartPtrModeling::handleGet(const CallEvent &Call, 361 CheckerContext &C) const { 362 ProgramStateRef State = C.getState(); 363 const auto *IC = dyn_cast<CXXInstanceCall>(&Call); 364 if (!IC) 365 return; 366 367 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 368 if (!ThisRegion) 369 return; 370 371 SVal InnerPointerVal; 372 if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) { 373 InnerPointerVal = *InnerValPtr; 374 } else { 375 const auto *CallExpr = Call.getOriginExpr(); 376 InnerPointerVal = C.getSValBuilder().conjureSymbolVal( 377 CallExpr, C.getLocationContext(), Call.getResultType(), C.blockCount()); 378 State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal); 379 } 380 381 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), 382 InnerPointerVal); 383 // TODO: Add NoteTag, for how the raw pointer got using 'get' method. 384 C.addTransition(State); 385 } 386 387 bool SmartPtrModeling::handleAssignOp(const CallEvent &Call, 388 CheckerContext &C) const { 389 ProgramStateRef State = C.getState(); 390 const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call); 391 if (!OC) 392 return false; 393 OverloadedOperatorKind OOK = OC->getOverloadedOperator(); 394 if (OOK != OO_Equal) 395 return false; 396 const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion(); 397 if (!ThisRegion) 398 return false; 399 400 const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(0).getAsRegion(); 401 // In case of 'nullptr' or '0' assigned 402 if (!OtherSmartPtrRegion) { 403 bool AssignedNull = Call.getArgSVal(0).isZeroConstant(); 404 if (!AssignedNull) 405 return false; 406 auto NullVal = C.getSValBuilder().makeNull(); 407 State = State->set<TrackedRegionMap>(ThisRegion, NullVal); 408 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, 409 llvm::raw_ostream &OS) { 410 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 411 !BR.isInteresting(ThisRegion)) 412 return; 413 OS << "Smart pointer "; 414 ThisRegion->printPretty(OS); 415 OS << " is assigned to null"; 416 })); 417 return true; 418 } 419 420 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion); 421 } 422 423 bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C, 424 const MemRegion *ThisRegion) const { 425 const auto *OtherSmartPtrRegion = Call.getArgSVal(0).getAsRegion(); 426 if (!OtherSmartPtrRegion) 427 return false; 428 429 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion); 430 } 431 432 bool SmartPtrModeling::updateMovedSmartPointers( 433 CheckerContext &C, const MemRegion *ThisRegion, 434 const MemRegion *OtherSmartPtrRegion) const { 435 ProgramStateRef State = C.getState(); 436 const auto *OtherInnerPtr = State->get<TrackedRegionMap>(OtherSmartPtrRegion); 437 if (OtherInnerPtr) { 438 State = State->set<TrackedRegionMap>(ThisRegion, *OtherInnerPtr); 439 auto NullVal = C.getSValBuilder().makeNull(); 440 State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal); 441 bool IsArgValNull = OtherInnerPtr->isZeroConstant(); 442 443 C.addTransition( 444 State, 445 C.getNoteTag([ThisRegion, OtherSmartPtrRegion, IsArgValNull]( 446 PathSensitiveBugReport &BR, llvm::raw_ostream &OS) { 447 if (&BR.getBugType() != smartptr::getNullDereferenceBugType()) 448 return; 449 if (BR.isInteresting(OtherSmartPtrRegion)) { 450 OS << "Smart pointer "; 451 OtherSmartPtrRegion->printPretty(OS); 452 OS << " is null after being moved to "; 453 ThisRegion->printPretty(OS); 454 } 455 if (BR.isInteresting(ThisRegion) && IsArgValNull) { 456 OS << "A null pointer value is moved to "; 457 ThisRegion->printPretty(OS); 458 BR.markInteresting(OtherSmartPtrRegion); 459 } 460 })); 461 return true; 462 } else { 463 // In case we dont know anything about value we are moving from 464 // remove the entry from map for which smart pointer got moved to. 465 auto NullVal = C.getSValBuilder().makeNull(); 466 State = State->remove<TrackedRegionMap>(ThisRegion); 467 State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal); 468 C.addTransition(State, C.getNoteTag([OtherSmartPtrRegion, 469 ThisRegion](PathSensitiveBugReport &BR, 470 llvm::raw_ostream &OS) { 471 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() || 472 !BR.isInteresting(OtherSmartPtrRegion)) 473 return; 474 OS << "Smart pointer "; 475 OtherSmartPtrRegion->printPretty(OS); 476 OS << " is null after; previous value moved to "; 477 ThisRegion->printPretty(OS); 478 })); 479 return true; 480 } 481 return false; 482 } 483 484 void ento::registerSmartPtrModeling(CheckerManager &Mgr) { 485 auto *Checker = Mgr.registerChecker<SmartPtrModeling>(); 486 Checker->ModelSmartPtrDereference = 487 Mgr.getAnalyzerOptions().getCheckerBooleanOption( 488 Checker, "ModelSmartPtrDereference"); 489 } 490 491 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { 492 const LangOptions &LO = mgr.getLangOpts(); 493 return LO.CPlusPlus; 494 } 495