验证码验证

1 学习网站

2 基本组件

2.1 parser

描述:将 js 代码转换为抽象语法树
用法:

1
2
3
let parse = require("@babel/parser").parse 

let traverse = require("@babel/traverse")

2.2 generator

描述:将抽象语法树还原为 JS 代码
用法:

1
2
let generate = require("@babel/generator").default
generate(ast).code

2.3 traverse

描述:遍历 node 节点
用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
traverse(ast,{
StringInditify: function(){

}
})
//方式二 enter 和 exit的区别
traverse(ast,{
StringInditify: {
exit: function(){

}
}
})

3 基本属性

3.1 Path

属性:

  • path.replaceWith 把这个节点替换成另外一个节点, 接收参数为 node
  • path.node 获取当前路径对应的节点。
  • path.parent 获取当前路径对应节点的父节点。
  • path.parentPath 获取当前路径对应节点的父路径。
  • path.scope 表示当前 path 下的作用域,这个也是写插件经常会用到的。
  • path.container 用于获取当前 path 下的所有兄弟节点(包括自身)。
  • path.type 获取当前 path 的节点类型。
  • path.key 获取当前 path 的 key 值,key 通常用于 path.get 函数。

函数:

  • path.get(key) 获取当前路径下指定属性名(key)对应的子路径。例如,path.get(“body”) 获取当前路径下名为 “body” 的子路径。
  • path.getSibling(index) 获取当前路径对应节点的兄弟节点的路径。通过指定索引(index)可以获取相应的兄弟路径。
  • path.getFunctionParent() 获取当前路径对应节点的最近的函数父节点的路径。
  • path.getPrevSibling() 获取当前 path 的前一个兄弟节点,返回的是 path 类型。
  • path.getAllPrevSiblings() 获取当前 path 的所有前兄弟节点,返回的是 Array 类型,其元素都是 path 类型。
  • path.getNextSibling() 获取当前 path 的后一个兄弟节点,返回的是 path 类型。
  • path.getAllNextSiblings() 获取当前 path 的所有后兄弟节点,返回的是 Array 类型,其元素都是 path 类型。
  • path.evaluate() 用于计算表达式的值,大家可以参考 constantFold 插件的写法。
  • path.findParent() 向上查找满足回调函数特征的 path,即判断上级路径是否包含有 XXX 类型的节点。
  • path.find() 功能与 path.findParent 方法一样,只不过从当前 path 开始进行遍历。
  • path.getFunctionParent() 获取函数类型父节点,如果不存在,返回 null。
  • path.getStatementParent() 获取 Statement 类型父节点,这个基本上都会有返回值,如果当前遍历的是 Program 或者 File 节点,则会报错。
  • path.getAncestry() 获取所有的祖先节点,没有实参,返回的是一个 Array 对象。
  • path.isAncestor(maybeDescendant) 判断当前遍历的节点是否为实参的祖先节点.
  • path.isDescendant(maybeAncestor) 判断当前遍历的节点是否为实参的子孙节点.
  • path.traverse(visitor) 遍历当前路径下的所有子节点,并应用指定的 visitor。
  • path.replaceWith(node) 用指定的节点替换当前路径对应的节点。
  • path.remove() 从 AST 中移除当前路径对应的节点。
  • path.insertBefore(nodes) 在当前路径对应节点之前插入一个或多个节点。
  • path.insertAfter(nodes) 在当前路径对应节点之后插入一个或多个节点。
  • path.toString() 用于将 AST 节点转换回对应的源代码字符串。

3.2 Scope

函数:

  • scope.block 表示当前作用域下的所有 node,参考上面的 this.block = node;
  • scope.dump() 输出当前每个变量的作用域信息。调用后直接打印,不需要加打印函数
  • scope.crawl() 重构 scope,在某种情况下会报错,不过还是建议在每一个插件的最后一行加上。
  • scope.rename(oldName, newName, block) 修改当前作用域下的的指定的变量名,oldname、newname 表示替换前后的变量名,为字符串。注意,oldName 需要有 binding,否则无法重命名。
  • scope.traverse(node, opts, state) 遍历当前作用域下的某些(个)插件。和全局的 traverse 用法一样。
  • scope.getBinding(name) 获取某个变量的 binding,可以理解为其生命周期。包含引用,修改之类的信息

