189 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines a CheckObjCUnusedIvars, a checker that10// analyzes an Objective-C class's interface/implementation to determine if it11// 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 26using namespace clang;27using namespace ento;28 29enum IVarState { Unused, Used };30typedef llvm::DenseMap<const ObjCIvarDecl*,IVarState> IvarUsageMap;31 32static 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 (const Expr *sub : POE->semantics()) {52 if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(sub))53 sub = OVE->getSourceExpr();54 Scan(M, sub);55 }56 57 for (const Stmt *SubStmt : S->children())58 Scan(M, SubStmt);59}60 61static 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(ID);71 if (I != M.end())72 I->second = Used;73}74 75static void Scan(IvarUsageMap& M, const ObjCContainerDecl *D) {76 // Scan the methods for accesses.77 for (const auto *I : D->instance_methods())78 Scan(M, I->getBody());79 80 if (const ObjCImplementationDecl *ID = dyn_cast<ObjCImplementationDecl>(D)) {81 // Scan for @synthesized property methods that act as setters/getters82 // to an ivar.83 for (const auto *I : ID->property_impls())84 Scan(M, 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, CID);90 }91 }92}93 94static 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>(I)) {98 SourceLocation L = FD->getBeginLoc();99 if (SM.getFileID(L) == FID)100 Scan(M, FD->getBody());101 }102}103 104static 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 private115 // (b) explicitly marked unused116 // (c) are iboutlets117 // (d) are unnamed bitfields118 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(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 unit144 // for functions inside the @implementation that reference these ivars.145 // FIXME: In the future hopefully we can just use the lexical DeclContext146 // to go from the ObjCImplementationDecl to the lexically "nested"147 // C functions.148 const SourceManager &SM = BR.getSourceManager();149 Scan(M, D->getDeclContext(), SM.getFileID(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 '" << *ID157 << "' 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(Ivar, BR.getSourceManager());162 BR.EmitBasicReport(ID, Checker, "Unused instance variable",163 "Optimization", os.str(), L);164 }165}166 167//===----------------------------------------------------------------------===//168// ObjCUnusedIvarsChecker169//===----------------------------------------------------------------------===//170 171namespace {172class ObjCUnusedIvarsChecker : public Checker<173 check::ASTDecl<ObjCImplementationDecl> > {174public:175 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,176 BugReporter &BR) const {177 checkObjCUnusedIvar(D, BR, this);178 }179};180}181 182void ento::registerObjCUnusedIvarsChecker(CheckerManager &mgr) {183 mgr.registerChecker<ObjCUnusedIvarsChecker>();184}185 186bool ento::shouldRegisterObjCUnusedIvarsChecker(const CheckerManager &mgr) {187 return true;188}189