首页 · ← SuperJ 手册 中文|EN

内存安全与安全:一则说明

为什么 SuperJ 没有借用检查器,以及为什么对大多数软件而言这是正确的权衡。

简短版

Rust 的借用检查器让一类 bug 不可能发生 — 内存安全 bug(use-after-free、double-free、缓冲区溢出、数据竞争)— 并为此收取永久的逐行税:每一个生命周期标注、每一次借用图博弈、每一次当检查器无法证明显而易见之事时你伸手去拿的 Rc<RefCell<>>,在第一次写时以及后续每次编辑时、由每个工程师、在项目整个生命周期内付出。这个税是复利的 — 它拖慢上手、拖慢迭代、把小重构变成借用考古,并驱使人们放弃本会做出的改动。

SuperJ 不收这笔税。你写的是 Java。作为交换,内存 bug 是可能的 — 它们在运行时崩溃,而不是在编译期被拒绝。问题在于:让一类 bug 不可能发生,是否值得为你写的一切永久收税。

答案,对大多数软件而言:不值得

借用检查器消除的那类 bug — 以内存损坏作为通往代码执行的路径 — 是严重的一类,但它也是的一类。它是驱动了二十年浏览器与内核安全补丁的那一类,对于那种软件它值得这笔税:

这就是一个内存 bug 即灾难性 bug 面无界的软件清单。这是一个真实的清单。它也只是被编写的软件的一小部分。

对于其余一切 — 服务器、服务、API 后端、数据流水线、网络守护进程,即生产软件的实际主体 — 威胁模型不是"攻击者通过内存损坏获得代码执行"。而是"逻辑返回了错误答案"或"服务在负载下崩溃"。内存安全对两者都无关。CVE 数据库偏向内存 bug 只是因为内存 bug 是那些会被分配 CVE 编号的;逻辑 bug 造成的事件从未被归档为漏洞。如果你按在生产中实际伤害用户的 bug 加权,逻辑 bug 占主导,而借用检查器对它们无能为力。

SuperJ 反而给你什么:确定性

这是交易的另一半,也是标准框定遗漏的那一半。SuperJ 的单线程 + SEDA 模型给你确定性:相同事件序列产生相同状态,逐位一致。这买到的是没有任何内存安全并发系统能提供的属性:

尖锐的版本:**内存安全是一个否定性属性("这类 bug 不可能发生"),通过引入非确定性获得,而非确定性是一个不安全性的正向来源("行为现在取决于攻击者能影响的东西")。**借用检查器移除一种失败模式,而并发模型打开另一种、更大的失败模式。确定性由构造关闭第二种,并让第一种可复现、可修复。

经济对比

RustSuperJ
逐行代价高、永久、每次编辑都付低 — Java 人体工学
它保证什么一类 bug 不可能(内存)一个属性存在(确定性)
帮什么逻辑 bug、死锁、其余约 90%内存 bug(直到你修复它们,而你能修)
收敛模型"它编译了,所以没有内存 bug"(对其余一切沉默)"它能复现,所以你能修任何 bug"

这种不对称正是要点。Rust 花费巨大努力消除一个少数派失败模式,并引入非确定性让多数派失败模式更难修。SuperJ 在少数派模式上不花力气,却给你一个让多数派模式可驾驭的工作流。

何时选哪个

业界把内存安全当作普遍值得的原因是,"内存安全"听起来像一个显而易见的好东西,而"人体工学代价"听起来像一句抱怨 — 但事实上前者是一个窄保证,后者是一种普遍税。在大多数情况下,你为一点付出很多。SuperJ 的论点是:Rust 让之不可能的那一类 bug,对大多数软件而言不值得让它不可能的代价 — 而确定性几乎不花代价,却给你一个能修复其余一切的工作流。

理想形态(以及为什么没人交付它)

单线程 Rust 同时具有两种属性:借用检查器的内存安全,且没有并发引入的非确定性。这严格优于任一纯选项。它也是没人谈论的配置,因为它击败了营销 — "无畏并发"在你不使用并发时就失去了意义。SuperJ 的赌注是:对大多数软件你不需要并发,而为了人体工学放弃内存安全,是比为了一个只对你并不拥有的威胁模型有意义的保证而放弃人体工学更好的权衡。

SuperJ — manual · generated from memory-safety.md at pack time · Powered by superJ — this site is served by superj_web 中文|EN