跳到正文
参考文档 / ZLIB × LEAN

从 C 到经过检查的定理

此页源文件快照 · f590bcbcf13b5db9 · 对应提交的原文 ↗

定理针对的是一个 Lean 计算。将它用于 zlib,需要审阅两个联系:运行时必须表示相关 C 操作,翻译必须对应选定的 C 源码。内核在这些选择确定后检查证明,并不检查它们与 C 的这两个联系。可信基础记录了这条边界。

各层的关系

flowchart TD
    C["固定的 zlib 源码 + 按顺序应用的补丁"] --> T["逐语句翻译"]
    R["运行时:对象、指针、算术、故障、循环"] --> T
    T --> P["执行证明:无故障、终止、帧性质、所有权"]
    T --> F["细化证明"]
    M["不涉及堆的算法和 API 模型"] --> F
    S["独立的格式规范"] --> M
    S --> F
    P --> A["公开调用与合法调用序列"]
    F --> A
层 职责 依据
选定的 C 固定被验证的程序 固定版本的 zlib/ 子模块,加上按文件名顺序应用的 patches/*.patch
运行时 为翻译后的操作提供可执行语义 独立审阅与语义回归测试
翻译 保留 C 语句及其顺序 源码审阅与差分测试;测试是证据,不是证明
模型与规范 描述程序必须做什么,而不预先选定实现方式 审阅定义、定义域及其与公开手册和数据格式的关系
证明 将计算连接到这些命题 Lean 内核检查与验收检查

安全性与功能正确性使用同一组具体状态。不涉及堆的数学内容放在 ZlibF/Math/;不能仅因为内存证明使用它,就让它依赖堆。中间算法模型可用于分离数学推理与内存推理,但仍需要细化证明,将模型连接到翻译后的函数。

选定源码与模块布局

首个目标是默认 x86_64 Linux 静态构建,使用 LP64 位宽和默认分配器。分配失败在范围内。其他构建配置和自定义分配器实现是独立范围,记录在表示限制中。绝不修改源码子模块。只有确认的上游缺陷才允许补丁,并须遵守仓库 AGENTS.md 与 patches/README.md 的规则。

位置 职责
ZlibF/Runtime/ 堆、指针、故障、循环和建模的库操作
ZlibF/Zlib/X.c.lean zlib/X.c 的翻译,以及该文件的规范与证明
ZlibF/Zlib/X.h.lean zlib/X.h 的翻译
ZlibF/Spec/ 不涉及堆的格式/API 关系与具体公开目标
ZlibF/Math/ 各证明共享的不涉及堆的事实
tests/ 差分证据、回归与验收检查

较大的 C 文件模块可以有支持子模块。函数名保留 C 名称,函数文档字符串以 C 签名开头。翻译规则详细描述对应关系。

对象堆与字段更新

例如,C 中的 s->strstart = 8 更新压缩状态的一项字段。对象堆把它表示为“在状态对象 s 中把 strstart 改成 8”,而不先计算该字段在机器地址中的字节偏移。这样,证明可以明确指出改了哪个对象、哪项字段,同时说明其他对象没有变化。

C 结构体表示为 Lean 记录。堆分别为记录、字节数组、字数组和其他元素类型设置表。指针命名一个对象、嵌入数组或静态数组,并附带元素索引;它不包含机器地址。

flowchart LR
    P["指针:空指针,或引用 + 索引"] --> B["堆字节块/字块"]
    P --> E["记录内嵌数组"]
    P --> S["静态数组"]
    H["堆"] --> B
    H --> K["记录表:流、inflate 状态、deflate 状态等"]
    K --> E
    H --> O["分配预言机、新鲜块计数器、任意初值"]

这样可以直接表达字段更新和帧性质。记录字段是投影,修改字段就是记录更新。更新字节不能改变另一张表中的记录。复制整个记录时,也复制其内嵌数组。生成的运行时结构参考列出全部字段、指针构造器和类型选择。

这种表示刻意避免 ABI 字节偏移,因此不能提供任意结构体的字节视图,也不能表示标量单元与缓冲区之间所有可能的别名。记录类型不保证数组字段的长度。需要时,由 WellFormed、Heap.RecordsWellFormed 和 Statics.WellFormed 提供这些事实。证明必须携带它们,或从表示不变量推出它们。

执行与故障

运行时返回结果值和最终堆,或者返回故障。Fault.ub 表示模型覆盖的未定义操作;Fault.outOfFuel 表示循环额度耗尽,终止证明必须排除它。安全性定义给出完整义务。

操作 模型行为
指针算术 保持在同一个存活数组内,允许尾后位置;解引用尾后位置会故障
读写 检查存活性与边界;写入还检查只读状态
指针差值/顺序比较 要求同一个存活数组和有效偏移;差值必须能由 ptrdiff_t 表示
指针相等 结构相等;不同引用构造器比较为不等
无符号算术 按 C 位宽回绕
有符号算术与移位 翻译检查溢出和无效移位计数
复制/填充 检查范围和可写性;memcpy 还检查不重叠
分配 使用失败预言机;成功时使用新鲜块标识
释放 接受 NULL;否则要求指向存活可写块的起点
循环 使用有限额度;耗尽是故障,绝不是普通退出

语言操作定义在 Space.lean、Monad.lean 和 Ptr.lean。建模的 C 库操作定义在 Libc.lean 和生成的 LibcTables.lean。生成的结构定义与记录访问操作位于 Schema.lean 和 Tables.lean。

证明必须尊重的语义差异

这些是设计限制,不是关于 C 的额外结论。准确细节以可信基础和表示限制为准。

差异 对证明的影响
新鲜整数单元有任意值 对每个任意初值函数量化;不能假设为零
每个新鲜单元在一次运行中保持同一个值 不能依赖 C 对未初始化单元的重复读取恰好一致
新鲜指针字段为 NULL 建立字段先写后读的事实;单独与 NULL 比较不能证明它
NULL + 0 成功;通用 NULL - NULL 故障 单独审阅翻译专用的任何例外
相等比较没有相邻对象地址的情况 不能利用不同对象的指针相等来证明 C 分支
没有通用地址、对齐或类型标签 显式审阅强制转换和地址敏感路径;结构体没有通用字节视图
CRC 观察地址低位 堆字节块使用对齐起点;静态和内嵌字节数组具有任意但稳定的起点余数。通用 ABI 布局和字转换的严格别名规则在模型之外
只读状态覆盖字节块和静态对象 不能假设任意记录或字块都能标记为只读
块标识永不复用 分配代码必须携带 Fresh;初始堆也必须具有互异对象标识
库复制按元素计数 正确转换 C 字节计数;整记录 memcpy 使用会检查重叠的记录复制

自动存储必须保留 C 生命周期,且不能消耗默认分配器的失败索引。地址传入调用的局部对象仍需要表示出的存储和清理。证明不能通过悄悄限制公开定义域来弥补表示缺口。

设计决定

这些标识用于追溯较早的设计讨论。它们描述所选方案;其可靠性义务仍见可信基础与证明指南。

ID 决定 义务
D1 使用具有类型化记录表的对象堆 论证所表示操作,并覆盖相关 C 别名。
D2 使用局部证明自动化 生成的证明项必须通过内核检查;不能用假定正确的验证条件生成器替代证明。
D3 手工逐语句翻译 C 审阅对应关系,并保留差分测试作为证据。
D4 建模压缩器中重叠的待输出/符号存储 对每个允许的内存级别证明生命周期和不覆盖条件。
D5 新鲜整数为任意值,新鲜指针为 NULL 对整数任意初值量化,并证明指针先写后读的历史。
D6 不为记录采用 ABI 字节布局 记录字节视图和标量/缓冲区别名需要另行表示。

组合调用时不预设答案

辅助函数定理证明实际翻译后辅助函数的性质。调用者建立其前置条件,再使用定理。辅助函数契约绝不能作为替代执行的假设。内部义务命名了调用者必须建立的条件。

每个状态记录的表示不变量携带边界、所有权和正确性所需的历史。初始化建立它,后续调用保持它,结束函数释放拥有的存储。这也建立了不变量可达的事实。整个序列的证明随后组合这些函数定理,包括拒绝和分配失败。方法见证明指南,验收标准见检查指南。

设计指南 · 文档首页

ZLIB × LEAN

全局目录

当前位置从 C 到经过检查的定理