Files
denyhosts/clamav/libclamav_rust/src/ffi_util.rs
2023-01-14 18:28:39 +08:00

309 lines
8.8 KiB
Rust

/*
* Various functions to ease working through FFI
*
* Copyright (C) 2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Authors: Scott Hutton
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
use std::{
ffi::{CStr, CString},
mem::ManuallyDrop,
os::raw::c_char,
};
/// Wraps a call to a "result-returning function", allowing it to specify an
/// error receiver and (optionally) result receiver.
///
/// If the `out` parameter is omitted, the function's return value will be
/// expected to be a pointer on success, or NULL if the function call returns an error.
///
/// If the `out` parameter is included, the function will return a `bool`
/// indicating success, and *either* populate the `out` input variable with the
/// result, or the `err` input variable with a pointer to an FFIError struct.
///
/// Example (on the Rust side)
/// ```
/// use frs_error::{frs_result, FFIError};
/// use num_traits::CheckedDiv;
///
/// pub fn checked_div<T>(numerator: T, denominator: T) -> Result<T, MyError>
/// where
/// T: CheckedDiv,
/// {
/// numerator
/// .checked_div(&denominator)
/// .ok_or(MyError::DivByZero)
/// }
///
/// #[no_mangle]
/// pub unsafe extern "C" fn checked_div_i64(
/// numerator: i64,
/// denominator: i64,
/// result: *mut i64,
/// err: *mut *mut FFIError,
/// ) -> bool {
/// frs_call!(out = result, err = err, checked_div(numerator, denominator))
/// }
/// ```
///
/// And from the C side:
/// ``` c
/// int64_t result;
/// FFIError *err = NULL;
///
/// if (checked_div_i64(10, 0, &result, &err)) {
/// return 0;
/// } else {
/// fprintf(stderr, "Error: %s\n", ffierror_fmt(err));
/// ffierror_free(err);
/// return 1;
/// }
/// ```
#[macro_export]
macro_rules! rrf_call {
(err = $err_out:ident, $($fn:ident).+( $($args:expr),* )) => {
if $err_out.is_null() {
panic!("{} is NULL", stringify!($err_out));
} else {
match $($fn).*( $($args),* ) {
Ok(()) => {
true
}
Err(e) => {
*$err_out = Box::into_raw(Box::new(e.into()));
false
}
}
}
};
(out = $result_out:ident, err=$err_out:ident, $($fn:ident).+( $($args:expr),* )) => {
if $err_out.is_null() {
panic!("{} is NULL", stringify!($err_out));
} else {
match $($fn).*( $($args),* ) {
Ok(result) => {
*$result_out = result;
true
}
Err(e) => {
*$err_out = Box::into_raw(Box::new(e.into()));
false
}
}
}
};
($fn:ident( err=$err_out:ident $(, $args:expr)* )) => {
if $err_out.is_null() {
panic!("{} is NULL", stringify!($err_out));
} else {
match $fn( $($args),* ) {
Ok(result) => {
Box::into_raw(Box::new(result))
// result as *const ::core::ffi::c_void
}
Err(e) => {
*$err_out = Box::into_raw(Box::new(e.into()));
std::ptr::null()
}
}
}
};
}
/// Consume the specified `Result<T,E>`, update output variables, and return true
/// (if Result::is_ok) or false (if Result::is_err).
//
/// The `Result`'s `E` must implement `std::error::Error`.
///
/// The `out` parameter is optional, and the Result's "ok" value will be ignored
/// if `out` is omitted.
///
/// Or for returning errors more explicitly without a function call:
/// ```
/// #[no_mangle]
/// pub unsafe extern "C" fn checked_div_i64(
/// numerator: i64,
/// denominator: i64,
/// out: *mut i64,
/// err: *mut *mut FFIError,
/// ) -> bool {
/// let div_result = checked_div(numerator, denominator);
///
/// // Do other things that examine `div_result`, but don't consume it
/// // ...
///
/// // Finally return
/// frs_result!(result_in = div_result, out = out, err = err)
/// }
/// ```
///
#[macro_export]
macro_rules! ffi_result {
(result_in=$result_in:ident, err=$err_out:ident) => {
if $err_out.is_null() {
panic!("{} is NULL", stringify!($err_out));
} else {
match $result_in {
Ok(_) => true,
Err(e) => {
*$err_out = Box::into_raw(Box::new(e.into()));
false
}
}
}
};
(result_in=$result_in:ident, out=$result_out:ident, err=$err:ident) => {
if $err.is_null() {
panic!("{} is NULL", stringify!($err));
} else {
match $result_in {
Ok(result) => {
*$result_out = result;
true
}
Err(e) => {
*$err = Box::into_raw(Box::new(e.into()));
false
}
}
}
};
}
/// Consume the specified error and update an output variable, returning false.
///
/// The given error must implement `std::error::Error`.
///
/// This macro is best used when specifically returning an error encountered
/// outside of wrapping a Result-returning function (such as input validation).
#[macro_export]
macro_rules! ffi_error {
(err=$err_out:ident, $err:expr) => {
if $err_out.is_null() {
panic!("{} is NULL", stringify!($err));
} else {
*$err_out = Box::into_raw(Box::new($err.into()));
false
}
};
}
/// A generic container for any error that implements `Into<std::error::Error>`
pub struct FFIError {
/// The contained error
error: Box<dyn std::error::Error>,
/// Cached formatted version of the error
c_string: Option<CString>,
}
impl FFIError {
pub(crate) fn get_cstring(&mut self) -> Result<&CStr, std::ffi::NulError> {
if self.c_string.is_none() {
self.c_string = Some(CString::new(format!("{}", self.error))?);
}
Ok(self.c_string.as_ref().unwrap().as_c_str())
}
}
impl<T: 'static + std::error::Error> From<T> for FFIError {
fn from(err: T) -> Self {
FFIError {
error: Box::new(err),
c_string: None,
}
}
}
/// Compute (and cache) a formatted error string from the provided [`FFIError`] pointer.
///
/// # Safety
///
/// `err` must not be NULL
#[no_mangle]
pub unsafe extern "C" fn ffierror_fmt(err: *mut FFIError) -> *const c_char {
assert!(!err.is_null());
let mut err: ManuallyDrop<Box<FFIError>> = ManuallyDrop::new(Box::from_raw(err));
match err.get_cstring() {
Ok(s) => s.as_ptr(),
Err(_) => CStr::from_bytes_with_nul_unchecked(b"<error string contains NUL>\0").as_ptr(),
}
}
/// Free a [`FFIError`] structure
///
/// # Safety
///
/// `err` must not be NULL
#[no_mangle]
pub unsafe extern "C" fn ffierror_free(err: *mut FFIError) {
assert!(!err.is_null());
let _: Box<FFIError> = Box::from_raw(err);
}
#[cfg(test)]
mod tests {
use super::FFIError;
#[test]
fn basic() {
// Capture a typical error
if let Err(e) = std::str::from_utf8(b"\x80") {
let _: FFIError = e.into();
}
}
#[test]
fn size() {
eprintln!("FFIError size = {}", std::mem::size_of::<FFIError>())
}
}
/// Verify that the given parameter is not NULL, and valid UTF-8,
/// returns a &str if successful else returns sys::cl_error_t_CL_EARG
///
/// # Examples
///
/// ```edition2018
/// use util::validate_str_param;
///
/// # pub extern "C" fn _my_c_interface(blah: *const c_char) -> sys::cl_error_t {
/// let blah = validate_str_param!(blah);
/// # }
/// ```
#[macro_export]
macro_rules! validate_str_param {
($ptr:ident) => {
if $ptr.is_null() {
warn!("{} is NULL", stringify!($ptr));
return false;
} else {
#[allow(unused_unsafe)]
match unsafe { CStr::from_ptr($ptr) }.to_str() {
Err(e) => {
warn!("{} is not valid unicode: {}", stringify!($ptr), e);
return false;
}
Ok(s) => s,
}
}
};
}