跳转到内容

Apron — 把区间/八边形/多面体塞进同一个插槽

是什么

Apron 是一个 C 库,把抽象解释里几种最常用的”数值抽象域”(区间、八边形、多面体、线性等式…)做成同一套 API 下的可插拔插槽。日常类比:相机机身和镜头分离,机身(你的分析器)不变,换个镜头(抽象域)就能从广角变长焦——精度和速度自己选。

你写一个静态分析器,主流程长这样:

let s' = Abstract1.assign_texpr man s "x" expr
let s2 = Abstract1.join man s' s_other
let widen = Abstract1.widening man s2 s2_next

这套 API 只调一次。底下挂 Box 就是区间分析、挂 Octagon 就是八边形、挂 Polka / PPL 就是多面体。换域不用改主流程一个字。

过去十几年所有学界静态分析工具(Mopsa、Pagai、Frama-C/Eva、Crab、Interproc)背后基本都靠 Apron 提供数值域。

为什么重要

不理解 Apron 的位置,下面这些事都没法解释:

  • 为什么 mine-octagon-2006cousot-halbwachs-polyhedra-1978 论文之后还有大量”组合域”工作——是因为 Apron 提供了组合骨架
  • 为什么学界 abstract interpretation 论文几乎不写”如何实现多面体的凸包”——Apron 把这部分一次性吃掉了
  • 为什么 Frama-C 的 Eva 插件、Mopsa、Crab-llvm 在工程实现上长得很像——它们底层共用同一个 numeric domain 接口
  • 为什么”换一个数值域看看精度提升多少”在 2009 年之后才成为家常便饭——之前这是博士论文级工程

核心要点

Apron 的设计可以拆成 三件事

  1. 统一抽象层:把所有数值域共有的操作抽出来,只暴露 8 类 API——赋值(assign)、约束(meet_constraint)、合并(join)、相交(meet)、投影(forget)、变量重命名、变宽(widening)、查询(bound)。这套 API 是数值抽象解释的”最小公倍数”。

  2. 域作为后端:每个数值域只需实现这套 API,就成为可插拔后端。已有后端:Box(区间)、Octagon(八边形)、NewPolka(多面体,Apron 自带)、PPL(外接 Parma Polyhedra Library)、PolkaGrid(多面体 + 线性同余)、T1p(zonotopes)。

  3. 管理器(manager)模式:调用者拿着一个 manager 句柄,所有 API 都接受 manager 作为第一个参数。换域 = 换 manager,主代码原样不动。OCaml 类比:Abstract1.t Manager.t,类型参数化决定底下是哪个域。

三件事加起来:同一套调用 + 多种实现 + 显式管理器 = 数值静态分析的”插件架构”。

附加细节:Apron 还内置 Environment(变量集合)和 Texpr(数值表达式 AST),让前端语言无关——C / Java / OCaml 程序的赋值都先翻译成 Texpr 再喂给域。这层薄薄的中间表示让一个分析器框架能挂多种前端,又是一个”复用层”。

实践案例

案例 1:用 Apron 写一个迷你分析器

OCaml 调用 Apron 的典型片段:

let man = Box.manager_alloc () (* 区间域 *)
let env = Environment.make [|x; y|] [||]
let top = Abstract1.top man env
(* x := x + 1 *)
let expr = Texpr1.binop Add (Texpr1.var env x) (Texpr1.cst env (Coeff.s_of_int 1)) ... in
let s' = Abstract1.assign_texpr man top x expr None

如果想换八边形:把第一行换成 Octagon.manager_alloc ()整段下游代码不变。这就是 Apron 卖点。

案例 2:精度 vs 速度的取舍

同一段代码:

if (x > 0 && x < 100 && y == x + 1) { ... }
  • Box(区间)只能记录 x ∈ [1, 99]y ∈ [2, 100],丢掉 y = x + 1 这个关系
  • Octagon(八边形)能记录 y − x ∈ [1, 1],保住一个差值约束
  • Polka(多面体)能记录任意线性等式不等式,最精确,但开销最大

Apron 让你在一行代码内切换三档精度做实验,论文写法就是”我们在 Apron 上换 4 个域跑同一基准”。

案例 3:reduced product(组合域)

Apron 还提供 PolkaGrid 这种组合域:把多面体(线性约束)和同余(mod 关系)联合起来。比如能同时表达 x ≥ 0 ∧ x ≤ 100 ∧ x mod 4 = 0。组合的核心是reduced product——两边互相精化对方的状态。Apron 把这个数学运算也封到 manager 后面,外部调用者无感知。

