AST学习笔记

基础部分

安装: npm install @babel/node

1. 常见节点类型

在这里插入图片描述

2. path和node

ast 实际上就是node,它是 构造函数的 Node 的实例化对象,它只具有对象属性(可以说,它就是个对象,除了有__clone方法之外),所以它可以利用对象去取值,直接修改等任何一系列的操作

//而path则不同
let {parse} = require("@babel/parser")
let ast = parse(`function hi(){console['\x6c\x6f\x67']('\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21');}hi();`)
traverse = require("@babel/traverse").default
traverse(ast, {
    StringLiteral: function(path){console.log(path)}
})

path 实际上是traverse的过程中,遍历的时候babel给我们传进去的一个对象,这个对象是构造函数 NodePath的实例化对象,这个对象相对于 node,具有非常非常多的功能和方法,这些方法将极大的方便我们进行后续的任何操作,其中,这个对象的node属性就可以取到遍历的内容的 node。

3. evaluate()的作用

  1. 例1
    针对作用域和引用,直接依据引用来计算出执行结果。
var a = 123 + '2sdasda31231';
var b = a;var a = "1232sdasda31231";
var b = "1232sdasda31231";
visitor = {
    "BinaryExpression|Identifier"(path){
        const {confident,value} = path.evaluate();
        confident && path.replaceInline(types.valueToNode(value))
    }
}

如图所示,对BinaryExpression和Identifier处进行了计算,得到结果值。经过试验,若加上Identifier进行traverse操作,则会报Maximum call stack size exceeded的错误。遇到一般的ob混淆时可以遍历BinaryExpression节点,还原如navigator[‘\x75\x73’+‘\x65\x72’+‘\x41\x67’+‘\x65\x6e’+‘\x74’]这样的混淆。

  1. 例2
let a = `let a = -0x1 * 0x21ea + -0x53b + -0x101 * -0x27`

let ast = parse(a)
traverse(ast, {
    BinaryExpression: function (path){
        if(path.parentPath.type === "VariableDeclarator"){   //目的是取到-0x1 * 0x21ea + -0x53b + -0x101 * -0x27这个代码对应的节点
            let value = path.evaluate().value
            path.replaceInline({type:"NumericLiteral", value:2})
        }
    }
})
console.log(generate(ast).code)
  1. 例3
let a = `let a =  -0x1 * 0x21ea + -0x53b + -0x101 * -0x27`
//使用exit进行最小单元计算,避免一些奇怪的问题
let ast = parse(a)
traverse(ast, {
    "BinaryExpression": {
        "exit": function (path){
            console.log(path.toString())
            let {confident,value} = path.evaluate();
            path.replaceInline({type:"NumericLiteral", value: value})
            console.log(confident, value)
        }
    }
})

console.log(generate(ast).code)

4. scope

scope基本理解

作用域(scope,或译作有效范围)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。

对于复杂程度不高的代码,可以可简单的理解为:
一个函数就是一个作用域。
一个变量就是一个绑定,依附在作用域上。

一个小demo:

js_code2 = `
function f(a){
	a = 105 + a;
	a++;
	return a
}
const a = f(100)
console.log(a)
`
ast_code2 = parse(js_code2);
traverse(ast_code2,{
	UpdateExpression(path){
		console.log('当前节点源码:\n',path + '')
		console.log('当前作用域节点源码:\n',path.scope.path + '')
		console.log('-------------------')
	}
})

输出结果:
在这里插入图片描述

scope.block

可以用 scope.block 获取标识符作用域,返回node提取作用域的node。 但是不适用于函数,函数需要path.scope.parent.block父级作用域(可能会出现异常)。
由于返回的是node对象,所以需要用generator生成code,小demo:

ast_code2 = parse(js_code2);
traverse(ast_code2,{
	UpdateExpression(path){
		console.log('当前节点源码:\n',path + '')
		console.log('block:\n',generator(path.scope.block).code + '')
		console.log('-------------------')
	}
})

输出结果:
在这里插入图片描述

scope.getBinding

