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>(Val: S)) { |
37 | const ObjCIvarDecl *D = Ex->getDecl(); |
38 | IvarUsageMap::iterator I = M.find(Val: 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>(Val: S)) { |
46 | Scan(M, S: BE->getBody()); |
47 | return; |
48 | } |
49 | |
50 | if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(Val: S)) |
51 | for (const Expr *sub : POE->semantics()) { |
52 | if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(Val: sub)) |
53 | sub = OVE->getSourceExpr(); |
54 | Scan(M, S: sub); |
55 | } |
56 | |
57 | for (const Stmt *SubStmt : S->children()) |
58 | Scan(M, S: 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(Val: 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, S: I->getBody()); |
79 | |
80 | if (const ObjCImplementationDecl *ID = dyn_cast<ObjCImplementationDecl>(Val: 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, D: 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, D: 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>(Val: I)) { |
98 | SourceLocation L = FD->getBeginLoc(); |
99 | if (SM.getFileID(SpellingLoc: L) == FID) |
100 | Scan(M, S: 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(c&: 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, C: D->getDeclContext(), FID: SM.getFileID(SpellingLoc: 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(D: Ivar, SM: BR.getSourceManager()); |
162 | BR.EmitBasicReport(DeclWithIssue: ID, Checker, BugName: "Unused instance variable" , |
163 | BugCategory: "Optimization" , BugStr: os.str(), Loc: 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, Checker: 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 | |