ArkAnalyzer源码初步分析I——分析ts项目流程

1.前言:


鸿蒙程序分析框架ArkAnalyzer(方舟分析器)
源码地址
入门文档

2.阅读入门文档后:

本人具有一定的Java开发经验。虽然我对 TypeScript(TS)和 ArkTS 还不熟,但很多概念对我这个 Java 开发者来说并不陌生,反而有种亲切感。

在我看来,ArkAnalyzer 本质上就是一个针对 ArkTS 语言的静态代码分析框架。这让我想起了我在 Java 世界里常用的工具:

  • 1.Soot 或 ASM:这些是用来分析和修改 Java 字节码的框架。ArkAnalyzer 似乎在做类似的事情,不过它操作的是更高层次的 ArkTS 源码。

  • Java Compiler API (JSR 199):这个 API 允许你在 Java 程序里调用 Java 编译器,并访问 AST(抽象语法树)。ArkAnalyzer 的第一步就是生成 AST,这完全是同一个思路。

  •  Checkstyle:这些是用于代码质量和安全扫描的工具。它们底层也依赖于静态分析。ArkAnalyzer 提供了构建这类高级工具所需的基础能力。

第二步:运行第一个基本示例 (第3.1章)

环境搭好了,我就要开始写代码了。照着 ex01 的例子来:

  1. 准备被分析的项目:我创建一个 demo_project 文件夹,并把 books.ts, bookService.ts, index.ts 这几个文件按结构放好。这个项目很简单,就是一个图书管理的模型和服务,非常典型的 Java 示例风格。

  2. 编写分析脚本 basicUsage.ts

    • import { SceneConfig, Scene } from 'arkanalyzer';:导入依赖,和 Java 的 import 一样。

    • const projectRoot = 'tests';:指定要分析的项目路径。

    • let config = new SceneConfig();

    • config.buildFromProjectDir(projectRoot);:这两步是配置分析器。

    • let scene = new Scene();

    • scene.buildSceneFromProjectDir(config);:这两步是执行分析,构建出 Scene 这个大对象。

    • 然后,我开始我的探索

      • scene.getFiles():获取所有文件,打印文件名。

      • scene.getClasses():获取所有类,打印类名。这里我注意到一个细节:输出了好几个 _DEFAULT_ARK_CLASS。文档解释说每个文件和命名空间都有一个默认类。这和 Java 不太一样,Java 的顶层只能是类、接口或枚举。TS 似乎更灵活,可以在文件顶层直接写函数或变量,ArkAnalyzer 把这些“游离”的代码放进一个默认类里,这个设计很合理。

      • scene.getMethods():获取所有方法。同样,我也看到了 _DEFAULT_ARK_METHOD。

      • 我尝试链式调用,比如 scene.getFiles()[0].getClasses()[0].getMethods(),来感受一下这个 API 的设计。

第三步:深入探索 CFG 和调用图 (第3.2章)

基本 API 熟悉后,我对更高级的功能感兴趣。

  • 获取 CFG (ex01.6)

    • 我找到 getBooksByAuthor 这个方法,然后调用 getBody().getCfg()。

    • 我最感兴趣的是 DotMethodPrinter。能把 CFG 导出成 .dot 文件太棒了!我知道 Graphviz 这个工具,它可以把 .dot 文件渲染成图片。这样我就能直观地看到那个 for 循环和 if 判断构成的流程图了。这对于理解复杂方法的逻辑非常有帮助。

  • 生成调用图 (ex02)

    • 这部分提到了 CHA, RTA, PTA 三种算法,这对我来说是新知识,但概念不难理解。

      • CHA (Class Hierarchy Analysis):基于类的继承关系来分析。比如 animal.sound(),如果 animal 的静态类型是 Animal,那么 CHA 会认为 Dog.sound(), Cat.sound(), Pig.sound() 都可能被调用。这是一种最快但最不精确的算法。

      • RTA (Rapid Type Analysis):在 CHA 的基础上,会去看代码里到底 new 了哪些类的实例。如果代码里只 new Cat() 和 new Dog(),那 RTA 就不会把 Pig.sound() 加到调用图里。比 CHA 精确。

      • PTA (Points-to Analysis):指针分析,最精确也最慢。它会去追踪每个变量可能指向哪些具体的对象实例。在 makeSound(new Dog()) 这个调用里,PTA 能够精确地知道传入 makeSound 函数的 animal 参数指向的就是一个 Dog 对象,所以 animal.sound() 只会调用 Dog.sound()。

    • 我把三种算法都跑一遍,对比它们的差异。

