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