使用 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

相关文章

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分