属性:

  • scope.block 可以用来获取标识符的作用域,返回 Node 对象,使用方法分为两种情况:变量 和 函数,变量获取当前作用域,函数获取函数本身作用域。

3.2.1 实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const a = 1000;
let b = 2000;
let obj = {
name: 'mankvis',
add: function (a) {
a = 400;
b = 300;
let e = 700;

function demo() {
let d = 600;
}

demo();
return a + a + b + 1000 + obj.name;
},
}
obj.add(100);

3.2.2 scope.block

变量获取 block 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const ast = parser.parse(jsCode);

traverse(ast, {
Identifier(path) {
if (path.node.name === 'e') {
console.log(generator(path.scope.block).code)
}
}
})
/*
function (a) {
a = 400;
b = 300;
let e = 700;

function demo() {
let d = 600;
}

demo();
return a + a + b + 1000 + obj.name;
}
*/

函数获取 block 属性:

1
2
3
4
5
6
7
8
9
10
11
12
traverse(ast, {
FunctionDeclaration(path) {
console.log(generator(path.scope.block).code)
}
})

/*
function demo() {
let d = 600;
}
*/

3.2.3 scope.dump

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
traverse(ast, {
FunctionDeclaration(path) {
console.log('\n\n这里是函数', path.node.id.name + '()');
path.scope.dump();
}
})

/*
这里是函数 demo()
------------------------------------------------------------
# FunctionDeclaration
- d { constant: true, references: 0, violations: 0, kind: 'let' }
# FunctionExpression
- a { constant: false, references: 2, violations: 1, kind: 'param' }
- e { constant: true, references: 0, violations: 0, kind: 'let' }
- demo { constant: true, references: 1, violations: 0, kind: 'hoisted' }
# Program
- a { constant: true, references: 0, violations: 0, kind: 'const' }
- b { constant: false, references: 1, violations: 1, kind: 'let' }
- obj { constant: true, references: 2, violations: 0, kind: 'let' }
------------------------------------------------------------
*/
  • ‘#’开头的是每一个作用域 ,上面有三个作用域,分别是 FunctionDeclaration、FunctionExpression 和 Program。
  • ‘-‘开头的是每一个绑定(binding),每一个 binding 都会包含几个关键信息,分别是:constant、references、violations、kind。
    • constant 表示是否为常量,为布尔值
    • references 表示被引用的次数
    • violations 表示被重新定义的次数
    • kind 表示声明类型,param 参数、hoisted 提升、var 变量、local 内部。

3.2.4 scope.getBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
traverse(ast, {
FunctionDeclaration(path) {
let bindingA = path.scope.getBinding('a');
console.log(bindingA);
},
})

/*
Binding {
identifier: Node {type: 'Identifier', ..., name: 'a'},
scope: Scope {
...,
block: Node {type: 'FunctionExpression', ...}
},
path: NodePath {...},
kind: 'param',
constantViolations: [...],
constant: false,
referencePaths: [...],
referenced: true,
references: 2
}
}
*/

getBinding 中传入的值必须是当前节点能够引用到的标识符名,如果传入一个不存在的 g ,这个标识符并不存在,或者说当前节点引用不到,那么 getBinding 会返回 undefined
由于 FunctionDeclaration 的作用域只是 demo 本身, demo 本身的作用域是 add 这个函数,所以是可以直接找到 a 的

  • identifier 是 a 标识符的 Node 对象
  • path 是 a 表示符的 Path 对象
  • kind 表示这是一个参数,但是并不代表就是当前 demo 的参数。实际上原始代码中,a 是 add 的参数(当函数中局部变量与全局变量重名时,使用的是局部变量)
  • referencePaths 假设标识符被引用,referencePaths 中会存放所有引用该标识符的节点的 Path 对象
  • constantViolations 假如标识符被修改,那么 constantViolations 中会存放所有修改该标识符的节点的 Path 对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //获取函数作用域方法:
    traverse(ast, {
    FunctionExpression(path) {
    let bindingA = path.scope.getBinding('a');
    let bindingDemo = path.scope.getBinding('demo');
    console.log(bindingA.referenced);
    console.log(bindingA.references);
    console.log(generator(bindingA.scope.block).code);
    console.log(generator(bindingDemo.scope.block).code);
    },
    })

    /*
    true
    2
    下面代码输出2次
    function (a) {
    a = 400;
    b = 300;
    let e = 700;

    function demo() {
    let d = 600;
    }

    demo();
    return a + a + b + 1000 + obj.name;
    }
    */

