语义存储和检索¶
语义存储和检索(src/lian/util/loader)是数据管理的核心基础设施模块。在大规模静态分析过程中,本模块高效地管理、存储和检索分析产生的海量中间数据;同时,面对有限的内存资源,通过缓存策略、分块存储机制和统一的数据访问接口,为Lian执行复杂程序分析提供数据管理基础。
语义存储和检索模块内部包含多种Loader,管理Lian中不同类型的分析结果。Loader框架采用了如下两类组织方式:
- 模板Loader类:定义数据访问的通用模式;
- 具体Loader实现:绑定到某一类分析结果。
模板类解决“这一类数据怎么被访问”的问题,而具体Loader解决“这是什么数据”的问题。
2 Loader模板体系¶
Loader框架通过一套继承体系实现了从通用到专用的分层抽象。底层的数据序列化由DataModel处理,Loader只关注数据的组织逻辑和访问模式优化。
2-1 通用数据管理模板GeneralLoader¶
GeneralLoader是Loader基类,实现了核心的缓存和分块存储机制。
缓存机制¶
它采用了两级缓存系统:
访问请求
↓
条目缓存 (Item Cache) ← LRU,存储反序列化后的对象
↓ (未命中)
分块缓存 (Bundle Cache) ← LRU,存储原始数据块
↓ (未命中)
硬盘文件 (Bundle Files)
数据存储机制¶
对于数据存储,采用分块存储策略,即数据不是逐条写入硬盘,而是先在内存中累积到"活跃分块"(Active Bundle):
save() → Active Bundle (内存)
↓ (累积到 MAX_ROWS)
export() → Bundle 文件 (硬盘)
当活跃分块达到配置的行数上限(config.MAX_ROWS),触发批量写入,生成形如cfg.bundle0的文件。这种批量I/O策略将数百次小写入合并为一次大写入,降低硬盘开销。
索引机制¶
GeneralLoader采用独立的索引文件记录数据项ID → Bundle文件ID的映射,例如:
method_id: 1001 → bundle0
method_id: 1002 → bundle0
method_id: 2001 → bundle1
查询时先通过索引定位Bundle,再从Bundle中提取具体数据。
抽象接口¶
GeneralLoader定义了三个抽象方法,子类必须实现以适配不同的数据结构:
query_flattened_item_when_loading():从Bundle中查询特定数据项的记录;unflatten_item_dataframe_when_loading():将扁平化的数据帧重建为复杂对象;flatten_item_when_saving():将复杂对象转换为扁平化的字典列表。
这种设计区分了数据存储职责(DataModel)与数据组织职责(Loader)。
2-2 UnitLevelLoader:源文件粒度数据管理¶
UnitLevelLoader专门处理以源文件(unit_id)为粒度的数据,如作用域层次结构、导出符号等。
2-3 MethodLevelAnalysisResultLoader:方法粒度数据管理¶
MethodLevelAnalysisResultLoader处理以方法(method_id)或更细粒度(如调用点CallSite)为单位的分析结果。
2-4 OneToManyMapLoader/ManyToOneMapLoader:映射关系管理¶
这类Loader专门处理ID之间的映射关系,如"一个类包含多个方法"、"一个方法属于一个类"。这些Loader提供了快速的双向查询能力,为调用关系、类型关系和符号归属关系提供索引支持。
3 具体Loader实现¶
各种具体Loader是Loader模板类的实例化实现。每个具体Loade管理特定的分析数据。它们继承相应的Loader模板类,并实现特定的序列化逻辑。重要的是,这些Loader不关心数据如何物理存储(由DataModel处理),而只关心数据的逻辑结构和访问模式。
3-1 结构性数据Loader¶
ModuleSymbolsLoader:
- 管理所有源文件和目录的元信息
- 建立文件路径与unit_id的双向映射
- 是分析流程中单元与路径映射的起始位置
UnitGIRLoader: - 存储单元的通用中间表示(GIR) - 提供细粒度的语句级访问
3-2 图结构Loader¶
CFGLoader:
- 存储控制流图的边:(src_stmt_id, dst_stmt_id, control_flow_type)
- 加载时重建NetworkX有向图
SymbolGraphLoader:
- 处理混合节点图:节点既包括语句ID,也包括SymbolDefNode
- 根据边的方向区分"定义"边和"使用"边
- 通过条件字段(defined_symbol_*vsused_symbol_*)实现稀疏存储
StateFlowGraphLoader:
- 存储状态流图,节点为SFGNode,边为SFGEdge
- 通过to_tuple()/from_tuple()方法序列化复杂对象
ImportGraphLoader: - 管理三个相关数据结构: - 符号级导入图(节点为符号ID) - 节点元信息(符号详细信息) - 单元级依赖图(节点为unit_id) - 分离存储、统一查询
TypeGraphLoader: - 存储类型继承关系图 - 支持查询父类和子类
3-3 数据流分析 Loader¶
BitVectorManagerLoader:
- 存储语义节点(SymbolDefNode/StateDefNode)到位位置的映射
- 采用单向存储、双向重建策略
StmtStatusLoader:
- 存储语句的定义-使用信息
- 字段包括:defined_symbol、used_symbols、in_symbol_bits、out_symbol_bits等
SymbolStateSpaceLoader:
- 存储符号和状态的完整信息
- 区分Symbol和State两种类型
- State包含访问路径(AccessPoint列表)
3-4 方法摘要Loader¶
MethodDefUseSummaryLoader: - 存储方法的基本定义-使用摘要 - 字段:参数符号、局部符号、外部符号、返回符号
MethodSummaryLoader:
- 处理两种对象:
- MethodSummaryTemplate(键为method_id)
- MethodSummaryInstance(键为CallSite)
- 通过检查caller_id字段区分类型
CalleeParameterMappingLoader:
- 存储调用点的参数映射关系
- ParameterMapping包含参数索引、状态ID、访问路径等
3-5 调用关系Loader¶
CallGraphLoader: - 存储方法间的调用关系图 - 边包含调用语句ID
CallPathLoader:
- 存储完整的调用路径(CallPath)
- 每个路径是CallSite的序列
- 提供复合查询接口:
- get_callers_by_method_id():获取调用者
- get_call_path_between_two_methods():获取两方法间的路径
- get_lowest_common_ancestor():查找最近公共祖先
4 统一接口¶
语义存储和检索模块聚合了数十个具体Loader,提供统一的、语义化的访问接口。
4-1 统一的get/save接口¶
Loader类为每个专用Loader提供了语义化的访问方法,形成统一的命名规范:
基本模式:
# 保存数据
save_xxx(id, data)
# 读取数据
get_xxx(id) → data
# 转换映射
convert_xxx_to_yyy(xxx_id) → yyy_id
# 判断类型
is_xxx(id) → bool
示例:
# 模块符号
save_module_symbols(module_symbol_results)
get_module_symbol_table() → DataModel
# 单元 GIR
save_unit_gir(unit_id, unit_gir)
get_unit_gir(unit_id) → DataModel
# 方法 CFG
save_method_cfg(method_id, cfg)
get_method_cfg(method_id) → nx.DiGraph
# ID 转换
convert_method_id_to_unit_id(method_id) → unit_id
convert_stmt_id_to_method_id(stmt_id) → method_id
# 类型判断
is_method_decl(stmt_id) → bool
is_parameter_decl_of_method(stmt_id, method_id) → bool
这种统一接口屏蔽了底层 Loader 的复杂性,使得上层分析代码无需关心数据存储在哪里、如何序列化。
4-2 智能查询封装¶
Loader 提供了大量高级查询方法,封装了复杂的逻辑:
调用关系查询:
get_callers(method, phase="p3", match_by="name", entry_point_id=-1)
get_callees(method, phase="p3", match_by="name", entry_point_id=-1)
通过参数选择对应阶段的实现:
- phase="p2":使用CallGraphLoader
- phase="p3":使用CallPathLoader
- match_by="name":返回方法名集合
- match_by="id":返回方法ID集合
源代码查询:
get_stmt_source_code_with_comment(stmt_id) → List[str]
get_method_decl_source_code(method_id) → str
get_unit_source_code_by_stmt_id(stmt_id) → List[str]
这些方法读取文件,基于既定规则处理注释和边界情况: - 注释的识别和包含 - 方法声明与方法体的区分 - 行号映射和边界处理
方法名称查询:
get_method_name_by_method_id(method_id) → str
# 返回"ClassName.method_name"或"method_name"
自动拼接类名,提供更友好的显示。
5 多阶段分析支持(P1/P2/P3)¶
Lian 的分析流程分为三个阶段,每个阶段的数据规模和精度递增: - P1:基础结构分析 - P2:从下而上语义分析 - P3:从上而下语义分析
Loader通过文件系统路径分离实现阶段间的数据隔离:
workspace/
├── frontend/ # 前端解析结果(GIR、模块符号等)
├── semantic_p1/ # P1 基础结构性分析
├── semantic_p2/ # P2 自下而上过程间分析
└── semantic_p3/ # P3 自上而下全局分析
5-1 P1:基础结构性分析¶
目标:进行单元内的基础分析,建立程序的结构性信息。
核心数据:
- 控制流与作用域:
CFGLoader:方法级控制流图ScopeHierarchyLoader:作用域层次结构-
UnitSymbolDeclSummaryLoader:符号声明摘要 -
符号与状态基础:
SymbolBitVectorManagerLoader (P1):符号位向量映射StmtStatusLoader (P1):语句定义-使用信息-
SymbolStateSpaceLoader (P1):符号状态空间 -
方法与类信息:
MethodDefUseSummaryLoader:方法定义-使用摘要MethodInternalCalleesLoader:方法内部调用点-
ClassIDToMethodsLoader:类到方法映射 -
调用图初步:
CallGraphLoader:初步调用关系GroupedMethodsLoader:方法分组
5-2 P2:自下而上过程间分析¶
目标:从被调用者向调用者传播信息,进行过程间的数据流分析。
核心数据:
- 符号与状态传播:
SymbolBitVectorManagerLoader (P2):扩展的符号位向量StateBitVectorManagerLoader (P2):状态位向量StmtStatusLoader (P2):更新的语句状态SymbolStateSpaceLoader (P2):扩展的符号状态空间-
SymbolStateSpaceSummaryLoader (P2):摘要信息 -
图结构细化:
SymbolGraphLoader (P2):符号依赖图-
StateFlowGraphLoader (P2):状态流图 -
方法摘要:
MethodSummaryLoader (Template):方法摘要模板-
CalleeParameterMappingLoader (P2):参数映射 -
调用图细化:
CallGraphLoader (P2):更精确的静态调用图
5-3 P3:自上而下全局分析¶
目标:从入口点出发,进行路径敏感的全局分析。
核心数据:
- 全局符号与状态:
SymbolBitVectorManagerLoader (P3):全局符号位向量StateBitVectorManagerLoader (P3):全局状态位向量StmtStatusLoader (P3):上下文敏感的语句状态SymbolStateSpaceLoader (P3):全局符号状态空间-
SymbolStateSpaceSummaryLoader (P3):全局摘要 -
调用路径:
-
CallPathLoader:核心组件,存储完整调用路径 -
上下文敏感:
MethodSummaryLoader (Instance):特定上下文的方法摘要CalleeParameterMappingLoader (P3):上下文敏感的参数映射SymbolGraphLoader (P3):上下文敏感的符号图StateFlowGraphLoader (P3):全局状态流图
6 扩展能力¶
语义存储和检索模提供数据读写和检索能力,方便上层分析直接使用。
6-1 源代码与GIR关联¶
问题:分析结果是基于GIR的(语句ID),但用户需要看源代码。
解决方案:
get_stmt_source_code_with_comment(stmt_id) → List[str]
- 自动定位语句所在的源文件
- 根据GIR中的行号信息提取对应代码
- 智能处理注释(通过
util.determine_comment_line()识别方法前的注释) - 对方法声明,返回整个方法体;对普通语句,只返回该语句
6-2 规则匹配与污点分析¶
问题:污点分析需要根据规则(如"哪些函数是源/汇")快速定位代码位置。
解决方案:
get_source_sink_stmt_ids_by_rules() → (source_stmt_ids, sink_stmt_ids)
工作流程:
- 从
RuleManager获取所有源/汇规则 - 按文件路径过滤规则(避免无效匹配)
- 遍历每个单元的GIR,对每条语句:
- 转换为可读的GIR字符串(
readable_gir.get_gir_str()) - 使用正则表达式匹配符号名
- 如果规则指定了行号,额外检查行号是否匹配
- 返回匹配的语句ID集合
6-3 调用关系统一查询¶
问题:不同阶段的调用关系存储方式不同(P2用图,P3用路径),查询接口也不同。
解决方案:
get_callers(method, phase="p3", match_by="name", entry_point_id=-1)
get_callees(method, phase="p3", match_by="name", entry_point_id=-1)
参数说明:
- method:方法名或方法ID
- phase:"p2"/"p3"/"all"(自动合并)
- match_by:"name"(返回方法名集合)/"id"(返回方法ID集合)
- entry_point_id:指定入口点(仅对P3有效)
6-4 类型层次查询¶
能力:
get_parent_class_by_class_name(class_name) → List[TypeNode]
get_son_class_by_class_name(class_name) → List[TypeNode]
用途:分析继承关系、虚函数调用、类型转换等。
实现:遍历TypeGraphLoader中的类型图,根据边的parent_name字段查询。
6-5 模块导入关系查询¶
能力:
get_import_node_with_name(unit_id, import_name) → SymbolNodeInImportGraph
get_edges_and_nodes_with_edge_attrs_in_import_graph(node_id, attr_dict)
用途:分析模块依赖、解析符号引用。
设计:
- ImportGraphLoader维护符号级导入图和单元级依赖图
- 支持按导入名称、边类型(内部/外部)、语句ID等属性查询
- 返回边和节点的组合(edge, node)
7 总结:¶
语义存储和检索框架实现了高效、灵活、可维护的数据管理,在可控的内存和I/O开销下,为高精度全局分析提供数据管理基础。