PTA算法简单分析:https://blog.csdn.net/2302_80118884/article/details/151649501?spm=1001.2014.3001.5501

一个例子:

// --- 基础类 ---
abstract class Animal { abstract sound(): void; }
class Dog extends Animal { sound() { /* 汪汪 */ } }
class Cat extends Animal { sound() { /* 喵喵 */ } }
class Pig extends Animal { sound() { /* 哼哼 */ } }class PetStore {bestSeller: Animal | null = null;inventory: Animal[] = [];setBestSeller(pet: Animal) {this.bestSeller = pet;}stockInventory(pets: Animal[]) {this.inventory = pets;}promoteBestSeller() {if (this.bestSeller) {this.bestSeller.sound();}}
}function main1() {const store = new PetStore();const myDog = new Dog();const myCat = new Cat();// 1. 将 Dog 实例设置到 store 的字段中store.setBestSeller(myDog);// 2. 调用一个方法,该方法会使用这个字段store.promoteBestSeller(); 
}

我可能会遇到的问题和想进一步了解的

  1. ArkUI 和 ViewTree:这是我完全陌生的领域。@State, @Prop 这种装饰器看起来像是某种数据绑定机制,类似于前端框架(React, Vue)或 Android Jetpack Compose。ViewTree 显然是用来分析 UI 结构的。作为一个后端 Java 开发者,这部分对我来说很新奇。我会好奇这个 ViewTree 是如何从代码中构建出来的,以及它能用来做什么样的 UI 分析(比如,查找所有未绑定的 UI 控件?分析页面跳转逻辑?)。

  2. 数据流分析的深度:ex05.1 空指针检测 很有用,这在 Java 里就是 NullPointerException 分析。我想知道 ArkAnalyzer 的数据流分析能力有多强?它能处理多复杂的场景?比如跨文件、跨模块的污点分析(Taint Analysis),即追踪一个用户输入(可能是恶意的)在系统中的流向,最终是否被未经验证地执行。

  3. 可扩展性:我能自定义规则吗?比如,我想写一个检查器,规定我们项目中所有的 Service 类都必须以 Service 结尾。我可以通过 Scene API 遍历所有类,然后检查类名来实现。这个看起来很简单。但如果我想写一个更复杂的规则,比如“所有从数据库读取的数据,在返回给前端前必须经过一个特定的脱敏函数处理”,这就需要用到数据流分析了。ArkAnalyzer 是否提供了方便的 API 来让我构建这种自定义的、基于数据流的检查器?

从文档脚本分析ts源码:

import { SceneConfig, Scene} from 'arkanalyzer';
const projectRoot = 'tests';
let config: SceneConfig = new SceneConfig();
//1
config.buildFromProjectDir(projectRoot);
let scene: Scene = new Scene();
//2
scene.buildSceneFromProjectDir(config);

1.调用 config.buildFromProjectDir('tests') 时,SceneConfig 对象内部主要完成了 三件核心任务,这是一个为后续分析进行“信息采集”和“环境设置”的关键步骤。

3.buildFromProjectDir 函数源码

// src/SceneConfig.tspublic buildFromProjectDir(targetProjectDirectory: string): void {// 任务1:记录项目的根目录路径this.targetProjectDirectory = targetProjectDirectory;// 任务2:根据目录路径推断项目名称this.targetProjectName = path.basename(targetProjectDirectory);// 任务3:扫描并收集项目下所有的源文件this.projectFiles = getAllFiles(targetProjectDirectory, this.options.supportFileExts!, this.options.ignoreFileNames);
}

三大任务详解

任务 1: 记录项目根目录 (this.targetProjectDirectory = targetProjectDirectory)
  • 发生了什么
    这行代码非常直接,它把你传入的字符串 'tests' 保存到了 SceneConfig 对象的 targetProjectDirectory 这个内部属性里。

  • 为什么重要
    这是整个分析的 “锚点”。后续所有操作,比如解析 tsconfig.json、查找依赖、构建 Scene 等,都需要知道项目的根目录在哪里。targetProjectDirectory 就是这个基准路径。

