莲花Lian程序分析框架¶
莲花Lian系统是新一代面向多语言场景的高精度程序分析框架,旨在为多种语言提供统一的、强大的程序分析能力,包括指针分析、数据流分析、污点分析等。
1 背景¶
程序分析是理解程序行为、验证软件正确性、保障系统安全的基石性技术。过去几十年中,围绕C/C++与Java等传统工业语言的研究已形成成熟程序分析技术体系。在C/C++领域,SVF和Phasar等工具通过对别名关系与堆对象的精细建模支持高精度指针分析;在Java领域,Soot和WALA等框架利用相对稳定的类型系统与引用语义构建可复用的指向分析基础设施。这些高精度技术已在代码审计和漏洞挖掘等场景中得到广泛应用。
然而,近年来编程语言生态发生显著变化。Python在人工智能与数据分析中占据主导地位,JavaScript/TypeScript在前端和全栈工程开发中得到广泛采用(TypeScript在2025年GitHub语言流行度排名第一),Go则在云服务和系统编程领域发挥重要作用。相比传统主流语言(如C/C++和Java),这些语言场景的程序分析能力的精度和扩展性存在不足。
这种能力缺口正在演化为软件安全与可靠性领域的现实风险。复杂行为也更难进行有保障的安全分析,潜在漏洞难以在开发阶段被程序分析发现。随着这些语言在金融系统、云基础设施与人工智能平台等关键领域的深度应用,程序分析能力不足将显著限制对现代软件系统进行系统性安全分析的能力。
2 困难¶
在新语言场景中,分析的挑战和困难主要根源于,很多语言在内存对象模型与运行时行为上具有极大的不确定性:
- 类型信息往往难以在编译期稳定获得
- 对象属性集合可在运行时增删与演化
- 对象形状的动态变化使字段集合难以确定
- 计算属性名以及动态代码加载使属性访问与控制流更难静态确定
- 高阶函数、闭包与动态分派进一步扩大潜在调用目标集
- 动态属性解析机制(例如JavaScript的原型链查找,以及Python中基于类层次与隐式属性表查找)使字段解析依赖运行时状态,从而模糊对象边界与字段语义
这些特性叠加加大了传统程序分析方法的难度,削弱了分析方法的多个重要假设,包括稳定对象布局、可静态判定的字段集合、可有效剪枝和保证算法收敛的类型约束。这些问题使得上下文敏感、流敏感的指针级程序分析难度极大,并很难针对大规模软件分析依然保持有效性和可扩展性。
针对动态语言的类型分析技术(如TAJS、SAFE、JSAI等),基于抽象解释进行分析,在特定语言JavaScript中已取得重要进展,但总体上仍面临结构性困难,一方面,需要在抽象域精度与堆对象的合并策略之间进行折中,另一方面,抽象解释在处理分支语句面临状态分割的问题,极易导致状态爆炸,即使引入widening等机制,工程级分析仍面临精度与效率的根本折中。
此外,还有个不可忽视的问题,以往的程序分析方法往往与特定的语言进行绑定,导致方法与工程实现无法扩展至其他语言复用。这意味着,支持一门语言往往需要重构从前端到核心分析的关键组件,包括AST到IR转换,控制流、数据流、指针分析等。这导致对新语言场景扩展的难度和工作量极大。
3 思路¶
要突破这一困境,需要针对新语言场景对程序分析方法论进行变革:
- 针对新语言场景分析能力缺失,需要一种具备高度可扩展性、可复用性的方法框架,以最小的成本和代价提供高质量程序分析能力;
- 在程序行为高度动态化的情况下,仍应能够构建可控的堆抽象模型与指向关系表达机制和分析方法,从而降低对显式类型约束的依赖。
要想达成这一目标,其核心在于构建统一的高精度程序分析框架,与此同时能够兼容语言差异性。
首先,语言语义往往具备一定共性,这为统一程序分析提供可能性。
语言语法和语义的设计和演化遵循一定的共性:
- 对于很多语言,图灵完备性是所有语言的基础
- 从上世纪50年代开始,语言从命令式、函数式等多个方向演化,一门新语言的设计必然参考以往语言的优点和缺点,语言之间互相影响和参考,不好的语法被改进,好的语法优点得以继承和创新
- 这种共性也在统计上得到了明显的特征。统计TIOBE Index Top50语言,以及近年来新提出的语言,例如V语言、Ordin等,大部分语言表现出的高度的共性,都具备相似的语法和语义,包含变量声明、数据流指令、控制流指令、函数调用和返回、面向对象编程等。
这种共性为统一程序分析奠定了基础,因此,可以将在两个层面上的统一:
- 在语法解析上,基于语言共性设计通用中间语言,将所有的语言代码的AST翻译为该通用中间语言;
- 在语义分析上,把所有的语义分析全部都放置在IR翻译之后,所有的语义分析全都在同一中间语言之上进行,包括作用域分析、类型分析、文件导入关系、控制流、数据流、指针分析、污点跟踪等。
因此,给定一门语言,如果支持目标语言AST转换为统一中间语言,即可在补充必要语言语义插件的前提下,快速为该语言提供统一且可配置的程序分析能力。
其次,语言之间也存在大量的差异性,这些差异性同样非常关键和重要,不可以被忽略。
在共性的前提下,语言差异性依然是广泛存在的:
- 类型:很多动态语言,如JavaScript、Python等,并不会提供类型信息,变量的类型检查和推断都是在运行时进行,其次,很多所谓的静态语言,也并不是传统意义上的非常严格的,例如有的语言会引入Anything类型。
- 变量声明:很多语言,不使用显式的变量声明,这给变量识别和传播带来很大的困难;
- 继承:面向对象继承广泛被应用,但是继承机制的实现往往存在差异,例如JavaScript家族往往采用基于原型链的继承机制,而Python的继承机制基于类层次;
- 函数声明的格式:在面向对象编程时,Python语言把self作为第一个参数,而其他很多语言则默认this为等价关键字,不需要额外参数传递。
- 属性访问:很多数组访问格式array[attr]不仅被应用于数组访问,还被非常灵活地应用到各种场景,如Python中字典的访问,JavaScript中则可能等价于属性访问。
- for..in:语言中for..in循环的格式往往非常灵活,在不同语言中,体现出不同的语义,例如PHP、Python、JavaScript都是不一样的。
针对语言差异性兼容一关键问题,可以采用基于插件的扩展机制,允许对程序分析各个模块进行插装和修改。因此,统一分析提供了分析的血肉和骨干,扩展性提供了高兼容性和无限可能性。
最后,虽然进行统一分析,但是并不意味着分析强度的减低。
类型缺失、高阶函数(higher-order function)、动态属性访问和解析等动态特性,使得原有基于类型等分析无法正常工作,难以保证程序分析的完整性。
在这些场景下,比以往都更加依赖于高精度、与类型系统无关的指针分析,即分析一个变量对应的内存对象:
- 在类型缺失情况下,可以根据被指向内存内容查看被指向内存对象的类型;
- 针对高阶函数method()或receiver.method(),指针分析可查看method或者receiver所对应的内存对象,确定真正被调用的函数。
但是指针分析需要进一步优化,兼顾精确度和效率:
- 字段敏感:字段访问依赖于字段值的求解,因此不仅要分析指向关系,还需要计算字段真实值;
- 内存对象抽象:内存对象的类型大大增多,不仅局限在传统的堆内存,其他类型内存也需要被纳入;
- 流敏感:SSA在变量级别上提供了一种近似流敏感的表达能力,但对广泛存在的字段操作上仍存在不足,如何达成流敏感需要新的途径;
- 结束条件:类型缺失以及待分析的内存对象增多,导致指向集的数量被大大拓展,算法的收敛变成一个问题。
4 技术¶
基于上述思路,莲花Lian程序分析框架主要包含四部分:
-
通用中间语言GIR翻译:以语言共性为根基,设计统一中间语言GIR(General IR),以代码AST为输入翻译为GIR;对于每门语言平均需要1600行代码即可完成AST到IR的转换;
-
统一指针分析引擎:抽象内存对象为地址、真实取值、形状,采用on-the-fly的指针分析策略,结合数据流def/use实现流敏感;
-
语言差异性兼容:通过插件机制,扩展统一分析能力,支持语言差异性兼容;
-
基于状态流图的污点跟踪:复用指针分析的结果和数据流结果设计状态流图,实现基于指针分析结果的高精度污点跟踪。
5 应用¶
Lian具备几个强大的特性,包括
- 具备高度可扩展性,能够低成本支持新的语言场景;
- 插件机制支持分析能力的灵活适配,便于开发与集成,从而兼容语言差异性;
- 高精度统一指针分析,支持可配置的上下文敏感、流敏感与字段敏感分析;
- 强大的污点跟踪,基于指针分析的结果分析;
- 中间结果的存储和复用,每个分析步骤都存放到数据库中,可以随时查询和分析;
- 提供多个MCP能力,非常容易对接大模型等AI技术需求。
这些特性使得Lian具备非常广阔的应用场景:
- 软件bug检测:Lian可以帮助进行软件bug检测,通过静态分析,发现软件中的bug;
- 安全分析:Lian可以帮助进行安全建模和审计分析,发现其中深层次安全漏洞;
- AI+:跟AI非常容易对接,可以利用Lian进行模型训练,进行模型预测。
6 其他重要说明¶
6-1 语言支持¶
Lian语言前端当前实现情况:
| 语言 | 状态 |
|---|---|
| Python | ✅ 完整支持 |
| JavaScript | ✅ 完整支持 |
| TypeScript | ✅ 完整支持 |
| Java | ✅ 完整支持 |
| Go | ✅ 完整支持 |
| C | ✅ 完整支持 |
| PHP | ✅ 完整支持 |
| ArkTS | ✅ 完整支持 |
| LLVM IR | ✅ 完整支持 |
| Rust MIR | 不成熟 |
| C# | 不成熟 |
| Ruby | 不成熟 |
| Smali | 不成熟 |
6-2 核心模块说明¶
src/lian/
├── lang/ # 语言前端
│ ├── xxx_parser.py # 某个语言的解析器
│ ├── common_parser.py # 所有语言解析器的公共类
│ └── lang_analysis.py # 语言分析主程序
├── basics/ # 基础结构性分析
│ ├── control_flow.py # 控制流分析
│ ├── entry_points.py # 入口点识别
│ ├── import_hierarchy.py # 模块导入关系分析
│ ├── scope_hierarchy.py # 作用域层级分析
│ ├── stmt_def_use_analysis.py # 定义-使用关系分析
│ └── type_hierarchy.py # 类型层级分析
├── core/ # 核心语义分析引擎
│ ├── global_semantics.py # 从上而下语义分析
│ ├── prelim_semantics.py # 从下而上语义分析
│ ├── resolver.py # 解析器
│ └── stmt_states.py # 语句状态分析
├── taint/ # 污点分析
│ ├── taint_analysis.py # 污点分析引擎
│ ├── taint_structs.py # 污点分析数据结构
│ └── rule_manager.py # 规则管理器
├── events/ # 插件系统,保证可扩展性,用于处理语言差异性
│ ├── event_manager.py # 事件管理器
│ └── default_event_handlers/ # 默认事件处理器
├── externs/ # 外部系统建模
│ └── extern_system.py # 外部系统集成
└── util/ # 工具模块
├── loader.py # 文件系统管理
├── data_model.py # 数据模型
└── readable_gir.py # GIR可读输出
6-3 配置选项¶
通过 default_settings/ 目录下的配置文件可以自定义:
entry.yaml:入口点规则配置source.yaml:污点源规则配置sink.yaml:污点汇聚点规则配置propagation.yaml:污点传播规则配置
7 总结¶
Lian是由复旦大学系统软件与可靠性课题组独立开发,基于通用中间语言GIR,进行统一的高精度的指针分析。其具有极强的可扩展性,原则上可支持任意具有明确语义定义的编程语言,兼顾这些语言差异性,实现高效安全分析。