3.2.5 scope.getOwnBinding

scope.getOwnBinding 该函数用于获取当前节点自己的绑定,也就是不包含父级作用域中定义的标识符的绑定,但是该函数会得到子函数中定义的标识符的绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function TestOwnBinding(path) {
path.traverse({
Identifier(p) {
let name = p.node.name;
console.log(name, !!p.scope.getOwnBinding(name));
}
});
}

traverse(ast, {
FunctionExpression(path) {
TestOwnBinding(path);
},
})

/*
a true
a true
b false
e true
demo false
d true
demo true
a true
a true
b false
obj false
name false
*/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function TestOwnBinding(path) {
path.traverse({
Identifier(p) {
let name = p.node.name;
let binding = p.scope.getBinding(name);
binding && console.log(name, generator(binding.scope.block).code === path.toString());
}
});
}

traverse(ast, {
FunctionExpression(path) {
TestOwnBinding(path);
},
})

/*
a true
a true
b false
e true
demo true
d false
demo true
a true
a true
b false
obj false
*/

3.2.6 scope.traverse

scope.traverse 方法可以用来遍历作用域中的节点。可以使用 Path 对象中的 scope,也可以使用 Binding 中的 scope,笔者推荐使用后者,下面来看下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
traverse(ast, {
FunctionDeclaration(path) {
let binding = path.scope.getBinding('a');
binding.scope.traverse(binding.scope.block, {
AssignmentExpression(p) {
if (p.node.left.name === 'a') {
p.node.right = types.numericLiteral(500);
}
},
})
}
})

/*
const a = 1000;
let b = 2000;
let obj = {
name: 'mankvis',
add: function (a) {
a = 500;
b = 300;
let e = 700;

function demo() {
let d = 600;
}

demo();
return a + a + b + 1000 + obj.name;
}
};
obj.add(100);
*/

3.2.7 scope.rename

可以使用 scope.rename 将标识符进行重命名,这个方法会同时修改所有引用该标识符的地方,例如将 add 函数中的 b 变量重命名为 x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
traverse(ast, {
FunctionExpression(path) {
let binding = path.scope.getBinding('b');
binding.scope.rename('b', 'x');
},
})

/*
const a = 1000;
let x = 2000;
let obj = {
name: 'mankvis',
add: function (a) {
a = 400;
x = 300;
let e = 700;

function demo() {
let d = 600;
}

demo();
return a + a + x + 1000 + obj.name;
}
};
obj.add(100);
*/

上述方法很方便的就把 b 改为了 x,但是如果随便指定一个变量名,可能会与现有标识符发生命名冲突,这时可以使用 scope.generateUidIdentifier 来生成一个标识符,生成的标识符不会与任何本地的标识符相冲突,代码如下:

1
2
3
4
5
6
7
8
9
traverse(ast, {
FunctionExpression(path) {
path.scope.generateUidIdentifier('uid');
// Node {type: "Identifier", name: "_uid"}
path.scope.generateUidIdentifier('_uid2');
// Node {type: "Identifier", name: "_uid2"}
},
})

3.2.8 scope.hasBinding

该方法查询某标识符是否有绑定,返回 true 或者 false。可以用 scope.getBinding("a") 代替,scope.getBinding("a") 返回 undefined,等同于 scope.hasBinding("a") 返回 false

3.2.9 scope.hasOwnBinding

该方法查询当前节点中是否有自己的绑定,返回布尔值,例如,对于 demo 函数,OwnBinding 只有一个 d,函数名 demo 虽然也是标识符,但不属于 demo 函数的 OwnBinding 范畴,是属于它的父级作用域中的,如:

1
2
3
4
5
6
7
8
9
10
traverse(ast, {
FunctionDeclaration(path) {
console.log(path.scope.parent.hasOwnBinding('demo'));
},
})

/*
true
*/

3.2.10 scope.getAllBindings

该方法获取当前节点的所有绑定,会返回一个对象。该对象以标识符名为属性名,对应的 Binding 为属性值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
traverse(ast, {
FunctionDeclaration(path) {
console.log(path.scope.getAllBindings());
}
})

