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
Scan(IvarUsageMap & M,const Stmt * S)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
Scan(IvarUsageMap & M,const ObjCPropertyImplDecl * D)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
Scan(IvarUsageMap & M,const ObjCContainerDecl * D)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
Scan(IvarUsageMap & M,const DeclContext * C,const FileID FID,const SourceManager & SM)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
checkObjCUnusedIvar(const ObjCImplementationDecl * D,BugReporter & BR,const CheckerBase * Checker)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:
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & mgr,BugReporter & BR) const178 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,
179 BugReporter &BR) const {
180 checkObjCUnusedIvar(D, BR, this);
181 }
182 };
183 }
184
registerObjCUnusedIvarsChecker(CheckerManager & mgr)185 void ento::registerObjCUnusedIvarsChecker(CheckerManager &mgr) {
186 mgr.registerChecker<ObjCUnusedIvarsChecker>();
187 }
188
shouldRegisterObjCUnusedIvarsChecker(const CheckerManager & mgr)189 bool ento::shouldRegisterObjCUnusedIvarsChecker(const CheckerManager &mgr) {
190 return true;
191 }
192