tanzercakir-commits/CodeSkeptic

GitHub: tanzercakir-commits/CodeSkeptic

基于 Clang LibTooling 的 C/C++ 路径敏感静态分析器,通过 CFG 数据流分析捕获编译器遗漏的空指针、泄漏、溢出等缺陷并附带追踪记录。

Stars: 1 | Forks: 0

# CodeSkeptic [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/tanzercakir-commits/CodeSkeptic/actions/workflows/ci.yml) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) [![Release](https://img.shields.io/github/v/release/tanzercakir-commits/CodeSkeptic?color=blue)](https://github.com/tanzercakir-commits/CodeSkeptic/releases) [![C++17](https://img.shields.io/badge/C%2B%2B-17-00599C.svg?logo=cplusplus)](https://en.cppreference.com/w/cpp/17) ![基于 Clang LibTooling 构建](https://img.shields.io/badge/Clang-LibTooling-262D3A.svg?logo=llvm) [![平台:Linux | macOS](https://img.shields.io/badge/platform-Linux%20%7C%20macOS-lightgrey.svg)](docs/windows-support.md) [![欢迎 PR](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 ``` ![CodeSkeptic 针对初稿文件的 HTML 报告](https://static.pigsec.cn/wp-content/uploads/repos/cas/5d/5d4f0aacdbddabe0f5a355ad8cd95a2c2ddd8f2ab4ea7373b2740d8e9418569c.png) ![发现结果的数据流追踪](https://raw.githubusercontent.com/tanzercakir-commits/CodeSkeptic/main/docs/img/trace.png) 后续步骤:完整的[用法参考](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, 图数据库, 知识图谱, 请求拦截, 错误基检测, 静态代码分析