文章提出用子进程契约来验证 AI 生成的 C++ 补丁:隔离构建、ASan/UBSan 检测、资源限制,而非依赖人工审查文本,能有效拦截内存耗尽、ABI 破坏等危险修复。
文本审查对于生成的 C++ 补丁来说是一个错误的安全边界。补丁可以语法正确、编译通过,但仍然会耗尽内存、更改 ABI,或传递错误的契约。真正重要的决策不是建议看起来是否合理,而是打补丁后的代码树在隔离状态下能否通过构建、sanitizer 和资源限制。本文将这个决策转化为一个子进程契约:提取 diff、在 worktree 中应用、在 ASan/UBSan 下编译、运行一个固定的探测程序,并记录证据。
一个免费端点使得那条诱人的路径变得足够廉价,以至于变得危险。如果第一步是"阅读答案并粘贴",模型可以生成看起来合理的 C++ 代码,但只在人类审查者不会模拟的条件下才会出问题。当模型的输出被视为不可信输入时,这个端点才变得有用。
MonkeyCode 的免费模型访问是获取候选建议的一种方式,如果账户提供了可运行的容器,它的免费服务器选项也可以托管运行器。披露:本文是作为 MonkeyCode 产品推广的一部分准备的。工作流将两个选项都视为黑盒,不依赖任何特定模型的 API、配额或端点行为。
生成的 C++ 以在 diff 视图中不可见的方式失败。一个变更可能引入未定义行为,只有 ASan 才能捕获。它可能在紧循环中分配内存并耗尽测试运行器的内存限制。它可能改变重载解析或 ABI,而周围的文字仍然看起来合理。文本审查默认接受看起来可能的变更。子进程门控默认拒绝,直到证据出现。
下面的测试工具足够小,可以在 CI 或容器中运行,它输出一个 JSON 裁决。它不试图理解模型的推理,只问补丁是否可以构建并在资源限制下通过一个确定性探测程序。
输入是一个 Markdown 文件,其中恰好包含一个 unified diff 块。输出是 summary.json,包含三种裁决之一:reject、accept_for_human_review 或 no_diff_found。阶段如下:
提取第一个带围栏的 diff 或 patch 块。
在一个分离的 git worktree 中应用它。
使用 address sanitizer 和 undefined-behavior sanitizer 构建打过补丁的代码树。
在墙上时钟超时和内存上限下运行一个固定的 contract_runner。
记录阶段、退出码、sanitizer 日志和原始补丁路径。
任何阶段失败都会停止链条。第 4 阶段通过仍然不是合并批准,而只是人类维护者的最低证据。
保存为 intake_suggestion.sh。它需要一个建议文件、一个 git 仓库和一个输出目录。
#!/usr/bin/env bash
set -euo pipefail
suggestion_md="${1:?usage: intake_suggestion.sh suggestion.md repo output}"
repo="${2:?}"
out="${3:?}"
mkdir -p "$out"
summary="$out/summary.json"
awk '
/^```
(diff|patch)?[[:space:]]*$/ {in_block=1; next}
/^```[[:space:]]*$/ {if (in_block) exit}
in_block {print}
' "$suggestion_md" > "$out/candidate.patch"
if ! grep -qE '^--- ' "$out/candidate.patch" || ! grep -qE '^[+][+][+] ' "$out/candidate.patch"; then
printf '{"verdict":"reject","stage":"extract","reason":"no_unified_diff"}\n' > "$summary"
exit 0
fi
worktree="${out}/worktree"
git -C "$repo" worktree add --detach "$worktree" HEAD >/dev/null
trap 'git -C "$repo" worktree remove --force "$worktree" 2>/dev/null || true' EXIT
if ! git -C "$worktree" apply --check "$out/candidate.patch" 2>"$out/apply.err"; then
printf '{"verdict":"reject","stage":"apply","reason":"patch_does_not_apply"}\n' > "$summary"
exit 0
fi
git -C "$worktree" apply "$out/candidate.patch"
cmake -S "$worktree" -B "$worktree/build" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -O1" >"$out/cmake.log" 2>&1
cmake --build "$worktree/build" -j2 >"$out/build.log" 2>&1
set +e
( ulimit -v 1048576; timeout 10s "$worktree/build/contract_runner" ) >"$out/run.log" 2>"$out/sanitizer.log"
code=$?
set -e
if [ "$code" -eq 0 ] && [ ! -s "$out/sanitizer.log" ]; then
verdict="accept_for_human_review"
stage="run"
reason="contract_passed"
else
verdict="reject"
stage="run"
reason="contract_failed_or_sanitizer_signal"
fi
printf '{"verdict":"%s","stage":"%s","reason":"%s","exit_code":%d}\n' "$verdict" "$stage" "$reason" "$code" > "$summary"
ulimit -v 上限是一个粗粒度的守卫,不是容器边界。当补丁来自外部服务时,在容器内运行整个脚本,而不是在开发者工作站上。
测试工具运行一个固定的二进制文件,因此仓库必须提供一个。保持它小而离线且确定性。关键是捕获崩溃、sanitizer 发现和资源滥用,而不是验证每个特性。
#include <cstddef>
#include <iostream>
#include <vector>
int main() {
constexpr std::size_t n = 1 << 16;
std::vector<int> values;
values.reserve(n);
for (std::size_t i = 0; i < n; ++i) {
values.push_back(static_cast<int>(i & 0x7fff));
}
if (values.size() != n) return 1;
std::cout << "contract: ok\n";
return 0;
}
在真实的仓库中,用最小的测试目标替换这个探测程序,来 exercise 被候选补丁触及的代码路径。关键属性是:一次通过的运行给出一个具体的人工制品,而一次失败的运行给出一个可操作的 sanitizer 日志。
这张表揭示了一个常见错误:团队将构建视为最终关卡。运行阶段是许多模型生成的 C++ 失败出现的地方。
contract_runner 通过并不是一个正确性证明。它只证明了打补丁的代码树在一种限制组合下编译并通过了一个确定性探测程序。它不证明线程安全、数据竞争的不存在、可移植性或算法复杂度。它也依赖于探测程序与补丁的相关性。
免费模型输出可以是非确定性的。在应用补丁之前记录原始建议和提交哈希,以便后来的复现者从相同的字节开始。第二次运行可能产生不同的建议,这就是为什么补丁文件而不是聊天记录是被测试的产物。
应该避免这种方法 的团队包括:没有可重现构建的团队、没有能够阅读 sanitizer 输出的维护者的团队,以及在安全关键代码上工作的团队——在这些情况下,子进程门控会产生虚假信心。在这些情况下,生成的补丁应该保持只读建议,直到它们通过正常审查流程。
如果你已经有 MonkeyCode 免费模型访问,最小的升级不是另一个 prompt。而是将每个生成的 diff 输入到一个子进程门控中,只让人类审查那些存活下来的。