@qidiandasheng
2022-08-09T22:49:36.000000Z
字数 20759
阅读 2568
iOS理论
LLVM是一个模块化和可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 轻量级编译器(简单的说属于LLVM前端的部分),目的是提供惊人的快速编译,比 GCC 快3倍。它与GNU C语言规范几乎完全兼容,并增加了额外的特性。
其中的clang static analyzer
主要是进行语法分析,语义分析和生成中间代码,当然这个过程会对代码进行检查,出错的和需要警告的会标注出来。LLVM 核心库提供一个优化器,对流行的 CPU 做代码生成支持。lld 是 Clang / LLVM
的内置链接器,clang
必须调用链接器来产生可执行文件。
在Xcode中Clang
的功能是首先对 Objective-C
代码做分析检查,然后将其转换为低级的类汇编代码:LLVM Intermediate Representation(LLVM 中间表达码)
。接着 LLVM
会执行相关指令将 LLVM IR
编译成目标平台上的本地字节码,这个过程的完成方式可以是即时编译 (Just-in-time
),或在编译的时候完成。
新建一个项目:
#import <Foundation/Foundation.h>
#define DEFINEEight 8
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:”starming”];
int rank = eight + six;
NSLog(@“%@ rank %d”, site, rank);
}
return 0;
}
命令行输入:
clang -ccc-print-phases main.m
可以看到编译源文件需要的几个不同的阶段:
0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output //预处理
2: compiler, {1}, ir //编译生成IR(中间代码)
3: backend, {2}, assembler //汇编器生成汇编代码
4: assembler, {3}, object //生成机器码
5: linker, {4}, image //链接
6: bind-arch, "x86_64", {5}, image //生成Image,也就是最后的可执行文件
通过-E查看clang在预编译处理这步做了什么
clang -E main.m
这个过程的处理包括宏的替换,头文件的导入。下面这些代码也会在这步处理。
在预编译后的顶部可以看到的许多行语句都是以 # 开头 (读作 hash)。这些被称为 行标记的语句告诉我们后面跟着的内容来自哪里。
我们都用过 #include
和 #import
。它们所做的事情是告诉预处理器将文件Foundation.h
中的内容插入到 #import
语句所在的位置。
这是一个递归的过程:Foundation.h
可能会包含其它的文件。由于这样的递归插入过程很多,所以我们需要确保记住相关行号信息。
为了确保无误,预处理器在发生变更的地方插入以 # 开头的 行标记。跟在 # 后面的数字是在源文件中的行号,而最后的数字是在新文件中的行号。
预处理完成后就会进行词法分析,这里会把代码切成一个个 Token,比如大小括号,等于号还有字符串等。
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
然后是语法分析,验证语法是否正确,然后将所有节点组成抽象语法树 AST 。
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
完成这些步骤后就可以开始IR中间代码的生成了,CodeGen 会负责将语法树自顶向下遍历逐步翻译成 LLVM IR
,IR 是编译过程的前端的输出后端的输入。
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
clang -S -fobjc-arc main.m -o main.s
clang -fmodules -c main.m -o main.o
clang main.o -o main
执行
./main
输出
starming rank 14
预处理
在计算机科学中,预处理器是程序中处理输入数据,产生能用来输入到其他程序的数据的程序。输出被称为输入数据预处理过的形式,常用在之后的程序比如编译器中。所作处理的数量和种类依赖于预处理器的类型,一些预处理器只能够执行相对简单的文本替换和宏展开,而另一些则有着完全成熟的编程语言的能力。
中间代码IR
源语言->中间代码->目标语言
中间代码(Intermediate Representation或者IR):复杂性介于源程序语言和机器语言的一种表示形式。
汇编语言
汇编语言(英语:assembly language)是一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。
使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程。
机器语言
机器语言是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合。它是计算机的设计者通过计算机的硬件结构赋予计算机的操作功能。机器语言具有灵活、直接执行和速度快等特点。不同种类的计算机其机器语言是不相通的,按某种计算机的机器指令编制的程序不能在另一种计算机上执行。
要用机器语言编写程序,编程人员需首先熟记所用计算机的全部指令代码和代码的涵义。手编程序时,程序员要自己处理每条指令和每一数据的存储分配和输入输出,还需记住编程过程中每步所使用的工作单元处在何种状态。这是一件十分繁琐的工作,编写程序花费的时间往往是实际运行时间的几十倍或几百倍。而且,这样编写出的程序完全是0与1的指令代码,可读性差且容易出错。在现今,除了计算机生产厂家的专业人员外,绝大多数程序员已经不再学习机器语言。
我们在看很多runtime
文章的时候,看到作者都用了clang
对文件进行重写转为C++代码来进行源码分析。使用clang -rewrite-objc xxx.m
系统就会创建一个对应的.cpp文件,然后你就可以进行分析了。
我们在运行clang -rewrite-objc xxx.m
会遇到如下的错误:
main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found
#import <UIKit/UIKit.h>
^
1 error generated.
其实就是没有找到对应的库的原因,给他指定一下对应的SDK就好了。解决方法如下:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxx.m
或者
xcrun -sdk iphonesimulator9.3 clang -rewrite-objc
//如果引入的有第三方库那可以这样写:
xcrun -sdk iphonesimulator9.3 clang -rewrite-objc –F /目录/第三方库名 xxx.m
xcrun -sdk iphonesimulator13.4 clang -S -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.4 xxx.m
//展示 SDK 版本命令
xcodebuild -showsdks
当然每次写这么长的命令会很麻烦,可通过alias简化工作:
进入终端,键入命令 vim ~/.bash_profile
在vim界面输入i进入编辑编辑状态并且键入:
alias rewriteoc='clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'
键入完毕,点esc退出编辑状态,再键入:wq退出vim并保存,执行source ~/.bash_profile
<-这句一定要执行,执行才会生效
最后cd到目录rewriteoc xxx.m
就可以了,马上就会在当前目录里生产对应的.cpp
文件。
当重写的文件里有weak的时候会报如下错误:
//即上面的xxx.m文件
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = [[NSString alloc] initWithFormat:@"hehe"];
id __weak weakSelf = str;
}
//clang -rewrite-objc xxx.m文件后报错
/var/folders/5r/jrhk7d9n3sqbkhc5x5xf5dmr0000gn/T/ViewController-8a7a55.mi:43374:23: error:
cannot create __weak reference because the current deployment target does
not support weak references
id __attribute__((objc_ownership(weak))) weakSelf = str;
^
1 error generated.
表示编译时weak
需要运行时环境的支持:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -fobjc-arc -fblocks -mios-version-min=8.0.0 -fobjc-runtime=ios-8.0.0 -O0 xxx.m
cross platform make
跨平台的安装(编译)工具,使用CMakeList.txt
来描述整个编译安装流程,然后根据目标平台进一步生成所需的本地化makefile
和文件等。
简单例子:
main.cpp
文件:
#include
int main()
{
std::cout<<"Hello word!"<<std::endl;
return0;
}
编辑CMakeLists.txt
文件:
PROJECT(main)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
ADD_EXECUTABLE(main ${DIR_SRCS})
执行命令:
cmake -G "Xcode" .
出现以下错误的话,表示找不到编译器,执行sudo xcode-select --switch /Applications/Xcode.app/
-- The C compiler identification is unknown
-- The CXX compiler identification is unknown
CMake Error at CMakeLists.txt:2 (project):
No CMAKE_C_COMPILER could be found.
CMake Error at CMakeLists.txt:2 (project):
No CMAKE_CXX_COMPILER could be found.
这里Clone clang
源码,clang
会有更新,我这里用的是当前最新的release_90
分支,如果你下面使用过程中遇到错误,可能是clang
版本不够新的缘故,可自行选择最新版本。
sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`
git clone -b release_90 git@github.com:llvm-mirror/llvm.git llvm
git clone -b release_90 git@github.com:llvm-mirror/clang.git llvm/tools/clang
git clone -b release_90 git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
git clone -b release_90 git@github.com:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
下面是三种编译Clang
源码的方式,这里推荐使用Xcode
编译的方式。
mkdir llvm_build
cd llvm_build
cmake ../llvm -DCMAKE_BUILD_TYPE:STRING=Release
make -j`sysctl -n hw.logicalcpu`
mkdir llvm_build
cd llvm_build
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=../llvm_release
ninja编译
cd llvm_build
ninja
耗时十几分钟,结果:
输出 [3516/3516] Linking CXX executable bin/opt
27.16 GB
ninja install
cd llvm_build
ninja install
将很多二进制安装到 llvm_release/bin 目录下。
mkdir llvm_xcode
cd llvm_xcode
cmake -G Xcode ../llvm -DCMAKE_INSTALL_PREFIX=../llvm_release
选择scheme
,ALL_BUILD
,执行build
(预计一个小时)。
基于LLVM开发Clang插件进行代码风格检查
使用Xcode开发iOS语法检查的Clang插件
cd到llvm/llvm/tools/clang/examples
1.打开这个目录下的CMakeLists.txt
文件,然后添加add_subdirectory(CodeChecker)
if(NOT CLANG_BUILD_EXAMPLES)
set_property(DIRECTORY PROPERTY EXCLUDE_FROM_ALL ON)
set(EXCLUDE_FROM_ALL ON)
endif()
add_subdirectory(CodeChecker)
add_subdirectory(clang-interpreter)
add_subdirectory(PrintFunctionNames)
add_subdirectory(AnnotateFunctions)
2.在当前目录创建新的文件夹CodeChecker
,并cd到CodeChecker
mkdir CodeChecker
cd CodeChecker
3.在新建的CodeChecker
目录下创建三个文件
touch CMakeLists.txt
touch CodeChecker.cpp
touch CodeChecker.exports
4.在新创建的CMakeLists.txt
中添加
if( NOT MSVC ) # MSVC mangles symbols differently
if( NOT LLVM_REQUIRES_RTTI )
if( NOT LLVM_REQUIRES_EH )
set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CodeChecker.exports)
endif()
endif()
endif()
add_llvm_library( CodeChecker MODULE BUILDTREE_ONLY
CodeChecker.cpp
)
if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
target_link_libraries(CodeChecker ${cmake_2_8_12_PRIVATE}
clangAST
clangBasic
clangFrontend
LLVMSupport
)
endif()
5.在CodeChecker.cpp
文件中加入
#include <iostream>
#include <stdio.h>
#include <string>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <functional>
#include <vector>
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Sema/Sema.h"
using namespace clang;
using namespace std;
namespace
{
static vector<string> split(const string &s, char delim)
{
vector<string> elems;
stringstream ss;
ss.str(s);
string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
class CodeVisitor : public RecursiveASTVisitor<CodeVisitor>
{
private:
CompilerInstance &Instance;
ASTContext *Context;
public:
void setASTContext (ASTContext &context)
{
this -> Context = &context;
}
private:
/**
判断是否为用户源码
@param decl 声明
@return true 为用户源码,false 非用户源码
*/
bool isUserSourceCode (Decl *decl)
{
string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();
if (filename.empty())
return false;
//非XCode中的源码都认为是用户源码
if(filename.find("/Applications/Xcode.app/") == 0)
return false;
return true;
}
/**
检测类名是否存在小写开头
@param decl 类声明
*/
void checkClassNameForLowercaseName(ObjCInterfaceDecl *decl)
{
StringRef className = decl -> getName();
//类名称必须以大写字母开头
char c = className[0];
if (isLowercase(c))
{
//修正提示
std::string tempName = className;
tempName[0] = toUppercase(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Instance.getDiagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowercase letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测类名是否包含下划线
@param decl 类声明
*/
void checkClassNameForUnderscoreInName(ObjCInterfaceDecl *decl)
{
StringRef className = decl -> getName();
//类名不能包含下划线
size_t underscorePos = className.find('_');
if (underscorePos != StringRef::npos)
{
//修正提示
std::string tempName = className;
std::string::iterator end_pos = std::remove(tempName.begin(), tempName.end(), '_');
tempName.erase(end_pos, tempName.end());
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告错误
DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden");
SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测方法名是否存在大写开头
@param decl 方法声明
*/
void checkMethodNameForUppercaseName(ObjCMethodDecl *decl)
{
//检查名称的每部分,都不允许以大写字母开头
Selector sel = decl -> getSelector();
int selectorPartCount = decl -> getNumSelectorLocs();
for (int i = 0; i < selectorPartCount; i++)
{
StringRef selName = sel.getNameForSlot(i);
char c = selName[0];
if (isUppercase(c))
{
//修正提示
std::string tempName = selName;
tempName[0] = toLowercase(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl -> getSelectorLoc(i);
SourceLocation nameEnd = nameStart.getLocWithOffset(selName.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Instance.getDiagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with uppercase letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
}
/**
检测方法中定义的参数名称是否存在大写开头
@param decl 方法声明
*/
void checkMethodParamsNameForUppercaseName(ObjCMethodDecl *decl)
{
for (ObjCMethodDecl::param_iterator it = decl -> param_begin(); it != decl -> param_end(); it++)
{
ParmVarDecl *parmVarDecl = *it;
StringRef name = parmVarDecl -> getName();
char c = name[0];
if (isUppercase(c))
{
//修正提示
std::string tempName = name;
tempName[0] = toLowercase(c);
StringRef replacement(tempName);
SourceLocation nameStart = parmVarDecl -> getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Instance.getDiagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with uppercase letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
}
/**
检测方法实现是否超过500行代码
@param decl 方法声明
*/
void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl)
{
if (decl -> hasBody())
{
//存在方法体
Stmt *methodBody = decl -> getBody();
string srcCode;
srcCode.assign(Instance.getSourceManager().getCharacterData(methodBody->getSourceRange().getBegin()),
methodBody->getSourceRange().getEnd().getRawEncoding() - methodBody->getSourceRange().getBegin().getRawEncoding() + 1);
vector<string> lines = split(srcCode, '\n');
if(lines.size() > 500)
{
DiagnosticsEngine &D = Instance.getDiagnostics();
unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines");
D.Report(decl -> getSourceRange().getBegin(), diagID);
}
}
}
/**
检测属性名是否存在大写开头
@param decl 属性声明
*/
void checkPropertyNameForUppercaseName(ObjCPropertyDecl *decl)
{
bool checkUppercaseNameIndex = 0;
StringRef name = decl -> getName();
if (name.find('_') == 0)
{
//表示以下划线开头
checkUppercaseNameIndex = 1;
}
//名称必须以小写字母开头
char c = name[checkUppercaseNameIndex];
if (isUppercase(c))
{
//修正提示
std::string tempName = name;
tempName[checkUppercaseNameIndex] = toLowercase(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告错误
DiagnosticsEngine &D = Instance.getDiagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Property name should not start with uppercase letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测属性名是否包含下划线
@param decl 属性声明
*/
void checkPropertyNameForUnderscoreInName(ObjCPropertyDecl *decl)
{
StringRef name = decl -> getName();
if (name.size() == 1)
{
//不需要检测
return;
}
//类名不能包含下划线
size_t underscorePos = name.find('_', 1);
if (underscorePos != StringRef::npos)
{
//修正提示
std::string tempName = name;
std::string::iterator end_pos = std::remove(tempName.begin() + 1, tempName.end(), '_');
tempName.erase(end_pos, tempName.end());
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告错误
DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Property name with `_` forbidden");
SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测委托属性是否有使用weak修饰
@param decl 属性声明
*/
void checkDelegatePropertyForUsageWeak (ObjCPropertyDecl *decl)
{
QualType type = decl -> getType();
StringRef typeStr = type.getAsString();
//Delegate
if(typeStr.find("<") != string::npos && typeStr.find(">") != string::npos)
{
ObjCPropertyDecl::PropertyAttributeKind attrKind = decl -> getPropertyAttributes();
string typeSrcCode;
typeSrcCode.assign(Instance.getSourceManager().getCharacterData(decl -> getSourceRange().getBegin()),
decl -> getSourceRange().getEnd().getRawEncoding() - decl -> getSourceRange().getBegin().getRawEncoding());
if(!(attrKind & ObjCPropertyDecl::OBJC_PR_weak))
{
DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Warning, "Delegate should be declared as weak.");
diagEngine.Report(decl -> getLocation(), diagID);
}
}
}
/**
检测常量名称是否存在小写开头
@param decl 常量声明
*/
void checkConstantNameForLowercaseName (VarDecl *decl)
{
StringRef className = decl -> getName();
//类名称必须以大写字母开头
char c = className[0];
if (isLowercase(c))
{
//修正提示
std::string tempName = className;
tempName[0] = toUppercase(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Instance.getDiagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Constant name should not start with lowercase letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测变量名称是否存在大写开头
@param decl 变量声明
*/
void checkVarNameForUppercaseName (VarDecl *decl)
{
StringRef className = decl -> getName();
//类名称必须以大写字母开头
char c = className[0];
if (isUppercase(c))
{
//修正提示
std::string tempName = className;
tempName[0] = toLowercase(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Instance.getDiagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Variable name should not start with uppercase letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测变量名称
@param decl 变量声明
*/
void checkVarName(VarDecl *decl)
{
if (decl -> isStaticLocal())
{
//静态变量
if (decl -> getType().isConstant(*this -> Context))
{
//常量
checkConstantNameForLowercaseName(decl);
}
else
{
//非常量
checkVarNameForUppercaseName(decl);
}
}
else if (decl -> isLocalVarDecl())
{
//本地变量
if (decl -> getType().isConstant(*this -> Context))
{
//常量
checkConstantNameForLowercaseName(decl);
}
else
{
//非常量
checkVarNameForUppercaseName(decl);
}
}
else if (decl -> isFileVarDecl())
{
//文件定义变量
if (decl -> getType().isConstant(*this -> Context))
{
//常量
checkConstantNameForLowercaseName(decl);
}
else
{
//非常量
checkVarNameForUppercaseName(decl);
}
}
}
public:
CodeVisitor (CompilerInstance &Instance)
:Instance(Instance)
{
}
/**
观察ObjC的类声明
@param declaration 声明对象
@return 返回
*/
bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *declaration)
{
if (isUserSourceCode(declaration))
{
checkClassNameForLowercaseName(declaration);
checkClassNameForUnderscoreInName(declaration);
}
return true;
}
/**
观察类方法声明
@param declaration 声明对象
@return 返回
*/
bool VisitObjCMethodDecl(ObjCMethodDecl *declaration)
{
if (isUserSourceCode(declaration))
{
checkMethodNameForUppercaseName(declaration);
checkMethodParamsNameForUppercaseName(declaration);
checkMethodBodyForOver500Lines(declaration);
}
return true;
}
/**
观察类属性声明
@param declaration 声明对象
@return 返回
*/
bool VisitObjCPropertyDecl(ObjCPropertyDecl *declaration)
{
if (isUserSourceCode(declaration))
{
checkPropertyNameForUppercaseName(declaration);
checkPropertyNameForUnderscoreInName(declaration);
checkDelegatePropertyForUsageWeak(declaration);
}
return true;
}
/**
观察变量声明
@param declaration 声明对象
@return 返回
*/
bool VisitVarDecl(VarDecl *declaration)
{
if (isUserSourceCode(declaration))
{
checkVarName(declaration);
}
return true;
}
/**
观察枚举常量声明
@param declaration 声明对象
@return 返回
*/
// bool VisitEnumConstantDecl (EnumConstantDecl *declaration)
// {
// return true;
// }
};
class CodeConsumer : public ASTConsumer
{
CompilerInstance &Instance;
std::set<std::string> ParsedTemplates;
public:
CodeConsumer(CompilerInstance &Instance,
std::set<std::string> ParsedTemplates)
: Instance(Instance), ParsedTemplates(ParsedTemplates), visitor(Instance)
{
}
bool HandleTopLevelDecl(DeclGroupRef DG) override
{
return true;
}
void HandleTranslationUnit(ASTContext& context) override
{
visitor.setASTContext(context);
visitor.TraverseDecl(context.getTranslationUnitDecl());
}
private:
CodeVisitor visitor;
};
class CodeASTAction : public PluginASTAction
{
std::set<std::string> ParsedTemplates;
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
llvm::StringRef) override
{
return llvm::make_unique<CodeConsumer>(CI, ParsedTemplates);
}
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &args) override
{
// DiagnosticsEngine &D = CI.getDiagnostics();
// D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
// "My plugin Started..."));
return true;
}
};
}
static clang::FrontendPluginRegistry::Add<CodeASTAction>
X("CodeChecker", "Code Checker");
1.cd到llvm
,注意不是llvm/llvm
,执行
mkdir llvm_build && cd llvm_build
cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel
2.然后会在llvm_build
文件目录中看到LLVM.xcodeproj
,用xcode打开,选择Automatically Create Schemes
3.Scheme
分别选择 clang
,CodeChecker
,libclang
编译
4.在llvm_build/Debug/lib
目录下可以找到我们的插件,如下图:
创建需要加载插件的项目,在Build Settings栏目中的OTHER C FLAGS
添加上如下内容:
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang 插件名字
我把插件拷贝的桌面了,所以我的是:
-Xclang -load -Xclang /Users/dasheng/Desktop/llvm/llvm_build/Debug/lib/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker
然后你build项目,可能有unable to load plugin '/Users/dasheng/Desktop/llvm/llvm_build/Debug/lib/CodeChecker.dylib
的error,这是由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误。
在Build Settings
栏目中新增两项用户定义的设置,分别为CC和CXX。 CC对应的是自己编译的clang的绝对路径,CXX对应的是自己编译的clang++的绝对路径。
/Users/dasheng/Desktop/llvm/llvm_build/Debug/bin/clang
/Users/dasheng/Desktop/llvm/llvm_build/Debug/bin/clang++
再build,会有以下错误:
在Build Settings
栏目中搜索index
,将Enable Index-Wihle-Building Functionality
的Default
改为NO
。
再build就能做代码风格检查啦:
下载XcodeHacking.zip:
https://raw.githubusercontent.com/kangwang1988/kangwang1988.github.io/master/others/XcodeHacking.zip
clang++ *** -Xclang -load -Xclang path-of-your-plugin.dylib -Xclang -add-plugin -Xclang your-pluginName -Xclang -plugin-arg-your-pluginName -Xclang your-pluginName-param
hack Xcode
:
sudo mv HackedClang.xcplugin /Applications/Xcode.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
sudo mv HackedBuildSystem.xcspec /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications
在Xcode->Target-Build Settings->Build Options->Compiler for C/C++/Objective-C选择Clang LLVM Trunk即可使得Xcode使用上文生成的的clang来编译。至于其他命令行参数均可通过Xcode中的编译选项设置完成。
深入剖析 iOS 编译 Clang / LLVM
iOS编译过程的原理和应用
LLVM_Clang
基于clang插件的一种iOS包大小瘦身方案
编译器编译器介绍
LLVM与Clang-开发者的惊愕
Objective-C编译成C++代码报错
深入剖析 iOS 编译 Clang LLVM
基于clang插件的一种iOS包大小瘦身方案
Hades:移动端静态分析框架