教你如何用AST语法树对代码“动手脚”

来源:互联网 发布:spring boot 执行sql 编辑:程序博客网 时间:2024/04/30 04:30

本文约稿:个推安卓开发工程师  刘斌


作为程序猿,每天都在写代码,但是有没有想过通过代码对写好的代码”动点手脚”呢?


今天就与大家分享——先抛一个问题:如何将图一代码改写为图二?


a425b4c96dcf437eb6782e0cd486b3c2.png

55094b232b2f46b7a2a6a9f3c3a429ba.png

此题需要把代码中和程序逻辑无关的字符串提取出来,替换为id。


比如个推日志输出类,缩短日志描述信息后,输出的日志就随之变短,根据映射表可以恢复真实原始日志。


通过何种方案改写?


你可能会想通过万能的“正则表达式”匹配替换,但当代码较为复杂时(如下图所示),

使用“正则表达法”则会将问题复杂化,难以确保所有代码的完美覆盖并匹配。

若通过AST语法树,可以很好地解决此问题。

b9d801e08f734360b64dec30c7821f27.png


什么是AST语法树?


AST(Abstract syntax tree)即为“抽象语法树”,简称语法树,指代码在计算机内存的一种树状数据结构,便于计算机理解和阅读。

e5aedebbd24c45de9acafd1fcb4b8b3c.jpg

一般只有语言的编译器开发人员或者从事语言设计的人员才涉及到语法树的提取和处理,所以很多人会对这个概念比较陌生。

15458a9f0c4f47fe8c84e6d696152436.jpg

上图即为语法树,左边树的节点对应右边相同颜色覆盖的代码块。

ef758ce21e7f441ba8f71e138bb40466.jpg

众所周知,Java 编译流程(上图)中也有对AST语法树的提取处理,那是否可以在此环节操作语法树呢?由于编译链代码栈太深,鲜有对外的接口和文档,使得其可操作性不强。不过,如果采用迂回战术如下图所示,可以对其进行操作。

0fb26c15ce2e43309a1b9590d91125d5.jpg

个推log-rewrite项目改写日志,就是用AST语法树进行的,流程图如下图所示。

e1a92d827fc84a00bb29d607c407ac9b.jpg

先把所有源码解析为AST语法树,遍历每一个编译单元与单元的类声明,在类声明里根据日志方法的签名找到所有的方法调用,然后遍历每个方法调用,将方法调用的第二个参数表达式放入递归方法,对字符串字面值进行改写。

 

对应的代码较为简短, 使用github的 Netflix-Skunkworks/rewrite开源库与kotlin语言,能读懂Java的你也一定能读明白。

807e6ac3e4d540f1835b0b6b813520fa.png

如果想将日志恢复原样,可根据前缀、后缀定制正则表达式,逐行匹配替换。如下图所示。

68fe3836410442b0b9b78fcd8e874574.png

AST有哪些应用场景?


1、 编译工具从ant到gradle的切换

2e9bfc231d304e1bb923e0437b01c616.png

此项目起步于ant主流时期,随着技术日渐成熟,gradle逐渐取代了ant的位置,演变成官方的编译打包方式。因为历史原因,若直接将上图类似预编译的代码切换到gradle较为棘手,通过AST语法树重写,再用gradle编译,就可以解决此问题。

05a9a29ab39e427db83759908e81cbcc.png


ee9be088ceea4ac5b13558bdb775b3e2.png

上图的#debug和#mdebug指令,也可以通过AST改写之后再进行编译。


2、 自动静态埋点

5451b5f92ef24a4d85fe2a644c876c14.png

代码中需要运营统计、数据分析等,需要通过代码埋点进行用户行为数据收集。传统的做法是手动在代码中添加埋点代码,但此过程较为繁琐,可能会对业务代码造成干扰,倘若通过改写AST语法树,在编译打包期添加这种类似的埋点代码,就可减少不必要的繁琐过程,使其更加高效。


最后附推荐操作AST类库链接&完整项目源码地址,希望可以帮助大家打开脑洞,设想更多的应用场景。


推荐操作AST类库链接

https://github.com/Netflix-Skunkworks/rewrite  

https://github.com/Javaparser/Javaparser

https://github.com/antlr/antlr4


完整项目源码地址如下,欢迎fork&start

https://github.com/foxundermoon/log-rewrite