1 //==- DeadStoresChecker.cpp - Check for stores to dead variables -*- C++ -*-==// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines a DeadStores, a flow-sensitive checker that looks for 10 // stores to variables that are no longer live. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/AST/ASTContext.h" 15 #include "clang/AST/Attr.h" 16 #include "clang/AST/ParentMap.h" 17 #include "clang/AST/RecursiveASTVisitor.h" 18 #include "clang/Analysis/Analyses/LiveVariables.h" 19 #include "clang/Lex/Lexer.h" 20 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 21 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 22 #include "clang/StaticAnalyzer/Core/Checker.h" 23 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 24 #include "llvm/ADT/BitVector.h" 25 #include "llvm/ADT/STLExtras.h" 26 #include "llvm/ADT/SmallString.h" 27 #include "llvm/Support/SaveAndRestore.h" 28 29 using namespace clang; 30 using namespace ento; 31 32 namespace { 33 34 /// A simple visitor to record what VarDecls occur in EH-handling code. 35 class EHCodeVisitor : public RecursiveASTVisitor<EHCodeVisitor> { 36 public: 37 bool inEH; 38 llvm::DenseSet<const VarDecl *> &S; 39 40 bool TraverseObjCAtFinallyStmt(ObjCAtFinallyStmt *S) { 41 SaveAndRestore<bool> inFinally(inEH, true); 42 return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtFinallyStmt(S); 43 } 44 45 bool TraverseObjCAtCatchStmt(ObjCAtCatchStmt *S) { 46 SaveAndRestore<bool> inCatch(inEH, true); 47 return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtCatchStmt(S); 48 } 49 50 bool TraverseCXXCatchStmt(CXXCatchStmt *S) { 51 SaveAndRestore<bool> inCatch(inEH, true); 52 return TraverseStmt(S->getHandlerBlock()); 53 } 54 55 bool VisitDeclRefExpr(DeclRefExpr *DR) { 56 if (inEH) 57 if (const VarDecl *D = dyn_cast<VarDecl>(DR->getDecl())) 58 S.insert(D); 59 return true; 60 } 61 62 EHCodeVisitor(llvm::DenseSet<const VarDecl *> &S) : 63 inEH(false), S(S) {} 64 }; 65 66 // FIXME: Eventually migrate into its own file, and have it managed by 67 // AnalysisManager. 68 class ReachableCode { 69 const CFG &cfg; 70 llvm::BitVector reachable; 71 public: 72 ReachableCode(const CFG &cfg) 73 : cfg(cfg), reachable(cfg.getNumBlockIDs(), false) {} 74 75 void computeReachableBlocks(); 76 77 bool isReachable(const CFGBlock *block) const { 78 return reachable[block->getBlockID()]; 79 } 80 }; 81 } 82 83 void ReachableCode::computeReachableBlocks() { 84 if (!cfg.getNumBlockIDs()) 85 return; 86 87 SmallVector<const CFGBlock*, 10> worklist; 88 worklist.push_back(&cfg.getEntry()); 89 90 while (!worklist.empty()) { 91 const CFGBlock *block = worklist.pop_back_val(); 92 llvm::BitVector::reference isReachable = reachable[block->getBlockID()]; 93 if (isReachable) 94 continue; 95 isReachable = true; 96 for (CFGBlock::const_succ_iterator i = block->succ_begin(), 97 e = block->succ_end(); i != e; ++i) 98 if (const CFGBlock *succ = *i) 99 worklist.push_back(succ); 100 } 101 } 102 103 static const Expr * 104 LookThroughTransitiveAssignmentsAndCommaOperators(const Expr *Ex) { 105 while (Ex) { 106 const BinaryOperator *BO = 107 dyn_cast<BinaryOperator>(Ex->IgnoreParenCasts()); 108 if (!BO) 109 break; 110 if (BO->getOpcode() == BO_Assign) { 111 Ex = BO->getRHS(); 112 continue; 113 } 114 if (BO->getOpcode() == BO_Comma) { 115 Ex = BO->getRHS(); 116 continue; 117 } 118 break; 119 } 120 return Ex; 121 } 122 123 namespace { 124 class DeadStoresChecker : public Checker<check::ASTCodeBody> { 125 public: 126 bool ShowFixIts = false; 127 bool WarnForDeadNestedAssignments = true; 128 129 void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, 130 BugReporter &BR) const; 131 }; 132 133 class DeadStoreObs : public LiveVariables::Observer { 134 const CFG &cfg; 135 ASTContext &Ctx; 136 BugReporter& BR; 137 const DeadStoresChecker *Checker; 138 AnalysisDeclContext* AC; 139 ParentMap& Parents; 140 llvm::SmallPtrSet<const VarDecl*, 20> Escaped; 141 std::unique_ptr<ReachableCode> reachableCode; 142 const CFGBlock *currentBlock; 143 std::unique_ptr<llvm::DenseSet<const VarDecl *>> InEH; 144 145 enum DeadStoreKind { Standard, Enclosing, DeadIncrement, DeadInit }; 146 147 public: 148 DeadStoreObs(const CFG &cfg, ASTContext &ctx, BugReporter &br, 149 const DeadStoresChecker *checker, AnalysisDeclContext *ac, 150 ParentMap &parents, 151 llvm::SmallPtrSet<const VarDecl *, 20> &escaped, 152 bool warnForDeadNestedAssignments) 153 : cfg(cfg), Ctx(ctx), BR(br), Checker(checker), AC(ac), Parents(parents), 154 Escaped(escaped), currentBlock(nullptr) {} 155 156 ~DeadStoreObs() override {} 157 158 bool isLive(const LiveVariables::LivenessValues &Live, const VarDecl *D) { 159 if (Live.isLive(D)) 160 return true; 161 // Lazily construct the set that records which VarDecls are in 162 // EH code. 163 if (!InEH.get()) { 164 InEH.reset(new llvm::DenseSet<const VarDecl *>()); 165 EHCodeVisitor V(*InEH.get()); 166 V.TraverseStmt(AC->getBody()); 167 } 168 // Treat all VarDecls that occur in EH code as being "always live" 169 // when considering to suppress dead stores. Frequently stores 170 // are followed by reads in EH code, but we don't have the ability 171 // to analyze that yet. 172 return InEH->count(D); 173 } 174 175 bool isSuppressed(SourceRange R) { 176 SourceManager &SMgr = Ctx.getSourceManager(); 177 SourceLocation Loc = R.getBegin(); 178 if (!Loc.isValid()) 179 return false; 180 181 FileID FID = SMgr.getFileID(Loc); 182 bool Invalid = false; 183 StringRef Data = SMgr.getBufferData(FID, &Invalid); 184 if (Invalid) 185 return false; 186 187 // Files autogenerated by DriverKit IIG contain some dead stores that 188 // we don't want to report. 189 if (Data.startswith("/* iig")) 190 return true; 191 192 return false; 193 } 194 195 void Report(const VarDecl *V, DeadStoreKind dsk, 196 PathDiagnosticLocation L, SourceRange R) { 197 if (Escaped.count(V)) 198 return; 199 200 // Compute reachable blocks within the CFG for trivial cases 201 // where a bogus dead store can be reported because itself is unreachable. 202 if (!reachableCode.get()) { 203 reachableCode.reset(new ReachableCode(cfg)); 204 reachableCode->computeReachableBlocks(); 205 } 206 207 if (!reachableCode->isReachable(currentBlock)) 208 return; 209 210 if (isSuppressed(R)) 211 return; 212 213 SmallString<64> buf; 214 llvm::raw_svector_ostream os(buf); 215 const char *BugType = nullptr; 216 217 SmallVector<FixItHint, 1> Fixits; 218 219 switch (dsk) { 220 case DeadInit: { 221 BugType = "Dead initialization"; 222 os << "Value stored to '" << *V 223 << "' during its initialization is never read"; 224 225 ASTContext &ACtx = V->getASTContext(); 226 if (Checker->ShowFixIts) { 227 if (V->getInit()->HasSideEffects(ACtx, 228 /*IncludePossibleEffects=*/true)) { 229 break; 230 } 231 SourceManager &SM = ACtx.getSourceManager(); 232 const LangOptions &LO = ACtx.getLangOpts(); 233 SourceLocation L1 = 234 Lexer::findNextToken( 235 V->getTypeSourceInfo()->getTypeLoc().getEndLoc(), 236 SM, LO)->getEndLoc(); 237 SourceLocation L2 = 238 Lexer::getLocForEndOfToken(V->getInit()->getEndLoc(), 1, SM, LO); 239 Fixits.push_back(FixItHint::CreateRemoval({L1, L2})); 240 } 241 break; 242 } 243 244 case DeadIncrement: 245 BugType = "Dead increment"; 246 LLVM_FALLTHROUGH; 247 case Standard: 248 if (!BugType) BugType = "Dead assignment"; 249 os << "Value stored to '" << *V << "' is never read"; 250 break; 251 252 // eg.: f((x = foo())) 253 case Enclosing: 254 if (!Checker->WarnForDeadNestedAssignments) 255 return; 256 BugType = "Dead nested assignment"; 257 os << "Although the value stored to '" << *V 258 << "' is used in the enclosing expression, the value is never " 259 "actually read from '" 260 << *V << "'"; 261 break; 262 } 263 264 BR.EmitBasicReport(AC->getDecl(), Checker, BugType, categories::UnusedCode, 265 os.str(), L, R, Fixits); 266 } 267 268 void CheckVarDecl(const VarDecl *VD, const Expr *Ex, const Expr *Val, 269 DeadStoreKind dsk, 270 const LiveVariables::LivenessValues &Live) { 271 272 if (!VD->hasLocalStorage()) 273 return; 274 // Reference types confuse the dead stores checker. Skip them 275 // for now. 276 if (VD->getType()->getAs<ReferenceType>()) 277 return; 278 279 if (!isLive(Live, VD) && 280 !(VD->hasAttr<UnusedAttr>() || VD->hasAttr<BlocksAttr>() || 281 VD->hasAttr<ObjCPreciseLifetimeAttr>())) { 282 283 PathDiagnosticLocation ExLoc = 284 PathDiagnosticLocation::createBegin(Ex, BR.getSourceManager(), AC); 285 Report(VD, dsk, ExLoc, Val->getSourceRange()); 286 } 287 } 288 289 void CheckDeclRef(const DeclRefExpr *DR, const Expr *Val, DeadStoreKind dsk, 290 const LiveVariables::LivenessValues& Live) { 291 if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) 292 CheckVarDecl(VD, DR, Val, dsk, Live); 293 } 294 295 bool isIncrement(VarDecl *VD, const BinaryOperator* B) { 296 if (B->isCompoundAssignmentOp()) 297 return true; 298 299 const Expr *RHS = B->getRHS()->IgnoreParenCasts(); 300 const BinaryOperator* BRHS = dyn_cast<BinaryOperator>(RHS); 301 302 if (!BRHS) 303 return false; 304 305 const DeclRefExpr *DR; 306 307 if ((DR = dyn_cast<DeclRefExpr>(BRHS->getLHS()->IgnoreParenCasts()))) 308 if (DR->getDecl() == VD) 309 return true; 310 311 if ((DR = dyn_cast<DeclRefExpr>(BRHS->getRHS()->IgnoreParenCasts()))) 312 if (DR->getDecl() == VD) 313 return true; 314 315 return false; 316 } 317 318 void observeStmt(const Stmt *S, const CFGBlock *block, 319 const LiveVariables::LivenessValues &Live) override { 320 321 currentBlock = block; 322 323 // Skip statements in macros. 324 if (S->getBeginLoc().isMacroID()) 325 return; 326 327 // Only cover dead stores from regular assignments. ++/-- dead stores 328 // have never flagged a real bug. 329 if (const BinaryOperator* B = dyn_cast<BinaryOperator>(S)) { 330 if (!B->isAssignmentOp()) return; // Skip non-assignments. 331 332 if (DeclRefExpr *DR = dyn_cast<DeclRefExpr>(B->getLHS())) 333 if (VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { 334 // Special case: check for assigning null to a pointer. 335 // This is a common form of defensive programming. 336 const Expr *RHS = 337 LookThroughTransitiveAssignmentsAndCommaOperators(B->getRHS()); 338 RHS = RHS->IgnoreParenCasts(); 339 340 QualType T = VD->getType(); 341 if (T.isVolatileQualified()) 342 return; 343 if (T->isPointerType() || T->isObjCObjectPointerType()) { 344 if (RHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull)) 345 return; 346 } 347 348 // Special case: self-assignments. These are often used to shut up 349 // "unused variable" compiler warnings. 350 if (const DeclRefExpr *RhsDR = dyn_cast<DeclRefExpr>(RHS)) 351 if (VD == dyn_cast<VarDecl>(RhsDR->getDecl())) 352 return; 353 354 // Otherwise, issue a warning. 355 DeadStoreKind dsk = Parents.isConsumedExpr(B) 356 ? Enclosing 357 : (isIncrement(VD,B) ? DeadIncrement : Standard); 358 359 CheckVarDecl(VD, DR, B->getRHS(), dsk, Live); 360 } 361 } 362 else if (const UnaryOperator* U = dyn_cast<UnaryOperator>(S)) { 363 if (!U->isIncrementOp() || U->isPrefix()) 364 return; 365 366 const Stmt *parent = Parents.getParentIgnoreParenCasts(U); 367 if (!parent || !isa<ReturnStmt>(parent)) 368 return; 369 370 const Expr *Ex = U->getSubExpr()->IgnoreParenCasts(); 371 372 if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(Ex)) 373 CheckDeclRef(DR, U, DeadIncrement, Live); 374 } 375 else if (const DeclStmt *DS = dyn_cast<DeclStmt>(S)) 376 // Iterate through the decls. Warn if any initializers are complex 377 // expressions that are not live (never used). 378 for (const auto *DI : DS->decls()) { 379 const auto *V = dyn_cast<VarDecl>(DI); 380 381 if (!V) 382 continue; 383 384 if (V->hasLocalStorage()) { 385 // Reference types confuse the dead stores checker. Skip them 386 // for now. 387 if (V->getType()->getAs<ReferenceType>()) 388 return; 389 390 if (const Expr *E = V->getInit()) { 391 while (const FullExpr *FE = dyn_cast<FullExpr>(E)) 392 E = FE->getSubExpr(); 393 394 // Look through transitive assignments, e.g.: 395 // int x = y = 0; 396 E = LookThroughTransitiveAssignmentsAndCommaOperators(E); 397 398 // Don't warn on C++ objects (yet) until we can show that their 399 // constructors/destructors don't have side effects. 400 if (isa<CXXConstructExpr>(E)) 401 return; 402 403 // A dead initialization is a variable that is dead after it 404 // is initialized. We don't flag warnings for those variables 405 // marked 'unused' or 'objc_precise_lifetime'. 406 if (!isLive(Live, V) && 407 !V->hasAttr<UnusedAttr>() && 408 !V->hasAttr<ObjCPreciseLifetimeAttr>()) { 409 // Special case: check for initializations with constants. 410 // 411 // e.g. : int x = 0; 412 // struct A = {0, 1}; 413 // struct B = {{0}, {1, 2}}; 414 // 415 // If x is EVER assigned a new value later, don't issue 416 // a warning. This is because such initialization can be 417 // due to defensive programming. 418 if (isConstant(E)) 419 return; 420 421 if (const DeclRefExpr *DRE = 422 dyn_cast<DeclRefExpr>(E->IgnoreParenCasts())) 423 if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { 424 // Special case: check for initialization from constant 425 // variables. 426 // 427 // e.g. extern const int MyConstant; 428 // int x = MyConstant; 429 // 430 if (VD->hasGlobalStorage() && 431 VD->getType().isConstQualified()) 432 return; 433 // Special case: check for initialization from scalar 434 // parameters. This is often a form of defensive 435 // programming. Non-scalars are still an error since 436 // because it more likely represents an actual algorithmic 437 // bug. 438 if (isa<ParmVarDecl>(VD) && VD->getType()->isScalarType()) 439 return; 440 } 441 442 PathDiagnosticLocation Loc = 443 PathDiagnosticLocation::create(V, BR.getSourceManager()); 444 Report(V, DeadInit, Loc, E->getSourceRange()); 445 } 446 } 447 } 448 } 449 } 450 451 private: 452 /// Return true if the given init list can be interpreted as constant 453 bool isConstant(const InitListExpr *Candidate) const { 454 // We consider init list to be constant if each member of the list can be 455 // interpreted as constant. 456 return llvm::all_of(Candidate->inits(), 457 [this](const Expr *Init) { return isConstant(Init); }); 458 } 459 460 /// Return true if the given expression can be interpreted as constant 461 bool isConstant(const Expr *E) const { 462 // It looks like E itself is a constant 463 if (E->isEvaluatable(Ctx)) 464 return true; 465 466 // We should also allow defensive initialization of structs, i.e. { 0 } 467 if (const auto *ILE = dyn_cast<InitListExpr>(E->IgnoreParenCasts())) { 468 return isConstant(ILE); 469 } 470 471 return false; 472 } 473 }; 474 475 } // end anonymous namespace 476 477 //===----------------------------------------------------------------------===// 478 // Driver function to invoke the Dead-Stores checker on a CFG. 479 //===----------------------------------------------------------------------===// 480 481 namespace { 482 class FindEscaped { 483 public: 484 llvm::SmallPtrSet<const VarDecl*, 20> Escaped; 485 486 void operator()(const Stmt *S) { 487 // Check for '&'. Any VarDecl whose address has been taken we treat as 488 // escaped. 489 // FIXME: What about references? 490 if (auto *LE = dyn_cast<LambdaExpr>(S)) { 491 findLambdaReferenceCaptures(LE); 492 return; 493 } 494 495 const UnaryOperator *U = dyn_cast<UnaryOperator>(S); 496 if (!U) 497 return; 498 if (U->getOpcode() != UO_AddrOf) 499 return; 500 501 const Expr *E = U->getSubExpr()->IgnoreParenCasts(); 502 if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) 503 if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) 504 Escaped.insert(VD); 505 } 506 507 // Treat local variables captured by reference in C++ lambdas as escaped. 508 void findLambdaReferenceCaptures(const LambdaExpr *LE) { 509 const CXXRecordDecl *LambdaClass = LE->getLambdaClass(); 510 llvm::DenseMap<const VarDecl *, FieldDecl *> CaptureFields; 511 FieldDecl *ThisCaptureField; 512 LambdaClass->getCaptureFields(CaptureFields, ThisCaptureField); 513 514 for (const LambdaCapture &C : LE->captures()) { 515 if (!C.capturesVariable()) 516 continue; 517 518 VarDecl *VD = C.getCapturedVar(); 519 const FieldDecl *FD = CaptureFields[VD]; 520 if (!FD) 521 continue; 522 523 // If the capture field is a reference type, it is capture-by-reference. 524 if (FD->getType()->isReferenceType()) 525 Escaped.insert(VD); 526 } 527 } 528 }; 529 } // end anonymous namespace 530 531 532 //===----------------------------------------------------------------------===// 533 // DeadStoresChecker 534 //===----------------------------------------------------------------------===// 535 536 void DeadStoresChecker::checkASTCodeBody(const Decl *D, AnalysisManager &mgr, 537 BugReporter &BR) const { 538 539 // Don't do anything for template instantiations. 540 // Proving that code in a template instantiation is "dead" 541 // means proving that it is dead in all instantiations. 542 // This same problem exists with -Wunreachable-code. 543 if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) 544 if (FD->isTemplateInstantiation()) 545 return; 546 547 if (LiveVariables *L = mgr.getAnalysis<LiveVariables>(D)) { 548 CFG &cfg = *mgr.getCFG(D); 549 AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D); 550 ParentMap &pmap = mgr.getParentMap(D); 551 FindEscaped FS; 552 cfg.VisitBlockStmts(FS); 553 DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped, 554 WarnForDeadNestedAssignments); 555 L->runOnAllBlocks(A); 556 } 557 } 558 559 void ento::registerDeadStoresChecker(CheckerManager &Mgr) { 560 auto *Chk = Mgr.registerChecker<DeadStoresChecker>(); 561 562 const AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions(); 563 Chk->WarnForDeadNestedAssignments = 564 AnOpts.getCheckerBooleanOption(Chk, "WarnForDeadNestedAssignments"); 565 Chk->ShowFixIts = 566 AnOpts.getCheckerBooleanOption(Chk, "ShowFixIts"); 567 } 568 569 bool ento::shouldRegisterDeadStoresChecker(const CheckerManager &mgr) { 570 return true; 571 } 572