解釋性語言和編譯性語言的區(qū)別劣像?
解釋性語言可以通過解釋器直接執(zhí)行相應的代碼粘衬,比如python語言项玛;而
編譯性語言要經(jīng)過編譯器編譯成相應的可執(zhí)行文件貌笨,然后才可以執(zhí)行。
傳統(tǒng)的編譯器設計
編譯器前端(Frontend)
編譯器前端的任務是解析源代碼稍计。它會進行:詞法分析躁绸,語法分析,語義分析臣嚣,檢查源代碼是否存在錯誤净刮,然后構建抽象語法樹(Abstract Syntax Tree,AST)硅则,LLVM的前端還會生成中間代碼(IR)淹父。
優(yōu)化器(Optimizer)
優(yōu)化器負責各種優(yōu)化。改善代碼的運行時間怎虫,例如消除冗余計算等暑认。
后端(Backend)/代碼生成器(CodeGenerator)
將代碼映射到目標指令集,生成機器語言大审,并且進行機器相關的代碼優(yōu)化蘸际。
iOS的編譯器架構
clang和llvm的聯(lián)系?
Objective C/C/C++使用的編譯器前端是Clang徒扶,swift是Swift粮彤。后端都是LLVM。LLVM是編譯器的框架系統(tǒng),clang只是編譯器的前端部分导坟。
LLVM的設計
其他的編譯器如GCC屿良,作為了一個整體的程序設計的,所以不夠靈活惫周。
LLVM最重要的設計就是尘惧,使用的通用的代碼表示形式IR,它是用來在編譯器中表示代碼的形式递递。所以LLVM可以靈活的增加前端和后端喷橙,只要增加的前端和后端使用同一的規(guī)則IR就可以。
Clang
Clang是LLVM的一個子項目登舞,它的誕生是為了替代GCC重慢。它屬于LLVM架構中的前端。
通過下面的命令可以打印源碼的編譯階段逊躁。
clang -ccc-print-phases main.m
打印結果如下:
0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
上面過程的解釋:
0:輸入原文件;
1:預處理過程隅熙,包括宏的替換稽煤,頭文件的導入等;
2:編譯階段囚戚,詞法分析酵熙、語法分析,最終生成IR代碼驰坊。
3:后端匾二,代碼優(yōu)化處理,最終生成匯編拳芙;
4:匯編生成目標文件察藐;
5:連接:鏈接需要的動態(tài)庫和靜態(tài)庫,生成可執(zhí)行文件舟扎;
6:通過不同的架構分飞,生成對象的可執(zhí)行文件;
預處理階段
clang -E main.m
執(zhí)行完成后可以看到文件的導入和宏的替換睹限。
編譯階段
詞法分析:把代碼切成一個個的Token譬猫,比如大小括號,字符串等羡疗。
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
語法分析:在詞法分析的基礎上將單詞序列組成起來染服,形成了語法樹,判斷程序代碼在結構上是否正確叨恨。
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
生成中間代碼:
通過下面的代碼可以生成.ll的IR代碼文件
clang -S -fobjc-arc -emit-llvm main.m
IR生成時可以通過參數(shù)選擇優(yōu)化柳刮,優(yōu)化級別分別為-O0 -O1 -O2 -O3 -Os
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
bitCode:Xcode7以后可通過bitcode開啟進一步的優(yōu)化,生成.bc的中間代碼。
clang -emit-llvm -c main.ll -o main.bc
生成匯編代碼:最終通過.ll或者.bc生成匯編代碼诚亚。
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s
生成匯編代碼的時候也可以選擇優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s
生成目標文件(匯編器):
clang -fmodules -c main.s -o main.o
生成可執(zhí)行文件(鏈接):
clang main.o -o main
創(chuàng)建Clang插件
-
在LLVM的目錄 /llvm/tools/clang/tools 創(chuàng)建新的插件:HKPlugin晕换。
-
修改/llvm/tools/clang/tools目錄下的CMakeLists.text文件,新增add_clang_subdirectory(HKPlugin)站宗。
在HKPlugin目錄下新增一個HKPlugin.cpp的文件以及CMakeLists.text配置文件闸准。在CMakeLists.text中填寫配置信息:
- 接下來我們利用cmake重新生成一下Xcode項目。在build_xcode目錄下cmake -G Xcode ../llvm
-
最后在LLVM的Xcode項目中可以看到Loadable modules 目錄下有自己的Plugin目錄了梢灭。下面我們就可以在里面編寫插件代碼了夷家。
一個簡單的插件代碼:
#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 HKPlugin {
class HKConsumer : public ASTConsumer{
public:
//解析完一個頂級的申明就會來這里執(zhí)行!
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"正在解析..."<<endl;
return true;
}
//解析完整個文件后被調用
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢!"<<endl;
}
};
//繼承PluginASTAction實現(xiàn)我們自定義的Action
class HKASTAction: 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 <HKConsumer>(new HKConsumer);
}
};
}
//注冊插件
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> X("HKPlugin", "This is the description of the plugin");
測試插件代碼
自己編寫的clang插件目錄 -isysroot /Applications/Xcode.app/Contents/Deve loper/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulat or12.2.sdk/ -Xclang -load -Xclang 插件(.dylib)路徑 -Xclang -add-plugin
-Xclang 插件名 -c 源碼路徑
測試結果:
自定義clang---屬性修飾符copy
在clang的語法分析階段,判斷屬性類型為NSString敏释,NSArray库快,NSDictionay的,是否使用copy修飾钥顽,如果不是copy修飾報出警告义屏。
@interface ViewController ()
/** */
@property (nonatomic, strong) NSString *name;
/** */
@property (nonatomic, strong) NSArray *arrs;
@end
我們使用clang的語法分析命令翻譯該文件
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk??????S DK?????? -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
得到的了編譯階段的語法樹:
可以看到屬性在語法樹中被翻譯為ObjCPropertyDecl
。我們可以在語法分析的階段監(jiān)聽ObjCPropertyDecl
這種類型的節(jié)點蜂大,然后進行判斷闽铐。想要監(jiān)聽某個類型的節(jié)點,我們可以使用MatchFinder
來獲取奶浦。
class ZFConsumer : public ASTConsumer{
private:
MatchFinder matcher;
ZFMatchCallback callBack;
public:
ZFConsumer() {
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callBack);
}
//解析完整個文件后被調用
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"ZF文件解析完畢!"<<endl;
matcher.matchAST(Ctx);
}
};
為Consumer增加了成員變量matcher兄墅,在Consumer的初始化中通過matcher來添加objcPropertyDecl
類型節(jié)點的監(jiān)聽。然后在文件解析完的回調方法HandleTranslationUnit
中澳叉,為matcher傳遞ASTContext隙咸。另外我們還需要創(chuàng)建回調的對象ZFMatchCallback。
class ZFMatchCallback: public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result) {
//通過結果獲取到節(jié)點成洗。
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
if (propertyDecl) {//如果節(jié)點有值
//拿到屬性的類型
string typeStr = propertyDecl->getType().getAsString();
cout<<typeStr<<"應該用copy修飾"<<endl;
}
}
};
監(jiān)聽到objcPropertyDecl
類型的節(jié)點后五督,會回調run
方法。這樣我們就可以獲取到節(jié)點以及節(jié)點的類型等信息瓶殃。
這樣進行監(jiān)聽話概荷,還會把引入的頭文件的信息全部都給攔截監(jiān)聽了,所以我們需要過濾下碌燕,如果是路徑包含"/Applications/Xcode.app/"就為系統(tǒng)的頭文件中的信息误证,需要過濾掉。
//判斷是否是自己的文件
bool isUserSourceCode(const string filename) {
if (filename.empty()) return false;
// 非Xcode中的源碼都認為是用戶源碼
if (filename.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
//獲取文件名稱
string filename = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
if (propertyDecl && isUserSourceCode(filename)) {//如果節(jié)點有值,并且是用戶文件
.......
}
其中的CI為CompilerInstance類型修壕,是通過ZFConsumer傳遞過來的愈捅。
然后我們還需要判斷當前的屬性是否正確使用了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;
}
//attrKind & ObjCPropertyDecl::OBJC_PR_copy 如果實行修飾符為copy返回true
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
}
如果該使用copy的地方?jīng)]有使用copy慈鸠,使用DiagnosticsEngine診斷引擎進行警告處理
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦用Copy"))<<typeStr;
這樣我們的clang插件就寫的差不多了蓝谨,使用自定義的clang和clang插件對ViewController.m文件的屬性就行語法檢測,結果如下:
NSString *應該用copy修飾而沒用Copy,發(fā)出警告譬巫!
demo1/ViewController.m:13:1: warning: NSString *這個地方推薦用Copy
@property (nonatomic, strong) NSString *name;
^
NSArray *應該用copy修飾而沒用Copy咖楣,發(fā)出警告!
demo1/ViewController.m:15:1: warning:
NSArray *這個地方推薦用Copy
@property (nonatomic, strong) NSArray *arrs;
^
2 warnings generated.
最后附上我們自定義的clang插件的完成代碼:
#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 ZFPlugin {
class ZFMatchCallback: 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:
ZFMatchCallback(CompilerInstance &CI):CI(CI) {}
virtual void run(const MatchFinder::MatchResult &Result) {
//通過結果獲取到節(jié)點诱贿。
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
//獲取文件名稱
string filename = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
if (propertyDecl && isUserSourceCode(filename)) {//如果節(jié)點有值,并且是用戶文件
//拿到屬性的類型
string typeStr = propertyDecl->getType().getAsString();
//拿到節(jié)點的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//attrKind & ObjCPropertyDecl::OBJC_PR_copy 如果實行修飾符為copy返回true
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該用copy修飾而沒用Copy,發(fā)出警告咕缎!"<<endl;
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦用Copy"))<<typeStr;
}
}
}
};
class ZFConsumer : public ASTConsumer{
private:
MatchFinder matcher;
ZFMatchCallback callBack;
public:
ZFConsumer(CompilerInstance &CI): callBack(CI) {
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callBack);
}
//解析完整個文件后被調用
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"ZF文件解析完畢!"<<endl;
matcher.matchAST(Ctx);
}
};
//繼承PluginASTAction實現(xiàn)我們自定義的Action
class ZFASTAction: 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 <ZFConsumer>(new ZFConsumer(CI));
}
};
}
//注冊插件
static FrontendPluginRegistry::Add<ZFPlugin::ZFASTAction> X("ZFPlugin", "This is the description of the plugin");
Xcode集成自定義插件
前面我們寫好了自定義的功能插件珠十,下面我們把插件集成到Xcode中去。
加載插件
打開測試的xcode項目凭豪,在Build Settings -> Other C Flags添加如下內容:
-Xclang -load -Xclang (.dylib)插件路徑 -Xclang -add-plugin -Xclang ZFPlugin焙蹭。
設置編譯器
-
設置編譯器clang為自己的clang。
clang插件需要對應版本的clang去加載嫂伞,如果版本不一致會導致編譯出錯孔厉。需要再Build Setting中新增兩項用戶定義的設置
-
分別為CC和CXX。
CC對應的是自己編譯的clang路徑帖努;
CXX對應的是自己編譯的clang++路徑烟馅;
-
接下來在Build Settings欄目中搜索Index,將Enable Index-While-Building Functionality的Default該為NO然磷。
-
然后可以達到我們的最終目的,使用自定義的clang插件檢測我們的代碼的語法了刊驴。