内容
# 代码怀疑者
[](https://github.com/tanzercakir-commits/CodeSkeptic/actions/workflows/ci.yml)
> **人人都能生成代码,代码怀疑者进行验证。**
基于Clang LibTooling构建的C/C++静态分析器,其核心是可重用的数据流分析引擎。代码怀疑者执行基于控制流图(CFG)的前向数据流分析,而不仅仅是抽象语法树(AST)模式匹配,因此它可以推理路径:指针在解引用时的状态,分配是否在每条路径上都被释放, 除数是否可以在到达除法运算的路径上为零。
长期目标是快速、可嵌入的**AI辅助开发的语义验证层**:一种设计用于坐在代码生成循环中的分析器,在毫秒内重新检查每个编辑并返回带有数据流跟踪的机器可读结果。

指向初稿代码,它会标记编译器忽略的问题—— unchecked `getenv`,`atoi`结果乘以`int`后溢出,错误路径上泄漏的工作缓冲区。 如果错误跨越多行,则结果携带**数据流跟踪**,包含源上下文:

这是在[`docs/demo.c`](docs/demo.c)上的实际输出——没有任何编译器警告。 `gcc -O2 -Wall -Wextra -Woverflow`和`clang -Winteger-overflow`都对`n * 4096`保持沉默:`-Woverflow`仅在编译时常量溢出时触发,而运行时从`atoi`接收的值不会触发。
### 它是如何比较的?
这三个发现,通过主流C/C++工具在[`docs/demo.c`](docs/demo.c)和[`docs/custom.c`](docs/custom.c)上运行。 有趣的部分不是代码怀疑者赢得了一行——而是**没有一个主流工具能够捕获所有三个,并且对于两个空指针情况,好的静态分析器彼此意见不一致**:
| 发现 | `-Wall -Wextra` | gcc `-fanalyzer` | clang `--analyze` | 代码怀疑者 |
|---------|:---:|:---:|:---:|:---:|
| 通过`getenv`的空指针解引用(库合约) | — | — | ✅ | ✅ + 跟踪 |
| 通过自定义返回空指针函数的空指针解引用 | — | ✅ | — | ✅ + 跟踪 |
| `atoi`导致的整数溢出 | — | — | — | ✅ + 跟踪 |
Clang的分析器将`getenv`建模为可为空,但不遵循手动编写的函数;gcc的`-fanalyzer`则相反;日常的`-Wall -Wextra`警告都不会标记它们。 代码怀疑者涵盖了类——库合约源,函数间自定义返回和运行时溢出——每个都有数据流跟踪。 这不是“其他人做不到”:这是日常警告网在这里有一个真正的空白,而分析器覆盖不均匀——这正是代码怀疑者构建的目标。
<sub>在上述两个文件上使用gcc 13.3和clang 18.1重现。 还手动检查了MSVC 2022(`/W4`和`/analyze`)和cppcheck 2.17(`--enable=all`),在`getenv`案例中保持沉默。</sub>
### 比原始代码AI审查更便宜(少6-59倍的令牌)
如果您已经对差异进行LLM循环以查找错误,则代码怀疑者使该循环**更便宜**。 LLM要求审查原始C/C++,必须吸收每一行并推理它们之间的路径。 代码怀疑者将代码视为**计算**——在令牌预算之外——并返回一个紧凑的确定性结果列表,附带跟踪。 其输出是**O(错误)而不是O(行)**:在文件有18行或2400行时保持不变,大约205个令牌。

| 输入(相同的3个错误) | LOC | 基线令牌 | 代码怀疑者 | 节省 |
|---------------------|-----:|----------------:|------------:|-------:|
| 小文件 | 257 | 1 342 | 205 | 6.5× |
| 模块 | 737 | 3 725 | 205 | 18.2× |
| 大文件 | 2417 | 12 090 | 205 | 59.0× |
诚实的免责声明:对于玩具文件(<〜50行),**没有节省** - 收益与源代码一样多。 交叉点约为〜50-80行;实际审查发生在它之上,节省随着大小增加。 而且它更便宜**且**更可靠 - 结果是确定性的,带有可验证的跟踪,而不是概率猜测。 完整方法,数字和限制: [docs/token-ablation.md](docs/token-ablation.md)。
## 规则
| 规则 | ID | 检测 |
|------|----|---------|
| 未初始化指针 | `uninit-ptr` | 某些路径上可能未赋值的指针解引用(CFG数据流) |
| 内存泄漏 | `memory-leak` | 函数退出时的泄漏和重新赋值泄漏,`malloc`/`calloc`/`strdup`/`free`和`new`/`delete`(CFG数据流与逃逸分析) |
| 双重释放 | `double-free` | 已经释放的指针被释放(共享内存泄漏数据流) |
| 释放后使用 | `use-after-free` | 已释放的指针的解引用(`*p`,`p->`,`p[i]`)(共享内存泄漏数据流) |
| 除零 | `div-by-zero` | 确定性和可能的整数除以零或取模,带有**分支条件细化** - 理解`if(z!= 0)`守卫,因此受保护的除法不会产生误报 |
| 空指针解引用 | `null-deref` | 确定性和可能的空指针解引用;跟踪`nullptr`/`NULL`/`0`流,带有分支条件细化(`if(p)`,`if(!p) return`,`p!= nullptr`,短路`&&`/`\|\|`);未知值保持沉默,因此未保护的参数不会产生警告 |
| 数组/堆边界 | `bounds` | 整个范围证明的越界访问,以及固定大小目标的副本(`memcpy`/`memmove`/`memset`,`strcpy`/`strcat`/`gets`)(CWE-125/787/120),在区间+扩展格上 |
| 整数溢出 | `int-overflow` | 已证明的操作数范围超出类型(CWE-190)的带符号乘法,包括不受信任的源(`int n = atoi(s); n * k`) |
| 合约验证 | `contract` | 声明的`// cs:`合约违规(前提条件,后置条件,所有权影响)- 由相同的推理合约摘要检查;`contract-syntax` / `contract-unsupported`使无法解析或无法验证的合约可见 |
| 策略强制执行 | `policy` | `cs:policy`模式禁止;v1提供`no-absolute-paths`(硬编码绝对路径字面量) |
**内在源召回率(v0.3):** 上述规则识别库调用的合约,使缺陷成为内在的 - `malloc`/`calloc`/`getenv`/`fopen`可能返回空指针,`atoi`/`strtol`/`scanf`提供无边界的不可信任值,`strcpy`/`strchr`没有边界/解引用其参数。 调用者合约(永远不是调用者数据)的关键点将AI编写的日常初稿形状 - `p = malloc(n); * p`,`x / atoi(s)`,`int n = atoi(s); n * k`,`strchr(getenv(x),':')` - 转换为结果,同时下游守卫完善状态并保持沉默。 在一个盲目的24程序AI语料库中,这提高了综合召回率从〜0(在非分配类)到**0.625,精度为1.000**(零个假阳性,包括在9个故意干净的程序上)。
**有针对性的路径敏感性:** 内存规则保持一组小的守卫状态,而不是一个合并的状态,根据变量的条件而定,这些变量在证明上不会改变(`if(mode == 5)p = malloc(...); ... if(mode == 5)free(p);`是干净的 - 两个守卫是相关的,因此“已分配但未释放”的路径是不可行的)。 函数调用条件永远不会被键控(两个`check()`调用可能不同),变量突变永远不会被键控,并且分离预算会逐渐降低到经典的合并分析。
**函数间(v1):** 具有可见正文的函数在规则运行之前会进行汇总 - 返回空(`find()`-style函数可以返回空指针,使其结果的未保护解引用成为警告,并附带跟踪注释),返回零(调用者可以返回零,使未保护的除以赋值结果的警告 - 经典的`data = badSource(); 100 / data`在函数或文件之间分割),以及参数影响(free-wrappers算作free,因此通过包装器捕获double-free/use-after-free;read-only helpers不再隐藏泄漏)。 递归安全的固定点;外部和别名调用者保持保守。
示例:
```
$ codeskeptic demo.cpp
CodeSkeptic: 2 发现
----------------------------------------
demo.cpp:4:13 [error] use-after-free: 使用后释放:'p' 在释放后被解引用
-> demo.cpp:2:5 'p' 在此处分配
-> demo.cpp:3:5 'p' 在此处释放
demo.cpp:9:12 [warning] div-by-zero: 可能的除零:'z' 在某些路径上可能为零
-> demo.cpp:7:5 'z' 赋值零
----------------------------------------
```
发现结果携带**数据流跟踪** - 导致错误的事件链。 跟踪在控制台上显示为缩进,在JSON输出中为`notes`数组,在SARIF中为`relatedLocations`(由GitHub代码扫描渲染)。
## 基准测试(NIST Juliet C/C++ 1.3)
每周CI运行分析器对抗 [NIST Juliet测试套件](https://samate.nist.gov/SARD/test-suites/112):每个CWE的400个文件,均匀采样所有变体家族。 如果函数名称包含`bad`,则发现结果计为真阳性;如果函数名称包含`good`,则发现结果计为假阳性。 **规则匹配**列仅计算针对测试的CWE的规则 - 即规则本身的精度。 **所有发现**列包括每个文件的所有规则输出(跨规则噪声,单独跟踪为FP-hunting材料)。
| CWE | 目标规则 | 规则精度 | 召回率 | 案例F1 |
|-----|-------------|---------------:|-------:|--------:|
| CWE-416 使用后释放 | `use-after-free` | **1.000** (200 TP / 0 FP) | 0.501 | **0.668** |
| CWE-476 空指针解引用 | `null-deref` | **1.000** (141 TP / 0 FP) | 0.352 | **0.521** |
| CWE-415 双重释放 | `double-free` | **1.000** (95 TP / 0 FP) | 0.241 | 0.388 |
| CWE-401 内存泄漏 | `memory-leak` | 0.716(案例级别) | 0.195 | 0.306 |
| CWE-369 除零 | `div-by-zero` | **1.000** (38 TP / 0 FP) | 0.095 | 0.174 |
| CWE-190 整数溢出 | `int-overflow` | **1.000** (42 TP / 0 FP\*) | 0.010 | 0.020 |
<sub>* CWE-190精度在完整的3080文件语料库中测量(42 TP,0 FP);召回率数字是CI采样的速率。 rand-source家族通过位移宏到达汇聚点,区间评估器无法折叠 - 文档记录的已知假阴性,因此采样的召回率故意较低,而精度是完美的。</sub>
这些数字的旅程:有针对性的路径敏感性(2026-07-10)降低了规则的假阳性(memory-leak 92 → 61,uninit-ptr 178 → 84,跨文件空指针解引用噪声 241 → 129)并**显现了以前错过的真阳性** - 相关守卫双重释放和使用后释放(+107 TP组合)是合并路径分析下的假阴性。 跨TU摘要(`--whole-program`)连接源/汇聚流在文件之间分割。 守卫的分离预算v2(2026-07-12)添加了调用条件键,流敏感的事实生命周期,常量戳记和蕴涵,析取消除以实现值实现的断言,以及引擎级别的收敛扩展 - CWE-416召回率从0.436 → 0.501,CWE-401精度从0.653 → 0.716在同一个步骤中删除了数百个现实世界的假阳性(查看下面的扫描表)。 注意事项: Juliet的`good`函数仅保证没有**测试的**CWE - 例如,CWE-416的`good`函数可能真正**泄漏**,因此`memory-leak`发现计为我们带来反弹,而可能正确。 **规则匹配**列是可靠的度量标准。
精度/命中率之外,框架报告**案例级别F1**(每个文件都是案例:匹配的发现`bad`函数是案例-TP,`good`函数案例-FP,沉默的坏文件FN)和第二个运行点仅限于`error`严重性发现。 有意 **没有ROC曲线** - 分析器基于证据并且是二进制的,而不是概率的 - 带有可扫除的阈值,AUC将是误导的。 **记分卡**(`scripts/juliet_expected.txt`)为每个CWE设定每个CWE的精度/命中率下限;任何代码PR低于它们都会失败CI。
阅读这些数字的注意事项:
- **五个规则上的零个假阳性**反映了设计选择,即未知值保持沉默 - 分析器仅在数据流证明某些东西时才发言。
- **命中率较低。** 许多Juliet变体通过源/汇聚调用链和类变体流过。 过程分析加上v1摘要捕获本地和基于包装器的部分。 CWE-369的低比率是设计使之如此:大多数Juliet变体使用浮点除法(IEEE754中的定义行为 - 有意不报告),或不透明的源(`rand()`,套接字),诚实的分析器无法调用零。
- **`memory-leak` 是唯一嘈杂的规则**(也是其他CWE的文件中大部分跨规则噪音),是当前的改进目标。
结果来自2026-07-18运行(v0.3)。 grep `JULIET_RESULT` 在每周工作流日志中找到当前数字。 v0.3召回系列新增第六个每个CWE的下限(CWE-190),并通过不可信源提高`div-by-zero`召回率 - 所有六个下限保持不变,精度为1.000。
## 现实世界扫描
合成基准奖励模式覆盖率;现实代码库惩罚假阳性。 每个项目都使用其自己的构建系统,从编译数据库中分析,并手动检查每个幸存的发现。 所有数字都来自一个分析器构建(2026-07-12);“初始”列是在项目首次尝试时报告的内容,在暴露的假阳性家族被修复之前。
| 项目 | 范围 | 初始 → 现在 | 人工验证的真实错误 |
|---------|-------|--------------:|------------------------|
| systemd | 494 文件(基本/核心/共享) | 414 → **53** | 3 个故意的泄漏形状习语,文档记录 |
| shadPS4 | 377 文件 | 209 → **22** | **3 个报告给上游 - 2 个合并 ([#4702](https://github.com/shadps4-emu/shadPS4/pull/4702), [#4703](https://github.com/shadps4-emu/shadPS4/pull/4703))** |
| libgit2 | 168 文件 | 149 → **44** | **11 个确认的 OOM 路径泄漏**(一个问题类,报告草稿) |
| llama.cpp | 完整构建 | 511 → **25** | 分类中 |
| rtp2httpd | 27k 行 | 4 → **3** | **1 个确认的 NULL 合约错误**,报告草稿 |
| NASA fprime | 216 文件 | 10 → **0** | 清洁(带有 `--fatal-asserts SwAssert` 声明 F´ 的断言处理程序) |
| abseil-cpp | LTS 标签 | 12 → **4** | - |
| Catch2 | 完整构建 | **0** | 清洁 |
**确认在野外。** 其中两个发现已被shadPS4维护者修复并合并上游:
- [#4703](https://github.com/shadps4-emu/shadPS4/pull/4703) (关闭 [#4696](https://github.com/shadps4-emu/shadPS4/issues/4696))—
`sceSaveDataMount/Mount2` 以 `&&` 检查指针,但在需要 `||` 的情况下,保护分支因此失败并 **解引用它刚刚检查过的指针**。 典型的看起来正确但读起来不正确 bug,此项目存在以捕获它。
- [#4702](https://github.com/shadps4-emu/shadPS4/pull/4702) (关闭 [#4698](https://github.com/shadps4-emu/shadPS4/issues/4698))—
`internal__Foprep` 在文件表耗尽时设置 `ENOMEM` 但继续运行,解引用下一行中的空 `FILE*`。
真正的错误,在一个真正的项目中,从编译数据库中找到并被代码的所有者接受 - 正是每个发现的数据流跟踪所指的内容。
有两个原因导致这些数字发生变化:
- **成语支持是配置,而不是代码**:项目分配器(`--alloc-functions git__malloc,... --free-functions git__free`),致命断言宏(`--fatal-asserts assert_fail_impl`)和清理属性( `_cleanup_free_`,`g_autofree`)被识别,以便分析看到代码的方式与项目意图相同。
- **每个假阳性家族成为一个带有固定测试的引擎特性**:指针关系有效性(systemd的`FOREACH_ARRAY`,一个根源的235个发现),跨变量相关性(标志/状态守卫,`assert(p || len <= 0)`合约),值选择重绕(llama的防御性三元宏),宏习语的逃逸分析(`TAKE_PTR`,`free_and_replace`,复合字面量)。 剩余的发现按项目分类和记录 - 没有一个被抑制列表隐藏。
## 架构
```
静态分析器(外观)
├─ 源管理器 — LibTooling 包装器:编译命令.json,AST 生产
├─ 规则引擎 — 规则注册表,使能/禁用,运行所有
│ └─ 规则(抽象)→ UninitPointerRule_Ex,MemoryLeakRule_Ex,DivByZeroRule
│ └─ 数据流引擎 — 通用工作列表求解器,基于 Clang CFG:
│ 分析 = { 状态,初始状态,合并,传输,
│ onStatement?,refineOnEdge?(假设边) }
├─ 报告器 — 控制台报告器,JSON 报告器
└─ 配置 — CLI 参数 + .codeskeptic.conf
```
编写新的流敏感规则意味着定义一个格(`State`),一个`transfer`函数,并且可选地定义`refineOnEdge`以细化状态沿分支边缘。 引擎处理CFG构造,工作列表和前驱体合并。
## 构建
需要CMake ≥ 3.20,C++17编译器和LLVM/Clang开发库(已在LLVM 18和20上测试;推荐LLVM 20)。
### Linux(Ubuntu 24.04)
```bash
sudo apt-get install -y llvm-20-dev libclang-20-dev libzstd-dev zlib1g-dev ninja-build
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/lib/llvm-20
cmake --build build
ctest --test-dir build # 52 tests
```
### macOS(Homebrew)
```bash
brew install llvm cmake ninja
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build
```
## 用法
```
codeskeptic <source_path> [options]
--source <path> 要分析的目录/文件
--build-path <path> compile_commands.json 目录
--json <file> JSON 输出文件
--sarif <file> SARIF 2.1.0 输出文件(GitHub 代码扫描)
--html <file> 自包含的 HTML 报告:摘要卡片兼作过滤器,数据流跟踪可嵌入源代码上下文,黑暗/光明主题 — 离线可用
--severity <level> 最低严重程度(info/warning/error)
--disable-rule <id> 禁用一条规则
--baseline <file> 抑制基线中记录的发现
--write-baseline <file> 记录当前发现为基线
--function <names> 只分析这些函数(逗号列表,普通或限定名称;可重复)
--lines <N-M,K> 只分析这些行范围内的函数
--fatal-asserts <names> 将这些函数视为永不返回(逗号列表)。对于项目,其断言失败处理程序故意缺乏 [[noreturn]]:终止失败路径,以便受保护的代码停止警告
--alloc-functions <names> 将这些函数视为堆分配器(逗号列表)。扩展到项目包装器的内存泄漏/双重释放/UAF 分析
--free-functions <names> 将这些函数视为释放器(其第一个参数被释放)
--owning-pointers <names> 将这些类模板视为拥有智能指针(逗号列表)。原始指针通过构造采用不再泄漏;std::unique_ptr/shared_ptr 内置
--untrusted-int-sources <names> 将这些函数的返回视为全范围不受信任的整数(逗号列表),与 atoi/strtol 相同的规则。对于协议/解析器长度字段从电线上读取,以便下游可能溢出的长度算术被报告
--report-paths <paths> 仅报告这些路径前缀下的发现(逗号列表)。过滤掉依赖项标头中的发现,但分析本身不受影响
--whole-program 两遍模式:首先收集所有文件中的函数摘要,然后分析
--summary-out <file> 将收获的跨文件摘要保存到文件中
--summary-in <file> 加载之前保存的摘要(增量全程序:单文件分析,具有整个项目的知识)
--lang <zh|en> 诊断消息语言(默认:zh)
```
还可以在 `.codeskeptic.conf` 文件中设置选项(`key=value` 行:
`source_path`, `build_path`, `output_format`, `json_output`,
`sarif_output`, `min_severity`, `enable_rule`, `disable_rule`, `lang`,
`function`, `fatal_asserts`, `alloc_functions`, `free_functions`).
### 抑制发现
可以使用源代码注释抑制个别发现:
```cpp
int x = 1 / z; // codeskeptic-disable-line
int y = 1 / w; // codeskeptic-disable-line div-by-zero
// codeskeptic-disable-next-line memory-leak
p = new int(7);
```
裸标记抑制当前行上的所有规则;用逗号或空格分隔的规则列表限制为那些规则。抑制的发现数量在标准错误流中报告。
### 基线工作流程
在现有代码库上采用分析器,而不需立即修复所有遗留问题:
```bash
codeskeptic src/ --write-baseline .codeskeptic-baseline # 记录并退出干净
codeskeptic src/ --baseline .codeskeptic-baseline # 只有新的发现才会失败
```
基线键是**行独立的**:它们不使用行号,而是哈希找到的源代码行的(修剪空格后的)文本,因此添加或删除文件中的其他代码不会使基线失效。如果标记的行本身发生变化,找到的内容会重新出现 — 有意为之,因为更改的行值得重新审视。相同的发现记录在相同的行上,按计数跟踪,因此基线一个实例永远不会隐藏第二个实例。旧的(v1,行号)基线文件仍然与其原始含义一起工作;使用 `--write-baseline` 重写以进行迁移。
### 增量分析
对于编辑检查循环(代理,IDE,预提交钩子),只分析更改的内容:
```bash
# 重新检查您编辑的函数(毫秒)
codeskeptic src/parser.cpp --function Parser::parse
# 只分析自 git 引用以来实际触摸的函数:
# 脚本从差异块中提取行范围并按文件传递
# --lines,因此整个未触摸的函数被完全跳过
scripts/analyze_diff.sh build/src/codeskeptic origin/main --severity error
```
跨文件知识通过摘要文件在增量运行中存活:
```bash
# 一次(或每天):从整个项目中收集函数摘要
codeskeptic src/ --summary-out .codeskeptic-summaries
# 然后:只分析更改的文件,同时具备整个项目的知识 —
# 例如,另一个文件中可能返回 null 的调用者仍然已知
codeskeptic src/parser.cpp --summary-in .codeskeptic-summaries
# analyze_diff.sh 转发额外选项,因此差异循环组合:
scripts/analyze_diff.sh build/src/codeskeptic origin/main \
--summary-in .codeskeptic-summaries --severity error
```
MCP `analyze` 工具通过其可选的 `summaries` 参数接受相同的文件,因此代理循环也可以获得跨文件知识。
过时或格式错误的摘要文件被整个拒绝(分析继续进行,但不使用它们,以保守的方式进行);冲突的条目合并为较弱的主张,因此错误的强主张不能通过文件进入。
### 语义回归门(摘要差异)
摘要文件是确定性的,因此可以比较两个收获结果作为**契约**:
```bash
codeskeptic src/ --summary-out before.txt # 例如,在 main 上
# ... 应用更改 ...
codeskeptic src/ --summary-out after.txt
codeskeptic --summary-diff before.txt after.txt
```
```
SUMMARY_DIFF WEAKENED find/1 returnNullness: NeverNull -> MaybeNull
[CodeSkeptic] 1 weakened, 0 strengthened, 0 changed, 0 added, 0 removed
[CodeSkeptic] weakened contracts: callers relying on them must be re-checked
```
`WEAKENED` 表示一个强有力的主张调用者可能依赖于丢失了 — 一个函数曾经不能返回 null,现在可以,一个调用者曾经只读,现在存储其参数。退出代码在这种情况下为 `1`,因此差异同时作为 CI 门:*此更改悄悄更改了函数契约;调用者值得审查*。获得的主张报告为 `STRENGTHENED`(信息性),方向漂移报告为 `CHANGED`,签名更改显示为 `REMOVED`+`ADDED`(键包括元数 — 元数更改会破坏调用者等)。
门是可配置的,以适应采用:`--gate warn`(或 `.codeskeptic.conf` 中的 `summary_diff_gate = warn`)保留完整的报告,但退出 `0`,因此项目可以在让它破坏 CI 之前观察其契约漂移。默认设置仍然为 `error` — 不可读的摘要文件无论如何都退出 `2`:无法读取其输入的门永远不会显示绿色。
### PR 审查(差异原生)
`review_diff.sh` 将分析器转换为 PR 审查器:它在**两个修订版**(在临时 git 工作树中,重用头部编译命令)和工作树中分析更改的文件,并报告**增量** — 此更改做了什么,而不是代码库已经具备的功能:
```bash
# 在 CI 中,检出 PR 头部后:
scripts/review_diff.sh build/src/codeskeptic \
"origin/${{ github.base_ref }}" --build-path build \
--exclude 'tests/*' --out review.md
```
Markdown 审查包含:
* **新发现** — 由更改引入,并带有数据流跟踪;发现和跟踪步骤位于更改的行上被标记。仅**移动**的发现(代码在其上方添加)不会重新出现:匹配使用基线的行内容键,纯重命名映射旧 → 新,因此重构 PR 保持静默。
* **新假设** — 默认情况下在审查模式下启用:新的推断的、未检查的前提条件(“参数 `p` 假定非空 — 解引用,从未检查”)是 CWE-476 形状审查旨在捕获的内容;在现场试验中,它指出 cJSON 的 #991 空指针解引用为单个发现,并在 116 次提交历史记录范围内产生零噪音(增量边界假设引擎的体积)。信息严重性 — 它告知,并仅在 `--strict` 下门控。选择 `--no-assumptions` 退出。
* **已修复的发现** — 存在于基准,但头部已消失。
* **契约更改** — 双方推断的契约的摘要差异;`WEAKENED` 条目门控。
* **覆盖率** — 未分析的内容以及原因(标头、删除的文件、`--exclude` 匹配、迭代能力函数)。未分析的文件中的“无警告”意味着**未检查**,审查会说明。
实际差异在可预测的位置会很吵闹 — 测试和供应商目录故意使用空路径。为此,`--exclude 'tests/*'`(可重复)跳过更改的文件**可见**:它们被列在覆盖率部分中,而不会被静默删除。
退出代码是判决,基于与规则本身相同的证据等级:**新的明确发现(错误)和弱化的契约门控;新的“可能”发现(警告)被报告但不** — 传递 `--strict` 以门控它们,或 `--gate warn` 以便始终退出 `0`,同时打印失败的判决(采用坡道)。最后一行是机器可读的,用于 CI 仪表板:
```
REVIEW_RESULT new_errors=1 new_warnings=0 fixed=1 weakened=1 gate=fail
```
两个分析器运行接收相同的设置( `--` 后的参数转发到两者 — `--alloc-functions`、 `--fatal-asserts` 或 `--summary-in .codeskeptic-summaries` 以使用整个项目知识,...);两个配置不同的运行之间的差异将不是差异。已知限制,说明而不是隐藏:标头更改不会分析 TU(它列在覆盖率部分),删除的文件的仅基准发现不会计为已修复。
最小 GitHub Actions 门控:
```yaml
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # review 需要基准提交
- name: Build codeskeptic # 或下载发布二进制文件
run: cmake -B build -G Ninja && cmake --build build
- name: Review PR diff
run: |
scripts/review_diff.sh build/src/codeskeptic \
"origin/${{ github.base_ref }}" --build-path build \
--exclude 'tests/*' --out review.md
```
### MCP 服务器(代理集成)
`codeskeptic --serve` 在标准输入/输出上运行 MCP(Model Context Protocol)服务器,公开一个 `analyze` 工具,该工具返回发现结果 — 带有数据流跟踪 — 作为结构化 JSON。像 Claude Code 这样的代理可以调用它在每次编辑后注册到 `.mcp.json` 中:
```json
{
"mcpServers": {
"codeskeptic": {
"command": "/path/to/codeskeptic",
"args": ["--serve"]
}
}
}
```
调用分析器也**比要求模型推理原始代码**更便宜 — O(bugs),而不是 O(lines),因此代理在实际大小的文件上花费 6-59 倍的 token 来定位内存安全漏洞,并获得确定性的答案和跟踪。[更便宜的 AI 原始代码审查](#更便宜的AI原始代码审查-659-fewer-tokens) 上有测量和诚实的注意事项。
`analyze` 工具接受 `path` 加上可选的 `build_path`、`functions` 和 `lines` — 因此代理可以将重新检查的范围限定到它刚刚编辑的函数,并且项目惯例参数(`fatal_asserts`、`alloc_functions`、`free_functions`)使分析看到自定义断言处理程序和分配器包装器与 CLI 标志相同。惯例注册是每个调用的:没有什么会泄漏到下一个请求中,长-lived 服务器进程。
退出代码为 `1` 时报告发现结果,为 `0` 时清洁 — 适用于 CI 门控。
### 编辑器和代码扫描集成(通过 SARIF)
SARIF 2.1.0 输出今天起与标准工具一起使用 — 不需要我们自己的插件:
**VS Code.** 安装
[SARIF Viewer](https://marketplace.visualstudio.com/items?itemName=MS-SarifVSCode.sarif-viewer)
扩展(Microsoft),然后:
```bash
codeskeptic src/ --sarif findings.sarif
code findings.sarif # 或:通过 SARIF 查看器面板打开
```
发现结果出现在结果面板中;单击一个会跳转到源代码行,CodeSkeptic 的数据流跟踪显示为**相关位置**(每个发现结果背后的分配/释放/空赋值链可逐步导航)。
**GitHub 代码扫描.** 将相同的文件上传到 CI,发现结果出现在存储库的安全选项卡和 PR 注释中。完整的 工作流程 — 构建 CodeSkeptic,生成项目的编译命令数据库,分析,上传:
```yaml
# .github/workflows/codeskeptic.yml
name: CodeSkeptic
on: [push, pull_request]
permissions:
contents: read
security-events: write # required to upload SARIF to code scanning
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build CodeSkeptic
run: |
sudo apt-get update
sudo apt-get install -y llvm-18-dev libclang-18-dev cmake
cmake -B cs-build -DCMAKE_BUILD_TYPE=Release
cmake --build cs-build -j
- name: Generate your project's compile_commands.json
run: cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Analyze -> SARIF
run: ./cs-build/src/codeskeptic . --build-path build --sarif codeskeptic.sarif || true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: codeskeptic.sarif
```
(`|| true` 因为 CodeSkeptic 在发现结果时退出 1;代码扫描执行自己的门控。)
对于可共享的、无工具查看相同发现结果的视图,请使用 `--html` —
一个自包含的文件,带有过滤器和源代码跟踪。
## 契约(`cs:`)
CodeSkeptic 的规则推断函数的作用。契约固定了它**应该**的作用 — 契约是一个**声明的函数摘要**,以结构化注释编写,并由相同的数据流检查:
```c
// cs: requires p != null
// cs: ensures return != null if n != 0
// cs: owns(cfg)
char *find_config(struct Cfg *cfg, const char *p, int n);
```
当 AI 生成的(或人类)代码后来破坏承诺时,声明和实际行为之间的差异是在确切的行上发现的:
当前强制执行的内容:
- `requires p != null` / `requires n != 0` — **假设/保证**:
在调用者内部,参数被信任(契约携带证明负担);每个可见的调用站点都针对调用者的自身数据流状态进行检查。`f(NULL)` 进入非空契约是一个错误,可能为空的参数是一个警告,保护参数是沉默的。关系逃逸(`requires p != null || n <= 0`)被尊重。
- `ensures return != null` / `!= 0` — 检查推断的
返回摘要;受保护的形式(`... if n != 0`)按路径在每个返回语句处检查,违反返回结果带有数据流跟踪。
- `owns(p)` / `borrows(p)` — 检查推断的参数
效果:`owns` 带有可证明的只读正文和`borrows`带有释放正文是违规(泄漏和双重释放形状)。
- `cs:policy no-absolute-paths` — AST 级模式禁止:硬编码的绝对路径在字符串字面量中是错误。按文件激活注释,或通过 `.codeskeptic.conf`(或 `--policy`)启用。
失败语义是有意的:被破坏的裸 `cs:` 契约是一个
**错误**(CI 失败 — 摩擦是重点;更改 `cs:` 行在审查 **是** 审计更改意图的跟踪)。`cs:ai` 标记机器提出的契约:相同的语法,违规降级为警告 — 工具提出,人类通过删除三个字符采用 — 工具提出,人类采用删除三个字符。不可解析的行是 `contract-syntax` 错误,检查可识别。
完整的设计(语法,检查子集,失败语义,推理)都在 [CONTRACTS.md](CONTRACTS.md) 中。
## 路线图
参见 [ROADMAP.md](ROADMAP.md) 中的完整
评估和分阶段路线图:SARIF 输出,抑制/基线
支持,基准驱动的精度测量,增量分析,
以及用于代理集成的 MCP 服务器模式。
## 许可
Apache License 2.0 — 参见 [LICENSE](LICENSE).
Apache License 2.0 — 参见 [LICENSE](LICENSE).
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器