#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Analysis/PathDiagnostic.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
using namespace clang;
using namespace ento;
enum IVarState { Unused, Used };
typedef llvm::DenseMap<const ObjCIvarDecl*,IVarState> IvarUsageMap;
static void Scan(IvarUsageMap& M, const Stmt *S) {
if (!S)
return;
if (const ObjCIvarRefExpr *Ex = dyn_cast<ObjCIvarRefExpr>(S)) {
const ObjCIvarDecl *D = Ex->getDecl();
IvarUsageMap::iterator I = M.find(D);
if (I != M.end())
I->second = Used;
return;
}
if (const BlockExpr *BE = dyn_cast<BlockExpr>(S)) {
Scan(M, BE->getBody());
return;
}
if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(S))
for (PseudoObjectExpr::const_semantics_iterator
i = POE->semantics_begin(), e = POE->semantics_end(); i != e; ++i) {
const Expr *sub = *i;
if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(sub))
sub = OVE->getSourceExpr();
Scan(M, sub);
}
for (const Stmt *SubStmt : S->children())
Scan(M, SubStmt);
}
static void Scan(IvarUsageMap& M, const ObjCPropertyImplDecl *D) {
if (!D)
return;
const ObjCIvarDecl *ID = D->getPropertyIvarDecl();
if (!ID)
return;
IvarUsageMap::iterator I = M.find(ID);
if (I != M.end())
I->second = Used;
}
static void Scan(IvarUsageMap& M, const ObjCContainerDecl *D) {
for (const auto *I : D->instance_methods())
Scan(M, I->getBody());
if (const ObjCImplementationDecl *ID = dyn_cast<ObjCImplementationDecl>(D)) {
for (const auto *I : ID->property_impls())
Scan(M, I);
for (const auto *Cat : ID->getClassInterface()->visible_categories()) {
if (const ObjCCategoryImplDecl *CID = Cat->getImplementation())
Scan(M, CID);
}
}
}
static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID,
const SourceManager &SM) {
for (const auto *I : C->decls())
if (const auto *FD = dyn_cast<FunctionDecl>(I)) {
SourceLocation L = FD->getBeginLoc();
if (SM.getFileID(L) == FID)
Scan(M, FD->getBody());
}
}
static void checkObjCUnusedIvar(const ObjCImplementationDecl *D,
BugReporter &BR,
const CheckerBase *Checker) {
const ObjCInterfaceDecl *ID = D->getClassInterface();
IvarUsageMap M;
for (const auto *Ivar : ID->ivars()) {
if (Ivar->getAccessControl() != ObjCIvarDecl::Private ||
Ivar->hasAttr<UnusedAttr>() || Ivar->hasAttr<IBOutletAttr>() ||
Ivar->hasAttr<IBOutletCollectionAttr>() ||
Ivar->isUnnamedBitfield())
continue;
M[Ivar] = Unused;
}
if (M.empty())
return;
Scan(M, D);
bool hasUnused = false;
for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I)
if (I->second == Unused) {
hasUnused = true;
break;
}
if (!hasUnused)
return;
const SourceManager &SM = BR.getSourceManager();
Scan(M, D->getDeclContext(), SM.getFileID(D->getLocation()), SM);
for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I)
if (I->second == Unused) {
std::string sbuf;
llvm::raw_string_ostream os(sbuf);
os << "Instance variable '" << *I->first << "' in class '" << *ID
<< "' is never used by the methods in its @implementation "
"(although it may be used by category methods).";
PathDiagnosticLocation L =
PathDiagnosticLocation::create(I->first, BR.getSourceManager());
BR.EmitBasicReport(D, Checker, "Unused instance variable", "Optimization",
os.str(), L);
}
}
namespace {
class ObjCUnusedIvarsChecker : public Checker<
check::ASTDecl<ObjCImplementationDecl> > {
public:
void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,
BugReporter &BR) const {
checkObjCUnusedIvar(D, BR, this);
}
};
}
void ento::registerObjCUnusedIvarsChecker(CheckerManager &mgr) {
mgr.registerChecker<ObjCUnusedIvarsChecker>();
}
bool ento::shouldRegisterObjCUnusedIvarsChecker(const CheckerManager &mgr) {
return true;
}