17330f729Sjoerg //==- ObjCUnusedIVarsChecker.cpp - Check for unused ivars --------*- C++ -*-==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This file defines a CheckObjCUnusedIvars, a checker that
107330f729Sjoerg // analyzes an Objective-C class's interface/implementation to determine if it
117330f729Sjoerg // has any ivars that are never accessed.
127330f729Sjoerg //
137330f729Sjoerg //===----------------------------------------------------------------------===//
147330f729Sjoerg
157330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
167330f729Sjoerg #include "clang/Analysis/PathDiagnostic.h"
177330f729Sjoerg #include "clang/AST/Attr.h"
187330f729Sjoerg #include "clang/AST/DeclObjC.h"
197330f729Sjoerg #include "clang/AST/Expr.h"
207330f729Sjoerg #include "clang/AST/ExprObjC.h"
217330f729Sjoerg #include "clang/Basic/LangOptions.h"
227330f729Sjoerg #include "clang/Basic/SourceManager.h"
237330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
247330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
257330f729Sjoerg
267330f729Sjoerg using namespace clang;
277330f729Sjoerg using namespace ento;
287330f729Sjoerg
297330f729Sjoerg enum IVarState { Unused, Used };
307330f729Sjoerg typedef llvm::DenseMap<const ObjCIvarDecl*,IVarState> IvarUsageMap;
317330f729Sjoerg
Scan(IvarUsageMap & M,const Stmt * S)327330f729Sjoerg static void Scan(IvarUsageMap& M, const Stmt *S) {
337330f729Sjoerg if (!S)
347330f729Sjoerg return;
357330f729Sjoerg
367330f729Sjoerg if (const ObjCIvarRefExpr *Ex = dyn_cast<ObjCIvarRefExpr>(S)) {
377330f729Sjoerg const ObjCIvarDecl *D = Ex->getDecl();
387330f729Sjoerg IvarUsageMap::iterator I = M.find(D);
397330f729Sjoerg if (I != M.end())
407330f729Sjoerg I->second = Used;
417330f729Sjoerg return;
427330f729Sjoerg }
437330f729Sjoerg
447330f729Sjoerg // Blocks can reference an instance variable of a class.
457330f729Sjoerg if (const BlockExpr *BE = dyn_cast<BlockExpr>(S)) {
467330f729Sjoerg Scan(M, BE->getBody());
477330f729Sjoerg return;
487330f729Sjoerg }
497330f729Sjoerg
507330f729Sjoerg if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(S))
517330f729Sjoerg for (PseudoObjectExpr::const_semantics_iterator
527330f729Sjoerg i = POE->semantics_begin(), e = POE->semantics_end(); i != e; ++i) {
537330f729Sjoerg const Expr *sub = *i;
547330f729Sjoerg if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(sub))
557330f729Sjoerg sub = OVE->getSourceExpr();
567330f729Sjoerg Scan(M, sub);
577330f729Sjoerg }
587330f729Sjoerg
597330f729Sjoerg for (const Stmt *SubStmt : S->children())
607330f729Sjoerg Scan(M, SubStmt);
617330f729Sjoerg }
627330f729Sjoerg
Scan(IvarUsageMap & M,const ObjCPropertyImplDecl * D)637330f729Sjoerg static void Scan(IvarUsageMap& M, const ObjCPropertyImplDecl *D) {
647330f729Sjoerg if (!D)
657330f729Sjoerg return;
667330f729Sjoerg
677330f729Sjoerg const ObjCIvarDecl *ID = D->getPropertyIvarDecl();
687330f729Sjoerg
697330f729Sjoerg if (!ID)
707330f729Sjoerg return;
717330f729Sjoerg
727330f729Sjoerg IvarUsageMap::iterator I = M.find(ID);
737330f729Sjoerg if (I != M.end())
747330f729Sjoerg I->second = Used;
757330f729Sjoerg }
767330f729Sjoerg
Scan(IvarUsageMap & M,const ObjCContainerDecl * D)777330f729Sjoerg static void Scan(IvarUsageMap& M, const ObjCContainerDecl *D) {
787330f729Sjoerg // Scan the methods for accesses.
797330f729Sjoerg for (const auto *I : D->instance_methods())
807330f729Sjoerg Scan(M, I->getBody());
817330f729Sjoerg
827330f729Sjoerg if (const ObjCImplementationDecl *ID = dyn_cast<ObjCImplementationDecl>(D)) {
837330f729Sjoerg // Scan for @synthesized property methods that act as setters/getters
847330f729Sjoerg // to an ivar.
857330f729Sjoerg for (const auto *I : ID->property_impls())
867330f729Sjoerg Scan(M, I);
877330f729Sjoerg
887330f729Sjoerg // Scan the associated categories as well.
897330f729Sjoerg for (const auto *Cat : ID->getClassInterface()->visible_categories()) {
907330f729Sjoerg if (const ObjCCategoryImplDecl *CID = Cat->getImplementation())
917330f729Sjoerg Scan(M, CID);
927330f729Sjoerg }
937330f729Sjoerg }
947330f729Sjoerg }
957330f729Sjoerg
Scan(IvarUsageMap & M,const DeclContext * C,const FileID FID,const SourceManager & SM)967330f729Sjoerg static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID,
977330f729Sjoerg const SourceManager &SM) {
987330f729Sjoerg for (const auto *I : C->decls())
997330f729Sjoerg if (const auto *FD = dyn_cast<FunctionDecl>(I)) {
1007330f729Sjoerg SourceLocation L = FD->getBeginLoc();
1017330f729Sjoerg if (SM.getFileID(L) == FID)
1027330f729Sjoerg Scan(M, FD->getBody());
1037330f729Sjoerg }
1047330f729Sjoerg }
1057330f729Sjoerg
checkObjCUnusedIvar(const ObjCImplementationDecl * D,BugReporter & BR,const CheckerBase * Checker)1067330f729Sjoerg static void checkObjCUnusedIvar(const ObjCImplementationDecl *D,
1077330f729Sjoerg BugReporter &BR,
1087330f729Sjoerg const CheckerBase *Checker) {
1097330f729Sjoerg
1107330f729Sjoerg const ObjCInterfaceDecl *ID = D->getClassInterface();
1117330f729Sjoerg IvarUsageMap M;
1127330f729Sjoerg
1137330f729Sjoerg // Iterate over the ivars.
1147330f729Sjoerg for (const auto *Ivar : ID->ivars()) {
1157330f729Sjoerg // Ignore ivars that...
1167330f729Sjoerg // (a) aren't private
1177330f729Sjoerg // (b) explicitly marked unused
1187330f729Sjoerg // (c) are iboutlets
1197330f729Sjoerg // (d) are unnamed bitfields
1207330f729Sjoerg if (Ivar->getAccessControl() != ObjCIvarDecl::Private ||
1217330f729Sjoerg Ivar->hasAttr<UnusedAttr>() || Ivar->hasAttr<IBOutletAttr>() ||
1227330f729Sjoerg Ivar->hasAttr<IBOutletCollectionAttr>() ||
1237330f729Sjoerg Ivar->isUnnamedBitfield())
1247330f729Sjoerg continue;
1257330f729Sjoerg
1267330f729Sjoerg M[Ivar] = Unused;
1277330f729Sjoerg }
1287330f729Sjoerg
1297330f729Sjoerg if (M.empty())
1307330f729Sjoerg return;
1317330f729Sjoerg
1327330f729Sjoerg // Now scan the implementation declaration.
1337330f729Sjoerg Scan(M, D);
1347330f729Sjoerg
1357330f729Sjoerg // Any potentially unused ivars?
1367330f729Sjoerg bool hasUnused = false;
1377330f729Sjoerg for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I)
1387330f729Sjoerg if (I->second == Unused) {
1397330f729Sjoerg hasUnused = true;
1407330f729Sjoerg break;
1417330f729Sjoerg }
1427330f729Sjoerg
1437330f729Sjoerg if (!hasUnused)
1447330f729Sjoerg return;
1457330f729Sjoerg
1467330f729Sjoerg // We found some potentially unused ivars. Scan the entire translation unit
1477330f729Sjoerg // for functions inside the @implementation that reference these ivars.
1487330f729Sjoerg // FIXME: In the future hopefully we can just use the lexical DeclContext
1497330f729Sjoerg // to go from the ObjCImplementationDecl to the lexically "nested"
1507330f729Sjoerg // C functions.
1517330f729Sjoerg const SourceManager &SM = BR.getSourceManager();
1527330f729Sjoerg Scan(M, D->getDeclContext(), SM.getFileID(D->getLocation()), SM);
1537330f729Sjoerg
1547330f729Sjoerg // Find ivars that are unused.
1557330f729Sjoerg for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I)
1567330f729Sjoerg if (I->second == Unused) {
1577330f729Sjoerg std::string sbuf;
1587330f729Sjoerg llvm::raw_string_ostream os(sbuf);
1597330f729Sjoerg os << "Instance variable '" << *I->first << "' in class '" << *ID
1607330f729Sjoerg << "' is never used by the methods in its @implementation "
1617330f729Sjoerg "(although it may be used by category methods).";
1627330f729Sjoerg
1637330f729Sjoerg PathDiagnosticLocation L =
1647330f729Sjoerg PathDiagnosticLocation::create(I->first, BR.getSourceManager());
1657330f729Sjoerg BR.EmitBasicReport(D, Checker, "Unused instance variable", "Optimization",
1667330f729Sjoerg os.str(), L);
1677330f729Sjoerg }
1687330f729Sjoerg }
1697330f729Sjoerg
1707330f729Sjoerg //===----------------------------------------------------------------------===//
1717330f729Sjoerg // ObjCUnusedIvarsChecker
1727330f729Sjoerg //===----------------------------------------------------------------------===//
1737330f729Sjoerg
1747330f729Sjoerg namespace {
1757330f729Sjoerg class ObjCUnusedIvarsChecker : public Checker<
1767330f729Sjoerg check::ASTDecl<ObjCImplementationDecl> > {
1777330f729Sjoerg public:
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & mgr,BugReporter & BR) const1787330f729Sjoerg void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,
1797330f729Sjoerg BugReporter &BR) const {
1807330f729Sjoerg checkObjCUnusedIvar(D, BR, this);
1817330f729Sjoerg }
1827330f729Sjoerg };
1837330f729Sjoerg }
1847330f729Sjoerg
registerObjCUnusedIvarsChecker(CheckerManager & mgr)1857330f729Sjoerg void ento::registerObjCUnusedIvarsChecker(CheckerManager &mgr) {
1867330f729Sjoerg mgr.registerChecker<ObjCUnusedIvarsChecker>();
1877330f729Sjoerg }
1887330f729Sjoerg
shouldRegisterObjCUnusedIvarsChecker(const CheckerManager & mgr)189*e038c9c4Sjoerg bool ento::shouldRegisterObjCUnusedIvarsChecker(const CheckerManager &mgr) {
1907330f729Sjoerg return true;
1917330f729Sjoerg }
192