简介

本文介绍使用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

escape_string patch

ECMA-262

-EOF-