Binding 对象用于存储绑定的信息,这个对象会作为Scope对象的一个属性存在,同一个作用域可以包含多个 Binding。
scope.getBinding获取标识符的绑定,用法如path.scope.getBinding(标识符),局部变量与全局变量重名时优先使用局部变量,返回一个binding构造函数的实例。path.scope.bindings 返回一个 对象,这个对象的 key,就是变量名,value 是一个 Binding对象,这个对象里面就有比较多的说法了,它有针对这个变量的非常非常多的信息:

  • referenced —> 是否被引用
  • referencePaths 引用的path
  • references 引用次数
  • path 这个变量的path
  • identifier 这个变量的node
  • hasValue 是否有值
  • kind 变量类型,也可以理解为标识符类型 (参数 param/let/var/const)
  • constantViolations 标识符(变量名)如果被修改,会存到这个里面
  • hasValue 该绑定是否有值
  • 可以利用作用域的 path.scope.generateUidIdentifier(“_0xabcdef”) 来创建一个不重复的 Identifier
traverse(ast_code2,{
	FunctionDeclaration(path){
		console.log('当前节点源码:\n',path + '')
		console.log(path.scope.getBinding('a'))
		console.log('-------------------')
	}
})

输出结果,可见a标识符在该作用域内(function)被引用了三次:
在这里插入图片描述

在这里插入图片描述

traverse(ast_code2,{
	FunctionDeclaration(path){
		console.log('当前节点源码:\n',path + '')
		console.log('当前标识符对应作用域中被引用的次数为:\n',path.scope.getBinding('a').references)
		console.log('当前标识符对应作用域的源码为:\n',path.scope.getBinding('a').scope.path + '')
		console.log('--------------------------------------')
	}
})

输出结果:
在这里插入图片描述

binding.scope.traverse()

实现在作用域内的遍历,用法与一般的traverse()一样。

scope.parent

就是返回上一级的作用域,也是一个 Scope对象

5. types

使用types.valueToNode方法可以快速将代码转为node节点:types.binaryExpression("+",types.valueToNode(1),types.valueToNode(1))

进阶部分

使用ast生成代码

  1. af = 100
    在这里插入图片描述
const generator = require("@babel/generator").default;
const types = require("@babel/types");
let g = types.assignmentExpression('=', types.identifier('af'), types.valueToNode(100))
console.log(g)
console.log(generator(g).code)
  1. var a = 1000 + b;
    在这里插入图片描述
let g2 = types.VariableDeclaration('var',
    [
    types.VariableDeclarator(types.identifier("a"), types.BinaryExpression("+", types.valueToNode(1000), types.identifier("b"))
    )
])
console.log(g2)
console.log(generator(g2).code)
  1. 函数调用: console.log(g);
    在这里插入图片描述
    computed代表是否使用[ ]进行取值,为true时候是console[‘log’] ,false是console.log
m = types.ExpressionStatement(
    types.CallExpression(
        types.MemberExpression(types.identifier("console"),types.identifier("log"),false),
        [types.identifier("g")]
    )
)
console.log(generator(m).code)
  1. 生成if-else语句
if (a === "666"){
    console.log(6)
}else{
    console.log(7)
}
types.ifStatement(
    types.binaryExpression("===",types.identifier("a"),types.valueToNode("666")),
    types.blockStatement([types.expressionStatement(types.callExpression(types.memberExpression(types.identifier("console"),types.identifier("log")),[types.valueToNode(6)]))]),
    types.blockStatement([types.expressionStatement(types.callExpression(types.memberExpression(types.identifier("console"),types.identifier("log")),[types.valueToNode(7)]))])
)
  1. 生成try-catch语句
try{
    console.log("success")
}catch (e){
    console.log(e)
}
generator(types.tryStatement(
    types.blockStatement([types.expressionStatement(types.callExpression(types.memberExpression(types.identifier("console"),types.identifier("log")),[types.valueToNode("success")]))]),
    types.catchClause(types.identifier("e"),types.blockStatement([types.expressionStatement(types.callExpression(types.memberExpression(types.identifier("console"),types.identifier("log")),[types.identifier("e")]))]))
)).code
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值