LLVM概念
- LLVM官网: https://llvm.org/
-
编译器架构图:
- Frontend:前端 → 词法分析、语法分析、语义分析、生成中间代码(LLVM IR)
- Optimizer:优化器 → 中间代码优化
- Backend:后端 → 生成机器码
-
传统编译器(如 CGG )的前端和后端没有完全分离,耦合在了一起,因而如果要支持一门新的语言或硬件平台,需要做大量的工作。
- 不同的前端后端使用统一的中间代码LLVM Intermediate Representation (LLVM IR)
-
- LLVM IR格式以 .ll结尾、以 .bc 的二进制格式结尾、内存格式
-
- Bitcode(Xcode 7之后)就是以.bc结尾的中间代码,是LLVM-IR在磁盘上的一种二进制表示形式。例如:clang -c -emit-llvm xxxx.m 生成 xxxx. bc
-
- 如果要转换成文本格式查看,例如:llvm-dis xxxx.bc -o xxxx.ll
-
- 苹果单独对 Bitcode 进行了额外的优化.
- i) 应用上传到 AppStore时,Xcode会将程序对应的 Bitcode一起上传;
- ii) AppStore会将 Bitcode重新编译为可执行程序,供用户下载;
- iii) Bitcode被Xcode打包成 xar文档,嵌入的 MachO中。
- 苹果单独对 Bitcode 进行了额外的优化.
-
- 如果需要支持一种新的编程语言、硬件设备,那么只需要实现一个新的前后端
- LLVM 同时支持 AOT 预先编译和 JIT 即时编译
Clang概念
- 官网:http://clang.llvm.org/
- Clang项目为LLVM 项目的C语言系列(C,C ++,Objective C / C ++,OpenCL,CUDA和RenderScript)中的语言提供语言前端和工具基础结构。提供了与GCC兼容的编译器驱动程序(clang)和与MSVC兼容的编译器驱动程序(clang-cl.exe)
- 说白了就是LLVM项目的一个子项目,LLVM的C语言家族前端。
- 相比于GCC,Clang具有如下优点
- 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
- 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
- 模块化设计:Clang采用基于库的模块化设计,易于IDE 集成及其他用途的重用
- 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据(metadata),有利于调试和错误报告
- 设计清晰简单,容易理解,易于扩展增强
编译流程
- Objective-C与swift都采用Clang作为编译器前端,编译器前端主要进行词法分析、语法分析、语义分析、生成中间代码,在这个过程中,会进行类型 检查,如果发现错误或者警告会标注出来在哪一行。
-
编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化,根据不同的系统架构生成不同的机器码。
自定义规则
执行oclint自带脚本scaffoldRule:
cd ~/oclint/oclint-scripts
./scaffoldRule Property -t ASTVisitor(参数可选为Generic,SourceCodeReader,ASTVisitor,ASTMatcher)
加载自定义规则Xcode项目
oclint-xcoderules目录下,执行create-xcode-rules.sh脚本
#! /bin/sh -e
cmake -G Xcode \
-D CMAKE_CXX_COMPILER=../build/llvm-install/bin/clang++ \
-D CMAKE_C_COMPILER=../build/llvm-install/bin/clang \
-D OCLINT_BUILD_DIR=../build/oclint-core \
-D OCLINT_SOURCE_DIR=../oclint-core \
-D OCLINT_METRICS_SOURCE_DIR=../oclint-metrics
\ -D OCLINT_METRICS_BUILD_DIR=../build/oclint-metrics \
-D LLVM_ROOT=../build/llvm-install/../oclint-rules
编译自定义项目生成dylib文件
复制到~/oclint/build/oclint-release/lib/oclint/rules下
调试规则
以下编译环境是在测试demo中的,添加到scheme下的run配置中:
-R=/Users/libing/oclint/oclint-xcoderules/rules.dl/Debug /Users/libing/oclint/oclint-xcodebuild/TestRule/TestRule/ViewController.m -- -x objective-c -isystem ~/oclint/build/oclint-release/lib/clang/5.0.1/include/ -arch x86_64 -fmessage-length=0 -fdiagnostics-show-note-include- stack -fmacro-backtrace-limit=0 -std=gnu11 -fobjc-arc -fobjc-weak -fmodules -gmodules -fmodules-cache-path=/Users/libing/Library/Developer/Xcode /DerivedData/ModuleCache.noindex -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fbuild-session-file=/Users/libing/Library/Developer/Xcode /DerivedData/ModuleCache.noindex/Session.modulevalidation -fmodules-validate-once-per-build-session -Wnon-modular-include-in-framework-module - Werror=non-modular-include-in-framework-module -Wno-trigraphs -fpascal-strings -O0 -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes - Werror=return-type -Wdocumentation -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Wno-objc-interface-ivars - Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wimplicit-retain-self -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch - Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wconditional-uninitialized - Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum- conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno- selector -Wno-strict-selector-match -Wundeclared-selector -Wdeprecated-implementations -DDEBUG=1 -DOBJC_OLD_DISPATCH_PROTOTYPES=0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk -fasm-blocks -fstrict-aliasing - Wprotocol -Wdeprecated-declarations -mios-simulator-version-min=12.2 -g -Wno-sign-conversion -Winfinite-recursion -Wcomma -Wblock-capture- autoreleasing -Wstrict-prototypes -Wno-semicolon-before-method-body -Wunguarded-availability -fobjc-abi-version=2 -fobjc-legacy-dispatch -index-store- path /Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Index/DataStore -iquote /Users/libing/Library/Developer /Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator/TestRule.build/TestRule- generated-files.hmap -I/Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build /Debug-iphonesimulator/TestRule.build/TestRule-own-target-headers.hmap -I/Users/libing/Library/Developer/Xcode/DerivedData/TestRule- fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator/TestRule.build/TestRule-all-target-headers.hmap -iquote /Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator /TestRule.build/TestRule-project-headers.hmap -I/Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Products /Debug-iphonesimulator/include -I/Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex /TestRule.build/Debug-iphonesimulator/TestRule.build/DerivedSources-normal/x86_64 -I/Users/libing/Library/Developer/Xcode/DerivedData/TestRule- fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator/TestRule.build/DerivedSources/x86_64 -I/Users/libing/Library /Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator/TestRule.build /DerivedSources -F/Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Products/Debug-iphonesimulator -MMD - MT dependencies -MF /Users/libing/Library/Developer/Xcode/DerivedData/TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build /Debug-iphonesimulator/TestRule.build/Objects-normal/x86_64/ViewController.d --serialize-diagnostics /Users/libing/Library/Developer/Xcode/DerivedData /TestRule-fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator/TestRule.build/Objects-normal/x86_64 /ViewController.dia -c /Users/libing/oclint/oclint-xcodebuild/TestRule/TestRule/ViewController.m -o /Users/libing/Library/Developer/Xcode/DerivedData/TestRule- fnyamuwmajnavbcsgeuhbzloxpbs/Build/Intermediates.noindex/TestRule.build/Debug-iphonesimulator/TestRule.build/Objects-normal/x86_64/ViewController.o
备注:-R=/Users/libing/oclint/oclint-xcoderules/rules.dl/Debug /Users/libing/oclint/oclint-xcodebuild/TestRule/TestRule/ViewController.m -- -x objective-c - isystem ~/oclint/build/oclint-release/lib/clang/5.0.1/include/ 加上图上从-arch x86_64 开始复制到最后,拼起来放到自定义规则的scheme的run配置中。
查看编译过程
clang -ccc-print-phases ViewController.m
0: input, "ViewController.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output → 预编译
2: compiler, {1}, ir → 前端编译,生成中间代码IR
3: backend, {2}, assembler → 后端Backend 4: assembler, {3}, object → 生成目标.o文件 5: linker, {4}, image → 链接
6: bind-arch, "x86_64", {5}, image → 绑定架构
查看 preprocessor(预处理)
clang -E ViewController.m
注意:出现fatal error: 'UIKit/UIKit.h' file not found,但不影响查看结果,如果不报错指定一下sdk
clang -E -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
词法分析,生成Token
clang -fmodules -E -Xclang -dump-tokens ViewController.m
语法分析,生成抽象AST语法树
通过语法分析可以清晰的看到语法树中的节点,对应的节点Decl(声明) / Stmt(语句、表达式),通过Google 搜索:clang XXX 可以直接定位到clang的该节 点文档说明。
// 分析所有声明
bool VisitDecl(Decl *decl) {
return true;// 返回true以继续遍历AST,返回false以终止遍历,退出Clang
}
// 分析表达式
bool VisitStmt(Stmt *S) {
return true;// 返回true以继续遍历AST,返回false以终止遍历,退出Clang
}
自定义规则源码
OClintScripts
自定义属性规范
- nonatomic 与 atomic 修饰:遗忘或错用了 atomic 修饰
bool VisitObjCPropertyDecl(ObjCPropertyDecl *node) {
if (node->isAtomic()) {
string description = "不应该用atomic修饰 " + node->getNameAsString() + " 请改用nonatomic";
addViolation(node, this, description);//发出警告
}
}
代码解析: 通过AST抽象语法树分析:
入口在VisitObjCPropertyDecl方法,那么在此方法中断点验证确实如此,接下来分析,属性中是否是atomic修饰,凭着灵感发现确实自动补全了isAtomic 方法,也可以这么写:
ObjCPropertyDecl::PropertyAttributeKind attributeKind = node->getPropertyAttributes();
if(!(attributeKind & ObjCPropertyDecl::OBJC_PR_nonatomic)){
string description = "不应该用atomic修饰 " + node->getNameAsString() + " 请改用nonatomic";
addViolation(node, this, description); //发出警告
}
- NSString 应尽量用 copy 修饰,避免用 strong 修饰
if(starts_with(typeStr, "NSString") && !(attributeKind & ObjCPropertyDecl::OBJC_PR_copy)) {
addViolation(node, this, typeStr + node->getNameAsString() + " 应尽量用 copy 修饰");
}
- Delegate 应该用 weak 修饰
if(typeStr.find("<")!=string::npos && typeStr.find(">")!=string::npos && starts_with(node->getType().getAsString(), "id")){ if(!(attributeKind & ObjCPropertyDecl::OBJC_PR_weak)){
cout<< node->getType().getAsString()<getNameAsString() + " 尽可能用 weak 修饰"); }
}
- Block 应该用 copy / strong 修饰
if (node->getType()->isBlockPointerType()) {
if(!((attributeKind & ObjCPropertyDecl::OBJC_PR_copy) || (attributeKind & ObjCPropertyDecl::OBJC_PR_strong))){
addViolation(node, this, node->getNameAsString() + " 应该用 copy / strong 修饰");
}
}
- 属性名称应该遵循驼峰命名,不应该以大写或_开头
string name = node->getNameAsString();
string::iterator itor = name.begin();
if(*itor >= 'A' && *itor <= 'Z'){
addViolation(node, this, "Property "+name+" 应该遵循驼峰命名,不应该以大写字母开头"); }
if(*itor == '_'){
addViolation(node, this, "Property "+name+" 应该遵循驼峰命名,不应该以下划线开头");
}
- 成员变量应该以_开头
bool VisitObjCIvarDecl(ObjCIvarDecl *node) {
string name = node->getNameAsString();
string::iterator itor = name.begin();
if(*itor != '_'){
addViolation(node, this, "成员变量 "+name+" 应该以下划线开头");
}
return true;
}
自定义空格规范
- 方法前面-或+请遵循空格规范、方法大括号请遵循空格规范
bool VisitObjCMethodDecl(ObjCMethodDecl *node)
{
string methodDeclStr;
ASTContext *context = _carrier->getASTContext();
SourceLocation begin = node->getSourceRange().getBegin();
SourceLocation end = node->getSourceRange().getEnd(); methodDeclStr.assign(context->getSourceManager().getCharacterData(begin),end.getRawEncoding()-begin.getRawEncoding());
for(string::iterator it = methodDeclStr.begin(); it!= methodDeclStr.end(); it++)
{
if (*it =='-' || *it =='+') {
if (it+2 < methodDeclStr.end()) {
if (!((*(it+1) == ' ')&&(*(it+2) == '('))) {
addViolation(node, this, "方法前面-或+请遵循空格规范");
return true;
}
}
}
if (*it =='{') {
if (it-1 >= methodDeclStr.begin()) {
if (*(it-1) != ' ' && *(it-1) != '\n') {
addViolation(node, this, "方法大括号请遵循空格规范");
}
}
return true;
}
}
return true;
}
};
- @property左括号、右括号、中间逗号、*修饰需要遵循空格规范
bool VisitObjCPropertyDecl(ObjCPropertyDecl *node)
{
string methodDeclStr;
ASTContext *context = _carrier->getASTContext();
SourceLocation begin = node->getSourceRange().getBegin();
SourceLocation end = node->getSourceRange().getEnd(); methodDeclStr.assign(context->getSourceManager().getCharacterData(begin),end.getRawEncoding()-begin.getRawEncoding());
for(string::iterator it = methodDeclStr.begin(); it!= methodDeclStr.end(); it++)
{
if (*it == '(') {
if (it-2 >= methodDeclStr.begin()) {
if (!(*(it-1) == ' ' && *(it-2) == 'y') && *(it+1) != '^') {
addViolation(node, this, node->getNameAsString() + "@property左括号请遵循空格规范");
return true;
}
}
}
if (*it == ',') {
if (it+1 < methodDeclStr.end()) {
if (*(it+1) != ' ') {
addViolation(node, this, node->getNameAsString() + "@property中逗号请遵循空格规范");
return true;
}
}
}
if (*it == ')') {
if (it+1 < methodDeclStr.end()) {
if (*(it+1) != ' ') {
addViolation(node, this, node->getNameAsString() + "@property右括号请遵循空格规范");
return true;
}
}
}
if (*it == '*') {
if (it-1 >= methodDeclStr.begin()) {
if (*(it-1) != ' ') {
addViolation(node, this, node->getNameAsString() + "@property属性*修饰请遵循空格规范");
return true;
}
}
}
}
return true;
}
自定义枚举规范
-
NS_ENUM/NS_OPTIONSenum
经过调试发现enum枚举走VisitEnumDecl方法,而NS_ENUM/NS_OPTIONS均不走。
bool VisitEnumDecl(EnumDecl *node)
{
addViolation(node, this, node->getNameAsString()+"枚举尽量使用NS_ENUM/NS_OPTIONS");
return true;
}
或者基于AbstractASTMatcherRule匹配
virtual void callback(const MatchFinder::MatchResult &result) override {
const EnumDecl *enumDecl = result.Nodes.getNodeAs("enumDecl");
if (enumDecl)
{
addViolation(node, this, "枚举尽量使用NS_ENUM/NS_OPTIONS");
}
}
virtual void setUpMatcher() override {
addMatcher(enumDecl().bind("enumDecl"));
}
- 枚举不要下划线开头
bool VisitEnumConstantDecl(EnumConstantDecl *node)
{
string name = node->getNameAsString();
string::iterator itor = name.begin();
if(*itor == '_'){
addViolation(node, this, "枚举不要下划线开头");
}
return true;
}
自定义类名规范
类名使用大写字母开头
bool VisitObjCProtocolDecl(ObjCProtocolDecl *node)
{
string name = node->getNameAsString();
string::iterator itor = name.begin();
if(!(*itor >= 'A' && *itor <= 'Z')){
addViolation(node, this, name+"协议名请用大写字母开头");
}
return true;
}
bool VisitObjCImplDecl(ObjCImplDecl *node)
{
string name = node->getNameAsString();
string::iterator itor = name.begin();
if(!(*itor >= 'A' && *itor <= 'Z')){
addViolation(node, this, name+"类名请用大写字母开头");
}
return true;
}
自定义方法格式规范
方法参数超过3个,需要折行并冒号对齐
bool VisitObjCMethodDecl(ObjCMethodDecl *node)
{
string name = node->getNameAsString();
if( node->param_size()>_threshold){
string methodDeclStr;
ASTContext *context = _carrier->getASTContext();
SourceLocation begin = node->getSourceRange().getBegin();
SourceLocation end = node->getSourceRange().getEnd(); methodDeclStr.assign(context->getSourceManager().getCharacterData(begin),end.getRawEncoding()-node->getSourceRange().getBegin().getRawEncoding());
string replaceName = formatObjcMethodName(methodDeclStr);
if(methodDeclStr.find(replaceName)){
addViolation(node, this, "方法参数超过"+to_string(_threshold)+"个,需要折行并冒号对齐");
}
}
return true;
}
修正oclint误报规范
PreferEarlyExit:使用提前退出/继续来简化代码并减少缩进
误报:懒加载与init初始化误判
修正思路:识别懒加载与init方法进行判断
bool starts_with(const string& s1, const string& s2) {
return s2.size() <= s1.size() && s1.compare(0, s2.size(), s2) == 0;
}
// 将Property入栈到数组与懒加载比较是否selector相同
bool VisitObjCPropertyDecl(ObjCPropertyDecl *node) {
if(std::find(_arraySelector.begin(), _arraySelector.end(), node->getNameAsString()) == _arraySelector.end()) {
_arraySelector.push_back(node->getNameAsString());
}
return true;
}
//以init开头的都认为是初始化方法
bool VisitObjCMethodDecl(ObjCMethodDecl *node) {
string selectorName = node->getSelector().getAsString();
if (starts_with(selectorName, "init") || std::find(_arraySelector.begin(), _arraySelector.end(), selectorName) != _arraySelector.end() ) {
_isSysSelector = true;
} else {
_isSysSelector = false;
}
return true;
}
//通过_isSysSelector修正误报
bool VisitCompoundStmt(CompoundStmt* compoundStmt) {
if (compoundStmt->size() < 2) {
return true;
}
if (_isSysSelector) {
return true;
}
auto last = compoundStmt->body_rbegin();
if (isFlowOfControlInterrupt(*last)) {
addViolationIfStmtIsLongIf(*++last);
}
return true;
}
自定义内存泄漏检测
由于目前xcode对block循环引用在编译时期已有提示:capturing 'self' strongly in this block is likely to lead to a retain cycle,但如果API 中的block并未检测。新增了此处关于block强引用的检测。
virtual void setUp() override {
_teamname = RuleConfiguration::stringForKey("TEAM_NAME", "xxxxx");
this->ignoreArr.push_back("UIView"); // 忽略UIView类的block检测
}
virtual void tearDown() override {}
bool isMasnoryBlock(BlockDecl *node) {
for (BlockDecl::param_iterator iterator = node->param_begin() ; iterator != node->param_end(); iterator ++)
{
if ((*iterator)->getType().getAsString().find("MASConstraintMaker") != string::npos) return true;
return false;
}
bool isIgnoreArrClass(ObjCMessageExpr *node) {
string type = node->getClassReceiver().getAsString();
vector::iterator ret = std::find(this->ignoreArr.begin(), this->ignoreArr.end(), type);
if(ret == this->ignoreArr.end()) {
return false;
} else {
return true;
}
}
bool VisitObjCMessageExpr(ObjCMessageExpr *node)
{
if (this->isIgnoreArrClass(node)) {
return true;
}
int argCount = node->getNumArgs();
Expr **exprArray = node->getArgs();
for (int i = 0; i < argCount; i ++ ) {
BlockExpr *expr = dyn_cast_or_null(exprArray[i]);
if (expr && expr->getBlockDecl()) {
BlockDecl *blockDecl = expr->getBlockDecl();
for (BlockDecl::capture_const_iterator iterator = blockDecl->capture_begin() ; iterator != blockDecl->capture_end(); iterator ++) {
ImplicitParamDecl *implicitParamDecl = dyn_cast_or_null(iterator->getVariable());
if (implicitParamDecl && implicitParamDecl->getName() == "self") {
if (!isMasnoryBlock(blockDecl)) {
string methodDeclStr;
ASTContext *context = _carrier->getASTContext();
SourceLocation begin = this->methodDecl->getSourceRange().getBegin();
SourceLocation end = this->methodDecl->getSourceRange().getEnd();
methodDeclStr.assign(context->getSourceManager().getCharacterData(begin),end.getRawEncoding()-begin.getRawEncoding());
string methodblock;
SourceLocation beginBlock = node->getSourceRange().getBegin();
SourceLocation endBlock = node->getSourceRange().getEnd();
methodblock.assign(context->getSourceManager(). getCharacterData(beginBlock),endBlock.getRawEncoding()-beginBlock.getRawEncoding());
if (methodDeclStr.find(methodblock)!=string::npos && methodDeclStr.find("@weakify(self)")==string::npos && methodDeclStr.find("__weak")==string::npos && node->getSelector().getAsString().find("animateWithDuration:")==string::npos)
{
addViolation(blockDecl, this,"block中强引用了self,注意使用weak修饰self");
}
}
}
}
}
}
return true;
}
bool VisitObjCMethodDecl(ObjCMethodDecl *node) {
this->methodDecl = node;
return true;
}