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