使用PHP-Parser生成AST抽象语法树

0、前言

最近项目的流程逐渐清晰,但是很多关键性的技术没有掌握,也只能一步一步摸索。

由于要做基于数据流分析的静态代码分析,所以前端的工作如:词法分析、语法分析必不可少。Yacc和Lex什么的就不再考虑了,查了一天的资料,发现两款比较适合,一款是Java下的ANTLR,另一款是专门做PHP AST生成的PHP-Parser。

ANTLR是编译原理领域比较著名的工具了,相对于Yacc和Lex,更加实用。但是对PHP的语法文件只有一个,折腾了半天才生成调通,发现不太适合,对于”$a=1”生成tokens竟然是[$,a,=,1],无法识别assignment,做得过于粗糙,令人无比失望。

相比之下,PHP-Parser更加专业一些,毕竟专注PHP的词法、语法分析工作。

1、介绍

PHP-Parser的项目主页是https://github.com/nikic/PHP-Parser。可以对多版本的PHP进行完美解析,生成一颗抽象语法树。

对于词法分析,PHP有个内置函数token_get_all()可以用来获取TOKENS,作为语法分析的输入,这个开源项目也是用的token_get_all()生成的token流。

2、安装

安装也很简单,这里我是使用的PHP中的包管理工具composer添加的,在项目目录中执行以下命令即可:

php composer.phar require nikic/php-parser

如果没有下载Composer,应该先执行下面的命令:

Curl -s http://getcomposer.org/installer | php

3、生成AST

使用composer添加php-parser之后,就可以方便使用。

首先介绍一下PHP-Parser中定义的一些节点类型:

(1)PhpParser\Node\Stmt是语句节点,不带任何返回信息(return)的结构,如赋值语句”$a = $b” ;

(2)PhpParser\Node\Expr是表达式节点,可以返回一个值的语言结构,如$var和func()。

(3)PhpParser\Node\Scalar是常量节点,可以用来表示任何常量值。如’string’,0,以及常量表达式。

(4)还有一些节点没有包括进去,如参数节点(PhpParser\Node\Arg)。

一些节点类的名称使用了下划线,这是为了避免和PHP关键字冲突。

PHP-parser的HelloWorld程序如下,该代码片段会生成AST:

输出结果为:

<span style="font-size:12px;">Array
(
    [0] => PhpParser\Node\Stmt\Echo_ Object
    (
            [subNodes:protected] => Array
                (
                    [exprs] => Array
                        (
                            [0] => PhpParser\Node\Scalar\String Object
                                (
                                    [subNodes:protected] => Array
                                        (
                                            [value] => 1+2
                                        )

                                    [attributes:protected] => Array
                                        (
                                            [startLine] => 1
                                            [endLine] => 1
                                        )

                                )

                            [1] => PhpParser\Node\Scalar\String Object
                                (
                                    [subNodes:protected] => Array
                                        (
                                            [value] => chongrui
                                        )

                                    [attributes:protected] => Array
                                        (
                                            [startLine] => 1
                                            [endLine] => 1
                                        )

                                )

                        )

                )

            [attributes:protected] => Array
                (
                    [startLine] => 1
                    [endLine] => 1
                )

        )

)</span>

可以看到,这课AST只有一个节点Echo_,此节点有一个子节点exprs,可以使用$stmts[0]->exprs进行访问。

对于节点中的attributes信息是用来存储startLine和endLine以及comments的。可以使用getAttributes(),getAttribute(‘startLine’),setAttribute(),hasAttribute()方法进行访问。

开始行号startLine可以通过getLine()/setLine()方法进行访问(也可以getAttribute(‘startLine’))。注释信息可以使用getDocComment()获取。

访问节点上的值:如访问值“chongrui”,使用$stmts[0]->exprs[1]->value;即可。

4、节点遍历

对抽象语法树的遍历非常方便,使用PhpParser\NodeTraverser类即可。同时,支持自定义的Visitor对象。因为在实际应用中,对PHP源码进行分析,往往是不知道AST的具体结构,这时需要动态的去判断每个节点的类型信息。

这些判断统一写到MyNodeVisitor中,该类继承了一个父类NodeVisitorAbstract,这个类中有一些方法:

(1)beforeTraverse()方法用于遍历之前,通常用来在遍历前对值进行重置。

(2)afterTraverse()方法和(1)相同,唯一不同的地方是遍历之后才触发。

(3)enterNode()和leaveNode()方法在对每个节点访问时触发。

enterNode在进入节点时触发,比如在访问节点的子节点之前。这个方法可以返回NodeTraverser::DONT_TRAVERSER_CHILDREN,用来跳过该节点的孩子节点。

leaveNode在遍历节点完成之后触发。它可以返回

NodeTraverser::REMOVE_NODE,这种情况下,当前节点会被删除。如果返回一个节点的集合,那么这些节点会并入到父节点的array中,比如array(A,B,C),B节点被array(X,Y,Z)替换,变成array(A,X,Y,Z,C) .