任务 2: 推断项目名称 (this.targetProjectName = path.basename(targetProjectDirectory))

path.basename('tests') 会返回路径的最后一部分,也就是 'tests' 这个字符串本身。所以,targetProjectName 属性也被赋值为 'tests'。如果你的路径是 'C:/Users/MyUser/MyApp',那么项目名就会被推断为 'MyApp'    项目名称在 Scene 中用于标识和区分不同的代码来源,尤其是在进行跨项目分析或处理依赖时,这个名称会非常有用。

任务 3: 扫描并收集所有源文件 (this.projectFiles = getAllFiles(...)
  • 它调用了一个名为 getAllFiles 的辅助函数(源码在 src/utils/getAllFiles.ts)。这个函数会:

    1. 从你指定的根目录 'tests' 开始。

    2. 递归地 遍历 'tests' 文件夹以及其下的所有子文件夹。

    3. 在遍历过程中,它会检查每一个文件的后缀名。

    4. 如果一个文件的后缀名存在于 this.options.supportFileExts 数组中(默认是 ['.ets', '.ts']),那么这个文件的 完整绝对路径 就会被收集起来。

    5. 如果配置了 ignoreFileNames,它还会跳过这些被忽略的文件。

    6. 最终,getAllFiles 返回一个包含所有符合条件的源文件绝对路径的字符串数组。

    7. 这个数组被赋值给 SceneConfig 对象的 projectFiles 属性。                        projectFiles 列表就是 Scene 对象接下来需要处理的 “工作清单”。在 scene.buildSceneFromProjectDir(config) 这一步中,Scene 会从 config 对象中获取这个文件列表,然后对列表中的每一个文件路径,执行我们之前讨论过的“读取 -> 解析AST -> 构建ArkFile”的完整流程。没有这个文件列表,Scene 就不知道要分析哪些文件。


所以,config.buildFromProjectDir('tests'); 这句看似简单的代码,实际上完成了一个至关重要的预处理阶段。它为 SceneConfig 对象填充了三个核心属性:

  1. targetProjectDirectory: 'tests' (分析的根在哪)

  2. targetProjectName: 'tests' (分析的是什么项目)

  3. projectFiles: ['D:\codeArk\tests\main.ts', 'D:\codeArk\tests\models\user.ts', 'D:\codeArk\tests\services\authService.ts'] (具体要分析哪些文件,路径是绝对的)

当这个 config 对象被传递给 new Scene() 并用于构建 Scene 时,Scene 就拥有了开始正式分析所需的所有初始信息。

4.buildSceneFromProjectDir源码

public buildSceneFromProjectDir(sceneConfig: SceneConfig): void {//环境初始化this.buildBasicInfo(sceneConfig);//生成ArkFilethis.genArkFiles();}

关键调用路径:buildSceneFromProjectDir->genArkFile->buildMethodBody->buildBody->build

关键调用路径逐步分析:https://blog.csdn.net/2302_80118884/article/details/151649408?spm=1001.2014.3001.5502

4.IR

它的标准格式是:
result = operand1 operator operand2

这行代码里正好有三个“地址”(或者说,三个变量/值的位置):

  1. result: 存放结果的地址。

  2. operand1: 第一个操作数的地址。

  3. operand2: 第二个操作数的地址。

这就是它叫“三地址码”的原因。

代码解读 (Stmt.ts, Expr.ts)

Stmt (Statement, 语句):可以理解为一条完整的 “指令”

Expr (Expression, 表达式):可以理解为构成指令的 “操作数”或“计算过程”

  • Stmt.ts (语句): 这里定义了所有可能的三地址码 指令。比如:

    • ArkAssignStmt: 赋值语句 (x = y)

    • ArkInvokeStmt: 调用语句 (foo(a, b))

    • ArkIfStmt: 条件跳转 (if (x > 0) goto L1)

    • ArkReturnStmt: 返回 (return x)

  • Expr.ts (表达式): 这里定义了构成语句的各种 操作数和计算。比如:

    ArkInstanceInvokeExpr: 封装了调用一个实例方法所需的所有信息。ArkNewExpr: new MyClass()           ArkConditionExpr: a < b                                                                                  

例子:let result = myCalculator.add(5, b);

                     [ArkAssignStmt]/               \/                 \(leftOp) /                   \ (rightOp)/                     \[Local (name='result')]         [ArkInstanceInvokeExpr]/          |          \/           |           \(base) /     (methodSignature)     \ (args)/               |              \[Local (name='myCalculator')]   [MethodSignature]     [Array](points to 'add' method)   ||-------------------|                 |(element 0) |                 | (element 1)|                 |[Constant (value=5)]    [Local (name='b')]

CFG → 调用图(CG)

这是“建立全局联系”阶段。 我们检查每个方法的 CFG,找出里面的调用指令,从而绘制出整个项目中方法与方法之间的调用关系网。

代码解读 (CallGraphBuilder.ts)

buildDirectCallGraph 方法:

public buildDirectCallGraph(methods: ArkMethod[]): void {this.buildCGNodes(methods);for (const method of methods) {let cfg = method.getCfg();if (cfg === undefined) {// abstract method cfg is undefinedcontinue;}let stmts = cfg.getStmts();//遍历一个方法CFG中每一条IR语句for (const stmt of stmts) {//检查这条语句是不是一个调用语句,// 是的话就提取出被调用方法的签名(方法名+参数类型)let invokeExpr = stmt.getInvokeExpr();if (invokeExpr === undefined) {continue;}//区分静态调用和动态调用。 // 如果是静态调用(比如 Math.random()),目标函数 callee 是唯一确定的。// 如果是动态调用(比如 new Array()),目标函数 callee 需要通过类型推断来确定。let callee: Method | undefined = this.getDCCallee(invokeExpr);// abstract method will also be added into direct cgif (callee && invokeExpr instanceof ArkStaticInvokeExpr) {//如果是静态调用,直接在调用图(cg)中添加一条从当前方法到目标方法的边。this.cg.addDirectOrSpecialCallEdge(method.getSignature(), callee, stmt);} else {//如果是动态调用(比如 obj.run(),obj 的具体类型不确定),就暂时记录下这个调用点的信息,// 等待后续的 CHA/RTA 分析来解析它可能的目标。this.cg.addDynamicCallInfo(stmt, method.getSignature(), callee);}}}}

buildClassHierarchyCallGraph / buildRapidTypeCallGraph:

这两个方法就是用来处理上面留下的动态调用问题的。它们利用类继承关系 (CHA) 或更精确的类型推断结果 (RTA) 来推测动态调用可能链接到哪些具体的方法实现,从而把调用图补充完整。

第六步:CG → 数据流分析(IFDS 框架)

这是“深度分析与求解”阶段。 有了调用图,我们就可以追踪数据在方法之间的流动了。这里提供的是一个通用的 数据流分析框架

代码解读 (DataflowProblem.ts, DataflowSolver.ts):
  1. DataflowProblem.ts:

    • 这是一个 abstract class (抽象类),定义了一个数据流问题的 “问卷”

    • Java类比: 这就像一个 interface。如果你想实现一个特定的分析(比如“空指针分析”),你就需要继承这个类,并回答问卷上的所有问题:

      • getNormalFlowFunction: 普通语句(如赋值)如何改变数据流信息?

      • getCallFlowFunction: 当调用一个函数时,数据流信息如何从调用者传递给被调用者?

      • createZeroValue: 分析开始时,初始的数据流信息是什么?

    • 这是一个非常优雅的设计, ArkAnalyzer的作者写好了通用的求解引擎(DataflowSolver),而用户只需要填写这份“问卷”就能定义自己的分析任务。

  2. DataflowSolver.ts:

    • solve(): 这是求解器的入口。

    • processCallNode(...): 这是处理跨函数数据流的核心逻辑。 当分析流程遇到一个函数调用时,它会:

      1. 用CHA等手段解析出所有可能被调用的目标方法。

      2. 对每个目标方法,执行用户在 DataflowProblem 里定义的 getCallFlowFunction,计算出传入被调用方法的数据流信息。

      3. 将新的信息推入被调用方法的入口,继续分析。

      4. 同时,它还处理了从被调用方法返回时数据流如何影响调用者后续代码的逻辑(exit-to-return 和 call-to-return)。

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

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    c#基础二(类和对象,构造器调用顺序、访问级别、重写和多态、抽象类和接口)

    一、类1.0对象初始化器class Student {public String name;public int age { get; set; } } internal class Program {static void Main(string[] args){ //写法一Student stunew Student();stu.name"Tom";stu.age20;//写法二Student stu2 new Student { name &qu…

    Qt之快捷键、事件处理、自定义按键——完成记事本项目

    快捷键我们电脑中的记事本中还支持快捷键&#xff0c;如“CTRLO”打开文件、“CTRLS”保存文件在Qt中使用QShortcut这个类创建快捷键在.cpp文件的构造函数中创建QShortcut对象&#xff0c;绑定打开文件和保存文件的槽函数放大缩小字体还是在.cpp的构造函数中编写代码Widget::Wi…

    Open cascade中如何使用BRepAlgoAPI_Splitter分割一个Face

    理论介绍 在OpenCASCADE几何建模内核中&#xff0c;BRepAlgoAPI_Splitter是一个强大的工具&#xff0c;用于将一个形状&#xff08;Shape&#xff09;用另一个形状&#xff08;Tool&#xff09;进行分割。这种操作在CAD建模中非常常见&#xff0c;比如用平面切割实体、用曲线分…

    【医疗 AI】Baichuan-M2 医疗大模型:技术解读与使用方法

    【医疗 AI】Baichuan-M2 医疗大模型&#xff1a;技术解读与使用方法1. Baichuan-M2 医疗大模型简介1.1 基本信息1.2 下载地址1.3 技术特点2. Baichuan-M2 模型技术报告2.1 摘要2.2 医学性能评估2.2.1 HealthBench基准2.2.2 中国医疗场景对比评估2.3 系统架构2.3.1 验证器系统2.…

    unity pcd 二进制版 简单显示文件对象(单色)

    unity Point Cloud Viewer and Tool 那个插件不支持pcd二进制&#xff0c;而且网上到处都是AI 我恨这种AI滥用&#xff0c;提供不了一点价值 好了&#xff0c;言归正传 可以在Point Cloud Viewer and Tool这个插件报错地方转用这个代码&#xff0c;具体咋结合请自行研究。 …

    强大的开源文档问答工具-Kotaemon

    Kotaemon 是一个基于 RAG&#xff08;Retrieval-Augmented Generation&#xff09;架构的开源文档问答工具&#xff0c;为用户提供与文档对话的智能交互体验。该项目同时服务于终端用户和开发者&#xff0c;具有高度的可扩展性和定制化能力。技术栈分析核心技术栈后端框架Pytho…

    区块链:搭建简单Fabric网络并调用智能合约

    使用docker服务搭建Hyperledger/fabric网络的详细教程&#xff0c;实现构建多节点的简单联盟链&#xff0c;并编写、调用智能合约实现投票业务。 目录 背景知识 Hyperledger Fabric 基本组件 交易(Transaction) 智能合约 实验目的 实验环境 基础依赖 安装Golang 安装do…

    Web前端面试题(2)

    Web前端面试题(附答案及解析)&#xff08;2025.9月最新版&#xff09;-CSDN博客 1.link 与 import 的区别和用法 主要区别 特性<link>import语法类型HTML标签CSS规则加载方式并行加载&#xff08;与其他资源同时加载&#xff09;串行加载&#xff08;必须等待主CSS文件…

    Paxos协议

    目录 Paxos 是什么&#xff08;What&#xff09; Paxos 的目的&#xff08;Why&#xff09; 角色与职责&#xff08;Who&#xff09; 基本流程&#xff08;How&#xff09; 常见问题与对策 什么是多数派&#xff08;Quorum&#xff09; Paxos vs Raft 异同点 Paxos 是什…

    第十二篇:Qcom Camx打印实时帧率 FPS

    一、第一种方式(有些低平台可能没有) adb shell setprop persist.vendor.camera.enableFPSLog TRUE adb shell setprop persist.vendor.camera.systemLogEnable TRUE adb shell setprop vendor.debug.camera.overrideLogLevels 0xff chi-cdk/core/chiframework/chxextensi…

    TRAE通用6A规则+敏捷开发5S规则

    网上研究别人的一些规则,也搞一份给大家 6A工作流项目规则 身份定义 你是一位资深的软件架构师和工程师,具备丰富的项目经验和系统思维能力。你的核心优势在于: 上下文工程专家:构建完整的任务上下文,而非简单的提示响应 规范驱动思维:将模糊需求转化为精确、可执行的规…

    【Nginx开荒攻略】Nginx主配置文件结构与核心模块详解:从0到1掌握nginx.conf:

    目录 引言 1 nginx.conf的整体结构 2 main全局块详解 2.1 核心指令解析 2.1.1 user&#xff1a;运行用户 2.1.2 worker_processes&#xff1a;工作进程数 2.1.3 pid&#xff1a;PID文件路径 2.1.4 worker_rlimit_nofile&#xff1a;文件描述符限制 2.2 main块配置示例…

    【前端教程】从基础到优化:一个登录页面的完善过程

    最近做了一个简单的登录页面,主要练习了文本框的onfocus与onblur事件的使用。虽然功能实现了,但仔细想想还有不少可以改进的地方。今天就来分享一下这个登录页面的开发过程和优化思路。 初始实现与解析 先来看一下最初的实现代码: <!DOCTYPE html> <html> &l…

    独家 | 抖音生活服务调整:涂晴接管市场和达人运营,旭凯担任北部大区负责人

    文/刀客doc(头条精选作者)刀客doc独家获悉&#xff0c;9月8日抖音生活服务完成新一轮组织调整&#xff0c;并已在内部all hands完成官宣。此次调整主要涉及北部大区、达人运营与市场部三大条线的人事轮换与汇报关系变更。核心变动如下&#xff1a;涂晴&#xff0c;原抖音生活服…

    class_9:java 抽象类和接口

    抽象类 需要用abstract 修饰类和接口abstract class Person{String address;String name;abstract public void eat();abstract public void drink();public void printInfo(){System.out.println("name " name);}} class Student extends Person{public void eat()…

    【C++】队列queue的使用

    语法 在 C 中&#xff0c;队列的语法如下&#xff1a; #include <queue>// 声明队列 std::queue<Type> q;这里 Type 是队列中存储元素的数据类型。 常用操作 队列提供了以下常用操作&#xff1a; empty(): 检查队列是否为空。 size(): 返回队列中的元素数量。 fron…

    HTTP 协议的基本格式

    目录 &#xff08;一&#xff09;HTTP是什么 &#xff08;二&#xff09;报文格式 &#xff08;1&#xff09;请求 ①首行 1.URL 2.方法&#xff08;method&#xff09; Ⅰ.GET Ⅱ.POST Ⅲ.PUT Ⅳ.DELETE 3.版本号 ②请求头&#xff08;header&#xff09; 1.键值对…

    计算机网络的基本概念-2

    1、数据交换技术&#xff1a;电路交换、报文交换与分组交换网络核心部分的关键设备是路由器&#xff0c;其工作方式是分组交换。要理解分组交换&#xff0c;必须先了解其前两种技术。1. 电路交换 (Circuit Switching)核心思想&#xff1a;通信前必须预先建立一条专用的物理通路…

    车载网络技术--SOME_IP协议详解

    文章目录前言SOME/IP概念SOME/IP协议格式SOME/IP功能介绍序列化序列化规则发布和订阅服务发现&#xff08;SOME/IP-SD&#xff09;SOME/IP-TP协议使用场景SOME/IP-TP协议参考文章&#xff1a;前言 本文介绍了SOME/IP协议的具体内容&#xff0c;包括报文格式&#xff0c;协议选…

    JVM 核心知识全解析:从类加载到垃圾回收的深度认知

    什么是JVM&#xff1f; JVM全称&#xff08;Java Virtual Machine&#xff09;&#xff0c;中译为&#xff1a;Java虚拟机 本质&#xff1a;是一个运行在计算机上的程序 职责&#xff1a;运行Java字节码文件&#xff08;因为计算机只能认识机器码文件&#xff0c;所以需要JVM将…