/*
[Object: null prototype] {
d: Binding {...},
a: Binding {...},
demo: Binding {...},
e: Binding {...},
b: Binding {...},
obj: Binding {...},
}
*/

遍历每一个 Binding,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
traverse(ast, {
BlockStatement(path) {
console.log('\n此块节点源码: \n', path.toString());
let bindings = path.scope.bindings;
console.log('作用域内被绑定数量:', Object.keys(bindings).length);
for (const bindingsKey in bindings) {
console.log('名字', bindingsKey);
let binding_ = bindings[bindingsKey];
console.log('类型:', binding_.kind);
console.log('定义:', binding_.identifier);
console.log('是否常量:', binding_.constant);
console.log('被修改信息记录:', binding_.constantViolations);
console.log('是否被引用:', binding_.referenced);
console.log('被引用次数:', binding_.references);
console.log('被引用信息NodePath记录', binding_.referencePaths);
}
console.log('-------------------------------------');
},
})

3.2.11 scope .hasReference

scope.hasReference(“a”) 表示查询当前节点中是否有 a 标识符的引用,返回布尔值。

3.2.12 scope .getBindingIdentifier

scope.getBindingIdentifier(“a”) 表示获取当前节点中绑定的 a 的标识符,返回 Identifier 的 Node 对象。同样,这个方法也有 Own 版本,为 scope.getOwnBindingIdentifier(“a”)。

3.3 node

属性:

  • path.node.type 获取当前节点的类型。
  • path.node.declarations 对于 VariableDeclaration 节点, 获取变量声明列表。
  • path.node.init.value 获取某个节点的值。
  • delete path.node.init; 删除节点,使用系统的 delete 方法。

3.4 types 类型

3.5 节点类型对照

  • Program 程序主体 整段代码的主体
  • VariableDeclaration 变量声明 声明一个变量,例如 var let const
  • FunctionDeclaration 函数声明 声明一个函数,例如 function
  • ExpressionStatement 表达式语句 通常是调用一个函数,例如 console.log()
  • BlockStatement 块语句 包裹在 {} 块内的代码,例如 if (condition){var a = 1;}
  • BreakStatement 中断语句 通常指 break
  • ContinueStatement 持续语句 通常指 continue
  • ReturnStatement 返回语句 通常指 return
  • SwitchStatement Switch 语句 通常指 Switch Case 语句中的 Switch
  • IfStatement If 控制流语句 控制流语句,通常指 if(condition){}else{}
  • Identifier 标识符 标识,例如声明变量时 var identi = 5 中的 identi
  • CallExpression 调用表达式 通常指调用一个函数,例如 console.log()
  • BinaryExpression 二进制表达式 通常指运算,例如 1+2
  • MemberExpression 成员表达式 通常指调用对象的成员,例如 console 对象的 log 成员
  • ArrayExpression 数组表达式 通常指一个数组,例如 [1, 3, 5]
  • NewExpression New 表达式 通常指使用 New 关键词
  • AssignmentExpression 赋值表达式 通常指将函数的返回值赋值给变量
  • UpdateExpression 更新表达式 通常指更新成员值,例如 i++
  • Literal 字面量 字面量
  • BooleanLiteral 布尔型字面量 布尔值,例如 true false
  • NumericLiteral 数字型字面量 数字,例如 100
  • StringLiteral 字符型字面量 字符串,例如 vansenb
  • SwitchCase Case 语句 通常指 Switch 语句中的 Case
  • SequenceExpression 一个序列表达式,也就是由逗号分割的表达式序列
  • FunctionExpression 函数赋值语句

4 常用技巧

4.1 基础使用框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// decrypt.js
const fs = require('fs');
var util = require('util');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const types = require('@babel/types');
const generator = require('@babel/generator').default;

// 程序启动时间
var time_start = new Date().getTime()
// 读取文件
process.argv.length > 2 ? encode_file = process.argv[2] : encode_file = 'encode.js';
process.argv.length > 3 ? decode_file = process.argv[3] : decode_file = 'decode.js';

let jscode = fs.readFileSync(encode_file, {encoding: 'utf-8'});
// 转换为 ast 树
let ast = parser.parse(jscode);

const visitor =
{
// 此处编写 babel 处理代码、插件代码
ASTNodeTypeHere(path, state) {
}
// 如
// Identifier(path, state) {}
// Identifier(path) {}
}

