use alloc::borrow::Cow;
use core::ffi::{
CStr, c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint,
c_ulong, c_ulonglong, c_ushort, c_void,
};
use core::ptr::NonNull;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
use libffi::middle::Type;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
use libffi::{
low,
middle::{Arg, Cif, Closure, CodePtr, Ret},
};
#[cfg(any(unix, windows))]
use libloading::Library;
#[cfg(unix)]
use libloading::os::unix::Library as UnixLibrary;
#[cfg(any(unix, windows))]
use parking_lot::{Mutex, RwLock};
use rustpython_wtf8::{Wtf8, Wtf8Buf};
#[cfg(any(unix, windows))]
use std::{collections::HashMap, ffi::OsStr, sync::OnceLock};
use widestring::WideCStr;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub type FfiType = Type;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub type FfiArg<'a> = Arg<'a>;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub type FfiCodePtr = CodePtr;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub type FfiCif = low::ffi_cif;
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
type CallbackIntResult = low::ffi_arg;
#[cfg(not(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
)))]
type CallbackIntResult = c_int;
#[cfg(any(unix, windows, target_os = "wasi"))]
pub type WChar = libc::wchar_t;
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub type WChar = u32;
#[cfg(any(unix, windows, target_os = "wasi"))]
type TimeT = libc::time_t;
#[cfg(not(any(unix, windows, target_os = "wasi")))]
type TimeT = i64;
std::thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static CTYPES_LOCAL_ERRNO: core::cell::Cell<i32> = const { core::cell::Cell::new(0) };
}
pub fn get_errno() -> i32 {
CTYPES_LOCAL_ERRNO.with(|e| e.get())
}
pub fn set_errno(value: i32) -> i32 {
CTYPES_LOCAL_ERRNO.with(|e| {
let old = e.get();
e.set(value);
old
})
}
#[cfg(not(windows))]
pub fn with_swapped_errno<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let saved_errno = crate::os::get_errno();
let saved_ctypes_errno = CTYPES_LOCAL_ERRNO.with(|e| e.get());
crate::os::set_errno(saved_ctypes_errno);
let result = f();
let new_error = crate::os::get_errno();
CTYPES_LOCAL_ERRNO.with(|e| e.set(new_error));
crate::os::set_errno(saved_errno);
result
}
pub fn with_callback_errno_preserved<F, R>(use_errno: bool, f: F) -> R
where
F: FnOnce() -> R,
{
if !use_errno {
return f();
}
let saved = crate::os::get_errno();
let result = f();
let _current = crate::os::get_errno();
crate::os::set_errno(saved);
result
}
#[cfg(windows)]
std::thread_local! {
static CTYPES_LOCAL_LAST_ERROR: core::cell::Cell<u32> = const { core::cell::Cell::new(0) };
}
#[cfg(windows)]
pub fn get_last_error() -> u32 {
CTYPES_LOCAL_LAST_ERROR.with(|e| e.get())
}
#[cfg(windows)]
pub fn set_last_error(value: u32) -> u32 {
CTYPES_LOCAL_LAST_ERROR.with(|e| {
let old = e.get();
e.set(value);
old
})
}
#[cfg(windows)]
pub fn with_swapped_last_error<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let saved_last_error = crate::windows::get_last_error();
let saved_ctypes_last_error = CTYPES_LOCAL_LAST_ERROR.with(|e| e.get());
crate::windows::set_last_error(saved_ctypes_last_error);
let result = f();
let new_error = crate::windows::get_last_error();
CTYPES_LOCAL_LAST_ERROR.with(|e| e.set(new_error));
crate::windows::set_last_error(saved_last_error);
result
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(target_os = "windows")
))]
const LONG_DOUBLE_SIZE: usize = core::mem::size_of::<u128>();
#[cfg(target_os = "windows")]
const LONG_DOUBLE_SIZE: usize = core::mem::size_of::<c_double>();
#[cfg(not(any(
all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(target_os = "windows")
),
target_os = "windows"
)))]
const LONG_DOUBLE_SIZE: usize = core::mem::size_of::<c_double>();
pub fn simple_type_size(ty: &str) -> Option<usize> {
match ty {
"c" | "b" => Some(core::mem::size_of::<c_schar>()),
"u" => Some(core::mem::size_of::<WChar>()),
"B" | "?" => Some(core::mem::size_of::<c_uchar>()),
"h" | "v" => Some(core::mem::size_of::<c_short>()),
"H" => Some(core::mem::size_of::<c_ushort>()),
"i" => Some(core::mem::size_of::<c_int>()),
"I" => Some(core::mem::size_of::<c_uint>()),
"l" => Some(core::mem::size_of::<c_long>()),
"L" => Some(core::mem::size_of::<c_ulong>()),
"q" => Some(core::mem::size_of::<c_longlong>()),
"Q" => Some(core::mem::size_of::<c_ulonglong>()),
"f" => Some(core::mem::size_of::<c_float>()),
"d" => Some(core::mem::size_of::<c_double>()),
"g" => Some(LONG_DOUBLE_SIZE),
"z" | "Z" | "P" | "X" | "O" => Some(core::mem::size_of::<usize>()),
"void" => Some(0),
_ => None,
}
}
pub fn simple_type_align(ty: &str) -> Option<usize> {
match ty {
"c" | "b" => Some(core::mem::align_of::<c_schar>()),
"u" => Some(core::mem::align_of::<WChar>()),
"B" | "?" => Some(core::mem::align_of::<c_uchar>()),
"h" | "v" => Some(core::mem::align_of::<c_short>()),
"H" => Some(core::mem::align_of::<c_ushort>()),
"i" => Some(core::mem::align_of::<c_int>()),
"I" => Some(core::mem::align_of::<c_uint>()),
"l" => Some(core::mem::align_of::<c_long>()),
"L" => Some(core::mem::align_of::<c_ulong>()),
"q" => Some(core::mem::align_of::<c_longlong>()),
"Q" => Some(core::mem::align_of::<c_ulonglong>()),
"f" => Some(core::mem::align_of::<c_float>()),
"d" => Some(core::mem::align_of::<c_double>()),
"g" => {
#[cfg(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(target_os = "windows")
))]
{
Some(core::mem::align_of::<u128>())
}
#[cfg(not(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(target_os = "windows")
)))]
{
Some(core::mem::align_of::<c_double>())
}
}
"z" | "Z" | "P" | "X" | "O" => Some(core::mem::align_of::<usize>()),
"void" => Some(0),
_ => None,
}
}
pub fn c_long_bytes_endian(value: i128, swapped: bool) -> Vec<u8> {
let value = value as c_long;
int_to_sized_bytes_endian(value as i64, core::mem::size_of::<c_long>(), swapped)
}
pub fn c_ulong_bytes_endian(value: i128, swapped: bool) -> Vec<u8> {
let value = value as c_ulong;
uint_to_sized_bytes_endian(value as u64, core::mem::size_of::<c_ulong>(), swapped)
}
pub fn simple_type_pep3118_code(code: char) -> char {
match code {
'i' if core::mem::size_of::<c_int>() == 2 => 'h',
'i' if core::mem::size_of::<c_int>() == 4 => 'i',
'i' if core::mem::size_of::<c_int>() == 8 => 'q',
'I' if core::mem::size_of::<c_int>() == 2 => 'H',
'I' if core::mem::size_of::<c_int>() == 4 => 'I',
'I' if core::mem::size_of::<c_int>() == 8 => 'Q',
'l' if core::mem::size_of::<c_long>() == 4 => 'l',
'l' if core::mem::size_of::<c_long>() == 8 => 'q',
'L' if core::mem::size_of::<c_long>() == 4 => 'L',
'L' if core::mem::size_of::<c_long>() == 8 => 'Q',
'?' if core::mem::size_of::<bool>() == 1 => '?',
'?' if core::mem::size_of::<bool>() == 2 => 'H',
'?' if core::mem::size_of::<bool>() == 4 => 'L',
'?' if core::mem::size_of::<bool>() == 8 => 'Q',
_ => code,
}
}
pub enum StringAtError {
NullPointer,
TooLong,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RawMemoryViewError {
NullPointer,
NegativeSize,
}
#[derive(Debug, Clone, Copy)]
pub struct RawMemoryView {
ptr: usize,
size: usize,
readonly: bool,
}
impl RawMemoryView {
pub fn new(ptr: usize, size: isize, readonly: bool) -> Result<Self, RawMemoryViewError> {
if ptr == 0 {
return Err(RawMemoryViewError::NullPointer);
}
if size < 0 {
return Err(RawMemoryViewError::NegativeSize);
}
Ok(Self {
ptr,
size: size as usize,
readonly,
})
}
pub fn size(self) -> usize {
self.size
}
pub fn readonly(self) -> bool {
self.readonly
}
pub unsafe fn bytes(self) -> &'static [u8] {
unsafe { borrow_memory(self.ptr as *const u8, self.size) }
}
pub unsafe fn bytes_mut(self) -> &'static mut [u8] {
unsafe { borrow_memory_mut(self.ptr as *mut u8, self.size) }
}
}
#[cfg(unix)]
pub use libc::{RTLD_GLOBAL, RTLD_LOCAL};
#[cfg(not(unix))]
pub const RTLD_LOCAL: i32 = 0;
#[cfg(not(unix))]
pub const RTLD_GLOBAL: i32 = 0;
pub const SIZEOF_TIME_T: usize = core::mem::size_of::<TimeT>();
pub const FUNCFLAG_STDCALL: u32 = 0x0;
pub const FUNCFLAG_CDECL: u32 = 0x1;
pub const FUNCFLAG_HRESULT: u32 = 0x2;
pub const FUNCFLAG_PYTHONAPI: u32 = 0x4;
pub const FUNCFLAG_USE_ERRNO: u32 = 0x8;
pub const FUNCFLAG_USE_LASTERROR: u32 = 0x10;
pub const TYPEFLAG_ISPOINTER: u32 = 0x100;
pub const TYPEFLAG_HASPOINTER: u32 = 0x200;
pub const DICTFLAG_FINAL: u32 = 0x1000;
pub const CTYPES_MAX_ARGCOUNT: usize = 1024;
#[cfg(all(unix, not(target_os = "wasi")))]
pub fn dlopen_mode(load_flags: Option<i32>) -> i32 {
load_flags.unwrap_or(libc::RTLD_NOW | libc::RTLD_LOCAL) | libc::RTLD_NOW
}
#[cfg(not(all(unix, not(target_os = "wasi"))))]
pub fn dlopen_mode(load_flags: Option<i32>) -> i32 {
load_flags.unwrap_or(0)
}
#[cfg(target_os = "macos")]
pub fn dyld_shared_cache_contains_path(path: &str) -> Result<bool, alloc::ffi::NulError> {
let c_path = alloc::ffi::CString::new(path)?;
unsafe extern "C" {
fn _dyld_shared_cache_contains_path(path: *const c_char) -> bool;
}
Ok(unsafe { _dyld_shared_cache_contains_path(c_path.as_ptr()) })
}
pub unsafe fn wcslen(ptr: NonNull<WChar>) -> usize {
unsafe { WideCStr::from_ptr_str(ptr.as_ptr().cast()).len() }
}
pub unsafe fn read_c_string_bytes(ptr: *const c_char) -> Vec<u8> {
unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec()
}
#[inline]
pub fn read_pointer_from_buffer(buffer: &[u8]) -> usize {
const PTR_SIZE: usize = core::mem::size_of::<usize>();
buffer
.first_chunk::<PTR_SIZE>()
.copied()
.map_or(0, usize::from_ne_bytes)
}
pub const WCHAR_SIZE: usize = core::mem::size_of::<WChar>();
#[inline]
pub const fn wchar_from_bytes(bytes: &[u8]) -> Option<u32> {
if bytes.len() < WCHAR_SIZE {
return None;
}
Some(if WCHAR_SIZE == 2 {
u16::from_ne_bytes([bytes[0], bytes[1]]) as u32
} else {
u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
})
}
#[inline]
pub fn wchar_to_bytes(ch: u32, buffer: &mut [u8]) {
if WCHAR_SIZE == 2 {
if buffer.len() >= 2 {
buffer[..2].copy_from_slice(&(ch as u16).to_ne_bytes());
}
} else if buffer.len() >= 4 {
buffer[..4].copy_from_slice(&ch.to_ne_bytes());
}
}
pub fn wstring_from_bytes(buffer: &[u8]) -> String {
let mut chars = Vec::new();
for chunk in buffer.chunks(WCHAR_SIZE) {
if chunk.len() < WCHAR_SIZE {
break;
}
let Some(code) = wchar_from_bytes(chunk) else {
break;
};
if code == 0 {
break;
}
if let Some(ch) = char::from_u32(code) {
chars.push(ch);
}
}
chars.into_iter().collect()
}
pub fn wchar_array_field_value(buffer: &[u8]) -> String {
let wchars: Vec<WChar> = buffer
.chunks(WCHAR_SIZE)
.filter_map(|chunk| wchar_from_bytes(chunk).filter(|&wchar| wchar != 0))
.map(|wchar| wchar as WChar)
.collect();
wide_chars_to_wtf8(&wchars).to_string()
}
pub fn write_wchar_array_value(buffer: &mut [u8], s: &Wtf8) -> Result<(), WCharArrayWriteError> {
let wchar_count = buffer.len() / WCHAR_SIZE;
let char_count = s.code_points().count();
if char_count > wchar_count {
return Err(WCharArrayWriteError::TooLong);
}
for (i, ch) in s.code_points().enumerate() {
let offset = i * WCHAR_SIZE;
wchar_to_bytes(ch.to_u32(), &mut buffer[offset..]);
}
let terminator_offset = char_count * WCHAR_SIZE;
if terminator_offset + WCHAR_SIZE <= buffer.len() {
wchar_to_bytes(0, &mut buffer[terminator_offset..]);
}
Ok(())
}
pub fn encode_wtf8_to_wchar_padded(s: &Wtf8, size: usize) -> Vec<u8> {
let mut wchar_bytes = Vec::with_capacity(size);
for cp in s.code_points().take(size / WCHAR_SIZE) {
let mut bytes = [0u8; 4];
wchar_to_bytes(cp.to_u32(), &mut bytes);
wchar_bytes.extend_from_slice(&bytes[..WCHAR_SIZE]);
}
while wchar_bytes.len() < size {
wchar_bytes.push(0);
}
wchar_bytes
}
pub fn clone_wchar_null_terminated(s: &Wtf8) -> Vec<u8> {
if size_of::<WChar>() == 2 {
utf16z_bytes(s)
} else {
s.code_points()
.flat_map(|cp| (cp.to_u32() as WChar).to_ne_bytes())
.chain((0 as WChar).to_ne_bytes())
.collect()
}
}
pub enum IntegerValue {
Signed(i64),
Unsigned(u64),
}
pub enum AddressValue {
ByteString(u8),
Integer(IntegerValue),
Float(f64),
Pointer(usize),
Bytes(Vec<u8>),
}
pub enum AddressWriteValue<'a> {
Pointer(usize),
U8(u8),
I16(i16),
I32(i32),
I64(i64),
Float(f64),
Bytes(&'a [u8]),
}
pub enum ArrayElementWriteValue<'a> {
Byte(u8),
Wchar(u32),
Pointer { value: usize, size: usize },
Float { value: f64, size: usize },
Bytes { bytes: &'a [u8], size: usize },
}
pub enum WCharArrayWriteError {
TooLong,
}
pub enum SimpleStorageValue {
Byte(u8),
Wchar(u32),
Signed(i128),
Float(f64),
Bool(bool),
Pointer(usize),
ObjectId(usize),
Zero,
}
pub enum DecodedValue {
Bytes(Vec<u8>),
Signed(i64),
Unsigned(u64),
Float(f64),
Bool(bool),
Pointer(usize),
String(String),
None,
}
pub enum CallbackResultValue {
Signed(i64),
Unsigned(u64),
Float(f64),
Pointer(usize),
Bool(bool),
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub enum FfiArgRef<'a> {
U8(&'a u8),
I8(&'a i8),
U16(&'a u16),
I16(&'a i16),
U32(&'a u32),
I32(&'a i32),
U64(&'a u64),
I64(&'a i64),
F32(&'a f32),
F64(&'a f64),
Pointer(&'a usize),
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug, Clone, Copy)]
pub enum FfiValue {
U8(u8),
I8(i8),
U16(u16),
I16(i16),
U32(u32),
I32(i32),
U64(u64),
I64(i64),
F32(f32),
F64(f64),
Pointer(usize),
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub enum CdeclArgValue {
Pointer(isize),
Int(isize),
}
pub const POINTER_SIZE: usize = core::mem::size_of::<usize>();
pub const POINTER_FORMAT: &str = "X{}";
pub fn pointer_size() -> usize {
POINTER_SIZE
}
pub fn pointer_format() -> &'static str {
POINTER_FORMAT
}
pub fn has_pointer_width(buffer: &[u8]) -> bool {
buffer.len() >= POINTER_SIZE
}
pub fn pointer_bytes(value: usize) -> Vec<u8> {
pointer_to_sized_bytes(value, POINTER_SIZE)
}
pub fn null_pointer_bytes() -> Vec<u8> {
vec![0; POINTER_SIZE]
}
pub fn zeroed_bytes(size: usize) -> Vec<u8> {
vec![0; size]
}
pub fn copy_to_sized_bytes(src: &[u8], size: usize) -> Vec<u8> {
let mut result = zeroed_bytes(size);
let len = src.len().min(size);
result[..len].copy_from_slice(&src[..len]);
result
}
pub fn char_array_assignment_bytes(src: &[u8]) -> &[u8] {
if let Some(null_pos) = src.iter().position(|&b| b == 0) {
&src[..=null_pos]
} else {
src
}
}
pub fn char_array_field_value(buffer: &[u8]) -> &[u8] {
let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
&buffer[..end]
}
pub fn write_char_array_value(buffer: &mut [u8], src: &[u8]) {
buffer[..src.len()].copy_from_slice(src);
if src.len() < buffer.len() {
buffer[src.len()] = 0;
}
}
pub fn write_char_array_raw(buffer: &mut [u8], src: &[u8]) {
buffer[..src.len()].copy_from_slice(src);
}
pub fn write_prefix_limited(buffer: &mut [u8], src: &[u8], size: usize) {
let copy_size = size.min(buffer.len()).min(src.len());
if copy_size > 0 {
buffer[..copy_size].copy_from_slice(&src[..copy_size]);
}
}
pub fn pointer_to_sized_bytes_endian(value: usize, size: usize, swapped: bool) -> Vec<u8> {
let mut bytes = pointer_to_sized_bytes(value, size);
if swapped {
bytes.reverse();
}
bytes
}
pub fn write_pointer_to_buffer_at(buffer: &mut [u8], offset: usize, size: usize, value: usize) {
if offset + size <= buffer.len() {
let ptr_bytes = pointer_to_sized_bytes(value, size);
buffer[offset..offset + size].copy_from_slice(&ptr_bytes);
}
}
pub fn write_array_element(buffer: &mut [u8], offset: usize, value: ArrayElementWriteValue<'_>) {
match value {
ArrayElementWriteValue::Byte(value) => {
if offset < buffer.len() {
buffer[offset] = value;
}
}
ArrayElementWriteValue::Wchar(value) => {
if offset + WCHAR_SIZE <= buffer.len() {
wchar_to_bytes(value, &mut buffer[offset..]);
}
}
ArrayElementWriteValue::Pointer { value, size } => {
write_pointer_to_buffer_at(buffer, offset, size, value);
}
ArrayElementWriteValue::Float { value, size } => {
if offset + size <= buffer.len()
&& let Some(float_bytes) = float_to_sized_bytes(value, size)
{
buffer[offset..offset + size].copy_from_slice(&float_bytes);
}
}
ArrayElementWriteValue::Bytes { bytes, size } => {
let copy_len = bytes.len().min(size);
if offset + copy_len <= buffer.len() {
buffer[offset..offset + copy_len].copy_from_slice(&bytes[..copy_len]);
}
}
}
}
pub fn read_array_element(
buffer: &[u8],
offset: usize,
element_size: usize,
type_code: Option<&str>,
) -> DecodedValue {
let Some(rest) = buffer.get(offset..) else {
return DecodedValue::Signed(0);
};
match type_code {
Some("c") => DecodedValue::Bytes(vec![buffer.get(offset).copied().unwrap_or(0)]),
Some("u") => {
let value = wchar_from_bytes(rest)
.and_then(char::from_u32)
.map(|c| c.to_string())
.unwrap_or_default();
DecodedValue::String(value)
}
Some("z") => {
if offset + element_size > buffer.len() {
return DecodedValue::None;
}
let ptr_bytes = &buffer[offset..offset + element_size];
let ptr_val = read_pointer_from_buffer(ptr_bytes);
unsafe {
match read_c_string_from_address(ptr_val) {
Some(bytes) => DecodedValue::Bytes(bytes),
None => DecodedValue::None,
}
}
}
Some("Z") => {
if offset + element_size > buffer.len() {
return DecodedValue::None;
}
let ptr_bytes = &buffer[offset..offset + element_size];
let ptr_val = read_pointer_from_buffer(ptr_bytes);
unsafe {
match read_wide_string_from_address(ptr_val) {
Some(s) => DecodedValue::String(s.to_string()),
None => DecodedValue::None,
}
}
}
Some("f") => DecodedValue::Float(
rest.first_chunk::<4>()
.copied()
.map_or(0.0, f32::from_ne_bytes) as f64,
),
Some("d" | "g") => DecodedValue::Float(
rest.first_chunk::<8>()
.copied()
.map_or(0.0, f64::from_ne_bytes),
),
_ => {
if let Some(bytes) = rest.get(..element_size) {
let is_unsigned = matches!(type_code, Some("B" | "H" | "I" | "L" | "Q"));
match int_from_bytes(bytes, element_size, is_unsigned) {
IntegerValue::Signed(value) => DecodedValue::Signed(value),
IntegerValue::Unsigned(value) => DecodedValue::Unsigned(value),
}
} else {
DecodedValue::Signed(0)
}
}
}
}
pub fn int_from_bytes(bytes: &[u8], size: usize, unsigned: bool) -> IntegerValue {
match (size, unsigned) {
(1, false) => IntegerValue::Signed(bytes[0] as i8 as i64),
(1, true) => IntegerValue::Unsigned(bytes[0].into()),
(2, false) => IntegerValue::Signed(i16::from_ne_bytes([bytes[0], bytes[1]]).into()),
(2, true) => IntegerValue::Unsigned(u16::from_ne_bytes([bytes[0], bytes[1]]).into()),
(4, false) => IntegerValue::Signed(
i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).into(),
),
(4, true) => IntegerValue::Unsigned(
u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).into(),
),
(8, false) => IntegerValue::Signed(i64::from_ne_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])),
(8, true) => IntegerValue::Unsigned(u64::from_ne_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])),
_ => IntegerValue::Signed(0),
}
}
pub fn int_to_sized_bytes(value: i64, size: usize) -> Vec<u8> {
match size {
1 => (value as i8).to_ne_bytes().to_vec(),
2 => (value as i16).to_ne_bytes().to_vec(),
4 => (value as i32).to_ne_bytes().to_vec(),
8 => value.to_ne_bytes().to_vec(),
_ => vec![0u8; size],
}
}
pub fn uint_to_sized_bytes(value: u64, size: usize) -> Vec<u8> {
match size {
1 => (value as u8).to_ne_bytes().to_vec(),
2 => (value as u16).to_ne_bytes().to_vec(),
4 => (value as u32).to_ne_bytes().to_vec(),
8 => value.to_ne_bytes().to_vec(),
_ => vec![0u8; size],
}
}
pub fn int_to_sized_bytes_endian(value: i64, size: usize, swapped: bool) -> Vec<u8> {
if swapped {
#[cfg(target_endian = "little")]
{
match size {
1 => (value as i8).to_ne_bytes().to_vec(),
2 => (value as i16).to_be_bytes().to_vec(),
4 => (value as i32).to_be_bytes().to_vec(),
8 => value.to_be_bytes().to_vec(),
_ => vec![0u8; size],
}
}
#[cfg(target_endian = "big")]
{
match size {
1 => (value as i8).to_ne_bytes().to_vec(),
2 => (value as i16).to_le_bytes().to_vec(),
4 => (value as i32).to_le_bytes().to_vec(),
8 => value.to_le_bytes().to_vec(),
_ => vec![0u8; size],
}
}
} else {
int_to_sized_bytes(value, size)
}
}
pub fn uint_to_sized_bytes_endian(value: u64, size: usize, swapped: bool) -> Vec<u8> {
if swapped {
#[cfg(target_endian = "little")]
{
match size {
1 => (value as u8).to_ne_bytes().to_vec(),
2 => (value as u16).to_be_bytes().to_vec(),
4 => (value as u32).to_be_bytes().to_vec(),
8 => value.to_be_bytes().to_vec(),
_ => vec![0u8; size],
}
}
#[cfg(target_endian = "big")]
{
match size {
1 => (value as u8).to_ne_bytes().to_vec(),
2 => (value as u16).to_le_bytes().to_vec(),
4 => (value as u32).to_le_bytes().to_vec(),
8 => value.to_le_bytes().to_vec(),
_ => vec![0u8; size],
}
}
} else {
uint_to_sized_bytes(value, size)
}
}
pub fn float_to_sized_bytes(value: f64, size: usize) -> Option<Vec<u8>> {
match size {
4 => Some((value as f32).to_ne_bytes().to_vec()),
8 => Some(value.to_ne_bytes().to_vec()),
_ => None,
}
}
pub fn float_to_sized_bytes_endian(value: f64, size: usize, swapped: bool) -> Option<Vec<u8>> {
if swapped {
#[cfg(target_endian = "little")]
{
match size {
4 => Some((value as f32).to_be_bytes().to_vec()),
8 => Some(value.to_be_bytes().to_vec()),
_ => None,
}
}
#[cfg(target_endian = "big")]
{
match size {
4 => Some((value as f32).to_le_bytes().to_vec()),
8 => Some(value.to_le_bytes().to_vec()),
_ => None,
}
}
} else {
float_to_sized_bytes(value, size)
}
}
pub fn pointer_to_sized_bytes(value: usize, size: usize) -> Vec<u8> {
let mut result = vec![0u8; size];
let bytes = value.to_ne_bytes();
let len = core::cmp::min(bytes.len(), size);
result[..len].copy_from_slice(&bytes[..len]);
result
}
pub fn wchar_code_to_bytes_endian(ch: u32, swapped: bool) -> Vec<u8> {
let mut buffer = vec![0u8; WCHAR_SIZE];
wchar_to_bytes(ch, &mut buffer);
if swapped {
buffer.reverse();
}
buffer
}
pub fn simple_storage_value_to_bytes_endian(
type_code: &str,
value: SimpleStorageValue,
swapped: bool,
) -> Vec<u8> {
match type_code {
"c" => match value {
SimpleStorageValue::Byte(value) => vec![value],
_ => vec![0],
},
"u" => match value {
SimpleStorageValue::Wchar(value) => wchar_code_to_bytes_endian(value, swapped),
_ => vec![0; WCHAR_SIZE],
},
"b" => match value {
SimpleStorageValue::Signed(value) => vec![(value as i8) as u8],
_ => vec![0],
},
"B" => match value {
SimpleStorageValue::Signed(value) => vec![value as u8],
_ => vec![0],
},
"h" => match value {
SimpleStorageValue::Signed(value) => {
int_to_sized_bytes_endian((value as i16).into(), 2, swapped)
}
_ => vec![0; 2],
},
"H" => match value {
SimpleStorageValue::Signed(value) => {
uint_to_sized_bytes_endian((value as u16).into(), 2, swapped)
}
_ => vec![0; 2],
},
"i" => match value {
SimpleStorageValue::Signed(value) => {
int_to_sized_bytes_endian((value as i32).into(), 4, swapped)
}
_ => vec![0; 4],
},
"I" => match value {
SimpleStorageValue::Signed(value) => {
uint_to_sized_bytes_endian((value as u32).into(), 4, swapped)
}
_ => vec![0; 4],
},
"l" => match value {
SimpleStorageValue::Signed(value) => c_long_bytes_endian(value, swapped),
_ => vec![0; simple_type_size("l").expect("invalid ctypes simple type")],
},
"L" => match value {
SimpleStorageValue::Signed(value) => c_ulong_bytes_endian(value, swapped),
_ => vec![0; simple_type_size("L").expect("invalid ctypes simple type")],
},
"q" => match value {
SimpleStorageValue::Signed(value) => {
int_to_sized_bytes_endian(value as i64, 8, swapped)
}
_ => vec![0; 8],
},
"Q" => match value {
SimpleStorageValue::Signed(value) => {
uint_to_sized_bytes_endian(value as u64, 8, swapped)
}
_ => vec![0; 8],
},
"f" => match value {
SimpleStorageValue::Float(value) => {
float_to_sized_bytes_endian(value, 4, swapped).expect("f32 size is fixed")
}
_ => vec![0; 4],
},
"d" => match value {
SimpleStorageValue::Float(value) => {
float_to_sized_bytes_endian(value, 8, swapped).expect("f64 size is fixed")
}
_ => vec![0; 8],
},
"g" => {
let value = match value {
SimpleStorageValue::Float(value) => value,
_ => 0.0,
};
let mut result =
float_to_sized_bytes_endian(value, 8, swapped).expect("f64 size is fixed");
result.resize(
simple_type_size("g").expect("invalid ctypes simple type"),
0,
);
result
}
"?" => match value {
SimpleStorageValue::Bool(value) => vec![u8::from(value)],
_ => vec![0],
},
"v" => match value {
SimpleStorageValue::Bool(value) => {
let value: i16 = if value { -1 } else { 0 };
int_to_sized_bytes_endian(value.into(), 2, swapped)
}
_ => vec![0; 2],
},
"P" | "z" | "Z" => match value {
SimpleStorageValue::Pointer(value) => {
uint_to_sized_bytes_endian(value as u64, pointer_size(), swapped)
}
_ => null_pointer_bytes(),
},
"O" => match value {
SimpleStorageValue::ObjectId(value) => {
uint_to_sized_bytes_endian(value as u64, pointer_size(), swapped)
}
_ => null_pointer_bytes(),
},
_ => vec![0],
}
}
pub fn utf16z_bytes(s: &Wtf8) -> Vec<u8> {
s.encode_wide()
.flat_map(|cp| cp.to_ne_bytes())
.chain(0u16.to_ne_bytes())
.collect()
}
pub fn clone_as_null_terminated(bytes: &[u8]) -> Vec<u8> {
if bytes.last() == Some(&0) {
bytes.to_vec()
} else {
bytes.iter().copied().chain(Some(0)).collect()
}
}
pub fn decode_type_code(type_code: &str, bytes: &[u8]) -> DecodedValue {
match type_code {
"c" => DecodedValue::Bytes(bytes.to_vec()),
"b" => DecodedValue::Signed(if !bytes.is_empty() {
bytes[0] as i8 as i64
} else {
0
}),
"B" => DecodedValue::Unsigned(if !bytes.is_empty() {
bytes[0].into()
} else {
0
}),
"h" => {
const SIZE: usize = core::mem::size_of::<c_short>();
DecodedValue::Signed(if bytes.len() >= SIZE {
c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into()
} else {
0
})
}
"H" => {
const SIZE: usize = core::mem::size_of::<c_ushort>();
DecodedValue::Unsigned(if bytes.len() >= SIZE {
c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into()
} else {
0
})
}
"i" => {
const SIZE: usize = core::mem::size_of::<c_int>();
DecodedValue::Signed(if bytes.len() >= SIZE {
c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into()
} else {
0
})
}
"I" => {
const SIZE: usize = core::mem::size_of::<c_uint>();
DecodedValue::Unsigned(if bytes.len() >= SIZE {
c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into()
} else {
0
})
}
"l" => {
const SIZE: usize = core::mem::size_of::<c_long>();
DecodedValue::Signed(if bytes.len() >= SIZE {
#[allow(
clippy::unnecessary_cast,
clippy::useless_conversion,
reason = "c_long width is platform-dependent"
)]
let val: i64 =
c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) as i64;
val
} else {
0
})
}
"L" => {
const SIZE: usize = core::mem::size_of::<c_ulong>();
DecodedValue::Unsigned(if bytes.len() >= SIZE {
#[allow(
clippy::unnecessary_cast,
clippy::useless_conversion,
reason = "c_ulong width is platform-dependent"
)]
let val: u64 =
c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) as u64;
val
} else {
0
})
}
"q" => {
const SIZE: usize = core::mem::size_of::<c_longlong>();
DecodedValue::Signed(if bytes.len() >= SIZE {
c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
})
}
"Q" => {
const SIZE: usize = core::mem::size_of::<c_ulonglong>();
DecodedValue::Unsigned(if bytes.len() >= SIZE {
c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
})
}
"f" => {
const SIZE: usize = core::mem::size_of::<c_float>();
DecodedValue::Float(if bytes.len() >= SIZE {
c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) as f64
} else {
0.0
})
}
"d" | "g" => {
const SIZE: usize = core::mem::size_of::<c_double>();
DecodedValue::Float(if bytes.len() >= SIZE {
c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0.0
})
}
"?" => DecodedValue::Bool(!bytes.is_empty() && bytes[0] != 0),
"v" => {
const SIZE: usize = core::mem::size_of::<c_short>();
let val = if bytes.len() >= SIZE {
c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
DecodedValue::Bool(val != 0)
}
"z" => unsafe {
match read_c_string_from_address(read_pointer_from_buffer(bytes)) {
Some(bytes) => DecodedValue::Bytes(bytes),
None => DecodedValue::None,
}
},
"Z" => unsafe {
match read_wide_string_from_address(read_pointer_from_buffer(bytes)) {
Some(s) => DecodedValue::String(s.to_string()),
None => DecodedValue::None,
}
},
"P" => DecodedValue::Pointer(read_pointer_from_buffer(bytes)),
"u" => {
let val = if bytes.len() >= core::mem::size_of::<WChar>() {
let wc = if core::mem::size_of::<WChar>() == 2 {
u16::from_ne_bytes([bytes[0], bytes[1]]) as u32
} else {
u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
};
char::from_u32(wc).unwrap_or('\0')
} else {
'\0'
};
DecodedValue::String(val.to_string())
}
_ => DecodedValue::None,
}
}
pub unsafe fn callback_arg_value(type_code: Option<&str>, ptr: *const c_void) -> DecodedValue {
match type_code {
Some("b") => DecodedValue::Signed(unsafe { *(ptr as *const i8) as i64 }),
Some("B") => DecodedValue::Unsigned(unsafe { *(ptr as *const u8) as u64 }),
Some("c") => DecodedValue::Bytes(vec![unsafe { *(ptr as *const u8) }]),
Some("h") => DecodedValue::Signed(unsafe { *(ptr as *const i16) as i64 }),
Some("H") => DecodedValue::Unsigned(unsafe { *(ptr as *const u16) as u64 }),
Some("i") => DecodedValue::Signed(unsafe { *(ptr as *const i32) as i64 }),
Some("I") => DecodedValue::Unsigned(unsafe { *(ptr as *const u32) as u64 }),
Some("l") => DecodedValue::Signed({
#[allow(
clippy::unnecessary_cast,
clippy::useless_conversion,
reason = "c_long width is platform-dependent"
)]
let val: i64 = unsafe { *(ptr as *const c_long) as i64 };
val
}),
Some("L") => DecodedValue::Unsigned({
#[allow(
clippy::unnecessary_cast,
clippy::useless_conversion,
reason = "c_ulong width is platform-dependent"
)]
let val: u64 = unsafe { *(ptr as *const c_ulong) as u64 };
val
}),
Some("q") => DecodedValue::Signed(unsafe { *(ptr as *const c_longlong) }),
Some("Q") => DecodedValue::Unsigned(unsafe { *(ptr as *const c_ulonglong) }),
Some("f") => DecodedValue::Float(unsafe { *(ptr as *const f32) as f64 }),
Some("d") => DecodedValue::Float(unsafe { *(ptr as *const f64) }),
Some("z") => {
let cstr_ptr = unsafe { *(ptr as *const *const c_char) };
if cstr_ptr.is_null() {
DecodedValue::None
} else {
DecodedValue::Bytes(unsafe { read_c_string_bytes(cstr_ptr) })
}
}
Some("Z") => {
let wstr_ptr = unsafe { *(ptr as *const *const WChar) };
if let Some(wstr_ptr) = NonNull::new(wstr_ptr.cast_mut()) {
DecodedValue::String(unsafe { read_wide_string(wstr_ptr) }.to_string())
} else {
DecodedValue::None
}
}
Some("P") => DecodedValue::Pointer(unsafe { *(ptr as *const usize) }),
Some("?") => DecodedValue::Bool(unsafe { *(ptr as *const u8) != 0 }),
_ => DecodedValue::None,
}
}
pub unsafe fn callback_arg_value_at(
type_code: Option<&str>,
args: *const *const c_void,
index: usize,
) -> DecodedValue {
let ptr = unsafe { *args.add(index) };
unsafe { callback_arg_value(type_code, ptr) }
}
pub unsafe fn write_callback_result(
type_code: Option<&str>,
result: *mut c_void,
value: CallbackResultValue,
) {
match (type_code, value) {
(Some("b"), CallbackResultValue::Signed(v)) => unsafe { *(result as *mut i8) = v as i8 },
(Some("B" | "c"), CallbackResultValue::Unsigned(v)) => unsafe {
*(result as *mut u8) = v as u8
},
(Some("h"), CallbackResultValue::Signed(v)) => unsafe { *(result as *mut i16) = v as i16 },
(Some("H"), CallbackResultValue::Unsigned(v)) => unsafe {
*(result as *mut u16) = v as u16
},
(Some("i"), CallbackResultValue::Signed(v)) => unsafe {
*(result as *mut CallbackIntResult) = v as i32 as CallbackIntResult
},
(Some("I"), CallbackResultValue::Unsigned(v)) => unsafe {
*(result as *mut u32) = v as u32
},
(Some("l"), CallbackResultValue::Signed(v)) => unsafe {
*(result as *mut c_long) = v as c_long
},
(Some("L"), CallbackResultValue::Unsigned(v)) => unsafe {
*(result as *mut c_ulong) = v as c_ulong
},
(Some("q"), CallbackResultValue::Signed(v)) => unsafe { *(result as *mut i64) = v },
(Some("Q"), CallbackResultValue::Unsigned(v)) => unsafe { *(result as *mut u64) = v },
(Some("f"), CallbackResultValue::Float(v)) => unsafe { *(result as *mut f32) = v as f32 },
(Some("d"), CallbackResultValue::Float(v)) => unsafe { *(result as *mut f64) = v },
(Some("P" | "z" | "Z"), CallbackResultValue::Pointer(v)) => unsafe {
*(result as *mut usize) = v
},
(Some("?"), CallbackResultValue::Bool(v)) => unsafe { *(result as *mut u8) = u8::from(v) },
_ => {}
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_value_from_type_code(type_code: &str, buffer: &[u8]) -> FfiValue {
match type_code {
"c" | "b" => FfiValue::I8(buffer.first().map_or(0, |&b| b as i8)),
"B" => FfiValue::U8(buffer.first().copied().unwrap_or(0)),
"h" => FfiValue::I16(buffer.first_chunk().copied().map_or(0, i16::from_ne_bytes)),
"H" => FfiValue::U16(buffer.first_chunk().copied().map_or(0, u16::from_ne_bytes)),
"i" => FfiValue::I32(buffer.first_chunk().copied().map_or(0, i32::from_ne_bytes)),
"I" => FfiValue::U32(buffer.first_chunk().copied().map_or(0, u32::from_ne_bytes)),
"l" | "q" => FfiValue::I64(if let Some(&bytes) = buffer.first_chunk::<8>() {
i64::from_ne_bytes(bytes)
} else if let Some(&bytes) = buffer.first_chunk::<4>() {
i32::from_ne_bytes(bytes).into()
} else {
0
}),
"L" | "Q" => FfiValue::U64(if let Some(&bytes) = buffer.first_chunk::<8>() {
u64::from_ne_bytes(bytes)
} else if let Some(&bytes) = buffer.first_chunk::<4>() {
u32::from_ne_bytes(bytes).into()
} else {
0
}),
"f" => FfiValue::F32(
buffer
.first_chunk::<4>()
.copied()
.map_or(0.0, f32::from_ne_bytes),
),
"d" | "g" => FfiValue::F64(
buffer
.first_chunk::<8>()
.copied()
.map_or(0.0, f64::from_ne_bytes),
),
"z" | "Z" | "P" | "O" => FfiValue::Pointer(read_pointer_from_buffer(buffer)),
"?" => FfiValue::U8(u8::from(buffer.first().is_some_and(|&b| b != 0))),
"u" => FfiValue::U32(buffer.first_chunk().copied().map_or(0, u32::from_ne_bytes)),
_ => FfiValue::Pointer(0),
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_value_from_type(buffer: &[u8], ty: Type) -> Option<FfiValue> {
if core::ptr::eq(ty.as_raw_ptr(), Type::u8().as_raw_ptr()) {
Some(FfiValue::U8(*buffer.first()?))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::i8().as_raw_ptr()) {
Some(FfiValue::I8(*buffer.first()? as i8))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::u16().as_raw_ptr()) {
Some(FfiValue::U16(u16::from_ne_bytes(
*buffer.first_chunk::<2>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::i16().as_raw_ptr()) {
Some(FfiValue::I16(i16::from_ne_bytes(
*buffer.first_chunk::<2>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::u32().as_raw_ptr()) {
Some(FfiValue::U32(u32::from_ne_bytes(
*buffer.first_chunk::<4>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::i32().as_raw_ptr()) {
Some(FfiValue::I32(i32::from_ne_bytes(
*buffer.first_chunk::<4>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::u64().as_raw_ptr()) {
Some(FfiValue::U64(u64::from_ne_bytes(
*buffer.first_chunk::<8>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::i64().as_raw_ptr()) {
Some(FfiValue::I64(i64::from_ne_bytes(
*buffer.first_chunk::<8>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::f32().as_raw_ptr()) {
Some(FfiValue::F32(f32::from_ne_bytes(
*buffer.first_chunk::<4>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::f64().as_raw_ptr()) {
Some(FfiValue::F64(f64::from_ne_bytes(
*buffer.first_chunk::<8>()?,
)))
} else if core::ptr::eq(ty.as_raw_ptr(), Type::pointer().as_raw_ptr()) {
Some(FfiValue::Pointer(read_pointer_from_buffer(buffer)))
} else {
None
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_type_from_code(ty: &str) -> Option<Type> {
match ty {
"c" => Some(Type::u8()),
"u" => Some(if core::mem::size_of::<WChar>() == 2 {
Type::u16()
} else {
Type::u32()
}),
"b" => Some(Type::i8()),
"B" | "?" => Some(Type::u8()),
"h" | "v" => Some(Type::i16()),
"H" => Some(Type::u16()),
"i" => Some(Type::i32()),
"I" => Some(Type::u32()),
"l" => Some(if core::mem::size_of::<c_long>() == 8 {
Type::i64()
} else {
Type::i32()
}),
"L" => Some(if core::mem::size_of::<c_ulong>() == 8 {
Type::u64()
} else {
Type::u32()
}),
"q" => Some(Type::i64()),
"Q" => Some(Type::u64()),
"f" => Some(Type::f32()),
"d" | "g" => Some(Type::f64()),
"z" | "Z" | "P" | "X" | "O" => Some(Type::pointer()),
"void" => Some(Type::void()),
_ => None,
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_repeat_type(elem_type: Type, len: usize) -> Type {
Type::structure(core::iter::repeat_n(elem_type, len))
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_byte_struct(size: usize) -> Type {
ffi_repeat_type(Type::u8(), size)
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_pointer_type() -> Type {
Type::pointer()
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_i32_type() -> Type {
Type::i32()
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_f64_type() -> Type {
Type::f64()
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_void_type() -> Type {
Type::void()
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn call_cdecl_i32(code_ptr: usize, arg_types: Vec<Type>, arg_values: &[isize]) -> c_int {
let ffi_args: Vec<_> = arg_values.iter().map(Arg::new).collect();
let cif = Cif::new(arg_types, Type::c_int());
let code_ptr = CodePtr::from_ptr(code_ptr as *const _);
unsafe { cif.call(code_ptr, &ffi_args) }
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn call_cdecl_i32_values(code_ptr: usize, args: &[CdeclArgValue]) -> c_int {
let mut arg_values = Vec::with_capacity(args.len());
let mut arg_types = Vec::with_capacity(args.len());
for arg in args {
match *arg {
CdeclArgValue::Pointer(value) => {
arg_values.push(value);
arg_types.push(Type::pointer());
}
CdeclArgValue::Int(value) => {
arg_values.push(value);
arg_types.push(Type::isize());
}
}
}
call_cdecl_i32(code_ptr, arg_types, &arg_values)
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_arg(value: FfiArgRef<'_>) -> Arg<'_> {
match value {
FfiArgRef::U8(v) => Arg::new(v),
FfiArgRef::I8(v) => Arg::new(v),
FfiArgRef::U16(v) => Arg::new(v),
FfiArgRef::I16(v) => Arg::new(v),
FfiArgRef::U32(v) => Arg::new(v),
FfiArgRef::I32(v) => Arg::new(v),
FfiArgRef::U64(v) => Arg::new(v),
FfiArgRef::I64(v) => Arg::new(v),
FfiArgRef::F32(v) => Arg::new(v),
FfiArgRef::F64(v) => Arg::new(v),
FfiArgRef::Pointer(v) => Arg::new(v),
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn ffi_arg_from_value(value: &FfiValue) -> Arg<'_> {
match value {
FfiValue::U8(v) => ffi_arg(FfiArgRef::U8(v)),
FfiValue::I8(v) => ffi_arg(FfiArgRef::I8(v)),
FfiValue::U16(v) => ffi_arg(FfiArgRef::U16(v)),
FfiValue::I16(v) => ffi_arg(FfiArgRef::I16(v)),
FfiValue::U32(v) => ffi_arg(FfiArgRef::U32(v)),
FfiValue::I32(v) => ffi_arg(FfiArgRef::I32(v)),
FfiValue::U64(v) => ffi_arg(FfiArgRef::U64(v)),
FfiValue::I64(v) => ffi_arg(FfiArgRef::I64(v)),
FfiValue::F32(v) => ffi_arg(FfiArgRef::F32(v)),
FfiValue::F64(v) => ffi_arg(FfiArgRef::F64(v)),
FfiValue::Pointer(v) => ffi_arg(FfiArgRef::Pointer(v)),
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn code_ptr_from_addr(addr: usize) -> Option<CodePtr> {
if addr == 0 {
None
} else {
Some(CodePtr(addr as *mut _))
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn null_code_ptr() -> CodePtr {
CodePtr(core::ptr::null_mut())
}
#[cfg(windows)]
pub enum ComMethodError {
NullComPointer,
NullVtablePointer,
NullFunctionPointer,
}
#[cfg(windows)]
pub const HRESULT_E_POINTER: i32 = crate::windows::HRESULT_E_POINTER;
#[cfg(windows)]
pub const HRESULT_S_OK: i32 = crate::windows::HRESULT_S_OK;
#[cfg(windows)]
pub fn format_error_message(code: Option<u32>) -> Option<String> {
crate::windows::format_error_message(code)
}
#[cfg(windows)]
pub fn resolve_com_vtable_entry(com_ptr: usize, idx: usize) -> Result<CodePtr, ComMethodError> {
if com_ptr == 0 {
return Err(ComMethodError::NullComPointer);
}
let vtable_ptr = unsafe { *(com_ptr as *const usize) };
if vtable_ptr == 0 {
return Err(ComMethodError::NullVtablePointer);
}
let fptr = unsafe {
let vtable = vtable_ptr as *const usize;
*vtable.add(idx)
};
if fptr == 0 {
return Err(ComMethodError::NullFunctionPointer);
}
Ok(CodePtr(fptr as *mut _))
}
#[cfg(windows)]
pub fn copy_com_pointer(src_ptr: usize, dst_addr: usize) -> i32 {
if dst_addr == 0 {
return HRESULT_E_POINTER;
}
if src_ptr != 0 {
unsafe {
let iunknown = src_ptr as *mut *const usize;
let vtable = *iunknown;
if vtable.is_null() {
return HRESULT_E_POINTER;
}
let addref_fn: extern "system" fn(*mut c_void) -> u32 =
core::mem::transmute(*vtable.add(1));
addref_fn(src_ptr as *mut c_void);
}
}
unsafe {
*(dst_addr as *mut usize) = src_ptr;
}
HRESULT_S_OK
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub struct CallbackThunk<U: 'static> {
#[allow(dead_code)]
closure: Closure<'static>,
userdata_ptr: *mut U,
code_ptr: CodePtr,
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
impl<U: 'static> CallbackThunk<U> {
pub fn new(
ffi_arg_types: Vec<Type>,
ffi_res_type: Type,
userdata: Box<U>,
callback: unsafe extern "C" fn(&low::ffi_cif, &mut c_void, *const *const c_void, &U),
) -> Self {
let cif = Cif::new(ffi_arg_types, ffi_res_type);
let userdata_ptr = Box::into_raw(userdata);
let userdata_ref: &'static U = unsafe { &*userdata_ptr };
let closure = Closure::new(cif, callback, userdata_ref);
let code_ptr = CodePtr(*closure.code_ptr() as *mut _);
Self {
closure,
userdata_ptr,
code_ptr,
}
}
pub fn code_ptr(&self) -> CodePtr {
self.code_ptr
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
impl<U: 'static> Drop for CallbackThunk<U> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.userdata_ptr));
}
}
}
pub fn simple_type_is_pointer(code: &str) -> bool {
matches!(code, "z" | "Z" | "P" | "s" | "X" | "O")
}
pub fn simple_type_chars() -> &'static str {
#[cfg(windows)]
{
"cbBhHiIlLdfuzZqQPXOv?g"
}
#[cfg(not(windows))]
{
"cbBhHiIlLdfuzZqQPOv?g"
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CTypeLayout {
Simple(char),
Pointer,
Struct { fields: Vec<Self>, size: usize },
Union { fields: Vec<Self>, size: usize },
Array {
element: Box<Self>,
length: usize,
size: usize,
},
Opaque { size: usize },
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
impl CTypeLayout {
pub fn size(&self) -> usize {
match self {
Self::Simple(code) => {
let mut buf = [0u8; 4];
simple_type_size(code.encode_utf8(&mut buf)).unwrap_or(0)
}
Self::Pointer => POINTER_SIZE,
Self::Struct { size, .. }
| Self::Union { size, .. }
| Self::Array { size, .. }
| Self::Opaque { size } => *size,
}
}
fn to_ffi_type(&self) -> Result<Type, CallError> {
match self {
Self::Simple(code) => {
let mut buf = [0u8; 4];
let code = code.encode_utf8(&mut buf);
ffi_type_from_code(code).ok_or_else(|| CallError::UnknownTypeCode(code.to_string()))
}
Self::Pointer => Ok(ffi_pointer_type()),
Self::Struct { fields, .. } => {
let mut ffi_fields = Vec::with_capacity(fields.len());
for field in fields {
ffi_fields.push(field.to_ffi_type()?);
}
Ok(Type::structure(ffi_fields))
}
Self::Array {
element, length, ..
} => Ok(ffi_repeat_type(element.to_ffi_type()?, *length)),
Self::Union { size, .. } | Self::Opaque { size } => Ok(ffi_byte_struct(*size)),
}
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug, Clone, Copy)]
pub enum CallArg<'a> {
Typed { code: &'a str, buffer: &'a [u8] },
Int(i32),
Double(f64),
Pointer(usize),
Aggregate {
layout: &'a CTypeLayout,
buffer: &'a [u8],
},
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug, Clone, Copy)]
pub enum CallRet<'a> {
Void,
Code(&'a str),
Pointer,
Aggregate(&'a CTypeLayout),
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug, Clone, Copy, Default)]
pub struct CallOptions {
pub use_errno: bool,
pub use_last_error: bool,
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug)]
pub enum CallValue {
Void,
Scalar(Vec<u8>),
Pointer(usize),
Aggregate(Vec<u8>),
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CallError {
NullFunctionPointer,
UnknownTypeCode(String),
BufferTooSmall {
expected: usize,
got: usize,
},
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
pub fn call(
addr: usize,
args: &[CallArg<'_>],
ret: CallRet<'_>,
options: CallOptions,
) -> Result<CallValue, CallError> {
enum Lowered<'a> {
Scalar(FfiValue),
Aggregate(&'a [u8]),
}
let code_ptr = code_ptr_from_addr(addr).ok_or(CallError::NullFunctionPointer)?;
let mut ffi_arg_types: Vec<Type> = Vec::with_capacity(args.len());
let mut lowered: Vec<Lowered<'_>> = Vec::with_capacity(args.len());
for arg in args {
match arg {
CallArg::Typed { code, buffer } => {
let ty = ffi_type_from_code(code)
.ok_or_else(|| CallError::UnknownTypeCode((*code).to_string()))?;
ffi_arg_types.push(ty);
lowered.push(Lowered::Scalar(ffi_value_from_type_code(code, buffer)));
}
CallArg::Int(value) => {
ffi_arg_types.push(ffi_i32_type());
lowered.push(Lowered::Scalar(FfiValue::I32(*value)));
}
CallArg::Double(value) => {
ffi_arg_types.push(ffi_f64_type());
lowered.push(Lowered::Scalar(FfiValue::F64(*value)));
}
CallArg::Pointer(value) => {
ffi_arg_types.push(ffi_pointer_type());
lowered.push(Lowered::Scalar(FfiValue::Pointer(*value)));
}
CallArg::Aggregate { layout, buffer } => {
let expected = layout.size();
if buffer.len() < expected {
return Err(CallError::BufferTooSmall {
expected,
got: buffer.len(),
});
}
ffi_arg_types.push(layout.to_ffi_type()?);
lowered.push(Lowered::Aggregate(buffer));
}
}
}
let ffi_return_type = match ret {
CallRet::Void => ffi_void_type(),
CallRet::Code(code) => {
ffi_type_from_code(code).ok_or_else(|| CallError::UnknownTypeCode(code.to_string()))?
}
CallRet::Pointer => ffi_pointer_type(),
CallRet::Aggregate(layout) => layout.to_ffi_type()?,
};
let ffi_args: Vec<Arg<'_>> = lowered
.iter()
.map(|arg| match arg {
Lowered::Scalar(value) => ffi_arg_from_value(value),
Lowered::Aggregate(buffer) => Arg::new(buffer.first().unwrap_or(&0u8)),
})
.collect();
let cif = Cif::new(ffi_arg_types, ffi_return_type);
let mut aggregate_buffer: Vec<u64> = match ret {
CallRet::Aggregate(layout) => vec![0u64; core::cmp::max(layout.size(), 8).div_ceil(8)],
_ => Vec::new(),
};
enum RawResult {
Void,
Pointer(usize),
Scalar(u64),
Aggregate,
}
let mut invoke = || -> RawResult {
match ret {
CallRet::Void => {
unsafe { cif.call::<()>(code_ptr, &ffi_args) };
RawResult::Void
}
CallRet::Code(code) if simple_type_is_pointer(code) => {
RawResult::Pointer(unsafe { cif.call::<usize>(code_ptr, &ffi_args) })
}
CallRet::Code(_) => {
RawResult::Scalar(unsafe { cif.call::<u64>(code_ptr, &ffi_args) })
}
CallRet::Pointer => {
RawResult::Pointer(unsafe { cif.call::<usize>(code_ptr, &ffi_args) })
}
CallRet::Aggregate(_) => {
unsafe {
cif.call_return_into(code_ptr, &ffi_args, Ret::new(&mut aggregate_buffer[..]));
}
RawResult::Aggregate
}
}
};
#[cfg(not(windows))]
let raw = if options.use_errno {
with_swapped_errno(invoke)
} else {
invoke()
};
#[cfg(windows)]
let raw = if options.use_last_error {
with_swapped_last_error(invoke)
} else {
invoke()
};
let result = match raw {
RawResult::Void => CallValue::Void,
RawResult::Pointer(ptr) => CallValue::Pointer(ptr),
RawResult::Scalar(value) => CallValue::Scalar(value.to_ne_bytes().to_vec()),
RawResult::Aggregate => {
let size = match ret {
CallRet::Aggregate(layout) => layout.size(),
_ => 0,
};
let bytes: Vec<u8> = aggregate_buffer
.iter()
.flat_map(|word| word.to_ne_bytes())
.collect();
CallValue::Aggregate(bytes[..size].to_vec())
}
};
Ok(result)
}
pub unsafe fn bytes_at(ptr: *const u8, len: usize) -> Vec<u8> {
unsafe { core::slice::from_raw_parts(ptr, len) }.to_vec()
}
pub unsafe fn borrow_memory(ptr: *const u8, size: usize) -> &'static [u8] {
unsafe { core::slice::from_raw_parts(ptr, size) }
}
pub unsafe fn borrow_memory_mut(ptr: *mut u8, size: usize) -> &'static mut [u8] {
unsafe { core::slice::from_raw_parts_mut(ptr, size) }
}
#[allow(
clippy::mut_from_ref,
reason = "ctypes borrowed buffers may wrap writable memory behind a shared slice"
)]
pub unsafe fn borrowed_slice_as_mut(slice: &[u8]) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(slice.as_ptr() as *mut u8, slice.len()) }
}
pub fn wide_chars_to_wtf8(wchars: &[WChar]) -> Wtf8Buf {
#[cfg(windows)]
{
Wtf8Buf::from_wide(wchars)
}
#[cfg(not(windows))]
{
#[allow(
clippy::useless_conversion,
reason = "wchar_t is i32 on some platforms and u32 on others"
)]
let s: String = wchars
.iter()
.filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32))
.collect();
Wtf8Buf::from_string(s)
}
}
pub unsafe fn read_wide_string(ptr: NonNull<WChar>) -> Wtf8Buf {
let wchars = unsafe { WideCStr::from_ptr_str(ptr.as_ptr().cast()) };
Wtf8Buf::from_string(wchars.to_string_lossy())
}
pub unsafe fn read_c_string_from_address(addr: usize) -> Option<Vec<u8>> {
if addr == 0 {
None
} else {
Some(unsafe { read_c_string_bytes(addr as *const c_char) })
}
}
pub unsafe fn read_wide_string_from_address(addr: usize) -> Option<Wtf8Buf> {
let ptr = NonNull::new(addr as *mut WChar)?;
Some(unsafe { read_wide_string(ptr) })
}
pub unsafe fn read_wide_string_with_len(ptr: NonNull<WChar>, len: usize) -> Wtf8Buf {
let wchars = unsafe { core::slice::from_raw_parts(ptr.as_ptr(), len) };
wide_chars_to_wtf8(wchars)
}
pub fn string_at(ptr: usize, size: isize) -> Result<Vec<u8>, StringAtError> {
if ptr == 0 {
return Err(StringAtError::NullPointer);
}
if size < 0 {
return Ok(unsafe { read_c_string_bytes(ptr as _) });
}
let len = {
let size_usize = size as usize;
if size_usize > isize::MAX as usize / 2 {
return Err(StringAtError::TooLong);
}
size_usize
};
Ok(unsafe { bytes_at(ptr as *const u8, len) })
}
pub fn wstring_at(ptr: usize, size: isize) -> Result<Wtf8Buf, StringAtError> {
let Some(ptr) = NonNull::new(ptr as *mut WChar) else {
return Err(StringAtError::NullPointer);
};
if size < 0 {
return Ok(unsafe { read_wide_string(ptr) });
}
let len = {
let size_usize = size as usize;
if size_usize > isize::MAX as usize / core::mem::size_of::<WChar>() {
return Err(StringAtError::TooLong);
}
size_usize
};
Ok(unsafe { read_wide_string_with_len(ptr, len) })
}
pub unsafe fn read_bytes_strided(start: *const u8, len: usize, step: isize) -> Vec<u8> {
if step == 1 {
return unsafe { bytes_at(start, len) };
}
let mut result = Vec::with_capacity(len);
let mut cur = start;
for _ in 0..len {
result.push(unsafe { *cur });
cur = unsafe { cur.offset(step) };
}
result
}
pub fn pointer_item_address(ptr_value: usize, index: isize, element_size: usize) -> usize {
let offset = index * element_size as isize;
(ptr_value as isize + offset) as usize
}
pub fn offset_address(base: usize, offset: isize) -> usize {
(base as isize + offset) as usize
}
pub unsafe fn read_pointer_char_slice(
ptr_value: usize,
start: isize,
len: usize,
step: isize,
element_size: usize,
) -> Vec<u8> {
let start_addr = pointer_item_address(ptr_value, start, element_size) as *const u8;
if step == 1 {
unsafe { bytes_at(start_addr, len) }
} else {
unsafe { read_bytes_strided(start_addr, len, step * element_size as isize) }
}
}
pub unsafe fn read_wide_string_strided(start: NonNull<WChar>, len: usize, step: isize) -> Wtf8Buf {
if step == 1 {
return unsafe { read_wide_string_with_len(start, len) };
}
let mut wchars = Vec::with_capacity(len);
let mut cur = start;
for _ in 0..len {
wchars.push(unsafe { cur.read() });
cur = unsafe { cur.offset(step) };
}
wide_chars_to_wtf8(&wchars)
}
pub unsafe fn read_pointer_wchar_slice(
ptr_value: usize,
start: isize,
len: usize,
step: isize,
) -> Option<Wtf8Buf> {
let start_addr = unsafe { NonNull::new(ptr_value as *mut WChar)?.offset(start) };
Some(unsafe { read_wide_string_strided(start_addr, len, step) })
}
pub unsafe fn read_value_at_address(
addr: usize,
size: usize,
type_code: Option<&str>,
) -> AddressValue {
let ptr = addr as *const u8;
match type_code {
Some("c") => AddressValue::ByteString(unsafe { *ptr }),
Some("b") => AddressValue::Integer(IntegerValue::Signed(unsafe { *ptr as i8 as i64 })),
Some("B") => AddressValue::Integer(IntegerValue::Unsigned(unsafe { (*ptr).into() })),
Some("h") => AddressValue::Integer(IntegerValue::Signed(
unsafe { core::ptr::read_unaligned(ptr as *const i16) }.into(),
)),
Some("H") => AddressValue::Integer(IntegerValue::Unsigned(
unsafe { core::ptr::read_unaligned(ptr as *const u16) }.into(),
)),
Some("i") => AddressValue::Integer(IntegerValue::Signed(
unsafe { core::ptr::read_unaligned(ptr as *const i32) }.into(),
)),
Some("I") => AddressValue::Integer(IntegerValue::Unsigned(
unsafe { core::ptr::read_unaligned(ptr as *const u32) }.into(),
)),
Some("l") => AddressValue::Integer(IntegerValue::Signed(unsafe {
core::ptr::read_unaligned(ptr as *const c_long)
} as i64)),
Some("L") => AddressValue::Integer(IntegerValue::Unsigned(unsafe {
core::ptr::read_unaligned(ptr as *const c_ulong)
} as u64)),
Some("q") => AddressValue::Integer(IntegerValue::Signed(unsafe {
core::ptr::read_unaligned(ptr as *const i64)
})),
Some("Q") => AddressValue::Integer(IntegerValue::Unsigned(unsafe {
core::ptr::read_unaligned(ptr as *const u64)
})),
Some("f") => {
AddressValue::Float(unsafe { core::ptr::read_unaligned(ptr as *const f32) as f64 })
}
Some("d" | "g") => {
AddressValue::Float(unsafe { core::ptr::read_unaligned(ptr as *const f64) })
}
Some("P" | "z" | "Z") => {
AddressValue::Pointer(unsafe { core::ptr::read_unaligned(ptr as *const usize) })
}
_ => AddressValue::Bytes(unsafe { bytes_at(ptr, size) }),
}
}
pub unsafe fn write_u8_at_address(addr: usize, value: u8) {
unsafe { *(addr as *mut u8) = value };
}
pub unsafe fn write_i16_at_address(addr: usize, value: i16) {
unsafe { core::ptr::write_unaligned(addr as *mut i16, value) };
}
pub unsafe fn write_i32_at_address(addr: usize, value: i32) {
unsafe { core::ptr::write_unaligned(addr as *mut i32, value) };
}
pub unsafe fn write_i64_at_address(addr: usize, value: i64) {
unsafe { core::ptr::write_unaligned(addr as *mut i64, value) };
}
pub unsafe fn write_pointer_at_address(addr: usize, value: usize) {
unsafe { core::ptr::write_unaligned(addr as *mut usize, value) };
}
pub unsafe fn write_f32_at_address(addr: usize, value: f32) {
unsafe { core::ptr::write_unaligned(addr as *mut f32, value) };
}
pub unsafe fn write_f64_at_address(addr: usize, value: f64) {
unsafe { core::ptr::write_unaligned(addr as *mut f64, value) };
}
pub unsafe fn write_value_to_address(addr: usize, size: usize, value: AddressWriteValue<'_>) {
match value {
AddressWriteValue::Pointer(value) => unsafe { write_pointer_at_address(addr, value) },
AddressWriteValue::U8(value) => unsafe { write_u8_at_address(addr, value) },
AddressWriteValue::I16(value) => unsafe { write_i16_at_address(addr, value) },
AddressWriteValue::I32(value) => unsafe { write_i32_at_address(addr, value) },
AddressWriteValue::I64(value) => unsafe { write_i64_at_address(addr, value) },
AddressWriteValue::Float(value) => match size {
4 => unsafe { write_f32_at_address(addr, value as f32) },
8 => unsafe { write_f64_at_address(addr, value) },
_ => {}
},
AddressWriteValue::Bytes(bytes) => unsafe { copy_bytes_to_address(addr, bytes, size) },
}
}
pub unsafe fn copy_bytes_to_address(addr: usize, bytes: &[u8], size: usize) {
let copy_len = bytes.len().min(size);
unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), addr as *mut u8, copy_len) };
}
pub fn write_simple_storage_buffer(buffer: &mut Cow<'_, [u8]>, bytes: &[u8]) {
match buffer {
Cow::Borrowed(slice) => {
unsafe {
copy_bytes_to_address(slice.as_ptr() as usize, bytes, slice.len());
}
}
Cow::Owned(vec) => {
vec.copy_from_slice(bytes);
}
}
}
pub fn write_cow_bytes_at_offset(buffer: &mut Cow<'_, [u8]>, offset: usize, bytes: &[u8]) {
if offset + bytes.len() > buffer.len() {
return;
}
match buffer {
Cow::Borrowed(slice) => {
unsafe {
copy_bytes_to_address(slice.as_ptr() as usize + offset, bytes, bytes.len());
}
}
Cow::Owned(vec) => {
vec[offset..offset + bytes.len()].copy_from_slice(bytes);
}
}
}
pub fn resize_owned_bytes(old_data: &[u8], new_size: usize) -> Vec<u8> {
let mut new_data = vec![0u8; new_size];
let copy_len = old_data.len().min(new_size);
new_data[..copy_len].copy_from_slice(&old_data[..copy_len]);
new_data
}
#[cfg(any(unix, windows, target_os = "wasi"))]
pub fn memmove_addr() -> usize {
libc::memmove as *const () as usize
}
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn memmove_addr() -> usize {
0
}
#[cfg(any(unix, windows, target_os = "wasi"))]
pub fn memset_addr() -> usize {
libc::memset as *const () as usize
}
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn memset_addr() -> usize {
0
}
#[cfg(any(unix, windows))]
pub enum LookupSymbolError {
LibraryNotFound,
LibraryClosed,
Load(String),
}
#[cfg(any(unix, windows))]
struct SharedLibrary {
lib: Mutex<Option<Library>>,
}
#[cfg(any(unix, windows))]
impl SharedLibrary {
#[cfg(windows)]
fn new(name: impl AsRef<OsStr>) -> Result<Self, libloading::Error> {
Ok(Self {
lib: Mutex::new(unsafe { Some(Library::new(name.as_ref())?) }),
})
}
#[cfg(unix)]
fn new_with_mode(name: impl AsRef<OsStr>, mode: i32) -> Result<Self, libloading::Error> {
Ok(Self {
lib: Mutex::new(Some(unsafe {
UnixLibrary::open(Some(name.as_ref()), mode)?.into()
})),
})
}
#[cfg(unix)]
fn from_raw_handle(handle: *mut c_void) -> Self {
Self {
lib: Mutex::new(Some(unsafe { UnixLibrary::from_raw(handle).into() })),
}
}
fn get_pointer(&self) -> usize {
let lib_lock = self.lib.lock();
if let Some(l) = &*lib_lock {
unsafe { core::mem::transmute_copy::<Library, usize>(l) }
} else {
0
}
}
fn lookup_data_symbol_addr(&self, symbol_name: &[u8]) -> Result<usize, LookupSymbolError> {
let lib_lock = self.lib.lock();
let Some(lib) = &*lib_lock else {
return Err(LookupSymbolError::LibraryClosed);
};
let pointer = unsafe {
lib.get::<*const u8>(symbol_name)
.map_err(|err| LookupSymbolError::Load(err.to_string()))?
};
Ok(*pointer as usize)
}
fn lookup_function_symbol_addr(&self, symbol_name: &[u8]) -> Result<usize, LookupSymbolError> {
let lib_lock = self.lib.lock();
let Some(lib) = &*lib_lock else {
return Err(LookupSymbolError::LibraryClosed);
};
let pointer = unsafe {
lib.get::<unsafe extern "C" fn()>(symbol_name)
.map_err(|err| LookupSymbolError::Load(err.to_string()))?
};
Ok(*pointer as *const () as usize)
}
}
#[cfg(any(unix, windows))]
struct ExternalLibs {
libraries: HashMap<usize, SharedLibrary>,
}
#[cfg(any(unix, windows))]
impl ExternalLibs {
fn new() -> Self {
Self {
libraries: HashMap::new(),
}
}
fn get_lib(&self, key: usize) -> Option<&SharedLibrary> {
self.libraries.get(&key)
}
#[cfg(windows)]
fn open_library(
&mut self,
library_path: impl AsRef<OsStr>,
) -> Result<usize, libloading::Error> {
let new_lib = SharedLibrary::new(library_path)?;
let key = new_lib.get_pointer();
if self.libraries.contains_key(&key) {
drop(new_lib);
return Ok(key);
}
self.libraries.insert(key, new_lib);
Ok(key)
}
#[cfg(unix)]
fn open_library_with_mode(
&mut self,
library_path: impl AsRef<OsStr>,
mode: i32,
) -> Result<usize, libloading::Error> {
let new_lib = SharedLibrary::new_with_mode(library_path, mode)?;
let key = new_lib.get_pointer();
if self.libraries.contains_key(&key) {
drop(new_lib);
return Ok(key);
}
self.libraries.insert(key, new_lib);
Ok(key)
}
#[cfg(unix)]
fn insert_raw_library_handle(&mut self, handle: *mut c_void) -> usize {
let key = handle as usize;
self.libraries
.insert(key, SharedLibrary::from_raw_handle(handle));
key
}
fn drop_library(&mut self, key: usize) {
self.libraries.remove(&key);
}
}
#[cfg(any(unix, windows))]
fn libcache() -> &'static RwLock<ExternalLibs> {
static LIBCACHE: OnceLock<RwLock<ExternalLibs>> = OnceLock::new();
LIBCACHE.get_or_init(|| RwLock::new(ExternalLibs::new()))
}
#[cfg(windows)]
pub fn open_library(name: impl AsRef<OsStr>) -> Result<usize, libloading::Error> {
libcache().write().open_library(name)
}
#[cfg(windows)]
pub fn load_library_ex_w(
name: &widestring::WideCStr,
flags: u32,
) -> std::io::Result<windows_sys::Win32::Foundation::HMODULE> {
let module = unsafe {
windows_sys::Win32::System::LibraryLoader::LoadLibraryExW(
name.as_ptr(),
core::ptr::null_mut(),
flags,
)
};
if module.is_null() {
Err(std::io::Error::last_os_error())
} else {
Ok(module)
}
}
#[cfg(windows)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn get_proc_address(
module: windows_sys::Win32::Foundation::HMODULE,
name: &CStr,
) -> Option<usize> {
unsafe {
windows_sys::Win32::System::LibraryLoader::GetProcAddress(module, name.as_ptr().cast())
}
.map(|addr| addr as usize)
.filter(|&addr| addr != 0)
}
#[cfg(windows)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn free_library(module: windows_sys::Win32::Foundation::HMODULE) -> std::io::Result<()> {
if unsafe { windows_sys::Win32::Foundation::FreeLibrary(module) } == 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}
#[cfg(windows)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn sys_string_len(bstr: *const u16) -> usize {
unsafe { windows_sys::Win32::Foundation::SysStringLen(bstr) as usize }
}
#[cfg(windows)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn sys_free_string(bstr: *const u16) {
unsafe { windows_sys::Win32::Foundation::SysFreeString(bstr) };
}
#[cfg(windows)]
pub fn sys_alloc_string_len(units: &[u16]) -> Option<*mut u16> {
let len = u32::try_from(units.len()).ok()?;
let bstr = unsafe { windows_sys::Win32::Foundation::SysAllocStringLen(units.as_ptr(), len) };
(!bstr.is_null()).then_some(bstr as *mut u16)
}
#[cfg(windows)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn co_task_mem_free(ptr: *mut core::ffi::c_void) {
unsafe { windows_sys::Win32::System::Com::CoTaskMemFree(ptr) };
}
#[cfg(windows)]
pub fn get_error_info() -> Option<*mut core::ffi::c_void> {
let mut pei = core::ptr::null_mut();
if unsafe { windows_sys::Win32::System::Com::GetErrorInfo(0, &mut pei) } != 0 {
None
} else {
Some(pei.cast())
}
}
#[cfg(windows)]
pub fn prog_id_from_clsid(guid: &[u8; 16]) -> Option<*mut u16> {
let mut progid = core::ptr::null_mut();
if unsafe {
windows_sys::Win32::System::Com::ProgIDFromCLSID(guid.as_ptr().cast(), &mut progid)
} != 0
|| progid.is_null()
{
None
} else {
Some(progid)
}
}
#[cfg(unix)]
pub fn open_library_with_mode(
name: impl AsRef<OsStr>,
mode: i32,
) -> Result<usize, libloading::Error> {
libcache().write().open_library_with_mode(name, mode)
}
#[cfg(not(unix))]
pub fn open_library_with_mode(
_name: impl AsRef<std::ffi::OsStr>,
_mode: i32,
) -> Result<usize, String> {
Err("dlopen() error".to_string())
}
#[cfg(unix)]
pub fn insert_raw_library_handle(handle: *mut c_void) -> usize {
libcache().write().insert_raw_library_handle(handle)
}
#[cfg(not(unix))]
pub fn insert_raw_library_handle(_handle: *mut c_void) -> usize {
0
}
#[cfg(any(unix, windows))]
pub fn drop_library(handle: usize) {
libcache().write().drop_library(handle);
}
#[cfg(not(any(unix, windows)))]
pub fn drop_library(_handle: usize) {}
#[cfg(any(unix, windows))]
pub fn lookup_data_symbol_addr(
handle: usize,
symbol_name: &[u8],
) -> Result<usize, LookupSymbolError> {
let cache = libcache().read();
if let Some(lib) = cache.get_lib(handle) {
return lib.lookup_data_symbol_addr(symbol_name);
}
#[cfg(windows)]
{
drop(cache);
lookup_raw_windows_symbol(handle, symbol_name)
}
#[cfg(not(windows))]
Err(LookupSymbolError::LibraryNotFound)
}
#[cfg(any(unix, windows))]
pub fn lookup_function_symbol_addr(
handle: usize,
symbol_name: &[u8],
) -> Result<usize, LookupSymbolError> {
let cache = libcache().read();
if let Some(lib) = cache.get_lib(handle) {
return lib.lookup_function_symbol_addr(symbol_name);
}
#[cfg(windows)]
{
drop(cache);
lookup_raw_windows_symbol(handle, symbol_name)
}
#[cfg(not(windows))]
Err(LookupSymbolError::LibraryNotFound)
}
#[cfg(windows)]
fn lookup_raw_windows_symbol(
handle: usize,
symbol_name: &[u8],
) -> Result<usize, LookupSymbolError> {
let owned;
let name = if let Ok(name) = CStr::from_bytes_with_nul(symbol_name) {
name
} else {
owned = alloc::ffi::CString::new(symbol_name)
.map_err(|err| LookupSymbolError::Load(err.to_string()))?;
owned.as_c_str()
};
match get_proc_address(handle as _, name) {
Some(addr) => Ok(addr),
None => {
match std::io::Error::last_os_error().raw_os_error() {
Some(6 | 126) => Err(LookupSymbolError::LibraryNotFound),
_ => Err(LookupSymbolError::Load(format!(
"function '{}' not found",
name.to_string_lossy()
))),
}
}
}
}
#[cfg(all(unix, not(target_os = "wasi")))]
pub fn dlopen_self(mode: c_int) -> Result<*mut c_void, String> {
let handle = unsafe { libc::dlopen(core::ptr::null(), mode) };
if handle.is_null() {
let err = unsafe { libc::dlerror() };
Err(if err.is_null() {
"dlopen() error".to_string()
} else {
unsafe { CStr::from_ptr(err) }
.to_string_lossy()
.into_owned()
})
} else {
Ok(handle)
}
}
#[cfg(not(any(windows, all(unix, not(target_os = "wasi")))))]
pub fn dlopen_self(_mode: c_int) -> Result<*mut c_void, String> {
Err("dlopen() error".to_string())
}
#[cfg(all(unix, not(target_os = "wasi")))]
pub fn dlsym_checked(handle: usize, symbol_name: &CStr) -> Result<*mut c_void, String> {
unsafe {
libc::dlerror();
}
let ptr = unsafe { libc::dlsym(handle as *mut c_void, symbol_name.as_ptr()) };
let err = unsafe { libc::dlerror() };
if !err.is_null() {
return Err(unsafe { CStr::from_ptr(err) }
.to_string_lossy()
.into_owned());
}
if ptr.is_null() {
return Err(format!(
"symbol '{}' not found",
symbol_name.to_string_lossy()
));
}
Ok(ptr)
}
#[cfg(not(any(windows, all(unix, not(target_os = "wasi")))))]
pub fn dlsym_checked(_handle: usize, symbol_name: &CStr) -> Result<*mut c_void, String> {
Err(format!(
"symbol '{}' not found",
symbol_name.to_string_lossy()
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_type_is_pointer_classifies_codes() {
assert!(simple_type_is_pointer("z"));
assert!(simple_type_is_pointer("Z"));
assert!(simple_type_is_pointer("P"));
assert!(simple_type_is_pointer("O"));
assert!(!simple_type_is_pointer("i"));
assert!(!simple_type_is_pointer("d"));
assert!(!simple_type_is_pointer(""));
}
#[test]
fn simple_type_chars_contains_expected_codes() {
let chars = simple_type_chars();
assert!(chars.contains('i'));
assert!(chars.contains('d'));
assert!(chars.contains('P'));
assert!(!chars.contains('@'));
assert!(!chars.contains(' '));
assert!(!chars.contains('1'));
}
#[cfg(all(
any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
),
not(any(target_env = "musl", target_env = "sgx"))
))]
mod call_tests {
use super::*;
extern "C" fn abs_i32(x: i32) -> i32 {
x.abs()
}
extern "C" fn add_i32(a: i32, b: i32) -> i32 {
a + b
}
extern "C" fn sqrt_f64(x: f64) -> f64 {
x.sqrt()
}
extern "C" fn noop() {}
#[repr(C)]
struct PairI32 {
a: i32,
b: i32,
}
extern "C" fn sum_pair(p: PairI32) -> i32 {
p.a + p.b
}
extern "C" fn ret_pair() -> PairI32 {
PairI32 { a: 10, b: 20 }
}
#[repr(C)]
struct PairF32 {
x: f32,
y: f32,
}
extern "C" fn sum_pair_f32(p: PairF32) -> f32 {
p.x + p.y
}
#[repr(C)]
struct Inner {
a: i32,
b: i32,
}
#[repr(C)]
struct Outer {
inner: Inner,
c: i32,
}
extern "C" fn sum_outer(o: Outer) -> i32 {
o.inner.a + o.inner.b + o.c
}
#[repr(C)]
struct ArrStruct {
arr: [i32; 3],
tag: i32,
}
extern "C" fn sum_arr_struct(s: ArrStruct) -> i32 {
s.arr[0] + s.arr[1] + s.arr[2] + s.tag
}
#[repr(C)]
struct Big {
a: i64,
b: i64,
c: i64,
}
extern "C" fn sum_big(v: Big) -> i64 {
v.a + v.b + v.c
}
extern "C" fn ret_big() -> Big {
Big { a: 1, b: 2, c: 3 }
}
#[allow(dead_code)]
#[repr(C)]
struct S3 {
a: u8,
b: u8,
c: u8,
}
extern "C" fn ret_s3() -> S3 {
S3 { a: 1, b: 2, c: 3 }
}
#[allow(dead_code)]
#[repr(C)]
struct S5 {
a: u8,
b: u8,
c: u8,
d: u8,
e: u8,
}
extern "C" fn ret_s5() -> S5 {
S5 {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5,
}
}
#[allow(dead_code)]
#[repr(C)]
struct S12 {
a: i32,
b: i32,
c: i32,
}
extern "C" fn ret_s12() -> S12 {
S12 {
a: 100,
b: 200,
c: 300,
}
}
fn addr_of(f: extern "C" fn() -> ()) -> usize {
f as *const () as usize
}
fn scalar_bytes(value: &CallValue) -> &[u8] {
match value {
CallValue::Scalar(bytes) => bytes,
other => panic!("expected Scalar, got {other:?}"),
}
}
fn aggregate_bytes(value: &CallValue) -> &[u8] {
match value {
CallValue::Aggregate(bytes) => bytes,
other => panic!("expected Aggregate, got {other:?}"),
}
}
fn i32_bytes(values: &[i32]) -> Vec<u8> {
values.iter().flat_map(|v| v.to_ne_bytes()).collect()
}
#[test]
fn calls_f64_scalar() {
let addr = sqrt_f64 as *const () as usize;
let result = call(
addr,
&[CallArg::Double(2.0)],
CallRet::Code("d"),
CallOptions::default(),
)
.unwrap();
match decode_type_code("d", scalar_bytes(&result)) {
DecodedValue::Float(v) => {
assert!((v - core::f64::consts::SQRT_2).abs() < 1e-12)
}
_ => panic!("expected Float return"),
}
}
#[test]
fn typed_scalar_arg_from_buffer() {
let addr = abs_i32 as *const () as usize;
let buffer = (-5i32).to_ne_bytes();
let result = call(
addr,
&[CallArg::Typed {
code: "i",
buffer: &buffer,
}],
CallRet::Code("i"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(
decode_type_code("i", scalar_bytes(&result)),
DecodedValue::Signed(5)
));
}
#[test]
fn typed_two_scalar_args() {
let addr = add_i32 as *const () as usize;
let a = 2i32.to_ne_bytes();
let b = 3i32.to_ne_bytes();
let result = call(
addr,
&[
CallArg::Typed {
code: "i",
buffer: &a,
},
CallArg::Typed {
code: "i",
buffer: &b,
},
],
CallRet::Code("i"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(
decode_type_code("i", scalar_bytes(&result)),
DecodedValue::Signed(5)
));
}
#[test]
fn void_return_is_void() {
let result = call(addr_of(noop), &[], CallRet::Void, CallOptions::default()).unwrap();
assert!(matches!(result, CallValue::Void));
}
#[test]
fn every_simple_code_is_accepted() {
for code in simple_type_chars().chars() {
let code = code.to_string();
assert!(
ffi_type_from_code(&code).is_some(),
"code {code:?} not accepted by call's arg/return lowering"
);
}
}
#[test]
fn scalar_lowering_helpers_agree_where_carrier_matches() {
let buffer = 0x1122_3344_5566_7788u64.to_ne_bytes();
for code in ["b", "B", "h", "H", "i", "I", "q", "Q", "d", "f"] {
let by_code = ffi_value_from_type_code(code, &buffer);
let by_type =
ffi_value_from_type(&buffer, ffi_type_from_code(code).unwrap()).unwrap();
assert_eq!(
format!("{by_code:?}"),
format!("{by_type:?}"),
"code {code}"
);
}
}
#[test]
fn scalar_lowering_helpers_diverge_for_signed_char() {
let buffer = [200u8];
assert!(matches!(
ffi_value_from_type_code("c", &buffer),
FfiValue::I8(-56)
));
assert!(matches!(
ffi_value_from_type(&buffer, ffi_type_from_code("c").unwrap()),
Some(FfiValue::U8(200))
));
}
#[test]
fn passes_struct_by_value() {
let addr = sum_pair as *const () as usize;
let layout = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('i'), CTypeLayout::Simple('i')],
size: core::mem::size_of::<PairI32>(),
};
let buffer = i32_bytes(&[3, 4]);
let result = call(
addr,
&[CallArg::Aggregate {
layout: &layout,
buffer: &buffer,
}],
CallRet::Code("i"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(
decode_type_code("i", scalar_bytes(&result)),
DecodedValue::Signed(7)
));
}
#[test]
fn passes_nested_struct_by_value() {
let addr = sum_outer as *const () as usize;
let inner = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('i'), CTypeLayout::Simple('i')],
size: core::mem::size_of::<Inner>(),
};
let layout = CTypeLayout::Struct {
fields: vec![inner, CTypeLayout::Simple('i')],
size: core::mem::size_of::<Outer>(),
};
let buffer = i32_bytes(&[5, 6, 7]);
let result = call(
addr,
&[CallArg::Aggregate {
layout: &layout,
buffer: &buffer,
}],
CallRet::Code("i"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(
decode_type_code("i", scalar_bytes(&result)),
DecodedValue::Signed(18)
));
}
#[test]
fn passes_array_in_struct_by_value() {
let addr = sum_arr_struct as *const () as usize;
let layout = CTypeLayout::Struct {
fields: vec![
CTypeLayout::Array {
element: Box::new(CTypeLayout::Simple('i')),
length: 3,
size: 12,
},
CTypeLayout::Simple('i'),
],
size: core::mem::size_of::<ArrStruct>(),
};
let buffer = i32_bytes(&[1, 2, 3, 4]);
let result = call(
addr,
&[CallArg::Aggregate {
layout: &layout,
buffer: &buffer,
}],
CallRet::Code("i"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(
decode_type_code("i", scalar_bytes(&result)),
DecodedValue::Signed(10)
));
}
#[test]
fn passes_float_pair_struct_by_value() {
let addr = sum_pair_f32 as *const () as usize;
let layout = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('f'), CTypeLayout::Simple('f')],
size: core::mem::size_of::<PairF32>(),
};
let mut buffer = Vec::new();
buffer.extend_from_slice(&1.5f32.to_ne_bytes());
buffer.extend_from_slice(&2.25f32.to_ne_bytes());
let result = call(
addr,
&[CallArg::Aggregate {
layout: &layout,
buffer: &buffer,
}],
CallRet::Code("f"),
CallOptions::default(),
)
.unwrap();
match decode_type_code("f", scalar_bytes(&result)) {
DecodedValue::Float(v) => assert!((v - 3.75).abs() < 1e-6),
_ => panic!("expected Float return"),
}
}
#[test]
fn passes_large_struct_by_value() {
let addr = sum_big as *const () as usize;
let layout = CTypeLayout::Struct {
fields: vec![
CTypeLayout::Simple('q'),
CTypeLayout::Simple('q'),
CTypeLayout::Simple('q'),
],
size: core::mem::size_of::<Big>(),
};
let mut buffer = Vec::new();
for v in [11i64, 22, 33] {
buffer.extend_from_slice(&v.to_ne_bytes());
}
let result = call(
addr,
&[CallArg::Aggregate {
layout: &layout,
buffer: &buffer,
}],
CallRet::Code("q"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(
decode_type_code("q", scalar_bytes(&result)),
DecodedValue::Signed(66)
));
}
#[test]
fn returns_small_struct_by_value() {
let layout = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('i'), CTypeLayout::Simple('i')],
size: core::mem::size_of::<PairI32>(),
};
let result = call(
ret_pair as *const () as usize,
&[],
CallRet::Aggregate(&layout),
CallOptions::default(),
)
.unwrap();
assert_eq!(aggregate_bytes(&result), i32_bytes(&[10, 20]).as_slice());
}
#[test]
fn returns_odd_size_structs_by_value() {
let s3 = CTypeLayout::Struct {
fields: vec![
CTypeLayout::Simple('B'),
CTypeLayout::Simple('B'),
CTypeLayout::Simple('B'),
],
size: 3,
};
let result = call(
ret_s3 as *const () as usize,
&[],
CallRet::Aggregate(&s3),
CallOptions::default(),
)
.unwrap();
assert_eq!(aggregate_bytes(&result), &[1u8, 2, 3]);
let s5 = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('B'); 5],
size: 5,
};
let result = call(
ret_s5 as *const () as usize,
&[],
CallRet::Aggregate(&s5),
CallOptions::default(),
)
.unwrap();
assert_eq!(aggregate_bytes(&result), &[1u8, 2, 3, 4, 5]);
let s12 = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('i'); 3],
size: 12,
};
let result = call(
ret_s12 as *const () as usize,
&[],
CallRet::Aggregate(&s12),
CallOptions::default(),
)
.unwrap();
assert_eq!(
aggregate_bytes(&result),
i32_bytes(&[100, 200, 300]).as_slice()
);
}
#[test]
fn returns_large_struct_by_value() {
let layout = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('q'); 3],
size: core::mem::size_of::<Big>(),
};
let result = call(
ret_big as *const () as usize,
&[],
CallRet::Aggregate(&layout),
CallOptions::default(),
)
.unwrap();
let mut expected = Vec::new();
for v in [1i64, 2, 3] {
expected.extend_from_slice(&v.to_ne_bytes());
}
assert_eq!(aggregate_bytes(&result), expected.as_slice());
}
#[test]
fn pointer_return_round_trips_address() {
extern "C" fn echo_ptr(p: usize) -> usize {
p
}
let addr = echo_ptr as *const () as usize;
let sentinel = 0xDEAD_BEEFusize;
let result = call(
addr,
&[CallArg::Pointer(sentinel)],
CallRet::Code("P"),
CallOptions::default(),
)
.unwrap();
assert!(matches!(result, CallValue::Pointer(p) if p == sentinel));
let result = call(
addr,
&[CallArg::Pointer(sentinel)],
CallRet::Pointer,
CallOptions::default(),
)
.unwrap();
assert!(matches!(result, CallValue::Pointer(p) if p == sentinel));
}
#[test]
fn layout_size_matches_repr_c() {
assert_eq!(CTypeLayout::Simple('i').size(), core::mem::size_of::<i32>());
assert_eq!(CTypeLayout::Pointer.size(), core::mem::size_of::<usize>());
assert_eq!(
CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('i'), CTypeLayout::Simple('i')],
size: 8,
}
.size(),
8
);
assert_eq!(CTypeLayout::Opaque { size: 5 }.size(), 5);
assert_eq!(
CTypeLayout::Array {
element: Box::new(CTypeLayout::Simple('i')),
length: 3,
size: 12,
}
.size(),
12
);
}
#[test]
fn union_layout_reports_size_and_lowers() {
let layout = CTypeLayout::Union {
fields: vec![CTypeLayout::Simple('i'), CTypeLayout::Simple('d')],
size: 8,
};
assert_eq!(layout.size(), 8);
assert!(layout.to_ffi_type().is_ok());
}
#[test]
fn null_addr_is_error() {
let result = call(0, &[], CallRet::Void, CallOptions::default());
assert_eq!(result.err(), Some(CallError::NullFunctionPointer));
}
#[test]
fn unknown_arg_code_is_error() {
let addr = noop as *const () as usize;
let result = call(
addr,
&[CallArg::Typed {
code: "@",
buffer: &[],
}],
CallRet::Void,
CallOptions::default(),
);
assert_eq!(
result.err(),
Some(CallError::UnknownTypeCode("@".to_string()))
);
}
#[test]
fn unknown_return_code_is_error() {
let addr = noop as *const () as usize;
let result = call(addr, &[], CallRet::Code("@"), CallOptions::default());
assert_eq!(
result.err(),
Some(CallError::UnknownTypeCode("@".to_string()))
);
}
#[test]
fn short_aggregate_buffer_is_error() {
let addr = sum_pair as *const () as usize;
let layout = CTypeLayout::Struct {
fields: vec![CTypeLayout::Simple('i'), CTypeLayout::Simple('i')],
size: 8,
};
let buffer = [0u8; 4];
let result = call(
addr,
&[CallArg::Aggregate {
layout: &layout,
buffer: &buffer,
}],
CallRet::Code("i"),
CallOptions::default(),
);
assert_eq!(
result.err(),
Some(CallError::BufferTooSmall {
expected: 8,
got: 4,
})
);
}
#[cfg(not(windows))]
const ERRNO_MARKER: i32 = 22;
#[cfg(not(windows))]
extern "C" fn write_errno_marker() -> i32 {
crate::os::set_errno(ERRNO_MARKER);
7
}
#[cfg(not(windows))]
#[test]
fn errno_swap_window_captures_and_restores() {
crate::os::set_errno(11);
super::super::CTYPES_LOCAL_ERRNO.with(|e| e.set(99));
let result = call(
write_errno_marker as *const () as usize,
&[],
CallRet::Code("i"),
CallOptions {
use_errno: true,
use_last_error: false,
},
)
.unwrap();
assert!(matches!(
decode_type_code("i", scalar_bytes(&result)),
DecodedValue::Signed(7)
));
assert_eq!(
super::super::CTYPES_LOCAL_ERRNO.with(|e| e.get()),
ERRNO_MARKER
);
assert_eq!(crate::os::get_errno(), 11);
}
}
}