ffi-code-review について
ffi-code-reviewは、Rustの外部関数インターフェースに特化したコードレビュー用スキルです。crateやビルド設定、ABIと型レイアウト、ポインターや所有権、コールバックを確認し、検証ゲートを通過してから指摘を報告します。
主な機能
- 01Cargo.tomlとbuild.rsを確認
- 02ABI宣言とシンボルのエクスポートを点検
- 03FFIの型レイアウトと文字列処理をレビュー
- 04所有権、コールバック、panic安全性を評価
- 05検証ゲート後に指摘を報告
活用シーン
- 01RustからCライブラリへのバインディングをレビュー
- 02FFI変更のメモリとABIのリスクを確認
- 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 コードレビュー
レビューワークフロー
- Cargo.toml を確認する -- Rust エディション(2024 では extern ブロックと unsafe 属性に破壊的変更があります)、
build-dependencies(bindgen、cc、pkg-config)、crate-type(cdylib、staticlib)、およびlinksキーを確認します - build.rs を確認する -- リンクディレクティブ(
cargo:rustc-link-lib、cargo:rustc-link-search)、bindgen の設定、C ソースのコンパイルを確認します - extern ブロックを確認する -- 呼び出し規約、シンボル宣言、安全性注釈を確認します
- 型レイアウトを確認する -- FFI を介するすべての型は、
#[repr(C)]またはプリミティブな FFI 型でなければなりません - 文字列とポインターの扱いを確認する -- CStr/CString の使用、null チェック、所有権の移転を確認します
- コールバックを確認する --
extern "C" fnポインター、FFI 境界を越える際のパニック安全性を確認します - ゲート -- 報告する前に、以下のゲートを完了します。「内部検証」で先に進まないでください
ゲート
順番に完了してください。各問題についてゲート 4に合格するまで、指摘事項を出力しないでください。
ゲート 1 — クレートのコンテキスト(ディスク上)
合格条件: レビュー対象クレートの Cargo.toml(該当する場合はワークスペースメンバーのパス)を開き、edition = と、この FFI に関係する links、crate-type、build-dependencies の項目を記録していること。
根拠のない指摘を防ぐ条件: エディション固有の指摘(unsafe extern "C" {}、#[unsafe(no_mangle)] など)には、この確認が必要です。edition が 2024 でない場合、2024 のみの要件を指摘しないでください。
ゲート 2 — リンクとバインディングのソース
合格条件: クレートがネイティブコードをリンクするか、bindgen/pkg-config を使用する場合、build.rs(またはチェックイン済みのバインディングのエントリポイント)を開いていること。build.rs がない場合は、バインディングが手書きであることを明記し、該当する extern / include! の箇所をレビューしていること。
成果物: 開いたファイルのパスを少なくとも 1 つ記録していること(例: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 型には、Rust の整数型ではなくバイト配列([u8; 4])を使用していること - FFI 境界を越える列挙型には、
#[repr(C)]または明示的な判別値を指定した#[repr(u8)]/#[repr(u32)]を使用していること - C スタイルのビットフラグ列挙型には、Rust の列挙型ではなく整数型をラップした新しい型(または
bitflagsクレート)を使用していること - 新しい値が追加される可能性のある C 列挙型を表す列挙型に
#[non_exhaustive]を付けていること
文字列の扱い
- C 文字列には
&strやStringではなく、CStr(借用)またはCString(所有)を使用していること -
CString::new()の結果について、内部にヌルバイトがないか確認していること(\0があるとErrを返します) -
.as_ptr()で導出した*const c_charポインターを使用する間、CStringが存続していること - 受け取った
*const c_charを、unsafe内でCStr::from_ptr()を使って検証していること - C 文字列が有効な UTF-8 であると仮定していないこと —
Resultを返すto_str()を使用します - OS パスには
&strではなく、OsStr/OsStringとCStrを使用していること
所有権とメモリ割り当て
- 所有権の契約が明確であること:誰が割り当て、誰が解放するか
- Rust が割り当てたメモリは Rust が解放し(
Box::from_raw)、C が割り当てたメモリは C が解放すること - ヒープの受け渡しで
Box::into_rawとBox::from_rawを正しく対にして使用していること - 配列を C に渡す際は
Vec::into_raw_partsを使用していること(ポインター+長さ+容量) - C に渡すすべての不透明な Rust 型にデストラクター関数を公開していること
- C が割り当てたメモリに対して
Dropが実行されないこと(また、その逆も同様)
コールバック
- コールバック型にクロージャや
fn(...)ではなく、extern "C" fn(...)を使用していること - FFI 境界を越えてパニックが巻き戻らないよう、コールバックで
std::panic::catch_unwindを使用していること - コールバックのコンテキストを
*mut c_voidとして渡し、呼び出し箇所で安全に復元していること - null 許容の関数ポインターに
Option<extern "C" fn(...)>を使用していること(ニッチ最適化)
Bindgen とビルドスクリプト
- Bindgen の出力をレビューして正しさを確認していること(自動生成された型には調整が必要な場合があります)
- 生のバインディングには
-sysクレートのパターンを使用し、安全なラッパーには別のクレートを使用していること -
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を使用している - 所有権が混乱している:C が割り当てたメモリを Rust のアロケーターで解放している(またはその逆)
catch_unwindを使わずに、パニックが FFI 境界を越えて巻き戻る- C のビットフラグに Rust の列挙型を使用している(無効な判別値は未定義動作を引き起こします)
extern "C" fnポインターが必要な箇所にクロージャを渡している
大(修正を推奨)
unsafeブロックまたは公開 FFI 関数に安全性に関するドキュメントがない- 受け取った
*const T/*mut Tを参照外しする前に null ポインターをチェックしていない - C がポインターを使用する前に
CStringが破棄される(ダングリングポインター) #[link(name = "...")]がなく、一部のプラットフォームでリンクに失敗する- Edition 2024:
externブロックにunsafe externが付いていない - Edition 2024:
#[no_mangle]が#[unsafe(...)]で囲まれていない
小(修正を検討)
- C の
intにc_intではなくi32を使用している(ほとんどのプラットフォームでは正しいものの、移植性がありません) - 拡張可能な C 列挙型に対応する列挙型に
#[non_exhaustive]がない - 保守性を高められる Bindgen を使わず、冗長な手動バインディングを使用している
- プラットフォームガードなしでバインディングをチェックインしている
情報
- 生のバインディングを
-sysクレートに分割する提案 - 異なる
*mut c_voidポインターに不透明なラッパー型を追加する提案 - null 許容ポインターに
Option<NonNull<T>>を使用する提案
有効なパターン(指摘しないでください)
- エディション 2024 での
unsafe extern "C" {}-- 外部宣言の正しい形式です - エディション 2024 での
#[unsafe(no_mangle)]-- シンボルエクスポートの正しい形式です - null 許容コールバックに対する
Option<extern "C" fn(...)>-- ニッチ最適化が保証されています - null 許容ポインターに対する
Option<NonNull<T>>-- ゼロコストの null 許容ポインターパターンです - 不透明な C 型に対する
*mut c_void-- 内部レイアウトが重要でない場合の標準的な方法です - 型安全な不透明ポインターのために
c_voidをラップする異なる空構造体 -- ポインターの取り違えを防ぎます - コンパイル時リテラルを使った
CStr::from_bytes_with_nul_unchecked-- リテラルが null 終端されていると分かっている場合は安全です - 制御された巻き戻しに対する
extern "C-unwind"-- RFC 2945 により有効です - bindgen クレートでの
include!(concat!(env!("OUT_DIR"), "/bindings.rs"))-- 標準的なパターンです - 所有権移転に対する
Box::into_raw/Box::from_rawの組み合わせ -- 対になっている場合の正しいパターンです
指摘を報告する前に
問題を報告する前に、ゲート 1~4 を順番に完了してください。ゲート 4 には review-verification-protocol スキルが含まれています。