mcpField
F
ffi-code-review

ffi-code-review

github.com/existential-birds测试与质量8566 次安装11 周前更新
代码仓库

关于 ffi-code-review

ffi-code-review 是专注于 Rust 外部函数接口的代码审查技能。它检查 crate 与构建配置、ABI 和类型布局、指针与所有权处理及回调,并要求完成验证关卡后再报告问题。

主要功能

  1. 01检查 Cargo.toml 与 build.rs
  2. 02核查 ABI 声明和符号导出
  3. 03审查 FFI 类型布局与字符串处理
  4. 04评估所有权、回调和 panic 安全
  5. 05通过验证关卡后报告问题

适用场景

  1. 01审查 Rust 到 C 库的绑定
  2. 02检查 FFI 更改中的内存与 ABI 风险
  3. 03评估构建脚本和生成的绑定

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill ffi-code-review

安装后在 Claude Code 中输入/ffi-code-review确认已加载。

附带文件references/safety-patterns.mdreferences/type-mapping.md

详细介绍

FFI 代码审查

审查工作流

  1. 检查 Cargo.toml —— 记录 Rust 版本(2024 版对 extern 块和 unsafe 属性有破坏性变更)、build-dependencies(bindgen、cc、pkg-config)、crate-type(cdylib、staticlib)以及 links 键
  2. 检查 build.rs —— 验证链接指令(cargo:rustc-link-lib、cargo:rustc-link-search)、bindgen 配置和 C 源代码编译
  3. 检查 extern 块 —— 验证调用约定、符号声明和安全注解
  4. 检查类型布局 —— 所有跨越 FFI 边界的类型都必须是 #[repr(C)] 或原始 FFI 类型
  5. 检查字符串和指针处理 —— CStr/CString 的使用、空指针检查、所有权转移
  6. 检查回调 —— extern "C" fn 指针,以及 FFI 边界上的 panic 安全性
  7. 关卡 —— 在报告前完成下方的关卡;不要因为“内部验证”而跳过步骤

关卡

按顺序完成。每个问题都必须通过关卡 4后,才能报告发现项。

