tanzercakir-commits/CodeSkeptic
GitHub: tanzercakir-commits/CodeSkeptic
基于 Clang LibTooling 的 C/C++ 路径敏感静态分析器,通过 CFG 数据流分析捕获编译器遗漏的空指针、泄漏、溢出等缺陷并附带追踪记录。
Stars: 1 | Forks: 0
# CodeSkeptic
[](https://github.com/tanzercakir-commits/CodeSkeptic/actions/workflows/ci.yml)
[](LICENSE)
[](https://github.com/tanzercakir-commits/CodeSkeptic/releases)
[](https://en.cppreference.com/w/cpp/17)

[](docs/windows-support.md)
[](https://github.com/tanzercakir-commits/CodeSkeptic/pulls)
真实的 C/C++ bug,确定性的数据流追踪,低噪声的 PR 门控。
CodeSkeptic 是一款基于 Clang
LibTooling 构建的、精度优先的静态分析器。它执行基于 CFG 的前向数据流分析——而不仅仅是
AST 模式匹配——因此它可以对*路径*进行推理:指针在解引用时的
状态是什么,分配是否在每条
路径上都被释放了,到达除法操作的路径上除数是否可能为零。只有当数据流证明了某件事时,它才会发声,并且每个
发现都带有证明它的追踪记录。
其长期目标是成为一个快速、可嵌入的 **AI 辅助开发的语义验证层**:一个位于
代码生成循环内部的分析器,在几毫秒内重新检查每次编辑,并
返回带有数据流追踪的、机器可读的发现结果。
## 快速开始
**二进制文件** —— Linux x86_64(macOS arm64:`codeskeptic-darwin-arm64.tar.gz`),
无需安装 LLVM;tarball 捆绑了 Clang 头文件和所有
非 glibc 库,并且每个版本在发布前都会在干净的容器中进行冒烟测试:
```
curl -sL https://github.com/tanzercakir-commits/CodeSkeptic/releases/latest/download/codeskeptic-linux-x86_64.tar.gz | tar xz
./codeskeptic-v*/bin/codeskeptic path/to/your.c
```
**Docker** —— 完全无需安装:
```
docker run --rm -v "$PWD:/work" ghcr.io/tanzercakir-commits/codeskeptic src/ --sarif out.sarif
```
**CI** —— [打包好的 action](action.yml),默认仅报告模式:
```
- uses: tanzercakir-commits/CodeSkeptic@v0.4.0
with: { path: src/, build-path: build }
```
### 从源码构建
Linux (Ubuntu 24.04) —— 从克隆到生成第一份报告只需四条命令(这个
精确的代码块会由 CI 在干净的 runner 上执行,因此它保持可靠):
```
sudo apt-get update && sudo apt-get install -y llvm-20-dev libclang-20-dev clang-20 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
./build/src/codeskeptic docs/demo.c
```
最后那条命令标记出了编译器在
[`docs/demo.c`](docs/demo.c) 中直接放行的问题——一个未检查的 `getenv`,一个相乘后超出 `int` 范围的 `atoi` 结果,一个在错误路径上泄漏的工作缓冲区——这些全都没有被 `gcc -O2 -Wall -Wextra -Woverflow` 捕获。发现结果如下:
```
demo.cpp:4:13 [error] use-after-free: Use after free: 'p' is dereferenced after being freed
-> demo.cpp:2:5 'p' allocated here
-> demo.cpp:3:5 'p' freed here
demo.cpp:9:12 [warning] div-by-zero: Possible division by zero: 'z' may be zero on some paths
-> demo.cpp:7:5 'z' assigned zero here
```
macOS (Homebrew):`brew install llvm cmake ninja`,然后执行相同的
`cmake` + 构建步骤(会自动找到 LLVM)。要求 CMake >=
3.20,一个 C++17 编译器,LLVM/Clang 开发库(已使用 LLVM 18
和 20 测试)。Windows [已有计划,但尚未实现](docs/windows-support.md)。
在实际项目中,将其指向你的编译数据库,即可获得带有可点击数据流追踪的离线 HTML 报告:
```
codeskeptic src/ --build-path build --report-paths $PWD/src --html report.html
```


后续步骤:完整的[用法参考](docs/usage.md) ·
[在大约一小时内针对*你的*代码进行评估](docs/evaluate.md) ·
[CI、编辑器和 agent 集成](docs/integrations.md)。
## 在真实代码上的验证
合成基准测试奖励模式覆盖率;而真实代码库则会对
每一个误报进行惩罚。下面的每个项目都是使用其自己的构建
系统构建的,并从其编译数据库进行分析,每一个存留的
发现都经过了人工分拣。所有数字均来自同一个分析器构建版本
(2026-07-12);“初始”列是在该项目首次尝试
扫描时报告的结果,时间在其暴露的误报家族被修复
之前。
| 项目 | 范围 | 初始 → 现在 | 人工验证的真实 bug |
|---------|-------|--------------:|------------------------|
| [systemd](https://github.com/systemd/systemd) | 494 个文件 (basic/core/shared) | 414 → **53** | 3 个刻意的泄漏型惯用法,已记录 |
| [shadPS4](https://github.com/shadps4-emu/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](https://github.com/libgit2/libgit2) | 168 个文件 | 149 → **44** | **11 个已确认的 OOM 路径泄漏**(单一问题类别,已起草报告) |
| [llama.cpp](https://github.com/ggml-org/llama.cpp) | 完整构建 | 511 → **25** | 分拣进行中 |
| [rtp2httpd](https://github.com/stackia/rtp2httpd) | 2.7万行代码 | 4 → **3** | **1 个已确认的 NULL 契约 bug**,已起草报告 |
| [NASA fprime](https://github.com/nasa/fprime) | 216 个文件 | 10 → **0** | 干净(配合 `--fatal-asserts SwAssert` 声明 F´ 的断言处理器) |
| [abseil-cpp](https://github.com/abseil/abseil-cpp) | LTS tag | 12 → **4** | — |
| [Catch2](https://github.com/catchorg/Catch2) | 完整构建 | **0** | 干净 |
那两个已合并的 shadPS4 修复正是本项目旨在捕获的典型的
看着对、读着错的 bug:一个带 `&&` 的 null
检查,而它原本需要 `||`,导致防护失效并
解引用了它刚刚检查过的指针
([#4703](https://github.com/shadps4-emu/shadPS4/pull/4703));以及一个
`ENOMEM` 路径在没有 `return` 的情况下落空,在下一行
解引用了 null 的 `FILE*`
([#4702](https://github.com/shadps4-emu/shadPS4/pull/4702))。真实的
bug,从编译数据库中发现,并被代码所有者接受。关于这些数字如何保持可信——惯用法配置
和误报家族处理流程——请参见
[docs/benchmarks.md](docs/benchmarks.md#reading-the-real-world-scan-numbers)。
## 它无法捕获的内容
CodeSkeptic 是精度优先的,而精度的代价是召回率。
请据此正确理解它的沉默:
- **干净的运行并不是安全证明。** 未知值在设计上保持沉默;
需要引擎尚不具备的推理能力(深度
别名分析、并发、任意算术)才能发现的 bug 不会被报告。
- **召回率是有界且经过测量的。** 在 Juliet 上,召回率范围从
0.496(释放后使用)到 0.052(整数溢出)——并且每一个
漏报案例都经过了*分类*:设计上的静默(浮点除法,
不透明的 `rand()` 源)与可解决的缺口,因此分母是
真实的。数字、分类及原因详见:[docs/benchmarks.md](docs/benchmarks.md)。
- **`memory-leak` 是唯一的一条嘈杂规则**(Juliet 精度为 0.714,而
其他规则为 1.000),也是常设的改进目标。请单独
评估它([方法](docs/evaluate.md))。
- 仅检查已覆盖的 bug 类别:[以下规则](#rules) —— 不包含代码风格,不包含
并发问题,也不包含广义上的未定义行为。
## 辅助使用,而非替代
保留你现有的防护网。CodeSkeptic 不会替代其中的任何一个——它增加了
一个低噪声、有追踪记录支撑的层,该层在其他工具
最薄弱的地方最为强大:对*新*更改(人工或 AI 生成)进行门控。
| 继续使用 | 它为你提供 | CodeSkeptic 增加了 |
|------------|--------------|------------------|
| 编译器警告 (`-Wall -Wextra`) | 廉价、通用的检查 | 警告无法看到的路径敏感型 bug |
| Sanitizers (ASan/UBSan/TSan) | 针对已执行路径的运行时证明 | 对测试从未运行的路径提供静态覆盖 |
| clang analyzer / gcc `-fanalyzer` | 广泛的启发式覆盖 | 确定性追踪,低噪声的 PR 增量门控([双向覆盖各不相同](docs/comparison.md)) |
| CodeQL 等 | 查询广度,安全分类法 | 在编辑循环内进行毫秒级的重新检查 |
| 模糊测试和测试 | 对真实执行的基准事实 | 代码运行*之前*的判定 |
## 规则
| 规则 | ID | 检测内容 |
|------|----|---------|
| 未初始化指针 | `uninit-ptr` | 解引用在某些路径上可能未赋值的指针 (CFG 数据流) |
| 内存泄漏 | `memory-leak` | 函数退出时的泄漏和重新赋值导致的泄漏,`malloc`/`calloc`/`strdup`/`free` 和 `new`/`delete` (基于 CFG 数据流及逃逸分析) |
| 重复释放 | `double-free` | 释放已经处于已释放状态的指针 (共享 memory-leak 数据流) |
| 释放后使用 | `use-after-free` | 解引用 (`*p`, `p->`, `p[i]`) 处于已释放状态的指针 (共享 memory-leak 数据流) |
| 除以零 | `div-by-zero` | 确定及可能的整数除法/取模为零,带有**分支条件细化** —— 理解 `if (z != 0)` 防护,因此受防护的除法不会产生误报 |
| Null 解引用 | `null-deref` | 确定及可能的 null 指针解引用;跟踪 `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) —— 包括 64 位操作数,隐式窄化为较小类型的结果 (`char r = d + 1`),以及不受信任的来源 (`int n = atoi(s); n * k`) |
| 契约验证 | `contract` | 违反声明的 `// cs:` 契约(前置条件、后置条件、所有权效应)——由推导摘要的同一数据流进行检查 |
| 策略执行 | `policy` | `cs:policy` 模式禁止;v1 版本提供 `no-absolute-paths`(硬编码的绝对路径字面量) |
该表格背后的机制——内部源召回、有针对性的
路径敏感性、过程间函数摘要——在
[docs/engine.md](docs/engine.md) 中有描述。
## 数据
两个维度,分开跟踪([完整方法论](docs/benchmarks.md)):
- **成熟代码 (NIST Juliet 1.3):** 六条规则中有五条的规则精度为 **1.000** (memory-leak 为 0.714);按 CWE 分类的召回率为 0.496 / 0.347 / 0.242 /
0.193 / 0.108 / 0.052 —— 这是带有已分类分母的下界(浮点
变体和不透明源属于设计上的静默,而不是缺口),每一次
改进都由棘轮式 CI 下限锁定。
- **初稿代码(任务维度):** 一个由对规则无感知的生成器编写的、包含 24 个程序的固化语料库,
对每个 PR 进行门控
(`tests/thesis_corpus/`):**在 9 个真正干净的程序中零误报,
捕获了 9/9 个在范围内的内存安全/溢出 bug** —— 范围外的漏报按文件记录,
而不是被隐藏。
每个数字都可以用一个脚本重现,并且 CI 强制执行锁定的
按 CWE 划分的下限、锁定的真实世界发现数量,以及每个 PR 上的自扫描
内部测试门控——下限只会随着深思熟虑且有据可查的
规则变更而联动改变。
**更低成本的 AI 审查,经测量:** 如果你已经在 diff 上循环使用 LLM,CodeSkeptic 的“提供发现结果而非原始代码”的输入是 **O(bugs),
而不是 O(lines)**——在真实大小的文件上可节省 6-59 倍的 token(在约 50 行以下没有节省;测量、方法和限制详见
[docs/token-ablation.md](docs/token-ablation.md))。
## 在你的工作流中
- **CI 门控 / PR 审查:** `scripts/review_diff.sh` 分析 base 和
head 分支,报告带有追踪记录的**增量**,并根据证据
阶梯退出(新的确定性发现会阻断;使用 `--gate warn` 进行仅报告模式的
采用——推荐在第一周使用)。SARIF 上传至 GitHub code
scanning;VS Code 通过 SARIF Viewer 渲染追踪。
[docs/integrations.md](docs/integrations.md)
- **Agents (MCP):** `codeskeptic --serve` 通过 stdio 暴露 `analyze` 工具
——结果以带有追踪的结构化 JSON 呈现,范围限定为
刚刚编辑过的函数。[docs/integrations.md](docs/integrations.md#mcp-server-agent-integration)
- **增量分析:** `--function`/`--lines` 在毫秒内重新检查单个函数;
`--summary-in` 在单文件运行中保留整个项目的知识。
[docs/usage.md](docs/usage.md#incremental-analysis)
- **语义回归门控:** 确定性的函数摘要作为*契约*进行 diff——
`NeverNull → MaybeNull` 是一个 `WEAKENED`(减弱)判定,它会在调用方
崩溃之前使 CI 失败。
[docs/integrations.md](docs/integrations.md#semantic-regression-gate-summary-diff)
- **基线与抑制:** 在无需首先修复历史记录的情况下,即可在遗留代码库上采用。
[docs/usage.md](docs/usage.md#baseline-workflow)
## 契约 (`cs:`)
规则推断函数做了什么;契约规定了它*应该*
做什么——声明为结构化注释,由相同的数据流进行检查:
```
// 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 生成(或人工编写)的代码随后打破了这个承诺时,声明行为与
实际行为之间的差异就是在破坏它的确切
行处的一个发现结果。违反契约即为错误(这种摩擦正是
目的所在);`cs:ai` 标记机器提议的契约,转而发出警告;
sidecar 文件用于覆盖你无法添加注解的第三方代码。完整的语法、
可检查的子集和失败语义:[CONTRACTS.md](CONTRACTS.md)。
## 文档导览
- [docs/usage.md](docs/usage.md) —— CLI 参考,配置,抑制,基线,增量分析
- [docs/evaluate.md](docs/evaluate.md) —— 在约 1 小时内在你自己的代码上进行评估
- [docs/integrations.md](docs/integrations.md) —— CI 门控,PR 审查,MCP,SARIF/VS Code
- [docs/benchmarks.md](docs/benchmarks.md) —— Juliet + AI 语料库方法论,防护措施
- [docs/comparison.md](docs/comparison.md) —— 对比主流工具,客观定位
- [docs/engine.md](docs/engine.md) —— 架构与分析机制
- [docs/token-ablation.md](docs/token-ablation.md) —— 6-59 倍 token 的测量
- [docs/reproduce.md](docs/reproduce.md) —— 每个已发布的数字,各仅需一条命令
- [profiles/](profiles/) —— 真实世界扫描背后的精确惯用法配置
- [CONTRACTS.md](CONTRACTS.md) —— `cs:` 契约语言
- [CONTRIBUTING.md](CONTRIBUTING.md) —— 构建,三名 CI 裁判机制,新手任务
- [ROADMAP.md](ROADMAP.md) —— 当前状态与近期计划([完整开发日志](docs/devlog/))
## 许可证
Apache License 2.0 —— 见 [LICENSE](LICENSE)。
标签:AI辅助编程, Bash脚本, C/C++, Clang LibTooling, MCP, 事务性I/O, 图数据库, 知识图谱, 请求拦截, 错误基检测, 静态代码分析