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