[关闭]
@qidiandasheng 2022-08-09T14:49:36.000000Z 字数 20759 阅读 2280

iOS 编译 LLVM/Clang(😁)

iOS理论


LLVM/Clang介绍

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),或在编译的时候完成。

截屏2021-07-03 下午12.34.07.png-1184.6kB

编译过程介绍

下载.png-475.8kB

源码编译

新建一个项目:

  1. #import <Foundation/Foundation.h>
  2. #define DEFINEEight 8
  3. int main(){
  4. @autoreleasepool {
  5. int eight = DEFINEEight;
  6. int six = 6;
  7. NSString* site = [[NSString alloc] initWithUTF8String:”starming”];
  8. int rank = eight + six;
  9. NSLog(@“%@ rank %d”, site, rank);
  10. }
  11. return 0;
  12. }

命令行输入:

  1. clang -ccc-print-phases main.m

可以看到编译源文件需要的几个不同的阶段:

  1. 0: input, "main.m", objective-c
  2. 1: preprocessor, {0}, objective-c-cpp-output //预处理
  3. 2: compiler, {1}, ir //编译生成IR(中间代码)
  4. 3: backend, {2}, assembler //汇编器生成汇编代码
  5. 4: assembler, {3}, object //生成机器码
  6. 5: linker, {4}, image //链接
  7. 6: bind-arch, "x86_64", {5}, image //生成Image,也就是最后的可执行文件

预编译阶段

通过-E查看clang在预编译处理这步做了什么

  1. clang -E main.m

这个过程的处理包括宏的替换,头文件的导入。下面这些代码也会在这步处理。

在预编译后的顶部可以看到的许多行语句都是以 # 开头 (读作 hash)。这些被称为 行标记的语句告诉我们后面跟着的内容来自哪里。

我们都用过 #include#import。它们所做的事情是告诉预处理器将文件Foundation.h 中的内容插入到 #import 语句所在的位置。

这是一个递归的过程:Foundation.h 可能会包含其它的文件。由于这样的递归插入过程很多,所以我们需要确保记住相关行号信息。

为了确保无误,预处理器在发生变更的地方插入以 # 开头的 行标记。跟在 # 后面的数字是在源文件中的行号,而最后的数字是在新文件中的行号。

词法分析

预处理完成后就会进行词法分析,这里会把代码切成一个个 Token,比如大小括号,等于号还有字符串等。

  1. clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

语法分析

然后是语法分析,验证语法是否正确,然后将所有节点组成抽象语法树 AST 。

  1. clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

合成中间代码

完成这些步骤后就可以开始IR中间代码的生成了,CodeGen 会负责将语法树自顶向下遍历逐步翻译成 LLVM IR,IR 是编译过程的前端的输出后端的输入。

  1. clang -S -fobjc-arc -emit-llvm main.m -o main.ll

生成汇编

  1. clang -S -fobjc-arc main.m -o main.s

生成目标文件

  1. clang -fmodules -c main.m -o main.o

生成可执行文件

  1. clang main.o -o main
  2. 执行
  3. ./main
  4. 输出
  5. starming rank 14

名词解析

预处理

在计算机科学中,预处理器是程序中处理输入数据,产生能用来输入到其他程序的数据的程序。输出被称为输入数据预处理过的形式,常用在之后的程序比如编译器中。所作处理的数量和种类依赖于预处理器的类型,一些预处理器只能够执行相对简单的文本替换和宏展开,而另一些则有着完全成熟的编程语言的能力。

中间代码IR

源语言->中间代码->目标语言
中间代码(Intermediate Representation或者IR):复杂性介于源程序语言和机器语言的一种表示形式。

汇编语言

汇编语言(英语:assembly language)是一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。

使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程。

机器语言

机器语言是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合。它是计算机的设计者通过计算机的硬件结构赋予计算机的操作功能。机器语言具有灵活、直接执行和速度快等特点。不同种类的计算机其机器语言是不相通的,按某种计算机的机器指令编制的程序不能在另一种计算机上执行。

