使用 AST语法树分析与修改Javascript 代码

2024-06-19 02:36

本文主要是介绍使用 AST语法树分析与修改Javascript 代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、AST语法树简介

当编写代码分析工具、代码美化工具、网站逆向分析等场景时,通常需要使用AST语法树技术。
比如项目开发过程中常遇到的场景:某个公共函数名需要更改,但被很多文件多处代码调用,手工修改非常容易漏改、改错等,而且还花时间。又如在网站逆向分析时,由于使用了混淆技术,代码难以阅读。 这时使用AST技术可以轻松完成这些任务。
对于重要项目,使用AST技术来开发自动化代码质量检查工具,也是提高项目质量的重要手段。

1.1 什么是AST

AST(Abstract Syntax Tree)抽象语法树,是源代码语法结构的一种抽象表示。常用于编译器、代码分析工具等。

如何将代码转为AST呢?

构建 AST 树的步骤
在这里插入图片描述

生成AST树之前,通常需要先将源代码转为 Parser Tree。
如1个表达式,2*7 + 3 , 先转为 Parser Tree,如下,其根节点,由 1个操作符 + 与2个 term 组成。左边的term 是 factor * factor 构成。 右边的term 为1个factor.。这个Parser Tree表示了完整的语法规则,但如果语句复杂,会造成树的层次复杂。

AST tree是Parse Tree的简化,其特点:
用操作符做为根节点。整个树由根节点,内部节点,叶节点组成
叶节点是由操作数组成,内部节点的子节点都是操作数(或称叶节点)。
内部节点不展示语法规则,因此是抽象的。

在这里插入图片描述

1.2 流行的AST工具库

当前有许多工具支持源代码转AST 树,以及逆向转回。

在线工具
https://astexplorer.net/

Babel/parser插件方案
babel/parser是JS社区最常用的解析器。但Babel方案节点多、API也很多,因此学习成本高, 另外编程代码工作量较大,可读性差。

jscodeshift
相比于Babel而言,jscodeshift的优势是匹配节点更简便一些,链式操作用起来更加顺手。还提供了命令行工具。

本文主要介绍,用jscodeshift 来进行AST解析。

2、jscodeshift库介绍

jscodeshift 是一个源代码转换工具,它基于 AST(抽象语法树)进行操作,使得开发者能够自定义转换规则来处理 JavaScript 代码。

当你使用 jscodeshift 解析 JavaScript 源文件后,会得到一个表示源文件结构的 AST。理解jscodeshift的AST树结构是最基础的知识点。

AST 树的每个节点都代表了源代码中的一部分。节点是object类型,由许多“属性–值”对组成。 比如 type, value, id, 子节点等。

在 AST 中,主要的概念和名词包括:

节点(Node):AST 的基本单元,节点通常包含类型、值、子节点等属性。
{
type: ‘Literal’,
value: ‘foo’,
raw: ‘“foo”’
}

类型(Type):节点的类型,比如 VariableDeclarator(变量声明符)、CallExpression(函数调用表达式)等。

值(Value):对于某些类型的节点,比如字面量节点(字符串、数字等),会有一个值属性,表示该节点的具体值。

子节点(Children or Subtree):节点通常会有子节点,这些子节点代表了该节点在语法树下的更具体的结构。例如,一个 CallExpression 节点可能有一个子节点是函数名,另一个子节点是参数列表。

jscodeshift 主要数据结构
1)NodePath对象: 是对Node的封装,
2) Collection对象 是1个Array,元素为NodePath,

3、使用jscodeshift修改源代码步骤

3.1 安装 jscodeshift

全局安装

npm i -g jscodeshift 

本地安装

npm install --save-dev  jscodeshift 

为测试目的,安装 lodash库

npm install --save-dev lodash 

3.2 准备1个源文件

如我们准备1个源文件 example.js , 包含1个add()函数, 及调用。 后面我们通过AST技术来修改函数名以及调用名。

const array = require('lodash/array')
var x = 1; 
var a1 = array.initial([1,2,3,4,5])
console.log(a1)
// define a function
function add(a,b){return a + b; 
}
x = add( 10,20)

3.3 使用AST 查找与修改函数名与调用名

对于初学者来说,可以先用在线工具可视化方式查看AST节点,确定要查找及替换节点名称。
访问地址:https://astexplorer.net/

3.3.1 先用在线工具查看 AST 结果

打开 https://astexplorer.net/ 网页,将example.js 代码拷贝过来后,会自动生成 AST数。
在这里插入图片描述
可以看到function 的节点 type 为FunctionDeclaration,

x = add( 10,20) 对应节点为 ExpressionStatement, 我们要查找的调用表达式为为右侧子节点,打开会看到, 子节点的type 为 CallExpression

3.3.2 编写代码

在example.js 同1目录下,新建1个文件ast.js,添加如下内容:

const path = require('path')
const fs = require('fs')
const jc = require('jscodeshift')  //引入jscodeshift // 读取源代码文件
const fpath =  path.join(__dirname, 'example.js')
const root = jc(fs.readFileSync(fpath, 'utf8'))let oldFuncName = "add"   //设置原函数名称//查找及修改函数名称
let newFuncName = "newAdd"
root.find(jc.FunctionDeclaration).forEach( (path) => {//如果函数名为oldFuncName, 则修改为新名称if (path.value.id.name == oldFuncName) {path.value.id.name = newFuncNameconsole.log("change name: ", path.value.id.name)}})// 修改函数调用名称
root.find(jc.CallExpression).forEach((path)=>{if(path.value.callee.name == oldFuncName){path.value.callee.name = newFuncNameconsole.log("change name of CallExpress: ", path.value.callee.name)}
})// 输出修改后的代码
console.log(root.toSource())

代码解释:

  • const root = jc(fs.readFileSync(fpath, 'utf8')) 将源码转为AST 树结构对象
  • root.find(jc.FunctionDeclaration) 在AST树中查找 type = 'FunctionDeclaration’ 的节点,可能有多个。 用forEach()方法遍历。
  • find()方法的查找结果为NodePath 集合, NodePath.value中保存 Node对象。 不同类型节点有不同的属性。
    • FunctionDeclaration 节点名称为 path.value.id.name,
    • CallExpression 调用函数名为 path.value.callee.name
  • oot.toSource() 将 AST 树还原为源代码。

3.3.3 测试代码

在代码所在目录下

node ast.js 

输出结果如下,可以看到,函数名与调用名均已改为 newAdd

change name:  newAdd
change name:  newAdd
const array = require('lodash/array')
var x = 1;
var a1 = array.initial([1,2,3,4,5])
console.log(a1)
// define a function
function newAdd(a,b){return a + b;
}
x = newAdd( 10,20)

怎么样,使用 AST 来修改代码也不复杂 ,而且效率远高于手工。可以按上述思路,修改变量名,添加、删除语句等操作,对自己的代码进行修改或美化。

这篇关于使用 AST语法树分析与修改Javascript 代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1073765

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关