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