要用机器语言编写程序,编程人员需首先熟记所用计算机的全部指令代码和代码的涵义。手编程序时,程序员要自己处理每条指令和每一数据的存储分配和输入输出,还需记住编程过程中每步所使用的工作单元处在何种状态。这是一件十分繁琐的工作,编写程序花费的时间往往是实际运行时间的几十倍或几百倍。而且,这样编写出的程序完全是0与1的指令代码,可读性差且容易出错。在现今,除了计算机生产厂家的专业人员外,绝大多数程序员已经不再学习机器语言。

重写OC源码转为C++

我们在看很多runtime文章的时候,看到作者都用了clang对文件进行重写转为C++代码来进行源码分析。使用clang -rewrite-objc xxx.m系统就会创建一个对应的.cpp文件,然后你就可以进行分析了。

我们在运行clang -rewrite-objc xxx.m会遇到如下的错误:

  1. main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found
  2. #import <UIKit/UIKit.h>
  3. ^
  4. 1 error generated.

其实就是没有找到对应的库的原因,给他指定一下对应的SDK就好了。解决方法如下:

  1. clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxx.m

或者

  1. xcrun -sdk iphonesimulator9.3 clang -rewrite-objc
  2. //如果引入的有第三方库那可以这样写:
  3. xcrun -sdk iphonesimulator9.3 clang -rewrite-objc F /目录/第三方库名 xxx.m
  1. xcrun -sdk iphonesimulator13.4 clang -S -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.4 xxx.m
  1. //展示 SDK 版本命令
  2. xcodebuild -showsdks

当然每次写这么长的命令会很麻烦,可通过alias简化工作:

  1. alias rewriteoc='clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'

问题

当重写的文件里有weak的时候会报如下错误:

  1. //即上面的xxx.m文件
  2. - (void)viewDidLoad {
  3. [super viewDidLoad];
  4. NSString *str = [[NSString alloc] initWithFormat:@"hehe"];
  5. id __weak weakSelf = str;
  6. }
  1. //clang -rewrite-objc xxx.m文件后报错
  2. /var/folders/5r/jrhk7d9n3sqbkhc5x5xf5dmr0000gn/T/ViewController-8a7a55.mi:43374:23: error:
  3. cannot create __weak reference because the current deployment target does
  4. not support weak references
  5. id __attribute__((objc_ownership(weak))) weakSelf = str;
  6. ^
  7. 1 error generated.

表示编译时weak需要运行时环境的支持:

  1. 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

基于LLVM/Clang的静态分析

如何编写 Clang 插件(戴铭)

37丨如何编写 Clang 插件?.html1226.1kB

cmake

cross platform make 跨平台的安装(编译)工具,使用CMakeList.txt来描述整个编译安装流程,然后根据目标平台进一步生成所需的本地化makefile和文件等。

简单例子:

main.cpp文件:

  1. #include
  2. int main()
  3. {
  4. std::cout<<"Hello word!"<<std::endl;
  5. return0;
  6. }

编辑CMakeLists.txt文件:

  1. PROJECT(main)
  2. CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
  3. AUX_SOURCE_DIRECTORY(. DIR_SRCS)
  4. ADD_EXECUTABLE(main ${DIR_SRCS})

执行命令:

  1. cmake -G "Xcode" .

出现以下错误的话,表示找不到编译器,执行sudo xcode-select --switch /Applications/Xcode.app/

  1. -- The C compiler identification is unknown
  2. -- The CXX compiler identification is unknown
  3. CMake Error at CMakeLists.txt:2 (project):
  4.  No CMAKE_C_COMPILER could be found.
  5. CMake Error at CMakeLists.txt:2 (project):
  6.  No CMAKE_CXX_COMPILER could be found.

Clone clang源码并编译安装

这里Clone clang源码,clang会有更新,我这里用的是当前最新的release_90分支,如果你下面使用过程中遇到错误,可能是clang版本不够新的缘故,可自行选择最新版本。

  1. sudo mkdir llvm
  2. sudo chown `whoami` llvm
  3. cd llvm
  4. export LLVM_HOME=`pwd`
  5. git clone -b release_90 git@github.com:llvm-mirror/llvm.git llvm
  6. git clone -b release_90 git@github.com:llvm-mirror/clang.git llvm/tools/clang
  7. git clone -b release_90 git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
  8. git clone -b release_90 git@github.com:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt

下面是三种编译Clang源码的方式,这里推荐使用Xcode编译的方式。

