Handle Panics When Crossing FFI Boundaries

2026-08-30 浏览 (1)


id: ffi-04 original_id: P.UNS.FFI.04 level: P impact: CRITICAL clippy: panic_in_result_fn

Handle Panics When Crossing FFI Boundaries

Summary

Panics must not unwind across FFI boundaries. Use catch_unwind or mark functions as extern "C-unwind".

Rationale

  • Unwinding across C code is undefined behavior
  • C has no concept of Rust panics
  • Can corrupt C stack frames and cause crashes
  • Even with panic=abort, still UB to attempt unwinding in extern "C"

Bad Example

// DON'T: Allow panics to escape to C
#[no_mangle]
pub extern "C" fn callback(data: *const u8, len: usize) -> i32 {
    let slice = unsafe { std::slice::from_raw_parts(data, len) };

    // If this panics, UB occurs!
    let sum: i32 = slice.iter().map(|&x| x as i32).sum();

    // If this panics due to overflow in debug, UB!
    process(sum)
}

// DON'T: Unwrap in extern functions
#[no_mangle]
pub extern "C" fn parse_config(path: *const c_char) -> i32 {
    let path = unsafe { CStr::from_ptr(path) };
    let config = std::fs::read_to_string(path.to_str().unwrap()).unwrap();  // Can panic!
    0
}

Good Example

use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};

// DO: Catch panics at FFI boundary
#[no_mangle]
pub extern "C" fn safe_callback(data: *const u8, len: usize) -> c_int {
    let result = catch_unwind(AssertUnwindSafe(|| {
        if data.is_null() || len == 0 {
            return -1;
        }

        let slice = unsafe { std::slice::from_raw_parts(data, len) };
        let sum: i32 = slice.iter().map(|&x| x as i32).sum();
        sum
    }));

    match result {
        Ok(value) => value,
        Err(_) => {
            // Log error, return error code
            eprintln!("Panic caught at FFI boundary");
            -1
        }
    }
}

// DO: Use Result-based API internally
#[no_mangle]
pub extern "C" fn parse_config(path: *const c_char) -> c_int {
    let result = catch_unwind(AssertUnwindSafe(|| -> Result<(), Box<dyn std::error::Error>> {
        let path = unsafe { CStr::from_ptr(path) }.to_str()?;
        let _config = std::fs::read_to_string(path)?;
        Ok(())
    }));

    match result {
        Ok(Ok(())) => 0,
        Ok(Err(e)) => {
            eprintln!("Error: {}", e);
            -1
        }
        Err(_) => {
            eprintln!("Panic in parse_config");
            -2
        }
    }
}

// DO: For Rust-calling-Rust across C, use "C-unwind"
#[no_mangle]
pub extern "C-unwind" fn rust_callback_can_unwind() {
    // This is OK to panic if called from Rust through C
    // The "C-unwind" ABI allows unwinding
    panic!("This is allowed");
}

FFI Error Handling Pattern

// Define error codes
const SUCCESS: c_int = 0;
const ERR_NULL_PTR: c_int = -1;
const ERR_INVALID_UTF8: c_int = -2;
const ERR_IO: c_int = -3;
const ERR_PANIC: c_int = -99;

// Thread-local for detailed error
thread_local! {
    static LAST_ERROR: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
}

fn set_error(msg: String) {
    LAST_ERROR.with(|e| *e.borrow_mut() = Some(msg));
}

#[no_mangle]
pub extern "C" fn get_last_error() -> *const c_char {
    LAST_ERROR.with(|e| {
        e.borrow().as_ref().map(|s| s.as_ptr() as *const c_char)
            .unwrap_or(std::ptr::null())
    })
}

Checklist

  • Does my extern "C" function use catch_unwind?
  • Am I avoiding unwrap/expect in FFI functions?
  • Do I return error codes for error conditions?
  • Have I considered using "C-unwind" for Rust-to-Rust through C?

Related Rules

  • ffi-08: Handle errors properly in FFI
  • safety-01: Panic safety

你可能感兴趣的文章

Do Not Expose Raw Pointers in Public APIs

Unsafe Code Authors Must Verify Safety Invariants

Ensure Consistent Data Layout for Custom Types

Add Safety Section in Docs for Public Unsafe Functions

Do Not Use Union Variants Across Different Lifetimes

Use MaybeUninit for Uninitialized Memory

Ensure C-ABI Compatibility for Strings Between Rust and C

Do Not Dereference Pointers Cast to Misaligned Types

Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types

Choose Appropriate Data Layout for Struct/Tuple/Enum

  • 所属分类: AI
  • 本文标签: rust
  • 版权声明: 本文链接 https://seaxiang.com/blog/VGuvP5QJ