name: 别名和指向分析
description: “实现指针程序的别名和指向分析。用于:(1) 优化编译器,(2) 验证内存安全,(3) 程序理解,(4) 并行化。”
version: 1.0.0
tags:
- 静态分析
- 别名分析
- 指向分析
- 指针
- 优化
difficulty: 中级
languages:
- python
- c++
- rust
dependencies:
- 数据流分析框架
别名和指向分析
实现指针程序的跨过程别名和指向分析。
何时使用
- 编译器优化
- 内存安全验证
- 程序理解
- 安全分析
- 并行化
- 错误检测
该技能的作用
- 指向分析 - 计算指针目标
- 别名检测 - 查找别名位置
- 跨过程 - 跨函数调用
- 流敏感 - 结合控制流
分析类型
| 类型 |
精度 |
速度 |
| 必须别名 |
确定别名 |
快速 |
| 可能别名 |
可能别名 |
中等 |
| 无别名 |
确定无别名 |
中等 |
关键概念
| 概念 |
描述 |
| 指向 |
指针可能到达的位置集合 |
| 指向集 |
指针可能引用的内存位置 |
| 别名 |
两个引用可能表示同一位置 |
| 必须别名 |
总是别名 |
| 可能别名 |
可能别名 |
| 流敏感性 |
不同程序点有不同结果 |
| 上下文敏感性 |
不同调用上下文有不同结果 |
| 分配站点 |
表示所有在该处创建的对象的抽象位置 |
| 基于包含 |
安徒生分析(子集约束) |
| 基于统一 |
斯蒂恩斯加德分析(并查集) |
实现
from dataclasses import dataclass, field
from typing import Dict, Set, List, Optional, Tuple
@dataclass
class Location:
"""内存位置"""
name: str
offset: Optional[int] = None
@dataclass
class PointsToSet:
"""指针可能指向的位置集合"""
targets: Set[Location] = field(default_factory=set)
class AliasAnalysis:
"""流不敏感别名分析"""
def __init__(self):
self.pts: Dict[str, PointsToSet] = {} # 指针 -> 指向集
self.globals: Set[str] = set()
self.heap: Set[str] = set()
def analyze(self, program: 'Program') -> Dict[Tuple[str, str], str]:
"""
计算别名关系
返回:{(x, y): alias_type}
其中 alias_type 在 {必须, 可能, 无} 中
"""
# 构建调用图
self.call_graph = self.build_call_graph(program)
# 初始化指向
self.initialize(program)
# 迭代分析
changed = True
while changed:
changed = self.iterate(program)
# 计算别名信息
return self.compute_aliases()
def iterate(self, program: 'Program') -> bool:
"""分析的单个迭代"""
changed = False
for func in program.functions:
for stmt in func.body:
changed |= self.analyze_stmt(stmt)
# 跨过程
for call in program.calls:
changed |= self.analyze_call(call)
return changed
def analyze_stmt(self, stmt: 'Stmt') -> bool:
"""分析单个语句"""
changed = False
match stmt:
case Assign(x, y):
# x = y: 复制指向
if y in self.pts:
old = self.pts.get(x, PointsToSet())
self.pts[x] = old
if old != self.pts.get(x):
changed = True
case AddressOf(x, y):
# x = &y: 指向 y
self.pts[x] = PointsToSet({Location(y)})
changed = True
case Load(x, y):
# x = *y: 解引用
if y in self.pts:
targets = self.pts[y].targets
new_pts = set()
for loc in targets:
if isinstance(loc, HeapLocation):
new_pts.add(loc)
if x not in self.pts or self.pts[x].targets != new_pts:
self.pts[x] = PointsToSet(new_pts)
changed = True
case Store(x, y):
# *x = y
if x in self.pts:
# y 的指向可能被存储
pass
case Alloc(x):
# x = new T: 堆分配
heap_loc = HeapLocation(f"heap_{len(self.heap)}")
self.pts[x] = PointsToSet({heap_loc})
self.heap.add(heap_loc.name)
changed = True
return changed
def compute_aliases(self) -> Dict[Tuple[str, str], str]:
"""计算别名对"""
aliases = {}
pointers = list(self.pts.keys())
for i, p1 in enumerate(pointers):
for p2 in pointers[i+1:]:
alias_type = self.check_alias(p1, p2)
if alias_type != "无":
aliases[(p1, p2)] = alias_type
aliases[(p2, p1)] = alias_type
return aliases
def check_alias(self, x: str, y: str) -> str:
"""检查别名关系"""
pts_x = self.pts.get(x, PointsToSet()).targets
pts_y = self.pts.get(y, PointsToSet()).targets
if not pts_x or not pts_y:
return "无"
# 必须别名:指向完全相同
if pts_x == pts_y and len(pts_x) == 1:
return "可能"
# 可能别名:交集非空
if pts_x & pts_y:
return "可能"
return "无"
安徒生分析
class AndersensAnalysis(AliasAnalysis):
"""安徒生基于包含的分析(O(n³))"""
def analyze(self, program: 'Program') -> Dict:
"""安徒生风格分析"""
# 约束生成
constraints = self.generate_constraints(program)
# 迭代解决约束
return self.solve_constraints(constraints)
def generate_constraints(self, program: 'Program') -> List:
"""生成指向约束"""
constraints = []
for func in program.functions:
for stmt in func.body:
match stmt:
case Assign(x, y):
# x ⊆ pts(y)
constraints.append(('子集', x, y))
case AddressOf(x, y):
# pts(x) ⊇ {y}
constraints.append(('指向', x, y))
case Load(x, y):
# 对于每个 p 在 pts(y) 中:pts(x) ⊆ pts(p)
constraints.append(('加载', x, y))
case Store(x, y):
# 对于每个 p 在 pts(x) 中:pts(p) ⊆ pts(y)
constraints.append(('存储', x, y))
return constraints
def solve_constraints(self, constraints: List) -> Dict:
"""通过固定点解决约束"""
# 初始化
self.pts = {v: PointsToSet() for v in get_all_vars()}
# 迭代直到固定点
while True:
old_pts = dict(self.pts)
for constraint in constraints:
self.apply_constraint(constraint)
if self.pts == old_pts:
break
return self.compute_aliases()
def apply_constraint(self, constraint):
"""应用单个约束"""
kind, x, y = constraint
match kind:
case '子集':
# pts(x) ⊆ pts(y)
self.pts[y].targets |= self.pts[x].targets
case '指向':
# pts(x) ⊇ {y}
self.pts[x].targets.add(Location(y))
case '加载':
# x = *y
for loc in self.pts.get(y, PointsToSet()).targets:
self.pts[x].targets |= self.pts.get(loc.name, PointsToSet()).targets
case '存储':
# *x = y
for loc in self.pts.get(x, PointsToSet()).targets:
self.pts[loc.name].targets |= self.pts.get(y, PointsToSet()).targets
斯蒂恩斯加德分析
class SteensgaardAnalysis(AliasAnalysis):
"""斯蒂恩斯加德基于统一的分析(近似线性)"""
def analyze(self, program: 'Program') -> Dict:
"""基于统一的分析"""
# 位置的并查集
self.parent = {}
self.rank = {}
# 初始化
for v in get_all_vars():
self.make_set(v)
# 统一
for func in program.functions:
for stmt in func.body:
self.unify_stmt(stmt)
# 从并集中提取指向
return self.extract_points_to()
def make_set(self, x):
self.parent[x] = x
self.rank[x] = 0
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
"""统一两个位置"""
px, py = self.find(x), self.find(y)
if px == py:
return
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
def unify_stmt(self, stmt):
"""基于语句统一"""
match stmt:
case Assign(x, y):
self.union(x, y)
case Load(x, y):
# 统一 x 与 y 的所有目标
pass
case Store(x, y):
pass
应用
- 死存储消除
- 标量替换
- 锁省略
- 内存安全
- 并行化(依赖分析)
- 错误检测(空指针、使用后释放)
- 安全分析(污点跟踪)
- IDE功能(查找引用)
- 垃圾收集器实现
提示
- 开始时使用安徒生分析以获得精度,斯蒂恩斯加德分析以获得速度
- 使用分配站点抽象处理堆
- 添加字段敏感性以处理结构
- 考虑上下文敏感性以处理函数
- 在投资精度前进行性能分析
- 选择精度与速度的权衡
- 仔细处理函数调用
- 考虑上下文敏感性
- 默认使用流不敏感分析
相关技能
常量传播传递 - 数据流分析
死代码消除器 - 死代码消除
数据流分析框架 - 分析框架
逃逸分析 - 使用指向进行堆分析
污点分析 - 结合指向分析
权威参考
| 参考 |
为何重要 |
| Andersen, “Program Analysis and Specialization for the C Programming Language” (博士论文, 1994) |
安徒生基于包含的分析 |
| Steensgaard, “Points-to Analysis in Almost Linear Time” (POPL 1996) |
基于统一的分析 |
| Emami, Ghiya, Hendren, “Context-Sensitive Interprocedural Points-to Analysis” (PLDI 1994) |
上下文敏感分析 |
| Landi, “Undecidability of Static Analysis” (LOPLAS 1992) |
理论限制 |
| Hardekopf & Lin, “The Ant and the Grasshopper” (PLDI 2007) |
快速基于包含的分析 |
权衡与限制
分析方法的权衡
| 方法 |
精度 |
复杂度 |
备注 |
| 安徒生分析 |
高 |
O(n³) |
基于包含 |
| 斯蒂恩斯加德分析 |
低 |
~O(n) |
基于统一 |
| 流敏感 |
高 |
昂贵 |
按语句 |
| 流不敏感 |
低 |
快速 |
按程序 |
何时不使用此技能
- 对于类型安全语言:引用类型使其更简单
- 对于低级代码:可能需要手动分析
- 对于小型程序:可能过度复杂
- 没有指针/引用的语言
- 当不需要可能别名信息时
- 性能关键的编译(使用快速分析)
复杂度考虑
- 安徒生分析:O(n³) 最坏情况,实用
- 斯蒂恩斯加德分析:近似线性
- 上下文敏感:无总结时指数级
限制
- 不可判定性:完整别名分析不可判定
- 不精确性:必须别名与可能别名的权衡
- 跨过程:需要调用图
- 可扩展性:流敏感昂贵
- 语言特性:异常、反射使问题复杂化
- 数组别名:难以精确处理
- 堆建模是近似的
评估标准
高质量实现应具备:
| 标准 |
需要关注什么 |
| 健全性 |
包含所有可能目标 |
| 精确度 |
最小化虚假别名 |
| 效率 |
合理的运行时间 |
| 正确性 |
匹配预期结果 |
质量指标
✅ 良好:健全、合理精确、高效
⚠️ 警告:太不精确(所有别名)或太慢
❌ 差:不健全(遗漏可能目标)
研究工具与工件
指针分析工具:
| 工具 |
应用 |
学习内容 |
| Soot |
Java |
指针分析 |
| WALA |
Java |
框架 |
| LLVM |
C/C++ |
别名分析 |
研究前沿
1. 字段敏感分析
2. 需求驱动分析
实现陷阱
| 陷阱 |
实际后果 |
解决方案 |
| 流敏感性 |
不精确 |
使用流敏感 |
| 跨过程 |
遗漏调用 |
构建调用图 |
| 不精确性 |
别名过多 |
流敏感性 |