美文网首页
AST笔记(技巧,插件)————持续更新

AST笔记(技巧,插件)————持续更新

作者: 周周周__ | 来源:发表于2020-06-06 20:58 被阅读0次

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();
            }
        }
    },

相关文章

网友评论

      本文标题:AST笔记(技巧,插件)————持续更新

      本文链接:https://www.haomeiwen.com/subject/usyknhtx.html