//调用插件,处理待处理 js ast 树
traverse(ast, visitor);
console.log('AST traverse completed.')

// 生成处理后的 js
let {code} = generator(ast);
console.log('AST generator completed.')
fs.writeFile(decode_file, code, (err) => {
});
console.log(util.format('The javascript code in [%s] has been processed.', encode_file))
console.log(util.format('The processing result has been saved to [%s].', decode_file))
// 程序结束时间
var time_end = new Date().getTime()
console.log(util.format('The program runs to completion, time-consuming: %s s', (time_end - time_start) / 1000))

4.2 打印原来的节点的源码 toString()

1
2
3
4
5
6
7
var js = 'var a = 1'
const visitor =
{
VariableDeclarator(path){
console.log(path.toString()); // a = 1
console.log(generator(path.node).code) // a= 1
}}

4.3 构造节点 t.valueToNode()

(可以代替t.NumericLiteral(234567),t.StringLiteral(‘1234’) 注意:使用这两种过传参类型必须和声明类型一样,如果使用 valueNode 的话就是传入参数的默认类型)

1
2
3
4
5
6
{
VariableDeclarator(path){
console.log(t.valueToNode('123')); // { type: 'StringLiteral', value: '123' }
console.log(t.valueToNode(123)); // { type: 'NumericLiteral', value: 123 }
console.log(t.valueToNode(null)) // { type: 'NullLiteral' }
}}

4.4 替换节点 replaceWith

1
2
3
4
5
{
'VariableDeclarator'(path){
console.log(path.toString()); //a = 1
path.replaceWith(t.valueToNode(1)) //var 1;
}}

4.5 删除节点 remove()

1
2
3
4
5
6
{
'VariableDeclarator'(path){
console.log(path.toString()) // a = 1 + 2;b = 2
if (path.node.id.name === 'b'){
path.remove()};
}} // 还原为var a = 1 + 2;

4.6 判断节点类型 isVariableDeclarator

1
2
3
4
5
6
7
{
'VariableDeclarator'(path){
console.log(path.toString()) //a = 1 + 2
console.log(path.type); // VariableDeclarator
console.log(t.isVariableDeclarator(path.node)) //true
console.log(path.isVariableDeclarator()) // true
}}

4.7 节点设置值 set(key,node)

1
2
3
4
5
6
7
{ // 将var a修改为 var a
'VariableDeclarator'(path){
console.log(path.toString()) //a
const {init} = path.node
console.log(init === path.node.init) //true
init || path.set('init', t.Identifier("1"))
}} //输出为var a = 1;

4.8 插入节点

1
2
3
4
5
6
{
'Identifier' (path) {
path.insertBefore(t.valueToNode('22')); //节点前插入
path.insertAfter(t.valueToNode('22'));//节点后插入
}
}

5 高级技巧

5.1 执行 path

使用 path.evaluate() ,返回的 confident 为 true 则表明成功执行,value 为返回值
示例:

1
let { confident, value } = path.evaluate();

5.2 多个节点组合查询

使用 | 把需要访问的节点组合起来即可,例如:

1
2
3
4
const visitor = {
'VariableDeclarator|FunctionDeclaration'(path) {}
};
traverse(ast, visitor);

5.3 删除无用的空语句

删除空语句 ;

1
2
3
4
5
6
const visitor =
{
'EmptyStatement'(path) {
path.remove();
}
}

5.4 删除无用节点

