1 // MoveChecker.cpp - Check use of moved-from objects. - C++ ---------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This defines checker which checks for potential misuses of a moved-from 11 // object. That means method calls on the object or copying it in moved-from 12 // state. 13 // 14 //===----------------------------------------------------------------------===// 15 16 #include "ClangSACheckers.h" 17 #include "clang/AST/ExprCXX.h" 18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 19 #include "clang/StaticAnalyzer/Core/Checker.h" 20 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 23 #include "llvm/ADT/StringSet.h" 24 25 using namespace clang; 26 using namespace ento; 27 28 namespace { 29 30 struct RegionState { 31 private: 32 enum Kind { Moved, Reported } K; 33 RegionState(Kind InK) : K(InK) {} 34 35 public: 36 bool isReported() const { return K == Reported; } 37 bool isMoved() const { return K == Moved; } 38 39 static RegionState getReported() { return RegionState(Reported); } 40 static RegionState getMoved() { return RegionState(Moved); } 41 42 bool operator==(const RegionState &X) const { return K == X.K; } 43 void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } 44 }; 45 46 class MoveChecker 47 : public Checker<check::PreCall, check::PostCall, 48 check::DeadSymbols, check::RegionChanges> { 49 public: 50 void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; 51 void checkPreCall(const CallEvent &MC, CheckerContext &C) const; 52 void checkPostCall(const CallEvent &MC, CheckerContext &C) const; 53 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; 54 ProgramStateRef 55 checkRegionChanges(ProgramStateRef State, 56 const InvalidatedSymbols *Invalidated, 57 ArrayRef<const MemRegion *> RequestedRegions, 58 ArrayRef<const MemRegion *> InvalidatedRegions, 59 const LocationContext *LCtx, const CallEvent *Call) const; 60 void printState(raw_ostream &Out, ProgramStateRef State, 61 const char *NL, const char *Sep) const override; 62 63 private: 64 enum MisuseKind { MK_FunCall, MK_Copy, MK_Move }; 65 66 struct ObjectKind { 67 bool Local : 1; // Is this a local variable or a local rvalue reference? 68 bool STL : 1; // Is this an object of a standard type? 69 }; 70 71 // Not all of these are entirely move-safe, but they do provide *some* 72 // guarantees, and it means that somebody is using them after move 73 // in a valid manner. 74 // TODO: We can still try to identify *unsafe* use after move, such as 75 // dereference of a moved-from smart pointer (which is guaranteed to be null). 76 const llvm::StringSet<> StandardMoveSafeClasses = { 77 "basic_filebuf", 78 "basic_ios", 79 "future", 80 "optional", 81 "packaged_task" 82 "promise", 83 "shared_future", 84 "shared_lock", 85 "shared_ptr", 86 "thread", 87 "unique_ptr", 88 "unique_lock", 89 "weak_ptr", 90 }; 91 92 // Should we bother tracking the state of the object? 93 bool shouldBeTracked(ObjectKind OK) const { 94 // In non-aggressive mode, only warn on use-after-move of local variables 95 // (or local rvalue references) and of STL objects. The former is possible 96 // because local variables (or local rvalue references) are not tempting 97 // their user to re-use the storage. The latter is possible because STL 98 // objects are known to end up in a valid but unspecified state after the 99 // move and their state-reset methods are also known, which allows us to 100 // predict precisely when use-after-move is invalid. In aggressive mode, 101 // warn on any use-after-move because the user has intentionally asked us 102 // to completely eliminate use-after-move in his code. 103 return IsAggressive || OK.Local || OK.STL; 104 } 105 106 // Obtains ObjectKind of an object. Because class declaration cannot always 107 // be easily obtained from the memory region, it is supplied separately. 108 ObjectKind classifyObject(const MemRegion *MR, const CXXRecordDecl *RD) const; 109 110 // Classifies the object and dumps a user-friendly description string to 111 // the stream. Return value is equivalent to classifyObject. 112 ObjectKind explainObject(llvm::raw_ostream &OS, 113 const MemRegion *MR, const CXXRecordDecl *RD) const; 114 115 bool isStandardMoveSafeClass(const CXXRecordDecl *RD) const; 116 117 class MovedBugVisitor : public BugReporterVisitor { 118 public: 119 MovedBugVisitor(const MoveChecker &Chk, 120 const MemRegion *R, const CXXRecordDecl *RD) 121 : Chk(Chk), Region(R), RD(RD), Found(false) {} 122 123 void Profile(llvm::FoldingSetNodeID &ID) const override { 124 static int X = 0; 125 ID.AddPointer(&X); 126 ID.AddPointer(Region); 127 // Don't add RD because it's, in theory, uniquely determined by 128 // the region. In practice though, it's not always possible to obtain 129 // the declaration directly from the region, that's why we store it 130 // in the first place. 131 } 132 133 std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, 134 BugReporterContext &BRC, 135 BugReport &BR) override; 136 137 private: 138 const MoveChecker &Chk; 139 // The tracked region. 140 const MemRegion *Region; 141 // The class of the tracked object. 142 const CXXRecordDecl *RD; 143 bool Found; 144 }; 145 146 bool IsAggressive = false; 147 148 public: 149 void setAggressiveness(bool Aggressive) { IsAggressive = Aggressive; } 150 151 private: 152 mutable std::unique_ptr<BugType> BT; 153 154 // Check if the given form of potential misuse of a given object 155 // should be reported. If so, get it reported. The callback from which 156 // this function was called should immediately return after the call 157 // because this function adds one or two transitions. 158 void modelUse(ProgramStateRef State, const MemRegion *Region, 159 const CXXRecordDecl *RD, MisuseKind MK, 160 CheckerContext &C) const; 161 162 // Returns the exploded node against which the report was emitted. 163 // The caller *must* add any further transitions against this node. 164 ExplodedNode *reportBug(const MemRegion *Region, const CXXRecordDecl *RD, 165 CheckerContext &C, MisuseKind MK) const; 166 167 bool isInMoveSafeContext(const LocationContext *LC) const; 168 bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; 169 bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const; 170 const ExplodedNode *getMoveLocation(const ExplodedNode *N, 171 const MemRegion *Region, 172 CheckerContext &C) const; 173 }; 174 } // end anonymous namespace 175 176 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState) 177 178 // If a region is removed all of the subregions needs to be removed too. 179 static ProgramStateRef removeFromState(ProgramStateRef State, 180 const MemRegion *Region) { 181 if (!Region) 182 return State; 183 for (auto &E : State->get<TrackedRegionMap>()) { 184 if (E.first->isSubRegionOf(Region)) 185 State = State->remove<TrackedRegionMap>(E.first); 186 } 187 return State; 188 } 189 190 static bool isAnyBaseRegionReported(ProgramStateRef State, 191 const MemRegion *Region) { 192 for (auto &E : State->get<TrackedRegionMap>()) { 193 if (Region->isSubRegionOf(E.first) && E.second.isReported()) 194 return true; 195 } 196 return false; 197 } 198 199 static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) { 200 if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(MR)) { 201 SymbolRef Sym = SR->getSymbol(); 202 if (Sym->getType()->isRValueReferenceType()) 203 if (const MemRegion *OriginMR = Sym->getOriginRegion()) 204 return OriginMR; 205 } 206 return MR; 207 } 208 209 std::shared_ptr<PathDiagnosticPiece> 210 MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, 211 BugReporterContext &BRC, BugReport &BR) { 212 // We need only the last move of the reported object's region. 213 // The visitor walks the ExplodedGraph backwards. 214 if (Found) 215 return nullptr; 216 ProgramStateRef State = N->getState(); 217 ProgramStateRef StatePrev = N->getFirstPred()->getState(); 218 const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region); 219 const RegionState *TrackedObjectPrev = 220 StatePrev->get<TrackedRegionMap>(Region); 221 if (!TrackedObject) 222 return nullptr; 223 if (TrackedObjectPrev && TrackedObject) 224 return nullptr; 225 226 // Retrieve the associated statement. 227 const Stmt *S = PathDiagnosticLocation::getStmt(N); 228 if (!S) 229 return nullptr; 230 Found = true; 231 232 SmallString<128> Str; 233 llvm::raw_svector_ostream OS(Str); 234 235 OS << "Object"; 236 ObjectKind OK = Chk.explainObject(OS, Region, RD); 237 if (OK.STL) 238 OS << " is left in a valid but unspecified state after move"; 239 else 240 OS << " is moved"; 241 242 // Generate the extra diagnostic. 243 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), 244 N->getLocationContext()); 245 return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true); 246 } 247 248 const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N, 249 const MemRegion *Region, 250 CheckerContext &C) const { 251 // Walk the ExplodedGraph backwards and find the first node that referred to 252 // the tracked region. 253 const ExplodedNode *MoveNode = N; 254 255 while (N) { 256 ProgramStateRef State = N->getState(); 257 if (!State->get<TrackedRegionMap>(Region)) 258 break; 259 MoveNode = N; 260 N = N->pred_empty() ? nullptr : *(N->pred_begin()); 261 } 262 return MoveNode; 263 } 264 265 void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region, 266 const CXXRecordDecl *RD, MisuseKind MK, 267 CheckerContext &C) const { 268 assert(!C.isDifferent() && "No transitions should have been made by now"); 269 const RegionState *RS = State->get<TrackedRegionMap>(Region); 270 271 if (!RS || isAnyBaseRegionReported(State, Region) 272 || isInMoveSafeContext(C.getLocationContext())) { 273 // Finalize changes made by the caller. 274 C.addTransition(State); 275 return; 276 } 277 278 ExplodedNode *N = reportBug(Region, RD, C, MK); 279 280 State = State->set<TrackedRegionMap>(Region, RegionState::getReported()); 281 C.addTransition(State, N); 282 } 283 284 ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, 285 const CXXRecordDecl *RD, 286 CheckerContext &C, 287 MisuseKind MK) const { 288 if (ExplodedNode *N = C.generateNonFatalErrorNode()) { 289 if (!BT) 290 BT.reset(new BugType(this, "Use-after-move", 291 "C++ move semantics")); 292 293 // Uniqueing report to the same object. 294 PathDiagnosticLocation LocUsedForUniqueing; 295 const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); 296 297 if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) 298 LocUsedForUniqueing = PathDiagnosticLocation::createBegin( 299 MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); 300 301 // Creating the error message. 302 llvm::SmallString<128> Str; 303 llvm::raw_svector_ostream OS(Str); 304 switch(MK) { 305 case MK_FunCall: 306 OS << "Method called on moved-from object"; 307 explainObject(OS, Region, RD); 308 break; 309 case MK_Copy: 310 OS << "Moved-from object"; 311 explainObject(OS, Region, RD); 312 OS << " is copied"; 313 break; 314 case MK_Move: 315 OS << "Moved-from object"; 316 explainObject(OS, Region, RD); 317 OS << " is moved"; 318 break; 319 } 320 321 auto R = 322 llvm::make_unique<BugReport>(*BT, OS.str(), N, LocUsedForUniqueing, 323 MoveNode->getLocationContext()->getDecl()); 324 R->addVisitor(llvm::make_unique<MovedBugVisitor>(*this, Region, RD)); 325 C.emitReport(std::move(R)); 326 return N; 327 } 328 return nullptr; 329 } 330 331 void MoveChecker::checkPostCall(const CallEvent &Call, 332 CheckerContext &C) const { 333 const auto *AFC = dyn_cast<AnyFunctionCall>(&Call); 334 if (!AFC) 335 return; 336 337 ProgramStateRef State = C.getState(); 338 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl()); 339 if (!MethodDecl) 340 return; 341 342 // Check if an object became moved-from. 343 // Object can become moved from after a call to move assignment operator or 344 // move constructor . 345 const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl); 346 if (ConstructorDecl && !ConstructorDecl->isMoveConstructor()) 347 return; 348 349 if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator()) 350 return; 351 352 const auto ArgRegion = AFC->getArgSVal(0).getAsRegion(); 353 if (!ArgRegion) 354 return; 355 356 // Skip moving the object to itself. 357 const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call); 358 if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion) 359 return; 360 361 if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC)) 362 if (IC->getCXXThisVal().getAsRegion() == ArgRegion) 363 return; 364 365 const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); 366 // Skip temp objects because of their short lifetime. 367 if (BaseRegion->getAs<CXXTempObjectRegion>() || 368 AFC->getArgExpr(0)->isRValue()) 369 return; 370 // If it has already been reported do not need to modify the state. 371 372 if (State->get<TrackedRegionMap>(ArgRegion)) 373 return; 374 375 const CXXRecordDecl *RD = MethodDecl->getParent(); 376 ObjectKind OK = classifyObject(ArgRegion, RD); 377 if (shouldBeTracked(OK)) { 378 // Mark object as moved-from. 379 State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved()); 380 C.addTransition(State); 381 return; 382 } 383 assert(!C.isDifferent() && "Should not have made transitions on this path!"); 384 } 385 386 bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const { 387 // We abandon the cases where bool/void/void* conversion happens. 388 if (const auto *ConversionDec = 389 dyn_cast_or_null<CXXConversionDecl>(MethodDec)) { 390 const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull(); 391 if (!Tp) 392 return false; 393 if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType()) 394 return true; 395 } 396 // Function call `empty` can be skipped. 397 return (MethodDec && MethodDec->getDeclName().isIdentifier() && 398 (MethodDec->getName().lower() == "empty" || 399 MethodDec->getName().lower() == "isempty")); 400 } 401 402 bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const { 403 if (!MethodDec) 404 return false; 405 if (MethodDec->hasAttr<ReinitializesAttr>()) 406 return true; 407 if (MethodDec->getDeclName().isIdentifier()) { 408 std::string MethodName = MethodDec->getName().lower(); 409 // TODO: Some of these methods (eg., resize) are not always resetting 410 // the state, so we should consider looking at the arguments. 411 if (MethodName == "reset" || MethodName == "clear" || 412 MethodName == "destroy" || MethodName == "resize" || 413 MethodName == "shrink") 414 return true; 415 } 416 return false; 417 } 418 419 // Don't report an error inside a move related operation. 420 // We assume that the programmer knows what she does. 421 bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const { 422 do { 423 const auto *CtxDec = LC->getDecl(); 424 auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec); 425 auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec); 426 auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec); 427 if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || 428 (MethodDec && MethodDec->isOverloadedOperator() && 429 MethodDec->getOverloadedOperator() == OO_Equal) || 430 isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) 431 return true; 432 } while ((LC = LC->getParent())); 433 return false; 434 } 435 436 bool MoveChecker::isStandardMoveSafeClass(const CXXRecordDecl *RD) const { 437 const IdentifierInfo *II = RD->getIdentifier(); 438 return II && StandardMoveSafeClasses.count(II->getName()); 439 } 440 441 MoveChecker::ObjectKind 442 MoveChecker::classifyObject(const MemRegion *MR, 443 const CXXRecordDecl *RD) const { 444 // Local variables and local rvalue references are classified as "Local". 445 // For the purposes of this checker, we classify move-safe STL types 446 // as not-"STL" types, because that's how the checker treats them. 447 MR = unwrapRValueReferenceIndirection(MR); 448 return { 449 /*Local=*/ 450 MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace()), 451 /*STL=*/ 452 RD && RD->getDeclContext()->isStdNamespace() && 453 !isStandardMoveSafeClass(RD) 454 }; 455 } 456 457 MoveChecker::ObjectKind 458 MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR, 459 const CXXRecordDecl *RD) const { 460 // We may need a leading space every time we actually explain anything, 461 // and we never know if we are to explain anything until we try. 462 if (const auto DR = 463 dyn_cast_or_null<DeclRegion>(unwrapRValueReferenceIndirection(MR))) { 464 const auto *RegionDecl = cast<NamedDecl>(DR->getDecl()); 465 OS << " '" << RegionDecl->getNameAsString() << "'"; 466 } 467 ObjectKind OK = classifyObject(MR, RD); 468 if (OK.STL) { 469 OS << " of type '" << RD->getQualifiedNameAsString() << "'"; 470 } 471 return OK; 472 } 473 474 void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { 475 ProgramStateRef State = C.getState(); 476 477 // Remove the MemRegions from the map on which a ctor/dtor call or assignment 478 // happened. 479 480 // Checking constructor calls. 481 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { 482 State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); 483 auto CtorDec = CC->getDecl(); 484 // Check for copying a moved-from object and report the bug. 485 if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { 486 const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); 487 const CXXRecordDecl *RD = CtorDec->getParent(); 488 MisuseKind MK = CtorDec->isMoveConstructor() ? MK_Move : MK_Copy; 489 modelUse(State, ArgRegion, RD, MK, C); 490 return; 491 } 492 } 493 494 const auto IC = dyn_cast<CXXInstanceCall>(&Call); 495 if (!IC) 496 return; 497 498 // Calling a destructor on a moved object is fine. 499 if (isa<CXXDestructorCall>(IC)) 500 return; 501 502 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 503 if (!ThisRegion) 504 return; 505 506 // The remaining part is check only for method call on a moved-from object. 507 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl()); 508 if (!MethodDecl) 509 return; 510 511 // We want to investigate the whole object, not only sub-object of a parent 512 // class in which the encountered method defined. 513 ThisRegion = ThisRegion->getMostDerivedObjectRegion(); 514 515 if (isStateResetMethod(MethodDecl)) { 516 State = removeFromState(State, ThisRegion); 517 C.addTransition(State); 518 return; 519 } 520 521 if (isMoveSafeMethod(MethodDecl)) 522 return; 523 524 // Store class declaration as well, for bug reporting purposes. 525 const CXXRecordDecl *RD = MethodDecl->getParent(); 526 527 if (MethodDecl->isOverloadedOperator()) { 528 OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator(); 529 530 if (OOK == OO_Equal) { 531 // Remove the tracked object for every assignment operator, but report bug 532 // only for move or copy assignment's argument. 533 State = removeFromState(State, ThisRegion); 534 535 if (MethodDecl->isCopyAssignmentOperator() || 536 MethodDecl->isMoveAssignmentOperator()) { 537 const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); 538 MisuseKind MK = 539 MethodDecl->isMoveAssignmentOperator() ? MK_Move : MK_Copy; 540 modelUse(State, ArgRegion, RD, MK, C); 541 return; 542 } 543 C.addTransition(State); 544 return; 545 } 546 } 547 548 modelUse(State, ThisRegion, RD, MK_FunCall, C); 549 } 550 551 void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper, 552 CheckerContext &C) const { 553 ProgramStateRef State = C.getState(); 554 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); 555 for (TrackedRegionMapTy::value_type E : TrackedRegions) { 556 const MemRegion *Region = E.first; 557 bool IsRegDead = !SymReaper.isLiveRegion(Region); 558 559 // Remove the dead regions from the region map. 560 if (IsRegDead) { 561 State = State->remove<TrackedRegionMap>(Region); 562 } 563 } 564 C.addTransition(State); 565 } 566 567 ProgramStateRef MoveChecker::checkRegionChanges( 568 ProgramStateRef State, const InvalidatedSymbols *Invalidated, 569 ArrayRef<const MemRegion *> RequestedRegions, 570 ArrayRef<const MemRegion *> InvalidatedRegions, 571 const LocationContext *LCtx, const CallEvent *Call) const { 572 if (Call) { 573 // Relax invalidation upon function calls: only invalidate parameters 574 // that are passed directly via non-const pointers or non-const references 575 // or rvalue references. 576 // In case of an InstanceCall don't invalidate the this-region since 577 // it is fully handled in checkPreCall and checkPostCall. 578 const MemRegion *ThisRegion = nullptr; 579 if (const auto *IC = dyn_cast<CXXInstanceCall>(Call)) 580 ThisRegion = IC->getCXXThisVal().getAsRegion(); 581 582 // Requested ("explicit") regions are the regions passed into the call 583 // directly, but not all of them end up being invalidated. 584 // But when they do, they appear in the InvalidatedRegions array as well. 585 for (const auto *Region : RequestedRegions) { 586 if (ThisRegion != Region) { 587 if (llvm::find(InvalidatedRegions, Region) != 588 std::end(InvalidatedRegions)) { 589 State = removeFromState(State, Region); 590 } 591 } 592 } 593 } else { 594 // For invalidations that aren't caused by calls, assume nothing. In 595 // particular, direct write into an object's field invalidates the status. 596 for (const auto *Region : InvalidatedRegions) 597 State = removeFromState(State, Region->getBaseRegion()); 598 } 599 600 return State; 601 } 602 603 void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State, 604 const char *NL, const char *Sep) const { 605 606 TrackedRegionMapTy RS = State->get<TrackedRegionMap>(); 607 608 if (!RS.isEmpty()) { 609 Out << Sep << "Moved-from objects :" << NL; 610 for (auto I: RS) { 611 I.first->dumpToStream(Out); 612 if (I.second.isMoved()) 613 Out << ": moved"; 614 else 615 Out << ": moved and reported"; 616 Out << NL; 617 } 618 } 619 } 620 void ento::registerMoveChecker(CheckerManager &mgr) { 621 MoveChecker *chk = mgr.registerChecker<MoveChecker>(); 622 chk->setAggressiveness(mgr.getAnalyzerOptions().getCheckerBooleanOption( 623 "Aggressive", false, chk)); 624 } 625