iOS底层探索之LLVM(三)——自定义Clang插件(下)
1. 写在前面
在上篇博客中已经介绍了LLVM
下载流程和LLVM
的编译流程,也对编译完成的LLVM
工程进行了 Clang
和clangTooling
的编译。
iOS底层探索之LLVM(二)——自定义Clang插件(上)
iOS底层探索之LLVM(一)——LLVM初体验
本篇博客将手把手教大家,进行代码编写,自定义一个
Clang
插件,最终实现的功能是对不正确使用属性修饰会进行报错,并提示正确的用词,实现效果如下。
2. 前期准备
2.1 新建插件
在/llvm/tools/clang/tools
目录下新建插件JPPlugins
(这个是你自己建的,名字随便都可以,你自己知道就可以)
2.2 修改CMakeLists.txt
修改/llvm/tools/clang/tools
目录下的文件CMakeLists.txt
,新增加一句add_clang_subdirectory(JPPlugins)
- 在
JPPlugins
目录下新建一个名为JPPlugins.cpp
的文件和CMakeLists.txt
的文件, 在CMakeLists.txt
中写上如下代码
add_llvm_library( JPPlugins MODULE BUILDTREE_ONLY
JPPlugins.cpp
)
2.3 编译插件
- 接下来再次使用
cmake
命令重新生成一下xcode
项目,还是在llvm_build
目录中 使用cmake -G Xcode ../llvm
命令。 - 最后可以在
LLVM
的Xcode
项目中可以看到Loadable modules
目录下有自己的Plugin
目录了,我兴致勃勃的打开工程一看。。。。。
什么?居然失败了,什么都没有啊!这是在和我开玩笑吗?我仔细一看,是否是
JPPlugins.cpp
文件和JPPlugins
插件同名导致失败的呢?我于是改了下cpp
的名称,同时把插件名的s
去掉了,还真就成功了!这就让我百思不得解了,就很奇怪!
- 我这倔脾气,我就不信这个邪了,我又改回原来最初的名称,
JPPlugins.cpp
文件和JPPlugins
插件依然是同名,我又编译了一次,发现还是失败了。 - 我又改成
JP.cpp
文件和插件名为JPPlugins
,这回不一样了,该成功了吧!结果还是失败了。 - 我又进行了第三次尝试,
JPPlugin.cpp
文件和插件名为JPPlugin
,这回我把s
去掉了,就是这么奇怪,这回成功了。
我也不去下什么结论, 反正同名的是可以编译成功的,具体是不是有s
的后缀导致的,我也不知道,反正我这两次的成功是和这个有关,时间比较多的老铁可以去验证一下,这里就不再折腾去验证了。
那么我们现在就可以去
cpp
里面编写Clang
插件的代码了。
3. 编写插件代码
废话不多写,直接上代码,步骤就不一一列出来了,都写在代码里面了。
#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 JPPlugin {// 第三步:扫描完毕回调// 4、自定义回调类,继承自MatchCallbackclass JPMatchCallback : public MatchFinder::MatchCallback {private:// CI传递路径:JPASTAction类中的CreateASTConsumer方法参数 -> JPASTConsumer的构造函数 -> JPMatchCallback的私有属性,通过构造函数从JPASTConsumer构造函数中获取CompilerInstance &CI;// 判断是否是自己的文件bool isUserSourceCode(const string fileName) {// 文件名不为空if (fileName.empty()) return false;// 非Xcode中的代码都认为是用户的if (0 == fileName.find("/Applications/Xcode.app/")) return false;return true;}// 判断是否应该用copy修饰bool isShouldUseCopy(const string typeStr) {// 判断类型是否是 NSString / NSArray / NSDictionaryif (typeStr.find("NSString") != string::npos ||typeStr.find("NSArray") != string::npos ||typeStr.find("NSDictionary") != string::npos) {return true;}return false;}public:// 构造方法JPMatchCallback(CompilerInstance &CI):CI(CI) {}// 重载run方法void run(const MatchFinder::MatchResult &Result) {// 通过Result获取节点对象,根据节点id("objcPropertyDecl")获取(此id需要与JPASTConsumer构造方法中bind的id一致)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();// 节点的描述信息ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();// 应该使用copy,但是没有使用copyif (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyAttribute::kind_copy)) {// 通过CI获取诊断引擎DiagnosticsEngine &diag = CI.getDiagnostics();// Report 报告/**错误位置:getLocation 节点位置错误:getCustomDiagID(等级,提示)*/diag.Report(propertyDecl->getLocation(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 这个属性推荐使用copy修饰!!"))<< typeStr;}}}};// 第二步:扫描配置完毕// 3、自定义JPASTConsumer,继承自抽象类 ASTConsumer,用于监听AST节点的信息 -- 过滤器class JPASTConsumer : public ASTConsumer {private:// AST 节点查找器(过滤器)MatchFinder matcher;// 回调对象JPMatchCallback callback;public:// 构造方法中创建MatchFinder对象JPASTConsumer(CompilerInstance &CI):callback(CI) { // 构造即将CI传递给callback// 添加一个MatchFinder,每个objcPropertyDecl节点绑定一个objcPropertyDecl标识(去匹配objcPropertyDecl节点)// 回调callback,其实是在CJLMatchCallback里面重写run方法(真正回调的是回调run方法)matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);}// 重载两个方法 HandleTopLevelDecl 和 HandleTranslationUnit// 解析完毕一个顶级的声明就回调一次(顶级节点,即全局变量,属性,函数等)bool HandleTopLevelDecl(DeclGroupRef D) {// cout<<"正在解析..."<<endl;return true;}// 当整个文件都解析完毕后回调void HandleTranslationUnit(ASTContext &Ctx) {// cout<<"文件解析完毕!!!"<<endl;// 将文件解析完毕后的上下文context(即AST语法树) 给 matchermatcher.matchAST(Ctx);}};//2、继承PluginASTAction,实现我们自定义的JPASTAction,即自定义AST语法树行为class JPASTAction : public PluginASTAction {public:// 重载ParseArgs 和 CreateASTConsumer方法/*解析给定的插件命令行参数- param CI 编译器实例,用于报告诊断。- return 如果解析成功,则为true;否则,插件将被销毁,并且不执行任何操作。该插件负责使用CompilerInstance的Diagnostic对象报告错误。*/bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) {return true;}// 返回自定义的JPASTConsumer对象,抽象类ASTConsumer的子类unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {/**传递CICI用于:- 判断文件是否是用户的- 抛出警告*/return unique_ptr<JPASTConsumer>(new JPASTConsumer(CI));}};
}// 第一步:注册插件,并自定义JPASTAction类// 1、注册插件
static FrontendPluginRegistry::Add<JPPlugin::JPASTAction> X("JPPlugin", "this is JPPlugin");
3.1 终端测试插件
新建立一个工程,在ViewController.m
里面写入如下代码
@interface ViewController ()@property (nonatomic , strong) NSString *name;
@property (nonatomic , strong) NSArray *array;@end
- 测试命令 如下
“ 自己编译的
clang
文件路径-isysroot
模拟器文件路径-Xclang -load -Xclang
插件路径(.dylib
)-Xclang -add-plugin -Xclang
插件名字-c
源码文件路径 ”
- 自己编译的
clang
文件路径为:llvm-project/llvm_build/Debug/bin/clang
模拟器文件路径为:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
,根据自己的电脑去判断。插件名字:就是你建立的插件的名字,我这里是
JPPlugin
源码文件路径:就是你需要插件去识别的文件的路径
例如下面
iOS底层探索之LLVM(三)——自定义Clang插件(下)相关推荐
- iOS底层探索(二) - 写给小白看的Clang编译过程原理
iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...
- iOS 底层探索篇 —— KVC 底层原理
iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...
- iOS底层探索二(OC 中 alloc 方法 初探)
前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...
- iOS 底层探索 - 消息转发
一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...
- 【iOS底层】11:消息转发
一.msgSend消息发送监听 在探索了很多次了lookUpImpOrForward方法中,查找完成后会写入cache 在写入cache中发现有个打印log的操作 我们来看下是否可以通过这个输出到本地 ...
- iOS 查漏补缺 - LLVM Clang
LLVM 是一个自由软件项目,它是一种编译器基础设施,以 C++ 写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端.它是为了任意一种编程语言而写成的程序,利用虚拟技术创造出编译时期 ...
- iOS底层-KVO分析与自定义
KVO分析与自定义 背景 准备 KVO一些细节 KVO探索分析 KVO 底层原理 小结: KVO自定义 自定义KVO要知道: 1,KVO是对setter方法进行观察,过滤实例方法 2,添加KVO(核心 ...
- 基于clang插件的一种iOS包大小瘦身方案
引子 \ 包瘦身,包瘦身,包瘦身,重要的事情说三遍. \ 最近公司一款iOS APP(本文只讨论使用Objective C开发的iOS安装包)一直在瘦身,我们团队的APP也愈发庞大了.而要解决这个问题 ...
- app启动页数秒加载 代码_iOS 底层探索 - 应用加载
一.前导知识 以下参考自 WWDC 2016 Optimizing App Startup Time : 1.1 Mach-O Mach-O is a bunch of file types for ...
- iOS底层原理之内存管理
文章目录 定时器 CADisplayLink.NSTimer GCD定时器 内存管理 iOS程序的内存布局 Tagged Pointer OC对象的内存管理 拷贝 引用计数的存储 dealloc 自动 ...
最新文章
- 职业经理人的核心技能
- python rbf神经网络_原创,基于径向基函数(RBF)神经网络RBF网络的举例应用!
- android服务的原理,Android学习笔记:IntentService
- BERT, ELMo, GPT-2: 这些上下文相关的表示到底有多上下文化?
- 单片机设置12分频c语言,AT89C51单片机,如何实现延迟一秒
- 新页面,简单的tree视图写法
- SAP 电商云 UI 持续集成里 docker 的使用场景一例
- 基于Java+SpringBoot+vue+element实现前后端分离蛋糕商城系统详细设计
- so库文件控制导出符号
- 环境变量environ
- Epic Citadel Demo展示互联网作为游戏平台的巨大能量
- fread与fread_s读取文件(二进制文件)
- QQ空间迁移_【山特C3KS_连接ESXI虚拟机】
- 如何锁定计算机桌面图标,解决win7、win10系统怎么锁定电脑桌面图标
- 漏洞补丁:windwos补丁下载(MS17-010)
- java排序之选择排序
- uni-app微信小程序短信验证、微信支付
- 工作3年以上的程序员现在都在做什么工作?
- java wms erp自动化立体仓库管理系统 进出库 源码 源代码 程序
- 谷歌黑客搜索看这些就够了!
热门文章
- iOS底层探索(二) - 写给小白看的Clang编译过程原理