关卡 1 — Crate 上下文(磁盘上的文件)
通过条件: 打开已审查 crate 的 Cargo.toml(如适用,使用工作区成员路径),并记录 edition =,以及与此 FFI 相关的 links、crate-type 或 build-dependencies。
禁止合理化: 与版本相关的发现项(unsafe extern "C" {}、#[unsafe(no_mangle)] 等)需要满足此条件——如果 edition 不是 2024,就不要标记仅适用于 2024 版的要求。

关卡 2 — 链接和绑定来源
通过条件: 如果 crate 链接了原生代码或使用 bindgen/pkg-config,则打开 build.rs(或已检入代码库的绑定入口)。如果没有 build.rs,则说明绑定是手写的,并审查相应的 extern / include! 位置。
产物: 至少一个已打开文件的路径(例如 build.rs、src/ffi.rs 或通过 include! 引用的 OUT_DIR 绑定)。

关卡 3 — 代码证据
通过条件: 每项计划报告的发现都必须有对应的 [FILE:LINE],证据须来自完整阅读过的函数/代码块,而不只是差异代码块或部分代码片段。

关卡 4 — 报告前协议
通过条件: 已加载并应用 review-verification-protocol 技能,其中包括针对 repr(C)、安全注释、所有权/回调或大量使用 bindgen 的代码的FFI 专项验证。

输出格式

按以下格式报告发现项:

[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.

快速参考

问题类型参考资料
C 到 Rust 的类型映射、repr(C) 布局、枚举、不透明类型references/type-mapping.md
安全封装、所有权转移、回调、build.rs、测试references/safety-patterns.md

审查清单

extern 块和调用约定
  • 外部函数声明使用 extern "C"(显式声明,而非裸 extern)
  • 2024 版:extern "C" {} 块写作 unsafe extern "C" {}
  • 暴露给 C 的函数使用 extern "C" fn(而非默认的 Rust 调用约定)
  • 调用约定与外部库匹配(Win32 API 使用 "C"、"system")
  • #[link(name = "...")] 指定正确的库名称
  • 静态链接时使用 #[link(name = "...", kind = "static")]
符号管理
  • 导出的函数使用 #[no_mangle] 保留符号名称
  • 2024 版:#[no_mangle] 写作 #[unsafe(no_mangle)]
  • 2024 版:#[export_name = "..."] 写作 #[unsafe(export_name = "...")]
  • Rust 名称与 C 符号不同时,使用 #[link_name = "..."]
  • 导出项为 pub(只有公开的 #[no_mangle] 符号会出现在库输出中)
类型布局
  • 每个跨越 FFI 的结构体/联合体都使用 #[repr(C)] —— Rust 的默认布局未定义
  • 基本类型使用 std::ffi / std::os::raw 中的对应类型(c_int、c_char、c_void)
  • C 使用 int 时,不要直接使用 i32 —— 应使用 c_int(宽度因平台而异)
  • 对于 __be32 等特殊 C 类型,应使用字节数组([u8; 4]),而非 Rust 整数
  • 跨越 FFI 的枚举使用 #[repr(C)] 或带显式判别值的 #[repr(u8)]/#[repr(u32)]
  • C 风格的位标志枚举使用整数类型的新类型包装(或 bitflags crate),而非 Rust 枚举
  • 对应可能新增值的 C 枚举的 Rust 枚举上使用 #[non_exhaustive]
字符串处理
  • C 字符串使用 CStr(借用)或 CString(拥有),绝不使用 &str 或 String
  • 检查 CString::new() 的结果中是否包含内部空字节(遇到 \0 时会返回 Err)
  • 由 CString 派生的 *const c_char 指针通过 .as_ptr() 获取后,确保 CString 的生命周期长于该指针的使用
  • 在 unsafe 中使用 CStr::from_ptr() 验证传入的 *const c_char
  • 不要假定 C 字符串是有效的 UTF-8 —— 使用会返回 Result 的 to_str()
  • OS 路径使用 OsStr/OsString 和 CStr,而非 &str
所有权与内存分配
  • 明确所有权约定:由谁分配、由谁释放
  • Rust 分配的内存由 Rust 释放(Box::from_raw),C 分配的内存由 C 释放
  • 正确配对用于堆内存转移的 Box::into_raw / Box::from_raw
  • 将数组传给 C 时使用 Vec::into_raw_parts(指针 + 长度 + 容量)
  • 为每个传给 C 的不透明 Rust 类型提供析构函数
  • 不要让 Drop 作用于 C 分配的内存(反之亦然)
回调
  • 回调类型使用 extern "C" fn(...),而非闭包或 fn(...)
  • 回调使用 std::panic::catch_unwind,防止 panic 展开穿越 FFI
  • 回调上下文通过 *mut c_void 传递,并在调用处安全地重建
  • 对可空函数指针使用 Option<extern "C" fn(...)>(空值优化)
Bindgen 与构建脚本
  • 检查 Bindgen 的输出是否正确(自动生成的类型可能需要调整)
  • 使用 -sys crate 模式提供原始绑定,并使用单独的 crate 提供安全封装
  • 在 build.rs 中正确使用 cargo:rustc-link-lib 和 cargo:rustc-link-search
  • 在 Cargo.toml 中使用 links 键,避免同一原生库被重复链接
  • 按构建目标生成平台专属绑定(而非只为单个平台生成后提交到代码库)
安全文档
  • 每个 unsafe 代码块都有解释不变量的 // SAFETY: 注释
  • 每个公开的 FFI 封装函数都记录安全要求
  • Edition 2024:unsafe fn 函数体在不安全操作周围使用显式的 unsafe {} 代码块

严重级别

严重(阻止合并)
  • 跨越 FFI 边界的类型缺少 #[repr(C)](内存布局未定义)
  • 字符串处理错误:需要 CStr/CString 时使用了 &str/String
  • 所有权混淆:使用 Rust 分配器释放 C 分配的内存(或反之)
  • 未使用 catch_unwind,导致 panic 展开穿越 FFI 边界
  • 将 Rust 枚举用于 C 位标志(无效判别值会导致未定义行为)
  • 需要 extern "C" fn 指针时传入了闭包
主要(应修复)
  • unsafe 代码块或公开 FFI 函数缺少安全文档
  • 解引用传入的 *const T / *mut T 前未检查空指针
  • C 使用指针之前 CString 已被释放(悬空指针)
  • 缺少 #[link(name = "...")],导致某些平台上链接失败
  • Edition 2024:extern 代码块未标记为 unsafe extern
  • Edition 2024:#[no_mangle] 未包裹在 #[unsafe(...)] 中
次要(建议修复)
  • C 的 int 使用了 i32 而非 c_int(大多数平台上正确,但不具可移植性)
  • 映射可扩展 C 枚举的枚举缺少 #[non_exhaustive]
  • 存在更易维护的 Bindgen 方案,却使用了冗长的手动绑定
  • 已提交到代码库的绑定缺少平台保护
信息性
  • 建议将原始绑定拆分到 -sys crate 中
  • 建议为不同的 *mut c_void 指针添加不透明包装类型
  • 建议对可空指针使用 Option<NonNull<T>>

有效模式(请勿标记)

  • edition 2024 中的 unsafe extern "C" {} —— 外部声明的正确形式
  • edition 2024 中的 #[unsafe(no_mangle)] —— 符号导出的正确形式
  • 用于可空回调的 Option<extern "C" fn(...)> —— 保证使用 niche 优化
  • 用于可空指针的 Option<NonNull<T>> —— 零成本可空指针模式
  • 用于不透明 C 类型的 *mut c_void —— 内部布局无关时的标准做法
  • 用 c_void 封装的不同空结构体,用于类型安全的不透明指针 —— 防止指针混淆
  • 使用编译期字面量调用 CStr::from_bytes_with_nul_unchecked —— 已知字面量以空字符结尾时安全
  • 使用 extern "C-unwind" 进行受控展开 —— 符合 RFC 2945
  • bindgen crate 中的 include!(concat!(env!("OUT_DIR"), "/bindings.rs")) —— 标准模式
  • 用于所有权转移的 Box::into_raw / Box::from_raw 配对 —— 配对使用时是正确模式

提交发现之前

报告任何问题之前,请按顺序完成 Gate 1-4;Gate 4 包含 review-verification-protocol 技能。

评论

支持 Markdown