Skills ffi-code-review
Reviews Rust FFI code for type safety, memory layout compatibility, string handling, callback patterns, and unsafe boundary correctness. Use when reviewing extern blocks, #[repr(C)] types, bindgen output, or code calling C/C++ libraries.
install
source · Clone the upstream repo
git clone https://github.com/openclaw/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/anderskev/ffi-code-review" ~/.claude/skills/clawdbot-skills-ffi-code-review && rm -rf "$T"
manifest:
skills/anderskev/ffi-code-review/SKILL.mdsource content
FFI Code Review
Review Workflow
- Check Cargo.toml -- Note Rust edition (2024 has breaking changes to extern blocks and unsafe attributes),
(bindgen, cc, pkg-config),build-dependencies
(crate-type
,cdylib
), andstaticlib
keylinks - Check build.rs -- Verify link directives (
,cargo:rustc-link-lib
), bindgen configuration, and C source compilationcargo:rustc-link-search - Check extern blocks -- Verify calling conventions, symbol declarations, and safety annotations
- Check type layout -- Every type crossing FFI must be
or a primitive FFI type#[repr(C)] - Check string and pointer handling -- CStr/CString usage, null checks, ownership transfers
- Check callbacks --
pointers, panic safety across FFI boundaryextern "C" fn - Verify before reporting -- Load
before submitting findingsbeagle-rust:review-verification-protocol
Output Format
Report findings as:
[FILE:LINE] ISSUE_TITLE Severity: Critical | Major | Minor | Informational Description of the issue and why it matters.
Quick Reference
| Issue Type | Reference |
|---|---|
| C-to-Rust type mapping, repr(C) layout, enums, opaque types | references/type-mapping.md |
| Safe wrappers, ownership transfer, callbacks, build.rs, testing | references/safety-patterns.md |
Review Checklist
extern Blocks and Calling Conventions
- Foreign function declarations use
(explicit, not bareextern "C"
)extern - Edition 2024:
blocks written asextern "C" {}unsafe extern "C" {} - Functions exposed to C use
(not default Rust calling convention)extern "C" fn - Calling convention matches the foreign library (
,"C"
for Win32 API)"system" -
specifies the correct library name#[link(name = "...")] -
used when statically linking#[link(name = "...", kind = "static")]
Symbol Management
- Exported functions use
to preserve symbol names#[no_mangle] - Edition 2024:
written as#[no_mangle]#[unsafe(no_mangle)] - Edition 2024:
written as#[export_name = "..."]#[unsafe(export_name = "...")] -
used when Rust name differs from C symbol#[link_name = "..."] - Exported items are
(only publicpub
symbols appear in library output)#[no_mangle]
Type Layout
- Every struct/union crossing FFI has
-- Rust's default layout is undefined#[repr(C)] - Primitive types use
/std::ffi
equivalents (std::os::raw
,c_int
,c_char
)c_void - No bare
where C usesi32
-- useint
(width varies by platform)c_int - Quirky C types like
use byte arrays (__be32
), not Rust integers[u8; 4] - Enums crossing FFI use
or#[repr(C)]
/#[repr(u8)]
with explicit discriminants#[repr(u32)] - C-style bitflag enums use a newtype around an integer (or
crate), not a Rust enumbitflags -
on enums representing C enumerations that may gain new values#[non_exhaustive]
String Handling
- C strings use
(borrowed) orCStr
(owned), neverCString
or&strString -
result is checked for interior null bytes (returnsCString::new()
onErr
)\0 -
outlives anyCString
pointer derived from it via*const c_char.as_ptr() - Incoming
validated with*const c_char
insideCStr::from_ptr()unsafe - No assumption that C strings are valid UTF-8 -- use
which returnsto_str()Result - OS paths use
/OsStr
andOsString
, notCStr&str
Ownership and Allocation
- Clear ownership contract: who allocates, who frees
- Rust-allocated memory freed by Rust (
), C-allocated freed by CBox::from_raw -
/Box::into_raw
paired correctly for heap transfersBox::from_raw -
used when passing arrays to C (pointer + length + capacity)Vec::into_raw_parts - Destructor functions exposed for every opaque Rust type given to C
- No
running on C-allocated memory (and vice versa)Drop
Callbacks
- Callback types are
, not closures orextern "C" fn(...)fn(...) - Callbacks use
to prevent panics from unwinding across FFIstd::panic::catch_unwind - Callback context passed as
with safe reconstruction at call site*mut c_void -
used for nullable function pointers (niche optimization)Option<extern "C" fn(...)>
Bindgen and Build Scripts
- Bindgen output reviewed for correctness (auto-generated types may need adjustment)
-
crate pattern used for raw bindings, separate crate for safe wrappers-sys -
usesbuild.rs
andcargo:rustc-link-lib
correctlycargo:rustc-link-search -
key inlinks
prevents duplicate linking of the same native libraryCargo.toml - Platform-specific bindings generated per-build (not checked in for a single platform)
Safety Documentation
- Every
block has aunsafe
comment explaining invariants// SAFETY: - Every public FFI wrapper function documents safety requirements
- Edition 2024:
bodies use explicitunsafe fn
blocks around unsafe opsunsafe {}
Severity Calibration
Critical (Block Merge)
- Missing
on types crossing FFI boundary (undefined memory layout)#[repr(C)] - Wrong string handling:
/&str
whereString
/CStr
requiredCString - Ownership confusion: freeing C-allocated memory with Rust's allocator (or vice versa)
- Panic unwinding across FFI boundary without
catch_unwind - Using Rust enum for C bitflags (invalid discriminant = undefined behavior)
- Passing closure where
pointer requiredextern "C" fn
Major (Should Fix)
- Missing safety documentation on
blocks or public FFI functionsunsafe - No null pointer check on incoming
/*const T
before dereferencing*mut T
dropped before its pointer is used by C (dangling pointer)CString- Missing
causing link failures on some platforms#[link(name = "...")] - Edition 2024:
block not markedexternunsafe extern - Edition 2024:
not wrapped in#[no_mangle]#[unsafe(...)]
Minor (Consider Fixing)
- Using
instead ofi32
for Cc_int
(correct on most platforms but not portable)int - Missing
on enums mapping to extensible C enumerations#[non_exhaustive] - Verbose manual bindings where bindgen would be more maintainable
- Checked-in bindings without platform guards
Informational
- Suggestions to split raw bindings into a
crate-sys - Suggestions to add opaque wrapper types for distinct
pointers*mut c_void - Suggestions to use
for nullable pointersOption<NonNull<T>>
Valid Patterns (Do NOT Flag)
in edition 2024 -- correct form for foreign declarationsunsafe extern "C" {}
in edition 2024 -- correct form for symbol export#[unsafe(no_mangle)]
for nullable callbacks -- niche optimization guaranteedOption<extern "C" fn(...)>
for nullable pointers -- zero-cost nullable pointer patternOption<NonNull<T>>
for opaque C types -- standard when internal layout is irrelevant*mut c_void- Distinct empty structs wrapping
for type-safe opaque pointers -- prevents pointer confusionc_void
with compile-time literal -- safe when literal is known null-terminatedCStr::from_bytes_with_nul_unchecked
for controlled unwinding -- valid per RFC 2945extern "C-unwind"
in bindgen crates -- standard patterninclude!(concat!(env!("OUT_DIR"), "/bindings.rs"))
/Box::into_raw
pairs for ownership transfer -- correct pattern when pairedBox::from_raw
Before Submitting Findings
Load and follow
beagle-rust:review-verification-protocol before reporting any issue.