FFI Code Review
Review Workflow
- Check Cargo.toml -- Note Rust edition (2024 has breaking changes to extern blocks and unsafe attributes),
build-dependencies (bindgen, cc, pkg-config), crate-type (cdylib, staticlib), and links key
- Check build.rs -- Verify link directives (
cargo:rustc-link-lib, cargo:rustc-link-search), bindgen configuration, and C source compilation
- Check extern blocks -- Verify calling conventions, symbol declarations, and safety annotations
- Check type layout -- Every type crossing FFI must be
#[repr(C)] or a primitive FFI type
- Check string and pointer handling -- CStr/CString usage, null checks, ownership transfers
- Check callbacks --
extern "C" fn pointers, panic safety across FFI boundary
- Gates -- Complete Gates below before reporting; do not skip ahead on “internal verification”
Gates
Complete in order. Do not emit findings until Gate 4 passes for each issue.
Gate 1 — Crate context (on disk)
PASS when: You opened the reviewed crate’s Cargo.toml (workspace member path if applicable) and recorded edition =, plus any of links, crate-type, or build-dependencies that matter for this FFI.
Blocks rationalization: Edition-specific findings (unsafe extern "C" {}, #[unsafe(no_mangle)], etc.) require this — if edition is not 2024, do not flag 2024-only requirements.
Gate 2 — Linkage and binding sources
PASS when: If the crate links native code or uses bindgen/pkg-config, you opened build.rs (or the checked-in bindings entry point). If there is no build.rs, you stated that bindings are hand-written and reviewed those extern / include! sites.
Artifact: At least one path you opened (e.g. build.rs, src/ffi.rs, or OUT_DIR bindings via include!).
Gate 3 — Code evidence
PASS when: Every planned finding has a target [FILE:LINE] from a full function/block you read, not only diff hunks or partial snippets.
Gate 4 — Pre-report protocol
PASS when: You loaded and applied beagle-rust:review-verification-protocol, including FFI-Specific Verification for repr(C), safety comments, ownership/callbacks, or bindgen-heavy code.
Output Format
Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
Quick Reference
Review Checklist
extern Blocks and Calling Conventions
Symbol Management
Type Layout
String Handling
Ownership and Allocation
Callbacks
Bindgen and Build Scripts
Safety Documentation
Severity Calibration
Critical (Block Merge)
- Missing
#[repr(C)] on types crossing FFI boundary (undefined memory layout)
- Wrong string handling:
&str/String where CStr/CString required
- 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
extern "C" fn pointer required
Major (Should Fix)
- Missing safety documentation on
unsafe blocks or public FFI functions
- No null pointer check on incoming
*const T / *mut T before dereferencing
CString dropped before its pointer is used by C (dangling pointer)
- Missing
#[link(name = "...")] causing link failures on some platforms
- Edition 2024:
extern block not marked unsafe extern
- Edition 2024:
#[no_mangle] not wrapped in #[unsafe(...)]
Minor (Consider Fixing)
- Using
i32 instead of c_int for C int (correct on most platforms but not portable)
- Missing
#[non_exhaustive] on enums mapping to extensible C enumerations
- Verbose manual bindings where bindgen would be more maintainable
- Checked-in bindings without platform guards
Informational
- Suggestions to split raw bindings into a
-sys crate
- Suggestions to add opaque wrapper types for distinct
*mut c_void pointers
- Suggestions to use
Option<NonNull<T>> for nullable pointers
Valid Patterns (Do NOT Flag)
unsafe extern "C" {} in edition 2024 -- correct form for foreign declarations
#[unsafe(no_mangle)] in edition 2024 -- correct form for symbol export
Option<extern "C" fn(...)> for nullable callbacks -- niche optimization guaranteed
Option<NonNull<T>> for nullable pointers -- zero-cost nullable pointer pattern
*mut c_void for opaque C types -- standard when internal layout is irrelevant
- Distinct empty structs wrapping
c_void for type-safe opaque pointers -- prevents pointer confusion
CStr::from_bytes_with_nul_unchecked with compile-time literal -- safe when literal is known null-terminated
extern "C-unwind" for controlled unwinding -- valid per RFC 2945
include!(concat!(env!("OUT_DIR"), "/bindings.rs")) in bindgen crates -- standard pattern
Box::into_raw / Box::from_raw pairs for ownership transfer -- correct pattern when paired
Before Submitting Findings
Complete Gates 1-4 in order before reporting any issue; Gate 4 incorporates beagle-rust:review-verification-protocol.