使用cmake

  1. mkdir llvm_build
  2. cd llvm_build
  3. cmake ../llvm -DCMAKE_BUILD_TYPE:STRING=Release
  4. make -j`sysctl -n hw.logicalcpu`

使用ninja

  1. mkdir llvm_build
  2. cd llvm_build
  3. cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=../llvm_release

ninja编译

  1. cd llvm_build
  2. ninja

耗时十几分钟,结果:

  1. 输出 [3516/3516] Linking CXX executable bin/opt
  2. 27.16 GB

ninja install

  1. cd llvm_build
  2. ninja install

将很多二进制安装到 llvm_release/bin 目录下。

使用Xcode(推荐)

  1. mkdir llvm_xcode
  2. cd llvm_xcode
  3. cmake -G Xcode ../llvm -DCMAKE_INSTALL_PREFIX=../llvm_release

选择schemeALL_BUILD,执行build(预计一个小时)。

编写插件源码

基于LLVM开发Clang插件进行代码风格检查
使用Xcode开发iOS语法检查的Clang插件

cd到llvm/llvm/tools/clang/examples

1.打开这个目录下的CMakeLists.txt文件,然后添加add_subdirectory(CodeChecker)

  1. if(NOT CLANG_BUILD_EXAMPLES)
  2. set_property(DIRECTORY PROPERTY EXCLUDE_FROM_ALL ON)
  3. set(EXCLUDE_FROM_ALL ON)
  4. endif()
  5. add_subdirectory(CodeChecker)
  6. add_subdirectory(clang-interpreter)
  7. add_subdirectory(PrintFunctionNames)
  8. add_subdirectory(AnnotateFunctions)

2.在当前目录创建新的文件夹CodeChecker,并cd到CodeChecker

  1. mkdir CodeChecker
  2. cd CodeChecker

3.在新建的CodeChecker目录下创建三个文件

  1. touch CMakeLists.txt
  2. touch CodeChecker.cpp
  3. touch CodeChecker.exports

4.在新创建的CMakeLists.txt中添加

  1. if( NOT MSVC ) # MSVC mangles symbols differently
  2. if( NOT LLVM_REQUIRES_RTTI )
  3. if( NOT LLVM_REQUIRES_EH )
  4. set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CodeChecker.exports)
  5. endif()
  6. endif()
  7. endif()
  8. add_llvm_library( CodeChecker MODULE BUILDTREE_ONLY
  9. CodeChecker.cpp
  10. )
  11. if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  12. target_link_libraries(CodeChecker ${cmake_2_8_12_PRIVATE}
  13. clangAST
  14. clangBasic
  15. clangFrontend
  16. LLVMSupport
  17. )
  18. endif()

