AST(Abstract Syntax Tree),中文抽象语法树,简称语法树(Syntax Tree),是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源代码中的一种结构。语法树不是某一种编程语言独有的,JavaScript、Python、Java、Golang 等几乎所有编程语言都有语法树。
小时候我们得到一个玩具,总喜欢把玩具拆解成一个一个小零件,然后按照我们自己的想法,把零件重新组装起来,一个新玩具就诞生了。而 JavaScript 就像一台精妙运作的机器,通过 AST 解析,我们也可以像童年时拆解玩具一样,深入了解 JavaScript 这台机器的各个零部件,然后重新按照我们自己的意愿来组装。
AST 的用途很广,IDE 的语法高亮、代码检查、格式化、压缩、转译等,都需要先将代码转化成 AST 再进行后续的操作,ES5 和 ES6 语法差异,为了向后兼容,在实际应用中需要进行语法的转换,也会用到 AST。AST 并不是为了逆向而生,但做逆向学会了 AST,在解混淆时可以如鱼得水。
AST 有一个在线解析网站:https://astexplorer.net/ ,顶部可以选择语言、编译器、是否开启转化等,如下图所示,区域①是源代码,区域②是对应的 AST 语法树,区域③是转换代码,可以对语法树进行各种操作,区域④是转换后生成的新代码。图中原来的 Unicode 字符经过操作之后就变成了正常字符。
语法树没有单一的格式,选择不同的语言、不同的编译器,得到的结果也是不一样的,在 JavaScript 中,编译器有 Acorn、Espree、Esprima、Recast、Uglify-JS 等,使用最多的是 Babel,后续的学习也是以 Babel 为例。
在编译原理中,编译器转换代码通常要经过三个步骤:词法分析(Lexical Analysis)、语法分析(Syntax Analysis)、代码生成(Code Generation),下图生动展示了这一过程:
词法分析阶段是编译过程的第一个阶段,这个阶段的任务是从左到右一个字符一个字符地读入源程序,然后根据构词规则识别单词,生成 token 符号流,比如 isPanda('')
,会被拆分成 isPanda
,(
,''
,)
四部分,每部分都有不同的含义,可以将词法分析过程想象为不同类型标记的列表或数组。
语法分析是编译过程的一个逻辑阶段,语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语,比如“程序”,“语句”,“表达式”等,前面的例子中,isPanda('')
就会被分析为一条表达语句 ExpressionStatement
,isPanda()
就会被分析成一个函数表达式 CallExpression
,` 就会被分析成一个变量
Literal` 等,众多语法之间的依赖、嵌套关系,就构成了一个树状结构,即 AST 语法树。
代码生成是最后一步,将 AST 语法树转换成可执行代码即可,在转换之前,我们可以直接操作语法树,进行增删改查等操作,例如,我们可以确定变量的声明位置、更改变量的值、删除某些节点等,我们将语句 isPanda('')
修改为一个布尔类型的 Literal
:true
,语法树就有如下变化:
Babel 是一个 JavaScript 编译器,也可以说是一个解析库,Babel 中文网:https://www.babeljs.cn/ ,Babel 英文官网:https://babeljs.io/ ,Babel 内置了很多分析 JavaScript 代码的方法,我们可以利用 Babel 将 JavaScript 代码转换成 AST 语法树,然后增删改查等操作之后,再转换成 JavaScript 代码。
Babel 包含的各种功能包、API、各方法可选参数等,都非常多,本文不一一列举,在实际使用过程中,应当多查询官方文档,或者参考文末给出的一些学习资料。Babel 的安装和其他 Node 包一样,需要哪个安装哪个即可,比如 npm install @babel/core @babel/parser @babel/traverse @babel/generator
在做逆向解混淆中,主要用到了 Babel 的以下几个功能包,本文也仅介绍以下几个功能包:
Babel 编译器本身,被拆分成了三个模块:@babel/parser
、@babel/traverse
、@babel/generator
,比如以下方法的导入效果都是一样的:
@babel/parser
可以将 JavaScript 代码解析成 AST 语法树,其中主要提供了两个方法:
部分可选参数 options
:
举个例子看得比较清楚:
{sourceType: "module"}
演示了如何添加可选参数,输出的就是 AST 语法树,这和在线网站 https://astexplorer.net/ 解析出来的语法树是一样的:
@babel/generator
可以将 AST 还原成 JavaScript 代码,提供了一个 generate
方法:generate(ast, [{options}], code)
。
部分可选参数 options
:
接着前面的例子,原代码是 const a = 1;
,现在我们把 a
变量修改为 b
,值 1
修改为 2
,然后将 AST 还原生成新的 JS 代码:
最终输出的是 const b=2;
,变量名和值都成功更改了,由于加了压缩处理,等号左右两边的空格也没了。
代码里 {minified: true}
演示了如何添加可选参数,这里表示压缩输出代码,generate
得到的 result
得到的是一个对象,其中的 code
属性才是最终的 JS 代码。
代码里 ast.program.body[0].declarations[0].id.name
是 a 在 AST 中的位置,ast.program.body[0].declarations[0].init.value
是 1 在 AST 中的位置,如下图所示:
当代码多了,我们不可能像前面那样挨个定位并修改,对于相同类型的节点,我们可以直接遍历所有节点来进行修改,这里就用到了 @babel/traverse
,它通常和 visitor
一起使用,visitor
是一个对象,这个名字是可以随意取的,visitor
里可以定义一些方法来过滤节点,这里还是用一个例子来演示:
这里的原始代码定义了 abcde 五个变量,其值有数字也有字符串,我们在 AST 中可以看到对应的类型为 NumericLiteral
和 StringLiteral
:
然后我们声明了一个 visitor
对象,然后定义对应类型的处理方法,traverse
接收两个参数,第一个是 AST 对象,第二个是 visitor
,当 traverse
遍历所有节点,遇到节点类型为 NumericLiteral
和 StringLiteral
时,就会调用 visitor
中对应的处理方法,visitor
中的方法会接收一个当前节点的 path
对象,该对象的类型是 NodePath
,该对象有非常多的属性,以下介绍几种最常用的:
PS:path
对象除了有很多属性以外,还有很多方法,比如替换节点、删除节点、插入节点、寻找父级节点、获取同级节点、添加注释、判断节点类型等,可在需要时查询相关文档或查看源码,后续介绍 @babel/types
部分将会举部分例子来演示,以后的实战文章中也会有相关实例,篇幅有限本文不再细说。
因此在上面的代码中,path.node.value
就拿到了变量的值,然后我们就可以进一步对其进行修改了。以上代码运行后,所有数字都会加上100后再乘以2,所有字符串都会被替换成 I Love JavaScript!
,结果如下:
如果多个类型的节点,处理的方式都一样,那么还可以使用 |
将所有节点连接成字符串,将同一个方法应用到所有节点:
visitor
对象有多种写法,以下几种写法的效果都是一样的:
以上几种写法中有用到了 enter
方法,在节点的遍历过程中,进入节点(enter)与退出(exit)节点都会访问一次节点,traverse
默认在进入节点时进行节点的处理,如果要在退出节点时处理,那么在 visitor
中就必须声明 exit
方法。
@babel/types
主要用于构建新的 AST 节点,前面的示例代码为 const a = 1;
,如果想要增加内容,比如变成 const a = 1; const b = a * 5 + 1;
,就可以通过 @babel/types
来实现。
首先观察一下 AST 语法树,原语句只有一个 VariableDeclaration
节点,现在增加了一个:
那么我们的思路就是在遍历节点时,遍历到 VariableDeclaration
节点,就在其后面增加一个 VariableDeclaration
节点,生成 VariableDeclaration
节点,可以使用 types.variableDeclaration()
方法,在 types 中各种方法名称和我们在 AST 中看到的是一样的,只不过首字母是小写的,所以我们不需要知道所有方法的情况下,也能大致推断其方法名,只知道这个方法还不行,还得知道传入的参数是什么,可以查文档,不过这里推荐直接看源码,非常清晰明了,以 Pycharm 为例,按住 Ctrl 键,再点击方法名,就进到源码里了:
可以看到需要 kind
和 declarations
两个参数,其中 declarations
是 VariableDeclarator
类型的节点组成的列表,所以我们可以先写出以下 visitor
部分的代码,其中 path.insertAfter()
是在该节点之后插入新节点的意思:
接下来我们还需要进一步定义 declarator
,也就是 VariableDeclarator
类型的节点,查询其源码如下:
观察 AST,id 为 Identifier
对象,init 为 BinaryExpression
对象,如下图所示:
先来处理 id,可以使用 types.identifier()
方法来生成,其源码为 function identifier(name: string)
,name 在这里就是 b 了,此时 visitor
代码就可以这么写:
然后再来看 init 该如何定义,首先仍然是看 AST 结构:
init 为 BinaryExpression
对象,left 左边是 BinaryExpression
,right 右边是 NumericLiteral
,可以用 types.binaryExpression()
方法来生成 init,其源码如下:
此时 visitor
代码就可以这么写:
然后继续构造 left 和 right,和前面的方法一样,观察 AST 语法树,查询对应方法应该传入的参数,层层嵌套,直到把所有的节点都构造完毕,最终的 visitor
代码应该是这样的:
注意:path.insertAfter()
插入节点语句后面加了一句 path.stop()
,表示插入完成后立即停止遍历当前节点和后续的子节点,添加的新节点也是 VariableDeclaration
,如果不加停止语句的话,就会无限循环插入下去。
插入新节点后,再转换成 JavaScript 代码,就可以看到多了一行新代码,如下图所示:
了解了 AST 和 babel 后,就可以对 JavaScript 混淆代码进行还原了,以下是部分样例,带你进一步熟悉 babel 的各种操作。
文章开头的图中举了个例子,正常字符被换成了 Unicode 编码:
观察 AST 结构:
我们发现 Unicode 编码对应的是 raw
,而 rawValue
和 value
都是正常的,所以我们可以将 raw
替换成 rawValue
或 value
即可,需要注意的是引号的问题,本来是 console["log"]
,你还原后变成了 console[log]
,自然会报错的,除了替换值以外,这里直接删除 extra 节点,或者删除 raw 值也是可以的,所以以下几种写法都可以还原代码:
还原结果:
之前K哥写过 JSFuck 混淆的还原 ,其中有介绍 ![]
可表示 false,!![]
或者 !+[]
可表示 true,在一些混淆代码中,经常有这些操作,把简单的表达式复杂化,往往需要执行一下语句,才能得到真正的结果,示例代码如下:
想要执行语句,我们需要了解 path.evaluate()
方法,该方法会对 path 对象进行执行操作,自动计算出结果,返回一个对象,其中的 confident
属性表示置信度,value
表示计算结果,使用 types.valueToNode()
方法创建节点,使用 path.replaceInline()
方法将节点替换成计算结果生成的新节点,替换方法有一下几种:
对应的 AST 处理代码如下:
最终结果:
有时候代码里会有一些并没有使用到的多余变量,删除这些多余变量有助于更加高效的分析代码,示例代码如下:
删除多余变量,首先要了解 NodePath
中的 scope
,scope
的作用主要是查找标识符的作用域、获取并修改标识符的所有引用等,删除未使用变量主要用到了 scope.getBinding()
方法,传入的值是当前节点能够引用到的标识符名称,返回的关键属性有以下几个:
所以我们可以通过 constantViolations
、referenced
、references
、referencePaths
多个参数来判断变量是否可以被删除,AST 处理代码如下:
处理后的代码(未使用的 b、c、e 变量已被删除):
有时候为了增加逆向难度,会有很多嵌套的 if-else 语句,大量判断为假的冗余逻辑代码,同样可以利用 AST 将其删除掉,只留下判断为真的,示例代码如下:
观察 AST,判断条件对应的是 test
节点,if 对应的是 consequent
节点,else 对应的是 alternate
节点,如下图所示:
AST 处理思路以及代码:
处理结果:
控制流平坦化是混淆当中最常见的,通过 if-else
或者 while-switch-case
语句分解步骤,示例代码:
AST 还原思路:
不同思路,写法多样,对于如何获取控制流数组,可以有以下思路:
所以 AST 处理代码就有两种写法,方法一:(code.js 即为前面的示例代码,为了方便操作,这里使用 fs 从文件中读取代码)
方法二:
以上代码运行后,原来的 switch-case
控制流就被还原了,变成了按顺序一行一行的代码,更加简洁明了:
本文有参考以下资料,也是比较推荐的在线学习资料:
const parse
=
require(
"@babel/parser"
).parse;
const parse
=
require(
"@babel/core"
).parse;
const traverse
=
require(
"@babel/traverse"
).default
const traverse
=
require(
"@babel/core"
).traverse
const parse
=
require(
"@babel/parser"
).parse;
const parse
=
require(
"@babel/core"
).parse;
const traverse
=
require(
"@babel/traverse"
).default
const traverse
=
require(
"@babel/core"
).traverse
参数
描述
allowImportExportEverywhere
默认 import
和 export
声明语句只能出现在程序的最顶层,设置为 true
则在任何地方都可以声明
allowReturnOutsideFunction
默认如果在顶层中使用 return
语句会引起错误,设置为 true
就不会报错
sourceType
默认为 script
,当代码中含有 import
、export
等关键字时会报错,需要指定为 module
errorRecovery
默认如果 babel 发现一些不正常的代码就会抛出错误,设置为 true
则会在保存解析错误的同时继续解析代码,错误的记录将被保存在最终生成的 AST 的 errors 属性中,当然如果遇到严重的错误,依然会终止解析
const parser
=
require(
"@babel/parser"
);
const code
=
"const a = 1;"
;
const ast
=
parser.parse(code, {sourceType:
"module"
})
console.log(ast)
const parser
=
require(
"@babel/parser"
);
const code
=
"const a = 1;"
;
const ast
=
parser.parse(code, {sourceType:
"module"
})
console.log(ast)
参数
描述
auxiliaryCommentBefore
在输出文件内容的头部添加注释块文字
auxiliaryCommentAfter
在输出文件内容的末尾添加注释块文字
comments
输出内容是否包含注释
compact
输出内容是否不添加空格,避免格式化
concise
输出内容是否减少空格使其更紧凑一些
minified
是否压缩输出代码
retainLines
尝试在输出代码中使用与源代码中相同的行号
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const code
=
"const a = 1;"
;
const ast
=
parser.parse(code, {sourceType:
"module"
})
ast.program.body[
0
].declarations[
0
].
id
.name
=
"b"
ast.program.body[
0
].declarations[
0
].init.value
=
2
const result
=
generate(ast, {minified: true})
console.log(result.code)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const code
=
"const a = 1;"
;
const ast
=
parser.parse(code, {sourceType:
"module"
})
ast.program.body[
0
].declarations[
0
].
id
.name
=
"b"
ast.program.body[
0
].declarations[
0
].init.value
=
2
const result
=
generate(ast, {minified: true})
console.log(result.code)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const code
=
`
const a
=
1500
;
const b
=
60
;
const c
=
"hi"
;
const d
=
787
;
const e
=
"1244"
;
`
const ast
=
parser.parse(code)
const visitor
=
{
NumericLiteral(path){
path.node.value
=
(path.node.value
+
100
)
*
2
},
StringLiteral(path){
path.node.value
=
"I Love JavaScript!"
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const code
=
`
const a
=
1500
;
const b
=
60
;
const c
=
"hi"
;
const d
=
787
;
const e
=
"1244"
;
`
const ast
=
parser.parse(code)
const visitor
=
{
NumericLiteral(path){
path.node.value
=
(path.node.value
+
100
)
*
2
},
StringLiteral(path){
path.node.value
=
"I Love JavaScript!"
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
属性
描述
toString()
当前路径的源码
node
当前路径的节点
parent
当前路径的父级节点
parentPath
当前路径的父级路径
type
当前路径的类型
const a
=
3200
;
const b
=
320
;
const c
=
"I Love JavaScript!"
;
const d
=
1774
;
const e
=
"I Love JavaScript!"
;
const a
=
3200
;
const b
=
320
;
const c
=
"I Love JavaScript!"
;
const d
=
1774
;
const e
=
"I Love JavaScript!"
;
const visitor
=
{
"NumericLiteral|StringLiteral"
(path) {
path.node.value
=
"I Love JavaScript!"
}
}
const visitor
=
{
"NumericLiteral|StringLiteral"
(path) {
path.node.value
=
"I Love JavaScript!"
}
}
const visitor
=
{
NumericLiteral(path){
path.node.value
=
(path.node.value
+
100
)
*
2
},
StringLiteral(path){
path.node.value
=
"I Love JavaScript!"
}
}
const visitor
=
{
NumericLiteral(path){
path.node.value
=
(path.node.value
+
100
)
*
2
},
StringLiteral(path){
path.node.value
=
"I Love JavaScript!"
}
}
const visitor
=
{
NumericLiteral: function (path){
path.node.value
=
(path.node.value
+
100
)
*
2
},
StringLiteral: function (path){
path.node.value
=
"I Love JavaScript!"
}
}
const visitor
=
{
NumericLiteral: function (path){
path.node.value
=
(path.node.value
+
100
)
*
2
},
StringLiteral: function (path){
path.node.value
=
"I Love JavaScript!"
}
}
const visitor
=
{
NumericLiteral: {
enter(path) {
path.node.value
=
(path.node.value
+
100
)
*
2
}
},
StringLiteral: {
enter(path) {
path.node.value
=
"I Love JavaScript!"
}
}
}
const visitor
=
{
NumericLiteral: {
enter(path) {
path.node.value
=
(path.node.value
+
100
)
*
2
}
},
StringLiteral: {
enter(path) {
path.node.value
=
"I Love JavaScript!"
}
}
}
const visitor
=
{
enter(path) {
if
(path.node.
type
=
=
=
"NumericLiteral"
) {
path.node.value
=
(path.node.value
+
100
)
*
2
}
if
(path.node.
type
=
=
=
"StringLiteral"
) {
path.node.value
=
"I Love JavaScript!"
}
}
}
const visitor
=
{
enter(path) {
if
(path.node.
type
=
=
=
"NumericLiteral"
) {
path.node.value
=
(path.node.value
+
100
)
*
2
}
if
(path.node.
type
=
=
=
"StringLiteral"
) {
path.node.value
=
"I Love JavaScript!"
}
}
}
function variableDeclaration(kind:
"var"
|
"let"
|
"const"
, declarations: Array<BabelNodeVariableDeclarator>)
function variableDeclaration(kind:
"var"
|
"let"
|
"const"
, declarations: Array<BabelNodeVariableDeclarator>)
const visitor
=
{
VariableDeclaration(path) {
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
}
}
const visitor
=
{
VariableDeclaration(path) {
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
}
}
function variableDeclarator(
id
: BabelNodeLVal, init?: BabelNodeExpression)
function variableDeclarator(
id
: BabelNodeLVal, init?: BabelNodeExpression)
const visitor
=
{
VariableDeclaration(path) {
let declarator
=
types.variableDeclarator(types.identifier(
"b"
), init)
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
}
}
const visitor
=
{
VariableDeclaration(path) {
let declarator
=
types.variableDeclarator(types.identifier(
"b"
), init)
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
}
}
function binaryExpression(
operator:
"+"
|
"-"
|
"/"
|
"%"
|
"*"
|
"**"
|
"&"
|
"|"
|
">>"
|
">>>"
|
"<<"
|
"^"
|
"=="
|
"==="
|
"!="
|
"!=="
|
"in"
|
"instanceof"
|
">"
|
"<"
|
">="
|
"<="
,
left: BabelNodeExpression | BabelNodePrivateName,
right: BabelNodeExpression
)
function binaryExpression(
operator:
"+"
|
"-"
|
"/"
|
"%"
|
"*"
|
"**"
|
"&"
|
"|"
|
">>"
|
">>>"
|
"<<"
|
"^"
|
"=="
|
"==="
|
"!="
|
"!=="
|
"in"
|
"instanceof"
|
">"
|
"<"
|
">="
|
"<="
,
left: BabelNodeExpression | BabelNodePrivateName,
right: BabelNodeExpression
)
const visitor
=
{
VariableDeclaration(path) {
let init
=
types.binaryExpression(
"+"
, left, right)
let declarator
=
types.variableDeclarator(types.identifier(
"b"
), init)
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
}
}
const visitor
=
{
VariableDeclaration(path) {
let init
=
types.binaryExpression(
"+"
, left, right)
let declarator
=
types.variableDeclarator(types.identifier(
"b"
), init)
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
}
}
const visitor
=
{
VariableDeclaration(path) {
let left
=
types.binaryExpression(
"*"
, types.identifier(
"a"
), types.numericLiteral(
5
))
let right
=
types.numericLiteral(
1
)
let init
=
types.binaryExpression(
"+"
, left, right)
let declarator
=
types.variableDeclarator(types.identifier(
"b"
), init)
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
path.stop()
}
}
const visitor
=
{
VariableDeclaration(path) {
let left
=
types.binaryExpression(
"*"
, types.identifier(
"a"
), types.numericLiteral(
5
))
let right
=
types.numericLiteral(
1
)
let init
=
types.binaryExpression(
"+"
, left, right)
let declarator
=
types.variableDeclarator(types.identifier(
"b"
), init)
let declaration
=
types.variableDeclaration(
"const"
, [declarator])
path.insertAfter(declaration)
path.stop()
}
}
console[
'\u006c\u006f\u0067'
](
'\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021'
)
console[
'\u006c\u006f\u0067'
](
'\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021'
)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const code
=
`console[
'\u006c\u006f\u0067'
](
'\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021'
)`
const ast
=
parser.parse(code)
const visitor
=
{
StringLiteral(path) {
/
/
以下方法均可
/
/
path.node.extra.raw
=
path.node.rawValue
/
/
path.node.extra.raw
=
/
/
delete path.node.extra
delete path.node.extra.raw
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const code
=
`console[
'\u006c\u006f\u0067'
](
'\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021'
)`
const ast
=
parser.parse(code)
const visitor
=
{
StringLiteral(path) {
/
/
以下方法均可
/
/
path.node.extra.raw
=
path.node.rawValue
/
/
path.node.extra.raw
=
/
/
delete path.node.extra
delete path.node.extra.raw
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
console[
"log"
](
"Hello world!"
);
console[
"log"
](
"Hello world!"
);
const a
=
!![]
+
!![]
+
!![];
const b
=
Math.floor(
12.34
*
2.12
)
const c
=
10
>>
3
<<
1
const d
=
String(
21.3
+
14
*
1.32
)
const e
=
parseInt(
"1.893"
+
"45.9088"
)
const f
=
parseFloat(
"23.2334"
+
"21.89112"
)
const g
=
20
<
18
?
'未成年'
:
'成年'
const a
=
!![]
+
!![]
+
!![];
const b
=
Math.floor(
12.34
*
2.12
)
const c
=
10
>>
3
<<
1
const d
=
String(
21.3
+
14
*
1.32
)
const e
=
parseInt(
"1.893"
+
"45.9088"
)
const f
=
parseFloat(
"23.2334"
+
"21.89112"
)
const g
=
20
<
18
?
'未成年'
:
'成年'
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const types
=
require(
"@babel/types"
)
const code
=
`
const a
=
!![]
+
!![]
+
!![];
const b
=
Math.floor(
12.34
*
2.12
)
const c
=
10
>>
3
<<
1
const d
=
String(
21.3
+
14
*
1.32
)
const e
=
parseInt(
"1.893"
+
"45.9088"
)
const f
=
parseFloat(
"23.2334"
+
"21.89112"
)
const g
=
20
<
18
?
'未成年'
:
'成年'
`
const ast
=
parser.parse(code)
const visitor
=
{
"BinaryExpression|CallExpression|ConditionalExpression"
(path) {
const {confident, value}
=
path.evaluate()
if
(confident){
path.replaceInline(types.valueToNode(value))
}
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const types
=
require(
"@babel/types"
)
const code
=
`
const a
=
!![]
+
!![]
+
!![];
const b
=
Math.floor(
12.34
*
2.12
)
const c
=
10
>>
3
<<
1
const d
=
String(
21.3
+
14
*
1.32
)
const e
=
parseInt(
"1.893"
+
"45.9088"
)
const f
=
parseFloat(
"23.2334"
+
"21.89112"
)
const g
=
20
<
18
?
'未成年'
:
'成年'
`
const ast
=
parser.parse(code)
const visitor
=
{
"BinaryExpression|CallExpression|ConditionalExpression"
(path) {
const {confident, value}
=
path.evaluate()
if
(confident){
path.replaceInline(types.valueToNode(value))
}
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
const a
=
3
;
const b
=
26
;
const c
=
2
;
const d
=
"39.78"
;
const e
=
parseInt(
"1.89345.9088"
);
const f
=
parseFloat(
"23.233421.89112"
);
const g
=
"\u6210\u5E74"
;
const a
=
3
;
const b
=
26
;
const c
=
2
;
const d
=
"39.78"
;
const e
=
parseInt(
"1.89345.9088"
);
const f
=
parseFloat(
"23.233421.89112"
);
const g
=
"\u6210\u5E74"
;
const a
=
1
;
const b
=
a
*
2
;
const c
=
2
;
const d
=
b
+
1
;
const e
=
3
;
console.log(d)
const a
=
1
;
const b
=
a
*
2
;
const c
=
2
;
const d
=
b
+
1
;
const e
=
3
;
console.log(d)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
const code
=
`
const a
=
1
;
const b
=
a
*
2
;
const c
=
2
;
const d
=
b
+
1
;
const e
=
3
;
console.log(d)
`
const ast
=
parser.parse(code)
const visitor
=
{
VariableDeclarator(path){
const binding
=
path.scope.getBinding(path.node.
id
.name);
/
/
如标识符被修改过,则不能进行删除动作。
if
(!binding || binding.constantViolations.length >
0
) {
return
;
}
/
/
未被引用
if
(!binding.referenced) {
path.remove();
}
/
/
被引用次数为
0
/
/
if
(binding.references
=
=
=
0
) {
/
/
path.remove();
/
/
}
/
/
长度为
0
,变量没有被引用过
/
/
if
(binding.referencePaths.length
=
=
=
0
) {
/
/
path.remove();
/
/
}
}
}
traverse(ast, visitor)
const result
=
generate(ast)
console.log(result.code)
const parser
=
require(
"@babel/parser"
);
const generate
=
require(
"@babel/generator"
).default
const traverse
=
require(
"@babel/traverse"
).default
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-4-27 09:53
被ITBOB编辑
,原因: 添加目录