`path.remove()

5.5 查看作用域 scope

path.scope.dump() 即可查看自底向上的作用域

5.6 同一节点使用多个函数

1
2
3
4
5
6
7
8
9
10
function log_a(path) { console.log('This is [a] function -- ' + path.node.init.value); }
function log_b(path) { console.log('This is [b] function -- ' + path.node.init.value); }
function log_c(path) { console.log('This is [c] function -- ' + path.node.init.value); }
const visitor =
{
'VariableDeclarator': {
'enter': [log_a, log_c, log_b]
}
}
traverse(ast, visitor);

请注意!
enter 需为数组!函数执行顺序为列表中函数顺序!

5.7 删除未使用的 function、var、let、const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const visitor =
{
VariableDeclarator(path) {
const { id } = path.node;
let binding = path.scope.getBinding(id.name);

if (binding.referenced) {
return;
}
path.remove();
path.scope.crawl();
},
FunctionDeclaration(path) {
const { id } = path.node;
// 防止函数中存在变量与函数名相同,且该变量在函数中使用,导致未去除未使用函数
let binding = path.scope.parent.getBinding(id.name);

if (binding.referenced) {
return;
}
path.remove();
// 手动更新 scope ,防止影响下个插件使用
path.scope.crawl();
}
}

5.8 a[‘bb’] 转换为 a.bb

1
2
3
4
5
6
7
8
9
10
11
12
const visitor =
{
MemberExpression(path) {
let { computed } = path.node;
// 获取 path property 子路径
let property = path.get('property');
if (computed && types.isStringLiteral(property)) {
property.replaceWith(types.identifier(property.node.value));
path.node.computed=false;
}
}
}

5.9 简单控制流

待处理代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var arr = "3|0|1|2|4".split("|");
var cnt = 0;
while (true) {
switch (arr[cnt++]) {
case "0":
console.log("This is case-block 0");
continue;
case "1":
console.log("This is case-block 1");
continue;
case "2":
console.log("This is case-block 2");
continue;
case "3":
console.log("This is case-block 3");
continue;
case "4":
console.log("This is case-block 4");
continue;
}
break;
}

简单还原后的结果为:

1
2
3
4
5
console.log("This is case-block 3");
console.log("This is case-block 0");
console.log("This is case-block 1");
console.log("This is case-block 2");
console.log("This is case-block 4");

插件为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  'WhileStatement'(path){
// console.log(path.node)
const {test, body} = path.node;
if (!t.isBooleanLiteral(test) || test.value !== true){return};
if (body.body.length === 0 || !t.isSwitchStatement(body.body[0])) {return};
let switch_state = body.body[0];
let{discriminant, cases} = switch_state;
if (!t.isMemberExpression(discriminant) || !t.isUpdateExpression(discriminant.property)){return};
//获取while循环变量名称
let arr_name = discriminant.object.name;
let arr = [];

//1、获取while所有以上兄弟节点,获取控制流属组
let all_pre_siblings = path.getAllPrevSiblings();
if (all_pre_siblings.length !== 2){return};
//2、根据循环变量名称判断控制流属组位置
all_pre_siblings.forEach(pre_pth =>{
const {declarations} = pre_pth.node;
let {id, init} = declarations[0];
if (arr_name == id.name){
arr = init.callee.object.value.split('|');
}
pre_pth.remove();
});
let ret_body = [];
arr.forEach(index =>{
let case_body = cases[index].consequent;
//如果case里面含有continue语句,将属组弹出
if (t.isContinueStatement(case_body[case_body.length -1]))
{case_body.pop()}
ret_body = ret_body.concat(case_body)
})
path.replaceInline(ret_body)
}

5.10 自执行函数替换(固定函数参数)

待处理代码:

1
!function(a,b) {c = a | b;}(111,222);

简单还原后的结果为:

1
!function() {c = 111 | 222}();

插件为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CallExpression(path) {
let callee = path.get('callee');
let arguments = path.get('arguments');
if (!callee.isFunctionExpression() || arguments.length === 0) { //这里实参的长度判断可以写死。
return;
}
//对着网站解析,获取形参
let params = callee.get('params');
let scope = callee.scope;
for (let i = 0; i < arguments.length; i++) { //遍历实参,因为形参可能比实参长。
let arg = params[i];
let {
name
} = arg.node;
const binding = scope.getBinding(name); // 在当前作用域中,找到参数的引用
console.log(binding)
if (!binding || binding.constantViolations.length > 0) { //形参有被改变,不能被还原
continue; // 表示在当前作用域中的引用,因为现在的作用域是整个函数,所有如果'!function(a,b) {c = a | b;a=1}(111,222);'或者'!function(a,b) {a=1;c = a | b}(111,222);'是无法修改a的值的
}
for (refer_path of binding.referencePaths) { //因为是字面量,所以直接替换,但是遇到Array类型的需要另外处理
//也无非获取父节点,然后判断索引
refer_path.replaceWith(arguments[i]);
//上面的参数可以是path,也可以是node。但是我遇到过path报错,node可以的情况。
}
arg.remove();
arguments[i].remove();
}
},

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 dacker1993@gmail

💰

×

Help us with donation