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 *> RequestedRegions, 57 ArrayRef<const MemRegion *> InvalidatedRegions, 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 // TODO: Some of these methods (eg., resize) are not always resetting 347 // the state, so we should consider looking at the arguments. 348 if (MethodName == "reset" || MethodName == "clear" || 349 MethodName == "destroy" || MethodName == "resize" || 350 MethodName == "shrink") 351 return true; 352 } 353 return false; 354 } 355 356 // Don't report an error inside a move related operation. 357 // We assume that the programmer knows what she does. 358 bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const { 359 do { 360 const auto *CtxDec = LC->getDecl(); 361 auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec); 362 auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec); 363 auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec); 364 if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || 365 (MethodDec && MethodDec->isOverloadedOperator() && 366 MethodDec->getOverloadedOperator() == OO_Equal) || 367 isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) 368 return true; 369 } while ((LC = LC->getParent())); 370 return false; 371 } 372 373 MoveChecker::ObjectKind MoveChecker::classifyObject(const MemRegion *MR, 374 const CXXRecordDecl *RD) { 375 MR = unwrapRValueReferenceIndirection(MR); 376 return { 377 /*Local=*/ 378 MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace()), 379 /*STL=*/ 380 RD && RD->getDeclContext()->isStdNamespace() 381 }; 382 } 383 384 MoveChecker::ObjectKind MoveChecker::explainObject(llvm::raw_ostream &OS, 385 const MemRegion *MR, 386 const CXXRecordDecl *RD) { 387 // We may need a leading space every time we actually explain anything, 388 // and we never know if we are to explain anything until we try. 389 if (const auto DR = 390 dyn_cast_or_null<DeclRegion>(unwrapRValueReferenceIndirection(MR))) { 391 const auto *RegionDecl = cast<NamedDecl>(DR->getDecl()); 392 OS << " '" << RegionDecl->getNameAsString() << "'"; 393 } 394 ObjectKind OK = classifyObject(MR, RD); 395 if (OK.STL) { 396 OS << " of type '" << RD->getQualifiedNameAsString() << "'"; 397 } 398 return OK; 399 } 400 401 void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { 402 ProgramStateRef State = C.getState(); 403 const LocationContext *LC = C.getLocationContext(); 404 ExplodedNode *N = nullptr; 405 406 // Remove the MemRegions from the map on which a ctor/dtor call or assignment 407 // happened. 408 409 // Checking constructor calls. 410 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { 411 State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); 412 auto CtorDec = CC->getDecl(); 413 // Check for copying a moved-from object and report the bug. 414 if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { 415 const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); 416 const RegionState *ArgState = State->get<TrackedRegionMap>(ArgRegion); 417 if (ArgState && ArgState->isMoved()) { 418 if (!isInMoveSafeContext(LC)) { 419 const CXXRecordDecl *RD = CtorDec->getParent(); 420 if(CtorDec->isMoveConstructor()) 421 N = reportBug(ArgRegion, RD, C, MK_Move); 422 else 423 N = reportBug(ArgRegion, RD, C, MK_Copy); 424 State = State->set<TrackedRegionMap>(ArgRegion, 425 RegionState::getReported()); 426 } 427 } 428 } 429 C.addTransition(State, N); 430 return; 431 } 432 433 const auto IC = dyn_cast<CXXInstanceCall>(&Call); 434 if (!IC) 435 return; 436 437 // Calling a destructor on a moved object is fine. 438 if (isa<CXXDestructorCall>(IC)) 439 return; 440 441 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 442 if (!ThisRegion) 443 return; 444 445 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl()); 446 if (!MethodDecl) 447 return; 448 449 // Store class declaration as well, for bug reporting purposes. 450 const CXXRecordDecl *RD = MethodDecl->getParent(); 451 452 // Checking assignment operators. 453 bool OperatorEq = MethodDecl->isOverloadedOperator() && 454 MethodDecl->getOverloadedOperator() == OO_Equal; 455 // Remove the tracked object for every assignment operator, but report bug 456 // only for move or copy assignment's argument. 457 if (OperatorEq) { 458 State = removeFromState(State, ThisRegion); 459 if (MethodDecl->isCopyAssignmentOperator() || 460 MethodDecl->isMoveAssignmentOperator()) { 461 const RegionState *ArgState = 462 State->get<TrackedRegionMap>(IC->getArgSVal(0).getAsRegion()); 463 if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) { 464 const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); 465 if(MethodDecl->isMoveAssignmentOperator()) 466 N = reportBug(ArgRegion, RD, C, MK_Move); 467 else 468 N = reportBug(ArgRegion, RD, C, MK_Copy); 469 State = 470 State->set<TrackedRegionMap>(ArgRegion, RegionState::getReported()); 471 } 472 } 473 C.addTransition(State, N); 474 return; 475 } 476 477 // The remaining part is check only for method call on a moved-from object. 478 479 // We want to investigate the whole object, not only sub-object of a parent 480 // class in which the encountered method defined. 481 while (const auto *BR = dyn_cast<CXXBaseObjectRegion>(ThisRegion)) 482 ThisRegion = BR->getSuperRegion(); 483 484 if (isMoveSafeMethod(MethodDecl)) 485 return; 486 487 if (isStateResetMethod(MethodDecl)) { 488 State = removeFromState(State, ThisRegion); 489 C.addTransition(State); 490 return; 491 } 492 493 // If it is already reported then we don't report the bug again. 494 const RegionState *ThisState = State->get<TrackedRegionMap>(ThisRegion); 495 if (!(ThisState && ThisState->isMoved())) 496 return; 497 498 // Don't report it in case if any base region is already reported 499 if (isAnyBaseRegionReported(State, ThisRegion)) 500 return; 501 502 if (isInMoveSafeContext(LC)) 503 return; 504 505 N = reportBug(ThisRegion, RD, C, MK_FunCall); 506 State = State->set<TrackedRegionMap>(ThisRegion, RegionState::getReported()); 507 C.addTransition(State, N); 508 } 509 510 void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper, 511 CheckerContext &C) const { 512 ProgramStateRef State = C.getState(); 513 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); 514 for (TrackedRegionMapTy::value_type E : TrackedRegions) { 515 const MemRegion *Region = E.first; 516 bool IsRegDead = !SymReaper.isLiveRegion(Region); 517 518 // Remove the dead regions from the region map. 519 if (IsRegDead) { 520 State = State->remove<TrackedRegionMap>(Region); 521 } 522 } 523 C.addTransition(State); 524 } 525 526 ProgramStateRef MoveChecker::checkRegionChanges( 527 ProgramStateRef State, const InvalidatedSymbols *Invalidated, 528 ArrayRef<const MemRegion *> RequestedRegions, 529 ArrayRef<const MemRegion *> InvalidatedRegions, 530 const LocationContext *LCtx, const CallEvent *Call) const { 531 if (Call) { 532 // Relax invalidation upon function calls: only invalidate parameters 533 // that are passed directly via non-const pointers or non-const references 534 // or rvalue references. 535 // In case of an InstanceCall don't invalidate the this-region since 536 // it is fully handled in checkPreCall and checkPostCall. 537 const MemRegion *ThisRegion = nullptr; 538 if (const auto *IC = dyn_cast<CXXInstanceCall>(Call)) 539 ThisRegion = IC->getCXXThisVal().getAsRegion(); 540 541 // Requested ("explicit") regions are the regions passed into the call 542 // directly, but not all of them end up being invalidated. 543 // But when they do, they appear in the InvalidatedRegions array as well. 544 for (const auto *Region : RequestedRegions) { 545 if (ThisRegion != Region) { 546 if (llvm::find(InvalidatedRegions, Region) != 547 std::end(InvalidatedRegions)) { 548 State = removeFromState(State, Region); 549 } 550 } 551 } 552 } else { 553 // For invalidations that aren't caused by calls, assume nothing. In 554 // particular, direct write into an object's field invalidates the status. 555 for (const auto *Region : InvalidatedRegions) 556 State = removeFromState(State, Region->getBaseRegion()); 557 } 558 559 return State; 560 } 561 562 void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State, 563 const char *NL, const char *Sep) const { 564 565 TrackedRegionMapTy RS = State->get<TrackedRegionMap>(); 566 567 if (!RS.isEmpty()) { 568 Out << Sep << "Moved-from objects :" << NL; 569 for (auto I: RS) { 570 I.first->dumpToStream(Out); 571 if (I.second.isMoved()) 572 Out << ": moved"; 573 else 574 Out << ": moved and reported"; 575 Out << NL; 576 } 577 } 578 } 579 void ento::registerMoveChecker(CheckerManager &mgr) { 580 MoveChecker *chk = mgr.registerChecker<MoveChecker>(); 581 chk->setAggressiveness(mgr.getAnalyzerOptions().getCheckerBooleanOption( 582 "Aggressive", false, chk)); 583 } 584