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