clang插件開發
1.在下載的llvm/tools/clang/tools/CmakeLists.txt中增加要添加的插件名稱
CMakeLists增加插件名BCPlugin@2x.png
2.在此目錄下新建插件BCPlugin文件夾并新建CMakeLists.txt文件和BCPlugin.cpp文件
BCPlugin文件夾下CMakeLists@2x.png
- 可以參考下LLVMHello插件下的CMakeLists.txt文件和Hello.cpp文件
LLVMHello@2x.png
- 在build_xcode目錄下編譯執行cmake -G Xcode ../llvm,此時是增量編譯,不會耗時太久,編譯成功如下
增量編譯@2x.png
增量編譯成功@2x.png
若遇到編譯報錯,刪掉llvm文件夾下面的CMakeCache.txt文件,重新編譯即可
CMakellvm報錯@2x.png
-- The C compiler identification is AppleClang 11.0.3.11030032
-- The CXX compiler identification is AppleClang 11.0.3.11030032
-- The ASM compiler identification is Clang
-- Found assembler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Error at CMakeLists.txt:238 (message):
In-source builds are not allowed.
Please create a directory and run cmake from there, passing the path
to this source directory as the last argument.
This process created the file `CMakeCache.txt' and the directory
`CMakeFiles'.
Please delete them.
-- Configuring incomplete, errors occurred!
See also "/Users/cloud/Documents/iOS/1113/llvm/CMakeFiles/CMakeOutput.log".
See also "/Users/cloud/Documents/iOS/1113/llvm/CMakeFiles/CMakeError.log".
3.編譯完成后在build_xcode打開編譯好的llvm工程,會發現新建的BCPlugin插件,手動管理在Manage Schemes-> + -> BCPlugin
BCPlugin@2x.png
30.gif
4.編寫插件代碼
- 1.拷貝頭文件信息
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
- 2.新建BCASTAction繼承于PluginASTAction返回繼承于抽象類ASTConsumer的BCConsumer,獲取頂級節點解析和解析完成,cmd+B編譯成功,會在build_xcode/debug/lib目錄下生成BCPlugin.dylib
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
namespace BCPlugin {
//自定義BCConsumer
class BCConsumer:public ASTConsumer{
public:
//解析完一個頂級的聲明,就回調一次
bool HandleTopLevelDecl(DeclGroupRef D){
cout<<"正在解析。。。。"<<endl;
return true;
};
//整個文件都解析完成的回調
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢!"<<endl;
}
};
//繼承PluginASTAction 實現我們自定義的Action
class BCASTAction:public PluginASTAction{
public:
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg){
return true;
}
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile){
return unique_ptr<BCConsumer>(new BCConsumer);
}
};
}
//注冊插件 BCPlugin
static FrontendPluginRegistry::Add<BCPlugin::BCASTAction>
BC("BCPlugin","this is BCPlugin");
BCPlugindylib@2x.png
- 3.編寫測試代碼hello.m,查看代碼中的頂級節點,在當前目錄下新建代碼文件夾下新建hello.m文件
int sum(int a);//頂級節點
int a;//頂級節點
int sum(int a){//頂級節點
int b = 10;
return a + b;
}
int sum2(int a,int b ){//頂級節點
int c = 10;
return a + b + c;
}
? 代碼 /Users/cloud/Documents/iOS/1113/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk/ -Xclang -load -Xclang /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib -Xclang -add-plugin -Xclang BCPlugin -c ./hello.m
正在解析。。。。
正在解析。。。。
正在解析。。。。
正在解析。。。。
文件解析完畢!
hello.m@2x.png
- 4.進行編譯,需要用到三個文件:自己編譯的clang文件,Xcode里面的模擬器sdk,自己新建的自定義插件編譯好的.dylib,編譯完成后在此hello.m目錄下會生成hello.o文件,源文件變成目標文件,機器能識別的文件,根據打印信息存在4個頂級節點
????????自己編譯的clang文件路徑???????? -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulat or12.2.sdk/
-Xclang -load -Xclang
????插件(.dylib)路勁???? -Xclang -add-plugin
-Xclang ?????? 插件名 -c ???????? ????????源碼路徑
/Users/cloud/Documents/iOS/1113/build_xcode/Debug/bin/clang -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk/
-Xclang -load -Xclang /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib
-Xclang -add-plugin -Xclang BCPlugin -c ./hello.m
自定義插件編譯hello.m@2x.png
31.gif
hello.o@2x.png
- 通過Xcode自帶系統的編譯器編譯ViewController.m,獲取抽象語法書節點
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
ObjCPropertyDecl@2x.png
- 5.獲取上下文所有節點數據并打印,打印了包含系統的所有節點數據
namespace BCPlugin {
class BCMatchCallback:public MatchFinder::MatchCallback{
public:
void run(const::MatchFinder::MatchResult &Result){
//通過result獲取到節點
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
if (propertyDecl) {
string typeStr = propertyDecl->getType().getAsString();
cout<<"------拿到了:"<<typeStr<<"-------"<<endl;
}
}
};
//自定義BCConsumer
class BCConsumer:public ASTConsumer{
private:
//AST節點的查找過濾器
MatchFinder matcher;
BCMatchCallback callback;
public:
BCConsumer(){
//添加一個MatchFinder去匹配objcPropertyDecl節點
//回調在BCMatchCallback里面的run方法
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
};
//解析完一個頂級的聲明,就回調一次
bool HandleTopLevelDecl(DeclGroupRef D){
cout<<"正在解析。。。。"<<endl;
return true;
};
//整個文件都解析完成的回調
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢!"<<endl;
matcher.matchAST(Ctx);
}
};
//繼承PluginASTAction 實現我們自定義的Action
class BCASTAction:public PluginASTAction{
public:
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg){
return true;
}
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile){
return unique_ptr<BCConsumer>(new BCConsumer);
}
};
}
//注冊插件 BCPlugin
static FrontendPluginRegistry::Add<BCPlugin::BCASTAction>
BC("BCPlugin","this is BCPlugin");
/Users/cloud/Documents/iOS/1113/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk/ -Xclang -load -Xclang /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib -Xclang -add-plugin -Xclang BCPlugin -c ./ViewController.m
獲取所有節點@2x.png
- 6.獲取文件名稱
獲取文件名稱@2x.png
- 7.過濾系統的節點,獲取自己的代碼節點
bool isUserSourceCode(const string fileName){
if (fileName.empty()) return false;
//非Xcode中的源碼都認為是用戶的
if (fileName.find("/Applications/Xcode.app/") == 0)
return false;
return true;
}
獲取自己的代碼節點過濾系統的@2x.png
- 8.NSString,NSArray,NSDictionary用copy修飾
//判斷是否應該用copy修飾
bool isShouldUseCopy(const string typeStr){
if (typeStr.find("NSString") != string::npos ||
typeStr.find("NSArray") != string::npos ||
typeStr.find("NSDictionary") != string::npos) {
return true;
}
return false;
}
提示必須copy修飾@2x.png
- 9.定位到代碼錯誤
//拿到節點的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
定位到代碼錯誤@2x.png
- 10.發出警告
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(diag.getCustomDiagID(DiagnosticsEngine::Warning, "這個地方推薦使用copy!!!"));
warning@2x.png
- 11.確定發生錯誤的位置,并警告
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "這個地方推薦使用copy!!!"));
確定警告位置@2x.png
- 12.選定占位符,指出具體需要用copy修飾的位置
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦使用copy!!!"))<<typeStr;
指定須用copy修飾的位置提示和警告@2x.png
- 13.刪除cout代碼,集成到Xcode中,編譯后集成到Xcode中
- 1.在Xcode -> Build Settings Other C Flags中輸入 -Xclang -load -Xclang 插件絕對路徑 -Xclang -add-plugin -Xclang 插件名稱(BCPlugin)
32.gif
- 2.Cmd + B編譯報錯,由于clang插件需要使用對應的版本去加載,如果版本不一致則會導致編譯錯誤,出現如下錯誤,網上download下來的llvm和Xcode的clang版本不一定匹配,Xcode集成的clang和插件所依賴的clang不一定一致
clang編譯版本錯誤@2x.png
??????
error: unable to load plugin '/Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib': 'dlopen(/Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib, 9): Symbol not found: __ZN5clang12ast_matchers16objcPropertyDeclE
Referenced from: /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib
Expected in: flat namespace
in /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib'
warning: Could not read serialized diagnostics file: Cannot Load File: Failed to open diagnostics file (in target 'Demo' from project 'Demo')
Command CompileC failed with a nonzero exit code
- ??在Build Settings欄目中新增兩項用戶自定義的設置CC和CXX,CC對應的是自己編譯的clang的絕對路徑,CXX對應的是自己編譯的clang++的絕對路徑,
自己編譯的clang和cxx的絕對路徑在build_xcode/Debug/bin/路徑下,clang為二進制可執行文件,clang++文件為快捷方式
AddUserDefinedSetting@2x.png
33.gif
- 3.Cmd + B繼續報錯,在Build settings中搜索index,將Enable index-While-Building Functionality的Default改為NO。
index錯誤@2x.png
indexDefault改為No@2x.png
-
4.Cmd + B重新編譯,自定義插件生效
自定義插件生效@2x.png
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
namespace BCPlugin {
class BCMatchCallback:public MatchFinder::MatchCallback{
private:
CompilerInstance &CI;
bool isUserSourceCode(const string fileName){
if (fileName.empty()) return false;
//非Xcode中的源碼都認為是用戶的
if (fileName.find("/Applications/Xcode.app/") == 0)
return false;
return true;
}
//判斷是否應該用copy修飾
bool isShouldUseCopy(const string typeStr){
if (typeStr.find("NSString") != string::npos ||
typeStr.find("NSArray") != string::npos ||
typeStr.find("NSDictionary") != string::npos) {
return true;
}
return false;
}
public:
BCMatchCallback(CompilerInstance &CI):CI(CI){}
//真正的回調!
void run(const::MatchFinder::MatchResult &Result){
//通過result獲取到節點
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
//打印文件名稱
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
//判斷節點有值且是用戶自己的代碼不是系統的
if (propertyDecl && isUserSourceCode(fileName)) {
//拿到節點的類型!并打印!
string typeStr = propertyDecl->getType().getAsString();
//拿到節點的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
// cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦使用copy!!!"))<<typeStr;
// cout<<"------拿到了:"<<typeStr<<"--屬于文件:"<<fileName<<endl;
}
}
};
//自定義BCConsumer
class BCConsumer:public ASTConsumer{
private:
//AST節點的查找過濾器
MatchFinder matcher;
BCMatchCallback callback;
public:
BCConsumer(CompilerInstance &CI):callback(CI){
//添加一個MatchFinder去匹配objcPropertyDecl節點
//回調在BCMatchCallback里面的run方法
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
};
//解析完一個頂級的聲明,就回調一次
bool HandleTopLevelDecl(DeclGroupRef D){
// cout<<"正在解析。。。。"<<endl;
return true;
};
//整個文件都解析完成的回調
void HandleTranslationUnit(ASTContext &Ctx) {
// cout<<"文件解析完畢!"<<endl;
matcher.matchAST(Ctx);
}
};
//繼承PluginASTAction 實現我們自定義的Action
class BCASTAction:public PluginASTAction{
public:
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg){
return true;
}
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile){
return unique_ptr<BCConsumer>(new BCConsumer(CI));
}
};
}
//注冊插件 BCPlugin
static FrontendPluginRegistry::Add<BCPlugin::BCASTAction>
BC("BCPlugin","this is BCPlugin");