1 //==- ObjCUnusedIVarsChecker.cpp - Check for unused ivars --------*- 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 CheckObjCUnusedIvars, a checker that 10 // analyzes an Objective-C class's interface/implementation to determine if it 11 // has any ivars that are never accessed. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #include "clang/AST/Attr.h" 16 #include "clang/AST/DeclObjC.h" 17 #include "clang/AST/Expr.h" 18 #include "clang/AST/ExprObjC.h" 19 #include "clang/Analysis/PathDiagnostic.h" 20 #include "clang/Basic/SourceManager.h" 21 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 22 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 23 #include "clang/StaticAnalyzer/Core/Checker.h" 24 #include "llvm/ADT/STLExtras.h" 25 26 using namespace clang; 27 using namespace ento; 28 29 enum IVarState { Unused, Used }; 30 typedef llvm::DenseMap<const ObjCIvarDecl*,IVarState> IvarUsageMap; 31 32 static void Scan(IvarUsageMap& M, const Stmt *S) { 33 if (!S) 34 return; 35 36 if (const ObjCIvarRefExpr *Ex = dyn_cast<ObjCIvarRefExpr>(S)) { 37 const ObjCIvarDecl *D = Ex->getDecl(); 38 IvarUsageMap::iterator I = M.find(D); 39 if (I != M.end()) 40 I->second = Used; 41 return; 42 } 43 44 // Blocks can reference an instance variable of a class. 45 if (const BlockExpr *BE = dyn_cast<BlockExpr>(S)) { 46 Scan(M, BE->getBody()); 47 return; 48 } 49 50 if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(S)) 51 for (const Expr *sub : POE->semantics()) { 52 if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(sub)) 53 sub = OVE->getSourceExpr(); 54 Scan(M, sub); 55 } 56 57 for (const Stmt *SubStmt : S->children()) 58 Scan(M, SubStmt); 59 } 60 61 static void Scan(IvarUsageMap& M, const ObjCPropertyImplDecl *D) { 62 if (!D) 63 return; 64 65 const ObjCIvarDecl *ID = D->getPropertyIvarDecl(); 66 67 if (!ID) 68 return; 69 70 IvarUsageMap::iterator I = M.find(ID); 71 if (I != M.end()) 72 I->second = Used; 73 } 74 75 static void Scan(IvarUsageMap& M, const ObjCContainerDecl *D) { 76 // Scan the methods for accesses. 77 for (const auto *I : D->instance_methods()) 78 Scan(M, I->getBody()); 79 80 if (const ObjCImplementationDecl *ID = dyn_cast<ObjCImplementationDecl>(D)) { 81 // Scan for @synthesized property methods that act as setters/getters 82 // to an ivar. 83 for (const auto *I : ID->property_impls()) 84 Scan(M, I); 85 86 // Scan the associated categories as well. 87 for (const auto *Cat : ID->getClassInterface()->visible_categories()) { 88 if (const ObjCCategoryImplDecl *CID = Cat->getImplementation()) 89 Scan(M, CID); 90 } 91 } 92 } 93 94 static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID, 95 const SourceManager &SM) { 96 for (const auto *I : C->decls()) 97 if (const auto *FD = dyn_cast<FunctionDecl>(I)) { 98 SourceLocation L = FD->getBeginLoc(); 99 if (SM.getFileID(L) == FID) 100 Scan(M, FD->getBody()); 101 } 102 } 103 104 static void checkObjCUnusedIvar(const ObjCImplementationDecl *D, 105 BugReporter &BR, 106 const CheckerBase *Checker) { 107 108 const ObjCInterfaceDecl *ID = D->getClassInterface(); 109 IvarUsageMap M; 110 111 // Iterate over the ivars. 112 for (const auto *Ivar : ID->ivars()) { 113 // Ignore ivars that... 114 // (a) aren't private 115 // (b) explicitly marked unused 116 // (c) are iboutlets 117 // (d) are unnamed bitfields 118 if (Ivar->getAccessControl() != ObjCIvarDecl::Private || 119 Ivar->hasAttr<UnusedAttr>() || Ivar->hasAttr<IBOutletAttr>() || 120 Ivar->hasAttr<IBOutletCollectionAttr>() || Ivar->isUnnamedBitField()) 121 continue; 122 123 M[Ivar] = Unused; 124 } 125 126 if (M.empty()) 127 return; 128 129 // Now scan the implementation declaration. 130 Scan(M, D); 131 132 // Any potentially unused ivars? 133 bool hasUnused = false; 134 for (IVarState State : llvm::make_second_range(M)) 135 if (State == Unused) { 136 hasUnused = true; 137 break; 138 } 139 140 if (!hasUnused) 141 return; 142 143 // We found some potentially unused ivars. Scan the entire translation unit 144 // for functions inside the @implementation that reference these ivars. 145 // FIXME: In the future hopefully we can just use the lexical DeclContext 146 // to go from the ObjCImplementationDecl to the lexically "nested" 147 // C functions. 148 const SourceManager &SM = BR.getSourceManager(); 149 Scan(M, D->getDeclContext(), SM.getFileID(D->getLocation()), SM); 150 151 // Find ivars that are unused. 152 for (auto [Ivar, State] : M) 153 if (State == Unused) { 154 std::string sbuf; 155 llvm::raw_string_ostream os(sbuf); 156 os << "Instance variable '" << *Ivar << "' in class '" << *ID 157 << "' is never used by the methods in its @implementation " 158 "(although it may be used by category methods)."; 159 160 PathDiagnosticLocation L = 161 PathDiagnosticLocation::create(Ivar, BR.getSourceManager()); 162 BR.EmitBasicReport(ID, Checker, "Unused instance variable", 163 "Optimization", os.str(), L); 164 } 165 } 166 167 //===----------------------------------------------------------------------===// 168 // ObjCUnusedIvarsChecker 169 //===----------------------------------------------------------------------===// 170 171 namespace { 172 class ObjCUnusedIvarsChecker : public Checker< 173 check::ASTDecl<ObjCImplementationDecl> > { 174 public: 175 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr, 176 BugReporter &BR) const { 177 checkObjCUnusedIvar(D, BR, this); 178 } 179 }; 180 } 181 182 void ento::registerObjCUnusedIvarsChecker(CheckerManager &mgr) { 183 mgr.registerChecker<ObjCUnusedIvarsChecker>(); 184 } 185 186 bool ento::shouldRegisterObjCUnusedIvarsChecker(const CheckerManager &mgr) { 187 return true; 188 } 189