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, check::EndFunction, 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 class MovedBugVisitor : public BugReporterVisitor { 65 public: 66 MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {} 67 68 void Profile(llvm::FoldingSetNodeID &ID) const override { 69 static int X = 0; 70 ID.AddPointer(&X); 71 ID.AddPointer(Region); 72 } 73 74 std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, 75 BugReporterContext &BRC, 76 BugReport &BR) override; 77 78 private: 79 // The tracked region. 80 const MemRegion *Region; 81 bool Found; 82 }; 83 84 mutable std::unique_ptr<BugType> BT; 85 ExplodedNode *reportBug(const MemRegion *Region, const CallEvent &Call, 86 CheckerContext &C, MisuseKind MK) const; 87 bool isInMoveSafeContext(const LocationContext *LC) const; 88 bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; 89 bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const; 90 const ExplodedNode *getMoveLocation(const ExplodedNode *N, 91 const MemRegion *Region, 92 CheckerContext &C) const; 93 }; 94 } // end anonymous namespace 95 96 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState) 97 98 // If a region is removed all of the subregions needs to be removed too. 99 static ProgramStateRef removeFromState(ProgramStateRef State, 100 const MemRegion *Region) { 101 if (!Region) 102 return State; 103 for (auto &E : State->get<TrackedRegionMap>()) { 104 if (E.first->isSubRegionOf(Region)) 105 State = State->remove<TrackedRegionMap>(E.first); 106 } 107 return State; 108 } 109 110 static bool isAnyBaseRegionReported(ProgramStateRef State, 111 const MemRegion *Region) { 112 for (auto &E : State->get<TrackedRegionMap>()) { 113 if (Region->isSubRegionOf(E.first) && E.second.isReported()) 114 return true; 115 } 116 return false; 117 } 118 119 std::shared_ptr<PathDiagnosticPiece> 120 MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, 121 BugReporterContext &BRC, BugReport &) { 122 // We need only the last move of the reported object's region. 123 // The visitor walks the ExplodedGraph backwards. 124 if (Found) 125 return nullptr; 126 ProgramStateRef State = N->getState(); 127 ProgramStateRef StatePrev = N->getFirstPred()->getState(); 128 const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region); 129 const RegionState *TrackedObjectPrev = 130 StatePrev->get<TrackedRegionMap>(Region); 131 if (!TrackedObject) 132 return nullptr; 133 if (TrackedObjectPrev && TrackedObject) 134 return nullptr; 135 136 // Retrieve the associated statement. 137 const Stmt *S = PathDiagnosticLocation::getStmt(N); 138 if (!S) 139 return nullptr; 140 Found = true; 141 142 std::string ObjectName; 143 if (const auto DecReg = Region->getAs<DeclRegion>()) { 144 const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl()); 145 ObjectName = RegionDecl->getNameAsString(); 146 } 147 std::string InfoText; 148 if (ObjectName != "") 149 InfoText = "'" + ObjectName + "' became 'moved-from' here"; 150 else 151 InfoText = "Became 'moved-from' here"; 152 153 // Generate the extra diagnostic. 154 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), 155 N->getLocationContext()); 156 return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true); 157 } 158 159 const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N, 160 const MemRegion *Region, 161 CheckerContext &C) const { 162 // Walk the ExplodedGraph backwards and find the first node that referred to 163 // the tracked region. 164 const ExplodedNode *MoveNode = N; 165 166 while (N) { 167 ProgramStateRef State = N->getState(); 168 if (!State->get<TrackedRegionMap>(Region)) 169 break; 170 MoveNode = N; 171 N = N->pred_empty() ? nullptr : *(N->pred_begin()); 172 } 173 return MoveNode; 174 } 175 176 ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, 177 const CallEvent &Call, CheckerContext &C, 178 MisuseKind MK) const { 179 if (ExplodedNode *N = C.generateNonFatalErrorNode()) { 180 if (!BT) 181 BT.reset(new BugType(this, "Usage of a 'moved-from' object", 182 "C++ move semantics")); 183 184 // Uniqueing report to the same object. 185 PathDiagnosticLocation LocUsedForUniqueing; 186 const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); 187 188 if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) 189 LocUsedForUniqueing = PathDiagnosticLocation::createBegin( 190 MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); 191 192 // Creating the error message. 193 std::string ErrorMessage; 194 switch(MK) { 195 case MK_FunCall: 196 ErrorMessage = "Method call on a 'moved-from' object"; 197 break; 198 case MK_Copy: 199 ErrorMessage = "Copying a 'moved-from' object"; 200 break; 201 case MK_Move: 202 ErrorMessage = "Moving a 'moved-from' object"; 203 break; 204 } 205 if (const auto DecReg = Region->getAs<DeclRegion>()) { 206 const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl()); 207 ErrorMessage += " '" + RegionDecl->getNameAsString() + "'"; 208 } 209 210 auto R = 211 llvm::make_unique<BugReport>(*BT, ErrorMessage, N, LocUsedForUniqueing, 212 MoveNode->getLocationContext()->getDecl()); 213 R->addVisitor(llvm::make_unique<MovedBugVisitor>(Region)); 214 C.emitReport(std::move(R)); 215 return N; 216 } 217 return nullptr; 218 } 219 220 // Removing the function parameters' MemRegion from the state. This is needed 221 // for PODs where the trivial destructor does not even created nor executed. 222 void MoveChecker::checkEndFunction(const ReturnStmt *RS, 223 CheckerContext &C) const { 224 auto State = C.getState(); 225 TrackedRegionMapTy Objects = State->get<TrackedRegionMap>(); 226 if (Objects.isEmpty()) 227 return; 228 229 auto LC = C.getLocationContext(); 230 231 const auto LD = dyn_cast_or_null<FunctionDecl>(LC->getDecl()); 232 if (!LD) 233 return; 234 llvm::SmallSet<const MemRegion *, 8> InvalidRegions; 235 236 for (auto Param : LD->parameters()) { 237 auto Type = Param->getType().getTypePtrOrNull(); 238 if (!Type) 239 continue; 240 if (!Type->isPointerType() && !Type->isReferenceType()) { 241 InvalidRegions.insert(State->getLValue(Param, LC).getAsRegion()); 242 } 243 } 244 245 if (InvalidRegions.empty()) 246 return; 247 248 for (const auto &E : State->get<TrackedRegionMap>()) { 249 if (InvalidRegions.count(E.first->getBaseRegion())) 250 State = State->remove<TrackedRegionMap>(E.first); 251 } 252 253 C.addTransition(State); 254 } 255 256 void MoveChecker::checkPostCall(const CallEvent &Call, 257 CheckerContext &C) const { 258 const auto *AFC = dyn_cast<AnyFunctionCall>(&Call); 259 if (!AFC) 260 return; 261 262 ProgramStateRef State = C.getState(); 263 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl()); 264 if (!MethodDecl) 265 return; 266 267 const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl); 268 269 const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call); 270 // Check if an object became moved-from. 271 // Object can become moved from after a call to move assignment operator or 272 // move constructor . 273 if (ConstructorDecl && !ConstructorDecl->isMoveConstructor()) 274 return; 275 276 if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator()) 277 return; 278 279 const auto ArgRegion = AFC->getArgSVal(0).getAsRegion(); 280 if (!ArgRegion) 281 return; 282 283 // Skip moving the object to itself. 284 if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion) 285 return; 286 if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC)) 287 if (IC->getCXXThisVal().getAsRegion() == ArgRegion) 288 return; 289 290 const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); 291 // Skip temp objects because of their short lifetime. 292 if (BaseRegion->getAs<CXXTempObjectRegion>() || 293 AFC->getArgExpr(0)->isRValue()) 294 return; 295 // If it has already been reported do not need to modify the state. 296 297 if (State->get<TrackedRegionMap>(ArgRegion)) 298 return; 299 // Mark object as moved-from. 300 State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved()); 301 C.addTransition(State); 302 } 303 304 bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const { 305 // We abandon the cases where bool/void/void* conversion happens. 306 if (const auto *ConversionDec = 307 dyn_cast_or_null<CXXConversionDecl>(MethodDec)) { 308 const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull(); 309 if (!Tp) 310 return false; 311 if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType()) 312 return true; 313 } 314 // Function call `empty` can be skipped. 315 return (MethodDec && MethodDec->getDeclName().isIdentifier() && 316 (MethodDec->getName().lower() == "empty" || 317 MethodDec->getName().lower() == "isempty")); 318 } 319 320 bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const { 321 if (!MethodDec) 322 return false; 323 if (MethodDec->hasAttr<ReinitializesAttr>()) 324 return true; 325 if (MethodDec->getDeclName().isIdentifier()) { 326 std::string MethodName = MethodDec->getName().lower(); 327 if (MethodName == "reset" || MethodName == "clear" || 328 MethodName == "destroy") 329 return true; 330 } 331 return false; 332 } 333 334 // Don't report an error inside a move related operation. 335 // We assume that the programmer knows what she does. 336 bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const { 337 do { 338 const auto *CtxDec = LC->getDecl(); 339 auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec); 340 auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec); 341 auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec); 342 if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || 343 (MethodDec && MethodDec->isOverloadedOperator() && 344 MethodDec->getOverloadedOperator() == OO_Equal) || 345 isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) 346 return true; 347 } while ((LC = LC->getParent())); 348 return false; 349 } 350 351 void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { 352 ProgramStateRef State = C.getState(); 353 const LocationContext *LC = C.getLocationContext(); 354 ExplodedNode *N = nullptr; 355 356 // Remove the MemRegions from the map on which a ctor/dtor call or assignment 357 // happened. 358 359 // Checking constructor calls. 360 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { 361 State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); 362 auto CtorDec = CC->getDecl(); 363 // Check for copying a moved-from object and report the bug. 364 if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { 365 const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); 366 const RegionState *ArgState = State->get<TrackedRegionMap>(ArgRegion); 367 if (ArgState && ArgState->isMoved()) { 368 if (!isInMoveSafeContext(LC)) { 369 if(CtorDec->isMoveConstructor()) 370 N = reportBug(ArgRegion, Call, C, MK_Move); 371 else 372 N = reportBug(ArgRegion, Call, C, MK_Copy); 373 State = State->set<TrackedRegionMap>(ArgRegion, 374 RegionState::getReported()); 375 } 376 } 377 } 378 C.addTransition(State, N); 379 return; 380 } 381 382 const auto IC = dyn_cast<CXXInstanceCall>(&Call); 383 if (!IC) 384 return; 385 // In case of destructor call we do not track the object anymore. 386 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); 387 if (!ThisRegion) 388 return; 389 390 if (dyn_cast_or_null<CXXDestructorDecl>(Call.getDecl())) { 391 State = removeFromState(State, ThisRegion); 392 C.addTransition(State); 393 return; 394 } 395 396 const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl()); 397 if (!MethodDecl) 398 return; 399 // Checking assignment operators. 400 bool OperatorEq = MethodDecl->isOverloadedOperator() && 401 MethodDecl->getOverloadedOperator() == OO_Equal; 402 // Remove the tracked object for every assignment operator, but report bug 403 // only for move or copy assignment's argument. 404 if (OperatorEq) { 405 State = removeFromState(State, ThisRegion); 406 if (MethodDecl->isCopyAssignmentOperator() || 407 MethodDecl->isMoveAssignmentOperator()) { 408 const RegionState *ArgState = 409 State->get<TrackedRegionMap>(IC->getArgSVal(0).getAsRegion()); 410 if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) { 411 const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); 412 if(MethodDecl->isMoveAssignmentOperator()) 413 N = reportBug(ArgRegion, Call, C, MK_Move); 414 else 415 N = reportBug(ArgRegion, Call, C, MK_Copy); 416 State = 417 State->set<TrackedRegionMap>(ArgRegion, RegionState::getReported()); 418 } 419 } 420 C.addTransition(State, N); 421 return; 422 } 423 424 // The remaining part is check only for method call on a moved-from object. 425 426 // We want to investigate the whole object, not only sub-object of a parent 427 // class in which the encountered method defined. 428 while (const auto *BR = dyn_cast<CXXBaseObjectRegion>(ThisRegion)) 429 ThisRegion = BR->getSuperRegion(); 430 431 if (isMoveSafeMethod(MethodDecl)) 432 return; 433 434 if (isStateResetMethod(MethodDecl)) { 435 State = removeFromState(State, ThisRegion); 436 C.addTransition(State); 437 return; 438 } 439 440 // If it is already reported then we don't report the bug again. 441 const RegionState *ThisState = State->get<TrackedRegionMap>(ThisRegion); 442 if (!(ThisState && ThisState->isMoved())) 443 return; 444 445 // Don't report it in case if any base region is already reported 446 if (isAnyBaseRegionReported(State, ThisRegion)) 447 return; 448 449 if (isInMoveSafeContext(LC)) 450 return; 451 452 N = reportBug(ThisRegion, Call, C, MK_FunCall); 453 State = State->set<TrackedRegionMap>(ThisRegion, RegionState::getReported()); 454 C.addTransition(State, N); 455 } 456 457 void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper, 458 CheckerContext &C) const { 459 ProgramStateRef State = C.getState(); 460 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); 461 for (TrackedRegionMapTy::value_type E : TrackedRegions) { 462 const MemRegion *Region = E.first; 463 bool IsRegDead = !SymReaper.isLiveRegion(Region); 464 465 // Remove the dead regions from the region map. 466 if (IsRegDead) { 467 State = State->remove<TrackedRegionMap>(Region); 468 } 469 } 470 C.addTransition(State); 471 } 472 473 ProgramStateRef MoveChecker::checkRegionChanges( 474 ProgramStateRef State, const InvalidatedSymbols *Invalidated, 475 ArrayRef<const MemRegion *> ExplicitRegions, 476 ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, 477 const CallEvent *Call) const { 478 // In case of an InstanceCall don't remove the ThisRegion from the GDM since 479 // it is handled in checkPreCall and checkPostCall. 480 const MemRegion *ThisRegion = nullptr; 481 if (const auto *IC = dyn_cast_or_null<CXXInstanceCall>(Call)) { 482 ThisRegion = IC->getCXXThisVal().getAsRegion(); 483 } 484 485 for (const auto *Region : ExplicitRegions) { 486 if (ThisRegion != Region) 487 State = removeFromState(State, Region); 488 } 489 490 return State; 491 } 492 493 void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State, 494 const char *NL, const char *Sep) const { 495 496 TrackedRegionMapTy RS = State->get<TrackedRegionMap>(); 497 498 if (!RS.isEmpty()) { 499 Out << Sep << "Moved-from objects :" << NL; 500 for (auto I: RS) { 501 I.first->dumpToStream(Out); 502 if (I.second.isMoved()) 503 Out << ": moved"; 504 else 505 Out << ": moved and reported"; 506 Out << NL; 507 } 508 } 509 } 510 void ento::registerMoveChecker(CheckerManager &mgr) { 511 mgr.registerChecker<MoveChecker>(); 512 } 513