5.在CodeChecker.cpp文件中加入

  1. #include <iostream>
  2. #include <stdio.h>
  3. #include <string>
  4. #include <fstream>
  5. #include <sstream>
  6. #include <algorithm>
  7. #include <functional>
  8. #include <vector>
  9. #include "clang/Frontend/FrontendPluginRegistry.h"
  10. #include "clang/Rewrite/Core/Rewriter.h"
  11. #include "clang/AST/AST.h"
  12. #include "clang/AST/ASTConsumer.h"
  13. #include "clang/AST/RecursiveASTVisitor.h"
  14. #include "clang/Frontend/CompilerInstance.h"
  15. #include "clang/Sema/Sema.h"
  16. using namespace clang;
  17. using namespace std;
  18. namespace
  19. {
  20. static vector<string> split(const string &s, char delim)
  21. {
  22. vector<string> elems;
  23. stringstream ss;
  24. ss.str(s);
  25. string item;
  26. while (getline(ss, item, delim)) {
  27. elems.push_back(item);
  28. }
  29. return elems;
  30. }
  31. class CodeVisitor : public RecursiveASTVisitor<CodeVisitor>
  32. {
  33. private:
  34. CompilerInstance &Instance;
  35. ASTContext *Context;
  36. public:
  37. void setASTContext (ASTContext &context)
  38. {
  39. this -> Context = &context;
  40. }
  41. private:
  42. /**
  43. 判断是否为用户源码
  44. @param decl 声明
  45. @return true 为用户源码,false 非用户源码
  46. */
  47. bool isUserSourceCode (Decl *decl)
  48. {
  49. string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();
  50. if (filename.empty())
  51. return false;
  52. //非XCode中的源码都认为是用户源码
  53. if(filename.find("/Applications/Xcode.app/") == 0)
  54. return false;
  55. return true;
  56. }
  57. /**
  58. 检测类名是否存在小写开头
  59. @param decl 类声明
  60. */
  61. void checkClassNameForLowercaseName(ObjCInterfaceDecl *decl)
  62. {
  63. StringRef className = decl -> getName();
  64. //类名称必须以大写字母开头
  65. char c = className[0];
  66. if (isLowercase(c))
  67. {
  68. //修正提示
  69. std::string tempName = className;
  70. tempName[0] = toUppercase(c);
  71. StringRef replacement(tempName);
  72. SourceLocation nameStart = decl->getLocation();
  73. SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
  74. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  75. //报告警告
  76. DiagnosticsEngine &D = Instance.getDiagnostics();
  77. int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowercase letter");
  78. SourceLocation location = decl->getLocation();
  79. D.Report(location, diagID).AddFixItHint(fixItHint);
  80. }
  81. }
  82. /**
  83. 检测类名是否包含下划线
  84. @param decl 类声明
  85. */
  86. void checkClassNameForUnderscoreInName(ObjCInterfaceDecl *decl)
  87. {
  88. StringRef className = decl -> getName();
  89. //类名不能包含下划线
  90. size_t underscorePos = className.find('_');
  91. if (underscorePos != StringRef::npos)
  92. {
  93. //修正提示
  94. std::string tempName = className;
  95. std::string::iterator end_pos = std::remove(tempName.begin(), tempName.end(), '_');
  96. tempName.erase(end_pos, tempName.end());
  97. StringRef replacement(tempName);
  98. SourceLocation nameStart = decl->getLocation();
  99. SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
  100. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  101. //报告错误
  102. DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
  103. unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden");
  104. SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
  105. diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
  106. }
  107. }
  108. /**
  109. 检测方法名是否存在大写开头
  110. @param decl 方法声明
  111. */
  112. void checkMethodNameForUppercaseName(ObjCMethodDecl *decl)
  113. {
  114. //检查名称的每部分,都不允许以大写字母开头
  115. Selector sel = decl -> getSelector();
  116. int selectorPartCount = decl -> getNumSelectorLocs();
  117. for (int i = 0; i < selectorPartCount; i++)
  118. {
  119. StringRef selName = sel.getNameForSlot(i);
  120. char c = selName[0];
  121. if (isUppercase(c))
  122. {
  123. //修正提示
  124. std::string tempName = selName;
  125. tempName[0] = toLowercase(c);
  126. StringRef replacement(tempName);
  127. SourceLocation nameStart = decl -> getSelectorLoc(i);
  128. SourceLocation nameEnd = nameStart.getLocWithOffset(selName.size() - 1);
  129. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  130. //报告警告
  131. DiagnosticsEngine &D = Instance.getDiagnostics();
  132. int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with uppercase letter");
  133. SourceLocation location = decl->getLocation();
  134. D.Report(location, diagID).AddFixItHint(fixItHint);
  135. }
  136. }
  137. }
  138. /**
  139. 检测方法中定义的参数名称是否存在大写开头
  140. @param decl 方法声明
  141. */
  142. void checkMethodParamsNameForUppercaseName(ObjCMethodDecl *decl)
  143. {
  144. for (ObjCMethodDecl::param_iterator it = decl -> param_begin(); it != decl -> param_end(); it++)
  145. {
  146. ParmVarDecl *parmVarDecl = *it;
  147. StringRef name = parmVarDecl -> getName();
  148. char c = name[0];
  149. if (isUppercase(c))
  150. {
  151. //修正提示
  152. std::string tempName = name;
  153. tempName[0] = toLowercase(c);
  154. StringRef replacement(tempName);
  155. SourceLocation nameStart = parmVarDecl -> getLocation();
  156. SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
  157. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  158. //报告警告
  159. DiagnosticsEngine &D = Instance.getDiagnostics();
  160. int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with uppercase letter");
  161. SourceLocation location = decl->getLocation();
  162. D.Report(location, diagID).AddFixItHint(fixItHint);
  163. }
  164. }
  165. }
  166. /**
  167. 检测方法实现是否超过500行代码
  168. @param decl 方法声明
  169. */
  170. void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl)
  171. {
  172. if (decl -> hasBody())
  173. {
  174. //存在方法体
  175. Stmt *methodBody = decl -> getBody();
  176. string srcCode;
  177. srcCode.assign(Instance.getSourceManager().getCharacterData(methodBody->getSourceRange().getBegin()),
  178. methodBody->getSourceRange().getEnd().getRawEncoding() - methodBody->getSourceRange().getBegin().getRawEncoding() + 1);
  179. vector<string> lines = split(srcCode, '\n');
  180. if(lines.size() > 500)
  181. {
  182. DiagnosticsEngine &D = Instance.getDiagnostics();
  183. unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines");
  184. D.Report(decl -> getSourceRange().getBegin(), diagID);
  185. }
  186. }
  187. }
  188. /**
  189. 检测属性名是否存在大写开头
  190. @param decl 属性声明
  191. */
  192. void checkPropertyNameForUppercaseName(ObjCPropertyDecl *decl)
  193. {
  194. bool checkUppercaseNameIndex = 0;
  195. StringRef name = decl -> getName();
  196. if (name.find('_') == 0)
  197. {
  198. //表示以下划线开头
  199. checkUppercaseNameIndex = 1;
  200. }
  201. //名称必须以小写字母开头
  202. char c = name[checkUppercaseNameIndex];
  203. if (isUppercase(c))
  204. {
  205. //修正提示
  206. std::string tempName = name;
  207. tempName[checkUppercaseNameIndex] = toLowercase(c);
  208. StringRef replacement(tempName);
  209. SourceLocation nameStart = decl->getLocation();
  210. SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
  211. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  212. //报告错误
  213. DiagnosticsEngine &D = Instance.getDiagnostics();
  214. int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Property name should not start with uppercase letter");
  215. SourceLocation location = decl->getLocation();
  216. D.Report(location, diagID).AddFixItHint(fixItHint);
  217. }
  218. }
  219. /**
  220. 检测属性名是否包含下划线
  221. @param decl 属性声明
  222. */
  223. void checkPropertyNameForUnderscoreInName(ObjCPropertyDecl *decl)
  224. {
  225. StringRef name = decl -> getName();
  226. if (name.size() == 1)
  227. {
  228. //不需要检测
  229. return;
  230. }
  231. //类名不能包含下划线
  232. size_t underscorePos = name.find('_', 1);
  233. if (underscorePos != StringRef::npos)
  234. {
  235. //修正提示
  236. std::string tempName = name;
  237. std::string::iterator end_pos = std::remove(tempName.begin() + 1, tempName.end(), '_');
  238. tempName.erase(end_pos, tempName.end());
  239. StringRef replacement(tempName);
  240. SourceLocation nameStart = decl->getLocation();
  241. SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
  242. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  243. //报告错误
  244. DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
  245. unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Property name with `_` forbidden");
  246. SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
  247. diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
  248. }
  249. }
  250. /**
  251. 检测委托属性是否有使用weak修饰
  252. @param decl 属性声明
  253. */
  254. void checkDelegatePropertyForUsageWeak (ObjCPropertyDecl *decl)
  255. {
  256. QualType type = decl -> getType();
  257. StringRef typeStr = type.getAsString();
  258. //Delegate
  259. if(typeStr.find("<") != string::npos && typeStr.find(">") != string::npos)
  260. {
  261. ObjCPropertyDecl::PropertyAttributeKind attrKind = decl -> getPropertyAttributes();
  262. string typeSrcCode;
  263. typeSrcCode.assign(Instance.getSourceManager().getCharacterData(decl -> getSourceRange().getBegin()),
  264. decl -> getSourceRange().getEnd().getRawEncoding() - decl -> getSourceRange().getBegin().getRawEncoding());
  265. if(!(attrKind & ObjCPropertyDecl::OBJC_PR_weak))
  266. {
  267. DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
  268. unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Warning, "Delegate should be declared as weak.");
  269. diagEngine.Report(decl -> getLocation(), diagID);
  270. }
  271. }
  272. }
  273. /**
  274. 检测常量名称是否存在小写开头
  275. @param decl 常量声明
  276. */
  277. void checkConstantNameForLowercaseName (VarDecl *decl)
  278. {
  279. StringRef className = decl -> getName();
  280. //类名称必须以大写字母开头
  281. char c = className[0];
  282. if (isLowercase(c))
  283. {
  284. //修正提示
  285. std::string tempName = className;
  286. tempName[0] = toUppercase(c);
  287. StringRef replacement(tempName);
  288. SourceLocation nameStart = decl->getLocation();
  289. SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
  290. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  291. //报告警告
  292. DiagnosticsEngine &D = Instance.getDiagnostics();
  293. int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Constant name should not start with lowercase letter");
  294. SourceLocation location = decl->getLocation();
  295. D.Report(location, diagID).AddFixItHint(fixItHint);
  296. }
  297. }
  298. /**
  299. 检测变量名称是否存在大写开头
  300. @param decl 变量声明
  301. */
  302. void checkVarNameForUppercaseName (VarDecl *decl)
  303. {
  304. StringRef className = decl -> getName();
  305. //类名称必须以大写字母开头
  306. char c = className[0];
  307. if (isUppercase(c))
  308. {
  309. //修正提示
  310. std::string tempName = className;
  311. tempName[0] = toLowercase(c);
  312. StringRef replacement(tempName);
  313. SourceLocation nameStart = decl->getLocation();
  314. SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
  315. FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
  316. //报告警告
  317. DiagnosticsEngine &D = Instance.getDiagnostics();
  318. int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Variable name should not start with uppercase letter");
  319. SourceLocation location = decl->getLocation();
  320. D.Report(location, diagID).AddFixItHint(fixItHint);
  321. }
  322. }
  323. /**
  324. 检测变量名称
  325. @param decl 变量声明
  326. */
  327. void checkVarName(VarDecl *decl)
  328. {
  329. if (decl -> isStaticLocal())
  330. {
  331. //静态变量
  332. if (decl -> getType().isConstant(*this -> Context))
  333. {
  334. //常量
  335. checkConstantNameForLowercaseName(decl);
  336. }
  337. else
  338. {
  339. //非常量
  340. checkVarNameForUppercaseName(decl);
  341. }
  342. }
  343. else if (decl -> isLocalVarDecl())
  344. {
  345. //本地变量
  346. if (decl -> getType().isConstant(*this -> Context))
  347. {
  348. //常量
  349. checkConstantNameForLowercaseName(decl);
  350. }
  351. else
  352. {
  353. //非常量
  354. checkVarNameForUppercaseName(decl);
  355. }
  356. }
  357. else if (decl -> isFileVarDecl())
  358. {
  359. //文件定义变量
  360. if (decl -> getType().isConstant(*this -> Context))
  361. {
  362. //常量
  363. checkConstantNameForLowercaseName(decl);
  364. }
  365. else
  366. {
  367. //非常量
  368. checkVarNameForUppercaseName(decl);
  369. }
  370. }
  371. }
  372. public:
  373. CodeVisitor (CompilerInstance &Instance)
  374. :Instance(Instance)
  375. {
  376. }
  377. /**
  378. 观察ObjC的类声明
  379. @param declaration 声明对象
  380. @return 返回
  381. */
  382. bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *declaration)
  383. {
  384. if (isUserSourceCode(declaration))
  385. {
  386. checkClassNameForLowercaseName(declaration);
  387. checkClassNameForUnderscoreInName(declaration);
  388. }
  389. return true;
  390. }
  391. /**
  392. 观察类方法声明
  393. @param declaration 声明对象
  394. @return 返回
  395. */
  396. bool VisitObjCMethodDecl(ObjCMethodDecl *declaration)
  397. {
  398. if (isUserSourceCode(declaration))
  399. {
  400. checkMethodNameForUppercaseName(declaration);
  401. checkMethodParamsNameForUppercaseName(declaration);
  402. checkMethodBodyForOver500Lines(declaration);
  403. }
  404. return true;
  405. }
  406. /**
  407. 观察类属性声明
  408. @param declaration 声明对象
  409. @return 返回
  410. */
  411. bool VisitObjCPropertyDecl(ObjCPropertyDecl *declaration)
  412. {
  413. if (isUserSourceCode(declaration))
  414. {
  415. checkPropertyNameForUppercaseName(declaration);
  416. checkPropertyNameForUnderscoreInName(declaration);
  417. checkDelegatePropertyForUsageWeak(declaration);
  418. }
  419. return true;
  420. }
  421. /**
  422. 观察变量声明
  423. @param declaration 声明对象
  424. @return 返回
  425. */
  426. bool VisitVarDecl(VarDecl *declaration)
  427. {
  428. if (isUserSourceCode(declaration))
  429. {
  430. checkVarName(declaration);
  431. }
  432. return true;
  433. }
  434. /**
  435. 观察枚举常量声明
  436. @param declaration 声明对象
  437. @return 返回
  438. */
  439. // bool VisitEnumConstantDecl (EnumConstantDecl *declaration)
  440. // {
  441. // return true;
  442. // }
  443. };
  444. class CodeConsumer : public ASTConsumer
  445. {
  446. CompilerInstance &Instance;
  447. std::set<std::string> ParsedTemplates;
  448. public:
  449. CodeConsumer(CompilerInstance &Instance,
  450. std::set<std::string> ParsedTemplates)
  451. : Instance(Instance), ParsedTemplates(ParsedTemplates), visitor(Instance)
  452. {
  453. }
  454. bool HandleTopLevelDecl(DeclGroupRef DG) override
  455. {
  456. return true;
  457. }
  458. void HandleTranslationUnit(ASTContext& context) override
  459. {
  460. visitor.setASTContext(context);
  461. visitor.TraverseDecl(context.getTranslationUnitDecl());
  462. }
  463. private:
  464. CodeVisitor visitor;
  465. };
  466. class CodeASTAction : public PluginASTAction
  467. {
  468. std::set<std::string> ParsedTemplates;
  469. protected:
  470. std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
  471. llvm::StringRef) override
  472. {
  473. return llvm::make_unique<CodeConsumer>(CI, ParsedTemplates);
  474. }
  475. bool ParseArgs(const CompilerInstance &CI,
  476. const std::vector<std::string> &args) override
  477. {
  478. // DiagnosticsEngine &D = CI.getDiagnostics();
  479. // D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
  480. // "My plugin Started..."));
  481. return true;
  482. }
  483. };
  484. }
  485. static clang::FrontendPluginRegistry::Add<CodeASTAction>
  486. X("CodeChecker", "Code Checker");

