/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/ffi/src/lib.rs
610 строк
12 KB
Bartek Iwańczuk
fix(ext/ffi): retain backing store references in nonblocking calls (#32775)
16 мар 2026, 21:43
Не верифицирован
16 мар 2026, 21:43
764ce2f
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. #![allow(clippy::print_stdout, reason = "FFI test output")] #![allow(clippy::print_stderr, reason = "FFI test output")] #![allow( clippy::undocumented_unsafe_blocks, reason = "FFI test code with simple unsafe usage" )] #![allow(non_upper_case_globals, reason = "matches FFI naming conventions")] use std::os::raw::c_void; use std::sync::Mutex; use std::thread::sleep; use std::time::Duration; static BUFFER: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; #[unsafe(no_mangle)] pub extern "C" fn print_something() { println!("something"); } /// # Safety /// /// The pointer to the buffer must be valid and initialized, and the length must /// not be longer than the buffer's allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn print_buffer(ptr: *const u8, len: usize) { unsafe { let buf = std::slice::from_raw_parts(ptr, len); println!("{buf:?}"); } } /// # Safety /// /// The pointer to the buffer must be valid and initialized, and the length must /// not be longer than the buffer's allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn print_buffer2( ptr1: *const u8, len1: usize, ptr2: *const u8, len2: usize, ) { unsafe { let buf1 = std::slice::from_raw_parts(ptr1, len1); let buf2 = std::slice::from_raw_parts(ptr2, len2); println!("{buf1:?} {buf2:?}"); } } #[unsafe(no_mangle)] pub extern "C" fn return_buffer() -> *const u8 { BUFFER.as_ptr() } #[unsafe(no_mangle)] pub extern "C" fn is_null_ptr(ptr: *const u8) -> bool { ptr.is_null() } #[unsafe(no_mangle)] pub extern "C" fn add_u32(a: u32, b: u32) -> u32 { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_i32(a: i32, b: i32) -> i32 { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_u64(a: u64, b: u64) -> u64 { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_i64(a: i64, b: i64) -> i64 { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_usize(a: usize, b: usize) -> usize { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_usize_fast(a: usize, b: usize) -> u32 { (a + b) as u32 } #[unsafe(no_mangle)] pub extern "C" fn add_isize(a: isize, b: isize) -> isize { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_f32(a: f32, b: f32) -> f32 { a + b } #[unsafe(no_mangle)] pub extern "C" fn add_f64(a: f64, b: f64) -> f64 { a + b } #[unsafe(no_mangle)] pub extern "C" fn and(a: bool, b: bool) -> bool { a && b } #[unsafe(no_mangle)] unsafe extern "C" fn hash(ptr: *const u8, length: u32) -> u32 { unsafe { let buf = std::slice::from_raw_parts(ptr, length as usize); let mut hash: u32 = 0; for byte in buf { hash = hash.wrapping_mul(0x10001000).wrapping_add(*byte as u32); } hash } } #[unsafe(no_mangle)] pub extern "C" fn sleep_blocking(ms: u64) { let duration = Duration::from_millis(ms); sleep(duration); } /// # Safety /// /// The pointer to the buffer must be valid and initialized, and the length must /// not be longer than the buffer's allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn fill_buffer(value: u8, buf: *mut u8, len: usize) { unsafe { let buf = std::slice::from_raw_parts_mut(buf, len); for itm in buf.iter_mut() { *itm = value; } } } /// # Safety /// /// The pointer to the buffer must be valid and initialized, and the length must /// not be longer than the buffer's allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn nonblocking_buffer(ptr: *const u8, len: usize) { unsafe { let buf = std::slice::from_raw_parts(ptr, len); assert_eq!(buf, vec![1, 2, 3, 4, 5, 6, 7, 8]); } } /// Verify that a buffer filled with a specific byte pattern remains valid after /// a delay. Used to test that nonblocking FFI calls keep the backing store alive /// even when GC is forced via --expose-gc. Sleeps to give JS time to call gc(), /// then validates the buffer contents. Returns the checksum of the buffer. /// /// # Safety /// /// The pointer to the buffer must be valid and initialized, and the length must /// not be longer than the buffer's allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn nonblocking_buffer_checksum( ptr: *const u8, len: usize, fill_value: u8, ) -> u64 { // Sleep to give JS time to call gc() and force collection sleep(Duration::from_millis(100)); unsafe { let buf = std::slice::from_raw_parts(ptr, len); let mut checksum: u64 = 0; for &byte in buf { // If GC freed the buffer, at least some bytes won't match fill_value assert_eq!( byte, fill_value, "Buffer corrupted: expected 0x{fill_value:02x}, got 0x{byte:02x}. \ Backing store was likely freed by GC." ); checksum += byte as u64; } checksum } } #[unsafe(no_mangle)] pub extern "C" fn get_add_u32_ptr() -> *const c_void { add_u32 as *const c_void } #[unsafe(no_mangle)] pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void { sleep_blocking as *const c_void } #[unsafe(no_mangle)] pub extern "C" fn call_fn_ptr(func: Option<extern "C" fn()>) { if func.is_none() { return; } let func = func.unwrap(); func(); } #[unsafe(no_mangle)] pub extern "C" fn call_fn_ptr_many_parameters( func: Option< extern "C" fn(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, *const u8), >, ) { if func.is_none() { return; } let func = func.unwrap(); func(1, -1, 2, -2, 3, -3, 4, -4, 0.5, -0.5, BUFFER.as_ptr()); } #[unsafe(no_mangle)] pub extern "C" fn call_fn_ptr_return_u8(func: Option<extern "C" fn() -> u8>) { if func.is_none() { return; } let func = func.unwrap(); println!("u8: {}", func()); } #[allow( clippy::not_unsafe_ptr_arg_deref, reason = "pointer is from trusted FFI callback" )] #[unsafe(no_mangle)] pub extern "C" fn call_fn_ptr_return_buffer( func: Option<extern "C" fn() -> *const u8>, ) { if func.is_none() { return; } let func = func.unwrap(); let ptr = func(); let buf = unsafe { std::slice::from_raw_parts(ptr, 8) }; println!("buf: {buf:?}"); } static STORED_FUNCTION: Mutex<Option<extern "C" fn()>> = Mutex::new(None); static STORED_FUNCTION_2: Mutex<Option<extern "C" fn(u8) -> u8>> = Mutex::new(None); #[unsafe(no_mangle)] pub extern "C" fn store_function(func: Option<extern "C" fn()>) { *STORED_FUNCTION.lock().unwrap() = func; if func.is_none() { println!("STORED_FUNCTION cleared"); } } #[unsafe(no_mangle)] pub extern "C" fn store_function_2(func: Option<extern "C" fn(u8) -> u8>) { *STORED_FUNCTION_2.lock().unwrap() = func; if func.is_none() { println!("STORED_FUNCTION_2 cleared"); } } #[unsafe(no_mangle)] pub extern "C" fn call_stored_function() { let f = *STORED_FUNCTION.lock().unwrap(); if let Some(f) = f { f(); } } #[unsafe(no_mangle)] pub extern "C" fn call_stored_function_2(arg: u8) { let f = *STORED_FUNCTION_2.lock().unwrap(); if let Some(f) = f { println!("{}", f(arg)); } } #[unsafe(no_mangle)] pub extern "C" fn call_stored_function_thread_safe() { std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(1500)); let f = *STORED_FUNCTION.lock().unwrap(); if let Some(f) = f { f(); } }); } #[unsafe(no_mangle)] pub extern "C" fn call_stored_function_thread_safe_and_log() { std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(1500)); let f = *STORED_FUNCTION.lock().unwrap(); if let Some(f) = f { f(); println!("STORED_FUNCTION called"); } }); } #[unsafe(no_mangle)] pub extern "C" fn call_stored_function_2_thread_safe(arg: u8) { std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(1500)); let f = *STORED_FUNCTION_2.lock().unwrap(); if let Some(f) = f { println!("Calling"); f(arg); } }); } #[unsafe(no_mangle)] pub extern "C" fn log_many_parameters( a: u8, b: u16, c: u32, d: u64, e: f64, f: f32, g: i64, h: i32, i: i16, j: i8, k: isize, l: usize, m: f64, n: f32, o: f64, p: f32, q: f64, r: f32, s: f64, ) { println!( "{a} {b} {c} {d} {e} {f} {g} {h} {i} {j} {k} {l} {m} {n} {o} {p} {q} {r} {s}" ); } #[unsafe(no_mangle)] pub extern "C" fn cast_u8_u32(x: u8) -> u32 { x as u32 } #[unsafe(no_mangle)] pub extern "C" fn cast_u32_u8(x: u32) -> u8 { x as u8 } #[unsafe(no_mangle)] pub extern "C" fn add_many_u16( a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16, i: u16, j: u16, k: u16, l: u16, m: u16, ) -> u16 { a + b + c + d + e + f + g + h + i + j + k + l + m } // FFI performance helper functions #[unsafe(no_mangle)] pub extern "C" fn nop() {} #[unsafe(no_mangle)] pub extern "C" fn nop_bool(_a: bool) {} #[unsafe(no_mangle)] pub extern "C" fn nop_u8(_a: u8) {} #[unsafe(no_mangle)] pub extern "C" fn nop_i8(_a: i8) {} #[unsafe(no_mangle)] pub extern "C" fn nop_u16(_a: u16) {} #[unsafe(no_mangle)] pub extern "C" fn nop_i16(_a: i16) {} #[unsafe(no_mangle)] pub extern "C" fn nop_u32(_a: u32) {} #[unsafe(no_mangle)] pub extern "C" fn nop_i32(_a: i32) {} #[unsafe(no_mangle)] pub extern "C" fn nop_u64(_a: u64) {} #[unsafe(no_mangle)] pub extern "C" fn nop_i64(_a: i64) {} #[unsafe(no_mangle)] pub extern "C" fn nop_usize(_a: usize) {} #[unsafe(no_mangle)] pub extern "C" fn nop_isize(_a: isize) {} #[unsafe(no_mangle)] pub extern "C" fn nop_f32(_a: f32) {} #[unsafe(no_mangle)] pub extern "C" fn nop_f64(_a: f64) {} #[unsafe(no_mangle)] pub extern "C" fn nop_buffer(_buffer: *mut [u8; 8]) {} #[unsafe(no_mangle)] pub extern "C" fn return_bool() -> bool { true } #[unsafe(no_mangle)] pub extern "C" fn return_u8() -> u8 { 255 } #[unsafe(no_mangle)] pub extern "C" fn return_i8() -> i8 { -128 } #[unsafe(no_mangle)] pub extern "C" fn return_u16() -> u16 { 65535 } #[unsafe(no_mangle)] pub extern "C" fn return_i16() -> i16 { -32768 } #[unsafe(no_mangle)] pub extern "C" fn return_u32() -> u32 { 4294967295 } #[unsafe(no_mangle)] pub extern "C" fn return_i32() -> i32 { -2147483648 } #[unsafe(no_mangle)] pub extern "C" fn return_u64() -> u64 { 18446744073709551615 } #[unsafe(no_mangle)] pub extern "C" fn return_i64() -> i64 { -9223372036854775808 } #[unsafe(no_mangle)] pub extern "C" fn return_usize() -> usize { 18446744073709551615 } #[unsafe(no_mangle)] pub extern "C" fn return_isize() -> isize { -9223372036854775808 } #[unsafe(no_mangle)] pub extern "C" fn return_f32() -> f32 { #[allow( clippy::excessive_precision, reason = "exact f32 representation needed for test" )] 0.20298023223876953125 } #[unsafe(no_mangle)] pub extern "C" fn return_f64() -> f64 { 1e-10 } // Parameters iteration #[unsafe(no_mangle)] pub extern "C" fn nop_many_parameters( _: u8, _: i8, _: u16, _: i16, _: u32, _: i32, _: u64, _: i64, _: usize, _: isize, _: f32, _: f64, _: *mut [u8; 8], _: u8, _: i8, _: u16, _: i16, _: u32, _: i32, _: u64, _: i64, _: usize, _: isize, _: f32, _: f64, _: *mut [u8; 8], ) { } // Statics #[unsafe(no_mangle)] pub static static_u32: u32 = 42; #[unsafe(no_mangle)] pub static static_i64: i64 = -1242464576485; #[repr(C)] pub struct Structure { _data: u32, } #[unsafe(no_mangle)] pub static mut static_ptr: Structure = Structure { _data: 42 }; static STRING: &str = "Hello, world!\0"; #[unsafe(no_mangle)] extern "C" fn ffi_string() -> *const u8 { STRING.as_ptr() } /// Invalid UTF-8 characters, array of length 14 #[unsafe(no_mangle)] pub static static_char: [u8; 14] = [ 0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, ]; #[derive(Debug)] #[repr(C)] pub struct Rect { x: f64, y: f64, w: f64, h: f64, } #[unsafe(no_mangle)] pub extern "C" fn make_rect(x: f64, y: f64, w: f64, h: f64) -> Rect { Rect { x, y, w, h } } #[unsafe(no_mangle)] pub extern "C" fn print_rect(rect: Rect) { println!("{rect:?}"); } #[derive(Debug)] #[repr(C)] pub struct Mixed { u8: u8, f32: f32, rect: Rect, usize: usize, array: [u32; 2], } /// # Safety /// /// The array pointer to the buffer must be valid and initialized, and the length must /// be 2. #[unsafe(no_mangle)] pub unsafe extern "C" fn create_mixed( u8: u8, f32: f32, rect: Rect, usize: usize, array: *const [u32; 2], ) -> Mixed { unsafe { let array = *array .as_ref() .expect("Array parameter should contain value"); Mixed { u8, f32, rect, usize, array, } } } #[unsafe(no_mangle)] pub extern "C" fn print_mixed(mixed: Mixed) { println!("{mixed:?}"); }