踩过的坑

  1. manager 不可跨线程共享:Apron 内部用全局状态做缓存,多线程并发分析得每个线程一个 manager,否则段错误。论文没强调,工程上踩了才知道。

  2. 多面体域的复杂度爆炸:n 个变量的多面体,凸包最坏 O(2^n) 顶点。NewPolka 默认开”严格不等式”会更慢,工程上常关掉换性能。

  3. MPQ vs double 精度:Apron 多面体后端默认用 GMP 大整数(精确但慢),可切到 double(快但有舍入)。论文实验全用 MPQ,工程上量大时常用 double + 后处理验证。

  4. Box ≠ 简单数组:Apron 的 Box 即区间域,但内部维护的是 Environment(变量名 → 索引映射),增删变量是 O(n)。频繁加变量比想象慢。

  5. 不动点收敛要自己控:Apron 提供 widening 算子但不替你跑不动点循环——主分析器要决定何时插 widening、何时 narrowing 收紧。新人容易直接把 join 当不动点用,结果循环里区间永远不收敛。

适用 vs 不适用场景

适用

  • 学术论文里的”评估我的新分析”——挂上 Apron 跑 4 个域得对照表
  • 中等规模工业静态分析(Frama-C、Mopsa、Crab-llvm 数十万行 C/LLVM)
  • 教学:让学生体会”换域 = 换精度”
  • 想要数值域的精确性证明(Apron 的 OCaml 接口便于和 Coq 验证对接,参考 compcert 系工具链)

不适用

  • 需要堆/指针/形状分析 → Apron 只管数值,要叠 sagiv-shape-analysis / reynolds-separation-logic
  • 飞控级零误报(Astrée 工业版) → 用了 Apron 思想但自己重写了所有域,因为 Apron 的精度/性能边界不够极端
  • 极大规模程序(千万行)→ 多面体域跑不动,需要自定义弱化版
  • 非数值属性(信息流、能力、effect) → Apron 是数值专用,其他属性得另写格

历史小故事(可跳过)

  • 1990s:Polka(Bertrand Jeannet 维护)和 PPL(Bagnara 等)各自实现多面体域,API 互不兼容;八边形(Antoine Miné)在 Astrée 内部又是另一套。
  • 2005-2008:Jeannet(INRIA)和 Miné(ENS)合作把这几个库的 API 抽出统一接口,用 OCaml 做胶水层,用 C 做底层,形成 Apron。
  • 2009 年:CAV 论文发表,Apron 成为学界事实标准。
  • 2010s:Mopsa、Pagai、Crab、Frama-C/Eva、Interproc 全部以 Apron 为数值域后端。
  • 2020 年代:仍在维护(github.com/antoinemine/apron),新增 zonotopes、disjunctive 包装。

之后十几年,“做新数值域 = 实现 Apron 接口 + 提交 PR”成了标准动作。

学到什么

  1. API 设计的复利:把 8 个共有操作抽成接口,成本一次,后续 15 年所有数值域工作都坐在这套接口上
  2. 学术工具也要工程化:论文可以只讲算法,但让别人能用需要 manager / environment / 内存管理这些工程层
  3. 域选择 = 调精度旋钮:抽象解释最实用的工程动作不是”发明新域”,而是”在已知域里挑组合”——Apron 把这个动作变得便宜
  4. 复用 vs 自研:学术界用 Apron 共享生态,工业界(Astrée)则自研换极端性能,两条路并存
  5. 接口稳定 = 长期红利:Apron 的 8 类 API 从 2009 年定稿到现在几乎没改,下游工具的代码十年不用大改也能换底层域,这是”小而稳”接口的复利
  6. manager 模式的迁移启发:把”算法的可变维度”显式化为可换插件,比硬编码或全局开关都好——同样的思路出现在 PyTorch 的 backend、LLVM 的 target 后端、数据库的存储引擎

延伸阅读

关联

反向链接

  • astree —— ASTRÉE 分析器 — 让飞机控制代码的静态分析做到零警告
  • compcert —— CompCert — 每条优化都被数学证明保持语义的 C 编译器
  • cousot-abstract-interpretation —— Cousot 抽象解释 — 给静态分析一套统一数学框架
  • cousot-halbwachs-polyhedra-1978 —— Cousot-Halbwachs 凸多面体域 — 让分析器自己发现变量间的线性关系
  • frama-c-2012 —— Frama-C — 一个开源平台把 C 程序的多种验证方法拼到一起
  • infer-biabduction —— Bi-Abduction — 让静态分析自动猜出函数缺什么前提
  • reynolds-separation-logic —— Separation Logic — 把 Hoare 逻辑扩到带指针的程序
  • sagiv-shape-analysis —— Sagiv 参数化形状分析 — 用三值逻辑证明链表树仍是链表树