使用PyV8构建JavaScript抽象语法树(AST)
简介
本文介绍使用PyV8生成JavaScript的抽象语法树的实现过程,使用该语法树进行JavaScript代码优化、变形、压缩等将在后续进行介绍。
编译器(解释器)对语言的处理过程大致分为:词法分析->语法分析->中间代码生成->代码优化->生成可执行模块/字节码解释执行。
通常情况下,中间代码生成之前的(词法分析、语法分析)称之为”前端“;其它称之为“后端”。
Google主导的开源项目V8是Chrome浏览器内置的JavaScript引擎,由于V8引擎在后端上针对不同的操作系统、硬件平台做了非常深入的优化,使得这款JavaScript引擎性能十分出众。
PyV8是针对V8引擎的Python封装,对V8引擎的多个核心组件JSContext,JSEngine等做了细致的封装,使得通过Python可以方便的控制V8引擎中JavaScript的运行。本文将借助PyV8的AST组件,来获得JavaScript源码对应的AST。由于PyV8编译配置较为麻烦,这里提供windows平台的二进制安装包,以方便进一步研究PyV8的使用。
PoC
使用的JavaScript代码如下:
/* file: code.js
* xjump.me#at#gmail#dot#com
*/
function a(arg1,arg2){
var a = 1;
var b = arg1;
var m = arg2;
var c = function(d){
e = d;
}
}
d = a(2);
生成AST的Python代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
file: jsobf_v1.py
author: xjump.me#at#gmail#dot#com
'''
import PyV8
import json
FILE = "code.js" # "jQuery.js" "jQuery.min.js"
f = file(FILE,"rb")
src = f.read()
f.close()
class Visitor(object):
def onProgram(self, prog):
self.ast = prog.toAST()
js = prog.toJSON()
self.json = json.loads(js)
def traverse_ast(ast,intent=0):
intent+=1
if len(ast)==1 or type(ast)!=list:
intent-=1
print "-"*intent, ast#, type(ast)
return
for i in ast:
traverse_ast(i,intent)
def pyv8_parse(content):
with PyV8.JSContext() as c:
with PyV8.JSEngine() as e:
s = e.compile(content)
visitor = Visitor()
s.visit(visitor)
# nice print AST as JSON String
print json.dumps(visitor.json, sort_keys=True, indent=4, separators=(',', ': '))
return visitor.json
# test
ast = pyv8_parse(src)
traverse_ast(ast)
Output
生成的语法树JSON输出较长,就不在这里列出,traverse输出如下:
- FunctionLiteral
- {u'name': u''}
-- Function
-- {u'name': u'a'}
--- FunctionLiteral
--- {u'name': u'a', u'parameter[1]': u'arg2', u'parameter[0]': u'arg1'
---- Declaration
---- {u'mode': u'VAR'}
----- Variable
----- {u'name': u'a'}
---- Declaration
---- {u'mode': u'VAR'}
----- Variable
----- {u'name': u'b'}
---- Declaration
---- {u'mode': u'VAR'}
----- Variable
----- {u'name': u'm'}
---- Declaration
---- {u'mode': u'VAR'}
----- Variable
----- {u'name': u'c'}
---- Block
----- ExpressionStatement
------ Assignment
------ {u'op': u'INIT_VAR'}
------- Variable
------- {u'name': u'a'}
------- Literal
------- {u'handle': 1}
---- Block
----- ExpressionStatement
------ Assignment
------ {u'op': u'INIT_VAR'}
------- Variable
------- {u'name': u'b'}
------- Variable
------- {u'name': u'arg1'}
---- Block
----- ExpressionStatement
------ Assignment
------ {u'op': u'INIT_VAR'}
------- Variable
------- {u'name': u'm'}
------- Variable
------- {u'name': u'arg2'}
---- Block
----- ExpressionStatement
------ Assignment
------ {u'op': u'INIT_VAR'}
------- Variable
------- {u'name': u'c'}
------- FunctionLiteral
------- {u'name': u'', u'parameter[0]': u'd'}
-------- ExpressionStatement
--------- Assignment
--------- {u'op': u'ASSIGN'}
---------- Variable
---------- {u'name': u'e'}
---------- Variable
---------- {u'name': u'd'}
-- ExpressionStatement
--- Assignment
--- {u'op': u'ASSIGN'}
---- Variable
---- {u'name': u'd'}
---- Call
----- Variable
----- {u'name': u'a'}
----- Literal
----- {u'handle': 2}
Reference
http://blog.peschla.net/2012/10/exploring-v8/
http://v8.googlecode.com/svn/trunk/src/ast.cc
http://www-archive.mozilla.org/js/language/grammar14.html
http://esprima.org/doc/index.html
https://code.google.com/p/pyv8/issues/detail?id=170
-EOF-