编译插件源代码生成插件.dylib

1.cd到llvm,注意不是llvm/llvm,执行

  1. mkdir llvm_build && cd llvm_build
  2. cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel

2.然后会在llvm_build文件目录中看到LLVM.xcodeproj,用xcode打开,选择Automatically Create Schemes

3.Scheme分别选择 clangCodeCheckerlibclang编译

4.在llvm_build/Debug/lib目录下可以找到我们的插件,如下图:
截屏2020-06-21 下午9.57.11.png-257.5kB

Xcode集成Plugin方式一(推荐)

创建需要加载插件的项目,在Build Settings栏目中的OTHER C FLAGS添加上如下内容:

  1. -Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang 插件名字

我把插件拷贝的桌面了,所以我的是:

  1. -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++的绝对路径。

  1. /Users/dasheng/Desktop/llvm/llvm_build/Debug/bin/clang
  2. /Users/dasheng/Desktop/llvm/llvm_build/Debug/bin/clang++

再build,会有以下错误:
截屏2020-06-21 下午10.37.38.png-60.5kB

Build Settings栏目中搜索index,将Enable Index-Wihle-Building FunctionalityDefault改为NO

再build就能做代码风格检查啦:
截屏2020-06-21 下午10.41.32.png-97.8kB

Xcode集成Plugin方式二

编译原理:如何制作clang插件来为iOS开发提效

下载XcodeHacking.zip:
https://raw.githubusercontent.com/kangwang1988/kangwang1988.github.io/master/others/XcodeHacking.zip

  1. clang++ *** -Xclang -load -Xclang path-of-your-plugin.dylib -Xclang -add-plugin -Xclang your-pluginName -Xclang -plugin-arg-your-pluginName -Xclang your-pluginName-param
  1. sudo mv HackedClang.xcplugin /Applications/Xcode.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
  2. sudo mv HackedBuildSystem.xcspec /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications
  3. 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:移动端静态分析框架

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注