离久的小站

clang插件-分析无用方法

2018/10/14 Share

插件源码clang-unusedMethod

在开始分析之前,定义了一些数据结构。

1
2
3
4
5
6
7
8
9
10
11
typedef map<string,ObjCMethodDecl*> MethodDeclMap;
typedef map<string,bool> MethodIsUsedMap;

class InterfaceModel {
public:
MethodDeclMap methodDeclMap = MethodDeclMap();
MethodIsUsedMap methodIsUsedMap = MethodIsUsedMap();
};

typedef map<string,InterfaceModel> InterfaceMap;
static InterfaceMap interfaceMap = InterfaceMap();

这里我们为每个类定义了一个 InterfaceModel 模型。主要保存着每个类的所包含的方法,以及类中每个方法是否被使用。

注意:我曾经试过将 MethodDeclMap、MethodIsUsedMap 两个合并成一个类似 InterfaceModel 的 MethodModel 模型。但发现编译时会报错。暂时不知道为什么。以后会研究一下。

由于这次我不需要分析协议的方法,所以我会把协议中的方法过滤掉。还要限制一下我们所分析到的 Decl 是否是系统的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
bool isProtocolMethod(ObjCInterfaceDecl *interfaceDecl, ObjCMethodDecl *methodDecl) {
string selectorName = methodDecl->getSelector().getAsString();
for (auto *protocolDecl : interfaceDecl->all_referenced_protocols()){
if (protocolDecl->lookupMethod(methodDecl->getSelector(), methodDecl->isInstanceMethod())) {
return true;
}
}
return false;
}
bool isSystemMethod (ObjCMethodDecl *methodDecl) {
ObjCInterfaceDecl *interfaceDecl = methodDecl->getClassInterface();
while (isUserSourceDecl(interfaceDecl)) {
interfaceDecl = interfaceDecl->getSuperClass();
}
if (interfaceDecl->lookupMethod(methodDecl->getSelector(), methodDecl->isInstanceMethod())) {
return true;
}
return false;
}
//判断decl来源是不是用户文件
bool isUserSourceDecl(const Decl *decl)
{
string filename = getFileNameDecl(decl);
return isUserSourceUsefulFileWithFilename(filename);
}
//判断stmt来源是不是用户文件
bool isUserSourceStmt(const Stmt *stmt)
{
string filename = getFileNameStmt(stmt);
return isUserSourceUsefulFileWithFilename(filename);
}
//有效文件(非系统文件、且非Pods文件、且是.m文件)
bool isUserSourceUsefulFileWithFilename(const string filename) {
bool result = (!isSystemSourceWithFilename(filename) &&
!isPodFileWithFilename(filename));
return result;
}

当我们解析到一个用户的 ObjCInterfaceDecl 时,会构建一个对应的 InterfaceModel 然后将它存入 map 中。

1
2
3
4
5
6
ObjCInterfaceDecl *interfaceDecl = (ObjCInterfaceDecl *)decl;
string interfaceName = interfaceDecl->getNameAsString();
if (interfaceMap.find(interfaceName) != interfaceMap.end()) {
InterfaceModel interfaceModel = InterfaceModel();
interfaceMap[interfaceName] = interfaceModel;
}

解析到 ObjCMethodDecl 时,会将 decl 存入对应类的模型中。若是第一次存进去,methodIsUsedMap 对应为false。
因为解析时,系统会解析两次。先将工程内全部头文件解析一次,然后在根据语法树进行解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void addMethod(ObjCInterfaceDecl *interfaceDecl, ObjCMethodDecl *methodDecl) {
ObjCInterfaceDecl *decl = interfaceDecl;
while (decl != NULL) {
if (decl->lookupMethod(methodDecl->getSelector(), methodDecl->isInstanceMethod())) {
string interfaceName = decl->getNameAsString();
string selector = methodDecl->getSelector().getAsString();
InterfaceModel *interfaceModel = &interfaceMap[interfaceName];
bool isExist = (interfaceModel->methodIsUsedMap.find(selector) != interfaceModel->methodIsUsedMap.end());
if (!isExist) {
interfaceModel->methodDeclMap[selector] = methodDecl;
interfaceModel->methodIsUsedMap[selector] = false;
}else {
interfaceModel->methodDeclMap[selector] = methodDecl;
}
}
decl = decl->getSuperClass();
}
}

解析 ObjCMessageExpr ,即我们执行调用 [obj method] 时。
和解析到 ObjCMethodDecl 差不多,只不过是将 methodIsUsedMap 对应为方法标记为 true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void addExpr(ObjCInterfaceDecl *interfaceDecl, ObjCMessageExpr *objcExpr) {
ObjCMethodDecl *methodDecl = objcExpr->getMethodDecl();
ObjCInterfaceDecl *decl = interfaceDecl;
while (decl != NULL && isUserSourceDecl(decl)) {
if (decl->lookupMethod(methodDecl->getSelector(), methodDecl->isInstanceMethod())) {
string interfaceName = decl->getNameAsString();
string selector = methodDecl->getSelector().getAsString();
InterfaceModel *interfaceModel = &interfaceMap[interfaceName];
bool isExist = (interfaceModel->methodIsUsedMap.find(selector) != interfaceModel->methodIsUsedMap.end());
if (!isExist) {
interfaceModel->methodDeclMap[selector] = methodDecl;
interfaceModel->methodIsUsedMap[selector] = true;
}else {
interfaceModel->methodIsUsedMap[selector] = true;
}
}
decl = decl->getSuperClass();
}
}

最后我们会将 InterfaceMap 中的每个 InterfaceModel 进行遍历。
对每个 InterfaceModel 中的 MethodIsUsedMap 进行遍历,若为 false ,则从 MethodDeclMap 中取出 Decl 进行警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void DiagnosticationResult() {
DiagnosticsEngine &Diagnostics = Instance.getDiagnostics();
for (InterfaceMap::iterator interfaceIter = interfaceMap.begin(); interfaceIter != interfaceMap.end(); ++interfaceIter) {
InterfaceModel *model = &interfaceIter->second;
for (MethodIsUsedMap::iterator methodIter = model->methodIsUsedMap.begin(); methodIter != model->methodIsUsedMap.end(); ++methodIter) {
string selector = methodIter->first;
bool isUsed = methodIter->second;
if (!isUsed) {
ObjCMethodDecl *methodDecl = model->methodDeclMap[selector];
if (methodDecl != NULL) {
int diagID = Diagnostics.getCustomDiagID(DiagnosticsEngine::Warning, "未使用方法 SEL : %0 ");
Diagnostics.Report(methodDecl->getLocStart(), diagID) << methodDecl->getSelector().getAsString();
}
}
}
}
}

此插件的缺点:
仅仅是对非协议方法、以及 [obj method] 形式的调用进行分析。
对于通知、performSelector等调用方法没有进行对应的分析。

CATALOG