使用 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 = newFuncName
            console.log("change name: ", path.value.id.name)
        }
    })

// 修改函数调用名称
root.find(jc.CallExpression).forEach((path)=>{
    if(path.value.callee.name == oldFuncName){
        path.value.callee.name = newFuncName
        console.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 来修改代码也不复杂 ,而且效率远高于手工。可以按上述思路,修改变量名,添加、删除语句等操作,对自己的代码进行修改或美化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/721215.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务开发与实战Day11 - 微服务面试篇

一、分布式事务 1. CAP定理 1998年,加州大学的计算机科学及Eric Brewer提出,分布式系统有三个指标: Consistency(一致性)Availability(可用性)Partition tolerance(分区容错性&am…

网络故障经典案例

一、背景分析 企业的网络经常出现整网卡顿的情况,表现为网页没法打开、微信飞书消息转圈圈、视频加载缓慢 等等问题,但有时候又正常,莫名其妙! 问题一直找不到,检查了网络架构没有任何问题,也找了运营商…

充电学习—6、电量计FuelGauge

电量计功能: 检测电池 计量电量 电量计首要工作: 计算电池的剩余容量、充满时容量、电量百分比 电量百分比 剩余容量 / 充满时容量 * 100% SOC RM / FCC * 100% 典型的一个电池包框架: 包含电芯、电量计IC、保护IC、充放电MOSFET、保险丝…

mysql分析常用锁

这里写自定义目录标题 1.未提交事物,阻塞DDL,继而阻塞所有同表的后续操作,查看未提交事务的进程2.存着正在进行的线程数据。3.根据processlist表中的id杀掉未释放的线程4.查看正在使用的表5.mysql为什么state会有waiting for handler commit6.什么情况导…

如何有效处理独立站遭受的网络攻击

随着电子商务的蓬勃发展,独立站成为了众多商家展示产品、吸引客户的重要平台。然而,这同时也吸引了不法分子的目光,使得独立站成为网络攻击的重灾区。本文将深入探讨独立站可能遭受的各种网络攻击类型,并提供一系列实用且可运行的…

Android开发系列(四)Jetpack Compose之Button

在Jetpack Compose中,Button是一个常用的用户界面组件,用于执行某些操作或触发某些事件。Button控件是可触摸的,并且通常会显示一个文本或图标来表示其功能。 要在Jetpack Compose中创建一个Button,可以使用Button()函数&#xf…

8.12 矢量图层面要素单一符号使用二(仅渲染中心点)

文章目录 前言仅渲染中心点(Centroid fill)QGis设置面符号为仅渲染中心点(Centroid fill)二次开发代码实现仅渲染中心点(Centroid fill) 总结 前言 本章介绍矢量图层线要素单一符号中仅渲染中心点&#xf…

Java每日作业day6.18

ok了家人们今天我们继续学习方法的更多使用,闲话少叙,我们来看今天学了什么 1.重载 在同一个类中,可不可以存在同名的方法?重载:在同一个类中,定义了多个同名的方法,但每个方法具有不同的参数类型或参数个…

SuiNS更新命名标准,增强用户体验

SuiNS将其面向用户的命名标准从 xxx.sui 更新为 xxx,让用户能够以一种适用于Web2和Web3世界的方式来代表自己。通过此更新,用户可以在其选择的名称前使用 ,而不是在名称后添加 .sui。 Sui命名服务于去年推出,旨在使Sui上的地址更…

C++ 63 之 函数模版和普通函数的区别

#include <iostream> #include <string> using namespace std;template<typename T> T myAdd(T a, T b){return a b; }int myAdd2(int a, int b){return a b; }int main() {int a 1;int b 2;char c a; // a的ask码 97// cout << myAdd(a,c) <…

探索C嘎嘎的奇妙世界:第十四关---STL(string的模拟实现)

1. string类的模拟实现 1.1 经典的string类问题 上一关已经对string类进行了简单的介绍&#xff0c;大家只要能够正常使用即可。在面试中&#xff0c;面试官总喜欢让学生自己来模拟实现string类&#xff0c;最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数…

vcruntime140_1.dll文件【安装包】【压缩包】【文件】【下载】

安装程序时有时候出现 类似无法启动程序&#xff0c;缺少vcruntime140_1.dll的提示&#xff0c;我们找到该文件并放到对应目录就可以&#xff1b;获取方法有很多&#xff0c;下面介绍两种&#xff1a;&#xff08;方法二更简便&#xff0c;不过建议两种方法都试试&#xff09; …

首张地下地图!D-Wave 专用量子计算机助力沙特阿美完成地震成像

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨浪味仙 排版丨沛贤 深度好文&#xff1a;800字丨3分钟阅读 摘要&#xff1a;过去两年中&#xff0c;沙特阿美研究中心一直在使用总部在加拿大的D-Wave 公司的专用量子计算技术&#xff0c;…

基于DDD的编码实践

分层设计 领域驱动设计&#xff08;Domain-driven design, DDD&#xff09; 作为一种复杂软件系统的应对方案&#xff0c;在设计和编码提供了一种新的解决方式&#xff0c;即领域驱动&#xff0c;要求程序员在设计和编码时从领域专家的角度出发来实现架构/代码&#xff0c;做到…

Canvas->不依赖View的Canvas,创建Canvas绘制Bitmap的某个区域

XML文件 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:o…

【Java】已解决java.lang.ArrayIndexOutOfBoundsException异常

文章目录 一、问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.lang.ArrayIndexOutOfBoundsException异常 一、问题背景 java.lang.ArrayIndexOutOfBoundsException 是 Java 中一个非常常见的运行时异常&#xff0c;它表明程序试图访问数…

POSIX信号量以及读写者模型/环形队列

POSIX信号量 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步,他的本质是一个计数器,对共享资源进行等待或释放 POSIX信号量的重要概念 1.计数器:信号量维护一个计数器&#xff0c;它…

Leaflet地图实例

ReactTypeScript实例&#xff1a; import React, { useEffect, useRef } from "react"; import * as L from "leaflet"; import "leaflet/dist/leaflet.css";const App: React.FC () > {const mapRef useRef<HTMLDivElement>(null);…

TestProject Python SDK入门

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;-CSDN博客跳槽涨薪的朋友们有福了&#xff0c;今天给大家推荐一个软件测试面试的刷题小程序。​编辑https://…

达梦导入导出

针对导出数据库表结构通常有 3 种方法&#xff1a; 使用 DTS 导出 打开 DTS 迁移工具&#xff0c;选择【DM-->SQL】并链接到数据库中&#xff0c;如下图所示&#xff1a; 添加定义脚本&#xff0c;并选择【迁移范围】&#xff08;仅迁移对象定义&#xff09;&#xff0c;如…