npm install @babel/core
(测试安装全局不成功,不知道原因)
查看ast的网站:https://astexplorer.net/
参考自夜幕蔡老板星球文章:
下边是个简单的例子:
const {
parse
} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
jscode = 'var s = 92; b = Z(12344, s);'
let ast = parse(jscode);
var test = {}
const visitor =
{
'VariableDeclarator|Identifier' (path) {
console.log(path.toString())
if (path.isVariableDeclarator()) {
test[path.node.id.name] = path.node.init.value
}
if (path.parent.type ==='CallExpression'&& test.hasOwnProperty(path.node.name)) {
path.replaceWith(t.valueToNode(test.s))
}
}
}
traverse(ast, visitor);
let {
code
} = generator(ast);
console.log(code)
常用方法:
图片.png常用方法补充:
获取所有的前兄弟节点:path.getAllPrevSiblings()
获取前一个兄弟节点:path.getPrevSibling()
常用节点:
图片.png以下套用模版:
const fs = require('fs');
const {parse} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
jscode = 'var a '
let ast = parse(jscode);
const visitor =
{
//下边插件代码
}}
traverse(ast,visitor);
let {code} = generator(ast);
console.log(code)
1、打印原来的节点的源码:
var js = 'var a = 1'
const visitor =
{
VariableDeclarator(path){
console.log(path.toString()); // a = 1
console.log(generator(path.node).code) // a= 1
}}
2、构造literal (可以代替t.NumericLiteral(234567),t.StringLiteral('1234') 注意:使用这两种过传参类型必须和声明类型一样,如果使用valueNode的话就是传入参数的默认类型)
{
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' }
}}
3、替换replaceWith (path.replaceInline可以用来代替path.replaceWith)
{
'VariableDeclarator'(path){
console.log(path.toString()); //a = 1
path.replaceWith(t.valueToNode(1)) //var 1;
}}
4、删除节点remove
{
'VariableDeclarator'(path){
console.log(path.toString()) // a = 1 + 2;b = 2
if (path.node.id.name === 'b'){
path.remove()};
}} // 还原为var a = 1 + 2;
5、判断节点类型(大部分情况下,在类型前边加is 如:isVariableDeclarator,两种使用方式如下)
{
'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
}}
6、节点设置值set(key, node)
{ // 将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;
7、插入节点
{
'Identifier' (path) {
path.insertBefore(t.valueToNode('22')); //节点前插入
path.insertAfter(t.valueToNode('22'));//节点后插入
}
8、作用域
scope相关:
path.scope 获取当前路径的作用域
scope.dump() 打印当前作用域
scope.rename(oldName, newName, block) 变量重命名,会修改所有的变量绑定
scope.getBinding(name) 获取当前所有绑定
scope.getBinding(name).referenced 绑定是否被引用
scope.getBinding(name).constantViolations 获取当前所有绑定修改
scope.getBinding(name).referencePaths 获取当前所有绑定路径
二、练习小题:
1、var b = 1+2
--> var b = 3
解1:
BinaryExpression(path) {
console.log(path) //具体可以打印出来看
var left = path.node.left.value
var right = path.node.right.value
var operator = path.node.operator
var res = eval(left+operator+right)
path.replaceInline(t.valueToNode(res))
},
解2:
BinaryExpression(path) {
const{confident, value} = path.evaluate();
path.replaceInline(t.valueToNode(value))
}
2、简单控制流
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;
}
简单还原后的结果为:
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");
插件为:
'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};
let arr_name = discriminant.object.name;
let arr = [];
let all_pre_siblings = path.getAllPrevSiblings();
if (all_pre_siblings.length !== 2){return};
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;
if (t.isContinueStatement(case_body[case_body.length -1]))
{case_body.pop()}
ret_body = ret_body.concat(case_body)
})
path.replaceInline(ret_body)
}
3、替换
!function(a,b) {c = a | b;}(111,222);
==>!function() {c = 111 | 222}();
好玩的地方是,通过函数内的作用域找到,在函数中的变量引用,注意,下边代码得作用域是函数的作用域,导致如果出现啊引用,将无法替换
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();
}
},
4、作用域内变量替换
var a = window;let b = a.btoa("Hello,AST");
=>let b = window.btoa("Hello,AST");
VariableDeclarator(path) {
const {id, init} = path.node;
if (!t.isIdentifier(init,{name: "window"})){return}
let name = id.name
let scope = path.scope;
const binding = scope.getBinding(name); // 获取windows在当前节点的作用域中的值
if (!binding || binding.constantViolations.length!==0){return};
scope.rename(name, 'window') //重点,上边有笔记
path.remove()
}
5、exit使用
var a = "Hello,AST"["charCodeAt"](5);
=> var a= 44
const visitor = {
"MemberExpression"(path)
{
let property = path.get('property');
if (property.isStringLiteral())
{
let value = property.node.value;
path.node.computed = false;
property.replaceWith(t.Identifier(value));
}
},
"CallExpression":{
exit:function(path)
{
const {confident,value} = path.evaluate();
confident && path.replaceInline(t.valueToNode(value));
}
},
}
6、if_to_switch
输入:
function test()
{
var index = 0,arr = [3, 0, 2, 1],pindex;
while (1)
{
pindex = arr[index++];
if (pindex<1){
console.log("this is index 0");
}else if (pindex<2){
return;
}
else if (pindex<3){
console.log("this is index 2");
} else {
console.log("Hello world!");
}
}
}
输出:
function test() {
var index = 0,
arr = [3, 0, 2, 1],
pindex;
while (1) {
switch (arr[index++]) {
case 0:
console.log("this is index 0");
break;
case 1:
return;
break;
case 2:
console.log("this is index 2");
break;
default:
console.log("Hello world!");
break;
}
}
}
test();
插件:
visitor1 = {
'WhileStatement': function (path) {
let case_item = {}
let discriminant
function handle(node) {
if (t.isIfStatement(node)) {
let left_name = node.test.left.name // 获得条件左边变量名称
if (!eval(left_name) && eval(left_name)!== 0){ // 如果arr数组取索引为undefind就结束循环
end_bool=true}
let right_value = node.test.right.value // 获得条件右边的值
if (eval(left_name) === right_value) { // 如果左右相等,就是获得的这个值,放到case_item中
case_item[right_value] = node.consequent.body[0] // 定义对象,把代码和数值对应
} // 如果左右不相等,进行递归,一直找到相等的
handle(node.alternate) // if 条件是循环相套的,所以需要进行递归
}
else {
case_item['default'] = node.body[0] // 如果不是循环条件,就是else中的值
} // 否则对else进行默认处理
}
let body2 = path.node.body.body[1] // 得到循环体
if (!t.isIfStatement(body2)) {
return
} // 判断类型
// 获取函数内初始数据,并执行,获得初始变量作用域
let parentpath = path.parentPath
let body1 = parentpath.node.body[0]
eval(generator(body1).code)
discriminant = path.node.body.body[0].expression.right
let end_bool = false
while (1) {
if(end_bool){break}
let body0 = generator(path.node.body.body[0]).code
eval(body0) // 执行初始值 pindex = arr[index++];获得条件参数变量作用域
handle(body2)
}
// 构造switch
let case_list = []
for (key in case_item){
if (key !== 'default'){
console.log(key)
console.log(case_item[key])
case_list.push(t.switchCase(t.NumericLiteral(parseInt(key, 10)-1), [case_item[key], t.BreakStatement()]))
}
else{
case_list.push(t.switchCase(undefined,[case_item['default'], t.BreakStatement()]))
}}
new_node = t.switchStatement(discriminant, case_list)
path.get("body.body.1").replaceInline(new_node)
path.get("body.body.0").remove()
}
}
三、绝密插件
1、变量的还原
# binding.referencePaths获取变量所有的引用
VariableDeclarator(path) {
const {id,init} = path.node;
if (!t.isLiteral(init)) {return};
let name = id.name;
const binding = path.scope.getBinding(name);
if (!binding || binding.constantViolations.length > 0) {return};
for (let refer of binding.referencePaths) {refer.replaceWith(init);};
path.remove()
}
#如果遍历的对象是FunctionDeclaration
# const binding = path.scope.parent.getBinding(name);
2、三元表达式,节点替换
输入:a = m?11:22
输出: m ? a = 11 : a = 22;
ConditionalExpression(path) {
let {test, consequent, alternate} = path.node; //分别获得m、11、22的路径
const a = path.parentPath.isAssignmentExpression(); // 判断父类路径是不是这个节点
if (a) {
let {
operator,
left
} = path.parentPath.node; // 如果是这个节点,获得当前节点的符号和赋值变量
if (operator == '=') { // 如果节点是赋值节点
consequent = t.AssignmentExpression('=', left, consequent), // 改写为Ass节点
alternate = t.AssignmentExpression('=', left, alternate),
path.parentPath.replaceWith(t.ConditionalExpression(test, consequent, alternate)) // 改写为Con节点,并替换原节点
}
}
},
3、变量声明,查找
"VariableDeclarator"(path)
{
let {id,init} = path.node;
if (init !== null) return;
const binding = path.scope.getBinding(id.name);
if (!binding || binding.constantViolations.length !== 1) return;
let vio_path = binding.constantViolations[0];
if (vio_path.isAssignmentExpression())
{
const {left,operator,right} = vio_path.node;
if (operator === '=' && t.isLiteral(right))
{
path.set('init',right);
vio_path.remove();
}
}
},
网友评论