下面的代码片段对$code进行解析,生成AST,并且在遍历时,当发现遍历节点时String类型时,就进行输出。

结果会输出1,2。

5、其他AST表示

有时候会将AST进行文本化持久保存,这个功能PHP-Parser也支持。

(1)简单的进行序列化

使用serialize()和unserialize()进行序列化和反序列化操作,可以对AST进行持久保存。

(2)易于阅读的保存形式

分别是完美打印和XML持久存储,在这里不做详细介绍,有需要的时候可以看项目的文档:

https://github.com/nikic/PHP-Parser/blob/master/doc/3_Other_node_tree_representations.markdown

6、总结

至少在PHP静态分析方面,PHP-Parser在功能方面大大优于ANTLR。如何构建一个PHP自动化审计系统,这个PHP-Parser肯定会发挥不小的作用:)~

时间: 2024-10-27 05:06:39

使用PHP-Parser生成AST抽象语法树的相关文章

AST 抽象语法树

提起 AST 抽象语法树,大家可能并不感冒.但是提到它的使用场景,也许会让你大吃一惊.原来它一直在你左右与你相伴,而你却不知. 一.什么是抽象语法树 在计算机科学中,抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码.树上的每个节点都表示源代码中的一种结构. 之所以说语法是「抽象」的,是因为这里的语法并不会表示出真实语法中出现的每个细节. 二.使用场景 JS 反编译,语法

AST抽象语法树 Javascript版

在javascript世界中,你可以认为抽象语法树(AST)是最底层. 再往下,就是关于转换和编译的"黑魔法"领域了. 现在,我们拆解一个简单的add函数 function add(a, b) { return a + b } 首先,我们拿到的这个语法块,是一个FunctionDeclaration(函数定义)对象. 用力拆开,它成了三块: 一个id,就是它的名字,即add 两个params,就是它的参数,即[a, b] 一块body,也就是大括号内的一堆东西 add没办法继续拆下去了

用python演示一个简单的AST(抽象语法树)

假设对'a + 3 * b'进行解释,其中a=2,b=5 代码很简单,就不再进行详细的解释了. Num = lambda env, n: n Var = lambda env, x: env[x] Add = lambda env, a, b:_eval(env, a) + _eval(env, b) Mul = lambda env, a, b:_eval(env, a) * _eval(env, b) _eval = lambda env, expr:expr[0](env, *expr[1

编译抽象语法树

对大多数开发人员来说,编译就意谓着产生本地代码,给人感觉就是一个字,难.但是,并不一定要产生本地代码,对于 DSL,通常产生其他更加通用的编程语言..NET 框架提供几个把抽象语法树编译成程序的功能. 技术的选择取决于几个因素.例如,如果语言针对的是开发人员,那么,生成文本文件就足够了,内容可以是 F#,也可以是其他语言,或者是编译过的程序集,能在程序中使用:然而,如果针对的是最终用户,那么几乎可以肯定,必须编译然后动态[ on the fly ]执行.表12-1 汇总了各种可用选项.   表1

五分钟了解抽象语法树(AST)babel是如何转换的?

抽象语法树 什么是抽象语法树? It is a hierarchical program representation that presents source code structure according to the grammar of a programming language, each AST node corresponds to an item of a source code. 抽象语法树是源代码语法结构的一种抽象表示.它以树状的形式表现编程语言的语法结构,树上的每个节点

Babel(抽象语法树,又称AST)

文章:https://juejin.im/post/5a9315e46fb9a0633a711f25 https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md 你了解过Babel吗? 了解过抽象语法树,又称AST,有学习过,也写过一个基于AST的乞丐版模板引擎,先是词法解析token,然后生产抽象语法树,然后更改抽象语法树,当然这是插件做的事情,最后根据新的A

JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript 是如何工作的:深入V8引擎&编写优化代码的5个技巧! JavaScript 是如何工作的:内存管理+如何处理4个常见的内存泄漏 ! JavaScript 是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式! JavaScript 是如何工作的:

复杂网络,抽象语法树

近期看了一些软件抽象为复杂网络,以及软件抽象成静态语法树的文章.做一个小总结. 1.复杂网络是由大量的边和点组成的,边点都可以有类型,加权值,边还可以有方向.如何计算边和点的权值是一个关键点,如何在不执行代码的情况下确定边的方向,目前不确定是否已经解决. 有许多工具,可以直接扫描软件源代码,抽象为复杂网络.然而我还没亲身实践,且做个记录. Dependency Finder分析编译后的java代码,能够提取依赖图. Doxygen是使用c++开发的基于源代码注释的文档生成工具.但是这个注释,是人

编译器开发系列--Ocelot语言1.抽象语法树

从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树,抽象语法树是生成中间代码的关键,而中间代码又是生成后端代码的关键. 整个编译器代码采用java语言编写,主要功能是对JavaCC生成的抽象语法树进行语义分析.优化,最后生成优化后的汇编代码,然后再用汇编器对汇编代码汇编生成机器码,最后再用命令链接生成Linux可执行文件,就可以直接在Linux上运行了. 整