From ee49cce726c27cdcb372b9dba7f2deab840d9e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20Sj=C3=B6green?= Date: Wed, 15 Dec 2021 15:41:49 +0100 Subject: [PATCH] feat(ext/ffi): implement UnsafePointer and UnsafePointerView (#12828) --- cli/dts/lib.deno.unstable.d.ts | 80 ++++++++- ext/ffi/00_ffi.js | 185 +++++++++++++++++-- ext/ffi/lib.rs | 269 ++++++++++++++++++++++++++-- runtime/build.rs | 2 +- runtime/js/90_deno_ns.js | 2 + runtime/permissions.rs | 49 +++-- test_ffi/src/lib.rs | 12 ++ test_ffi/tests/integration_tests.rs | 9 + test_ffi/tests/test.js | 35 +++- 9 files changed, 591 insertions(+), 52 deletions(-) diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index affa4b3fdd..6b7755ee51 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -117,16 +117,90 @@ declare namespace Deno { | "usize" | "isize" | "f32" - | "f64"; + | "f64" + | "pointer"; /** A foreign function as defined by its parameter and result types */ export interface ForeignFunction { - parameters: (NativeType | "buffer")[]; + parameters: NativeType[]; result: NativeType; /** When true, function calls will run on a dedicated blocking thread and will return a Promise resolving to the `result`. */ nonblocking?: boolean; } + type TypedArray = + | Int8Array + | Uint8Array + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + + /** **UNSTABLE**: Unsafe and new API, beware! + * + * An unsafe pointer to a memory location for passing and returning pointers to and from the ffi + */ + export class UnsafePointer { + constructor(value: bigint); + + value: bigint; + + /** + * Return the direct memory pointer to the typed array in memory + */ + static of(typedArray: TypedArray): UnsafePointer; + + /** + * Returns the value of the pointer which is useful in certain scenarios. + */ + valueOf(): bigint; + } + + /** **UNSTABLE**: Unsafe and new API, beware! + * + * An unsafe pointer view to a memory location as specified by the `pointer` + * value. The `UnsafePointerView` API mimics the standard built in interface + * `DataView` for accessing the underlying types at an memory location + * (numbers, strings and raw bytes). + */ + export class UnsafePointerView { + constructor(pointer: UnsafePointer); + + pointer: UnsafePointer; + + /** Gets an unsigned 8-bit integer at the specified byte offset from the pointer. */ + getUint8(offset?: number): number; + /** Gets a signed 8-bit integer at the specified byte offset from the pointer. */ + getInt8(offset?: number): number; + /** Gets an unsigned 16-bit integer at the specified byte offset from the pointer. */ + getUint16(offset?: number): number; + /** Gets a signed 16-bit integer at the specified byte offset from the pointer. */ + getInt16(offset?: number): number; + /** Gets an unsigned 32-bit integer at the specified byte offset from the pointer. */ + getUint32(offset?: number): number; + /** Gets a signed 32-bit integer at the specified byte offset from the pointer. */ + getInt32(offset?: number): number; + /** Gets an unsigned 64-bit integer at the specified byte offset from the pointer. */ + getBigUint64(offset?: number): bigint; + /** Gets a signed 64-bit integer at the specified byte offset from the pointer. */ + getBigInt64(offset?: number): bigint; + /** Gets a signed 32-bit float at the specified byte offset from the pointer. */ + getFloat32(offset?: number): number; + /** Gets a signed 64-bit float at the specified byte offset from the pointer. */ + getFloat64(offset?: number): number; + /** Gets a C string (null terminated string) at the specified byte offset from the pointer. */ + getCString(offset?: number): string; + /** Gets an ArrayBuffer of length `byteLength` at the specified byte offset from the pointer. */ + getArrayBuffer(byteLength: number, offset?: number): ArrayBuffer; + /** Copies the memory of the pointer into a typed array. Length is determined from the typed array's `byteLength`. Also takes optional offset from the pointer. */ + copyInto(destination: TypedArray, offset?: number): void; + } + /** A dynamic library resource */ export interface DynamicLibrary> { /** All of the registered symbols along with functions for calling them */ @@ -135,7 +209,7 @@ declare namespace Deno { close(): void; } - /** **UNSTABLE**: new API + /** **UNSTABLE**: Unsafe and new API, beware! * * Opens a dynamic library and registers symbols */ diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index 25eba02336..48e2e4f923 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -6,7 +6,142 @@ const __bootstrap = window.__bootstrap; const { ArrayBuffer, + Uint8Array, + BigInt, + Number, + TypeError, } = window.__bootstrap.primordials; + + function unpackU64([hi, lo]) { + return BigInt(hi) << 32n | BigInt(lo); + } + + function packU64(value) { + return [Number(value >> 32n), Number(value & 0xFFFFFFFFn)]; + } + + function unpackI64([hi, lo]) { + const u64 = unpackU64([hi, lo]); + return u64 >> 63n ? u64 - 0x10000000000000000n : u64; + } + + class UnsafePointerView { + pointer; + + constructor(pointer) { + this.pointer = pointer; + } + + getUint8(offset = 0) { + return core.opSync( + "op_ffi_read_u8", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getInt8(offset = 0) { + return core.opSync( + "op_ffi_read_i8", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getUint16(offset = 0) { + return core.opSync( + "op_ffi_read_u16", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getInt16(offset = 0) { + return core.opSync( + "op_ffi_read_i16", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getUint32(offset = 0) { + return core.opSync( + "op_ffi_read_u32", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getInt32(offset = 0) { + return core.opSync( + "op_ffi_read_i32", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getBigUint64(offset = 0) { + return unpackU64(core.opSync( + "op_ffi_read_u64", + packU64(this.pointer.value + BigInt(offset)), + )); + } + + getBigInt64(offset = 0) { + return unpackI64(core.opSync( + "op_ffi_read_u64", + packU64(this.pointer.value + BigInt(offset)), + )); + } + + getFloat32(offset = 0) { + return core.opSync( + "op_ffi_read_f32", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getFloat64(offset = 0) { + return core.opSync( + "op_ffi_read_f64", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getCString(offset = 0) { + return core.opSync( + "op_ffi_cstr_read", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getArrayBuffer(byteLength, offset = 0) { + const uint8array = new Uint8Array(byteLength); + this.copyInto(uint8array, offset); + return uint8array.buffer; + } + + copyInto(destination, offset = 0) { + core.opSync("op_ffi_buf_copy_into", [ + packU64(this.pointer.value + BigInt(offset)), + destination, + destination.byteLength, + ]); + } + } + + class UnsafePointer { + value; + + constructor(value) { + this.value = value; + } + + static of(typedArray) { + return new UnsafePointer( + unpackU64(core.opSync("op_ffi_ptr_of", typedArray)), + ); + } + + valueOf() { + return this.value; + } + } + class DynamicLibrary { #rid; symbols = {}; @@ -16,37 +151,67 @@ for (const symbol in symbols) { const isNonBlocking = symbols[symbol].nonblocking; + const types = symbols[symbol].parameters; this.symbols[symbol] = (...args) => { const parameters = []; const buffers = []; - for (const arg of args) { - if ( - arg?.buffer instanceof ArrayBuffer && - arg.byteLength !== undefined - ) { - parameters.push(buffers.length); - buffers.push(arg); + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const arg = args[i]; + + if (type === "pointer") { + if ( + arg?.buffer instanceof ArrayBuffer && + arg.byteLength !== undefined + ) { + parameters.push(buffers.length); + buffers.push(arg); + } else if (arg instanceof UnsafePointer) { + parameters.push(packU64(arg.value)); + buffers.push(undefined); + } else if (arg === null) { + parameters.push(null); + buffers.push(undefined); + } else { + throw new TypeError( + "Invalid ffi arg value, expected TypedArray, UnsafePointer or null", + ); + } } else { parameters.push(arg); } } if (isNonBlocking) { - return core.opAsync("op_ffi_call_nonblocking", { + const promise = core.opAsync("op_ffi_call_nonblocking", { rid: this.#rid, symbol, parameters, buffers, }); + + if (symbols[symbol].result === "pointer") { + return promise.then((value) => + new UnsafePointer(unpackU64(value)) + ); + } + + return promise; } else { - return core.opSync("op_ffi_call", { + const result = core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters, buffers, }); + + if (symbols[symbol].result === "pointer") { + return new UnsafePointer(unpackU64(result)); + } + + return result; } }; } @@ -63,5 +228,5 @@ return new DynamicLibrary(pathFromURL(path), symbols); } - window.__bootstrap.ffi = { dlopen }; + window.__bootstrap.ffi = { dlopen, UnsafePointer, UnsafePointerView }; })(this); diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index a21601e3b4..de4ff3ef23 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -1,10 +1,12 @@ // Copyright 2021 the Deno authors. All rights reserved. MIT license. use deno_core::error::bad_resource_id; +use deno_core::error::range_error; use deno_core::error::AnyError; use deno_core::include_js_files; use deno_core::op_async; use deno_core::op_sync; +use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::Extension; @@ -15,12 +17,15 @@ use deno_core::ZeroCopyBuf; use dlopen::raw::Library; use libffi::middle::Arg; use serde::Deserialize; +use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::c_void; +use std::ffi::CStr; use std::path::Path; use std::path::PathBuf; +use std::ptr; use std::rc::Rc; pub struct Unstable(pub bool); @@ -38,7 +43,7 @@ fn check_unstable(state: &OpState, api_name: &str) { } pub trait FfiPermissions { - fn check(&mut self, path: &Path) -> Result<(), AnyError>; + fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError>; } #[derive(Clone)] @@ -108,6 +113,18 @@ pub fn init(unstable: bool) -> Extension { ("op_ffi_load", op_sync(op_ffi_load::

)), ("op_ffi_call", op_sync(op_ffi_call)), ("op_ffi_call_nonblocking", op_async(op_ffi_call_nonblocking)), + ("op_ffi_ptr_of", op_sync(op_ffi_ptr_of::

)), + ("op_ffi_buf_copy_into", op_sync(op_ffi_buf_copy_into::

)), + ("op_ffi_cstr_read", op_sync(op_ffi_cstr_read::

)), + ("op_ffi_read_u8", op_sync(op_ffi_read_u8::

)), + ("op_ffi_read_i8", op_sync(op_ffi_read_i8::

)), + ("op_ffi_read_u16", op_sync(op_ffi_read_u16::

)), + ("op_ffi_read_i16", op_sync(op_ffi_read_i16::

)), + ("op_ffi_read_u32", op_sync(op_ffi_read_u32::

)), + ("op_ffi_read_i32", op_sync(op_ffi_read_i32::

)), + ("op_ffi_read_u64", op_sync(op_ffi_read_u64::

)), + ("op_ffi_read_f32", op_sync(op_ffi_read_f32::

)), + ("op_ffi_read_f64", op_sync(op_ffi_read_f64::

)), ]) .state(move |state| { // Stolen from deno_webgpu, is there a better option? @@ -133,7 +150,7 @@ enum NativeType { ISize, F32, F64, - Buffer, + Pointer, } impl From for libffi::middle::Type { @@ -152,7 +169,7 @@ impl From for libffi::middle::Type { NativeType::ISize => libffi::middle::Type::isize(), NativeType::F32 => libffi::middle::Type::f32(), NativeType::F64 => libffi::middle::Type::f64(), - NativeType::Buffer => libffi::middle::Type::pointer(), + NativeType::Pointer => libffi::middle::Type::pointer(), } } } @@ -172,7 +189,7 @@ union NativeValue { isize_value: isize, f32_value: f32, f64_value: f64, - buffer: *const u8, + pointer: *const u8, } impl NativeValue { @@ -215,12 +232,25 @@ impl NativeValue { NativeType::F64 => Self { f64_value: value_as_f64(value), }, - NativeType::Buffer => unreachable!(), + NativeType::Pointer => { + if value.is_null() { + Self { + pointer: ptr::null(), + } + } else { + Self { + pointer: u64::from( + serde_json::from_value::(value) + .expect("Expected ffi arg value to be a tuple of the low and high bits of a pointer address") + ) as *const u8, + } + } + } } } fn buffer(ptr: *const u8) -> Self { - Self { buffer: ptr } + Self { pointer: ptr } } unsafe fn as_arg(&self, native_type: NativeType) -> Arg { @@ -238,7 +268,7 @@ impl NativeValue { NativeType::ISize => Arg::new(&self.isize_value), NativeType::F32 => Arg::new(&self.f32_value), NativeType::F64 => Arg::new(&self.f64_value), - NativeType::Buffer => Arg::new(&self.buffer), + NativeType::Pointer => Arg::new(&self.pointer), } } } @@ -267,6 +297,21 @@ fn value_as_f64(value: Value) -> f64 { .expect("Expected ffi arg value to be a float") } +#[derive(Serialize, Deserialize, Debug)] +struct U32x2(u32, u32); + +impl From for U32x2 { + fn from(value: u64) -> Self { + Self((value >> 32) as u32, value as u32) + } +} + +impl From for u64 { + fn from(value: U32x2) -> Self { + (value.0 as u64) << 32 | value.1 as u64 + } +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct ForeignFunction { @@ -367,7 +412,7 @@ where check_unstable(state, "Deno.dlopen"); let permissions = state.borrow_mut::(); - permissions.check(&PathBuf::from(&path))?; + permissions.check(Some(&PathBuf::from(&path)))?; let lib = Library::open(&path).map_err(|e| { dlopen::Error::OpeningLibraryError(std::io::Error::new( @@ -394,25 +439,30 @@ struct FfiCallArgs { rid: ResourceId, symbol: String, parameters: Vec, - buffers: Vec, + buffers: Vec>, } fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result { - let buffers: Vec<&[u8]> = - args.buffers.iter().map(|buffer| &buffer[..]).collect(); + let buffers: Vec> = args + .buffers + .iter() + .map(|buffer| buffer.as_ref().map(|buffer| &buffer[..])) + .collect(); let native_values = symbol .parameter_types .iter() .zip(args.parameters.into_iter()) .map(|(&native_type, value)| { - if let NativeType::Buffer = native_type { - let idx: usize = value_as_uint(value); - let ptr = buffers[idx].as_ptr(); - NativeValue::buffer(ptr) - } else { - NativeValue::new(native_type, value) + if let NativeType::Pointer = native_type { + if let Some(idx) = value.as_u64() { + if let Some(&Some(buf)) = buffers.get(idx as usize) { + return NativeValue::buffer(buf.as_ptr()); + } + } } + + NativeValue::new(native_type, value) }) .collect::>(); @@ -465,7 +515,11 @@ fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result { NativeType::F64 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } - NativeType::Buffer => unreachable!(), + NativeType::Pointer => { + json!(U32x2::from(unsafe { + symbol.cif.call::<*const u8>(symbol.ptr, &call_args) + } as u64)) + } }) } @@ -507,6 +561,185 @@ async fn op_ffi_call_nonblocking( .unwrap() } +fn op_ffi_ptr_of( + state: &mut deno_core::OpState, + buf: ZeroCopyBuf, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(U32x2::from(buf.as_ptr() as u64)) +} + +fn op_ffi_buf_copy_into( + state: &mut deno_core::OpState, + (src, mut dst, len): (U32x2, ZeroCopyBuf, usize), + _: (), +) -> Result<(), AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + if dst.len() < len { + Err(range_error( + "Destination length is smaller than source length", + )) + } else { + let src = u64::from(src) as *const u8; + unsafe { ptr::copy(src, dst.as_mut_ptr(), len) }; + Ok(()) + } +} + +fn op_ffi_cstr_read( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + let ptr = u64::from(ptr) as *const i8; + Ok(unsafe { CStr::from_ptr(ptr) }.to_str()?.to_string()) +} + +fn op_ffi_read_u8( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u8) }) +} + +fn op_ffi_read_i8( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i8) }) +} + +fn op_ffi_read_u16( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u16) }) +} + +fn op_ffi_read_i16( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i16) }) +} + +fn op_ffi_read_u32( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u32) }) +} + +fn op_ffi_read_i32( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i32) }) +} + +fn op_ffi_read_u64( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(U32x2::from(unsafe { + ptr::read_unaligned(u64::from(ptr) as *const u64) + })) +} + +fn op_ffi_read_f32( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f32) }) +} + +fn op_ffi_read_f64( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f64) }) +} + #[cfg(test)] mod tests { #[cfg(target_os = "windows")] diff --git a/runtime/build.rs b/runtime/build.rs index 14e2e0362c..c83f130707 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -85,7 +85,7 @@ mod not_docs { impl deno_ffi::FfiPermissions for Permissions { fn check( &mut self, - _path: &Path, + _path: Option<&Path>, ) -> Result<(), deno_core::error::AnyError> { unreachable!("snapshotting!") } diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index e5e52b1f6f..029423ee16 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -135,6 +135,8 @@ createHttpClient: __bootstrap.fetch.createHttpClient, http: __bootstrap.http, dlopen: __bootstrap.ffi.dlopen, + UnsafePointer: __bootstrap.ffi.UnsafePointer, + UnsafePointerView: __bootstrap.ffi.UnsafePointerView, flock: __bootstrap.fs.flock, flockSync: __bootstrap.fs.flockSync, funlock: __bootstrap.fs.funlock, diff --git a/runtime/permissions.rs b/runtime/permissions.rs index 9a85e5e583..50c126f3f3 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -1044,22 +1044,39 @@ impl UnaryPermission { self.query(path) } - pub fn check(&mut self, path: &Path) -> Result<(), AnyError> { - let (resolved_path, display_path) = resolved_and_display_path(path); - let (result, prompted) = self.query(Some(&resolved_path)).check( - self.name, - Some(&format!("\"{}\"", display_path.display())), - self.prompt, - ); - if prompted { - if result.is_ok() { - self.granted_list.insert(FfiDescriptor(resolved_path)); - } else { - self.denied_list.insert(FfiDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; + pub fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { + if let Some(path) = path { + let (resolved_path, display_path) = resolved_and_display_path(path); + let (result, prompted) = self.query(Some(&resolved_path)).check( + self.name, + Some(&format!("\"{}\"", display_path.display())), + self.prompt, + ); + + if prompted { + if result.is_ok() { + self.granted_list.insert(FfiDescriptor(resolved_path)); + } else { + self.denied_list.insert(FfiDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + } } + + result + } else { + let (result, prompted) = + self.query(None).check(self.name, None, self.prompt); + + if prompted { + if result.is_ok() { + self.global_state = PermissionState::Granted; + } else { + self.global_state = PermissionState::Denied; + } + } + + result } - result } pub fn check_all(&mut self) -> Result<(), AnyError> { @@ -1314,7 +1331,7 @@ impl deno_websocket::WebSocketPermissions for Permissions { } impl deno_ffi::FfiPermissions for Permissions { - fn check(&mut self, path: &Path) -> Result<(), AnyError> { + fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { self.ffi.check(path) } } @@ -1740,7 +1757,7 @@ pub fn create_child_permissions( .ffi .granted_list .iter() - .all(|desc| main_perms.ffi.check(&desc.0).is_ok()) + .all(|desc| main_perms.ffi.check(Some(&desc.0)).is_ok()) { return Err(escalation_error()); } diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs index 38275e5474..b0206276a3 100644 --- a/test_ffi/src/lib.rs +++ b/test_ffi/src/lib.rs @@ -3,6 +3,8 @@ use std::thread::sleep; use std::time::Duration; +static BUFFER: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; + #[no_mangle] pub extern "C" fn print_something() { println!("something"); @@ -28,6 +30,16 @@ pub extern "C" fn print_buffer2( println!("{:?} {:?}", buf1, buf2); } +#[no_mangle] +pub extern "C" fn return_buffer() -> *const u8 { + BUFFER.as_ptr() +} + +#[no_mangle] +pub extern "C" fn is_null_ptr(ptr: *const u8) -> u8 { + ptr.is_null() as u8 +} + #[no_mangle] pub extern "C" fn add_u32(a: u32, b: u32) -> u32 { a + b diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index e16e97e24e..99a17f0a90 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -41,6 +41,15 @@ fn basic() { something\n\ [1, 2, 3, 4, 5, 6, 7, 8]\n\ [1, 2, 3, 4, 5, 6, 7, 8] [9, 10]\n\ + [1, 2, 3, 4, 5, 6, 7, 8]\n\ + [ 1, 2, 3, 4, 5, 6 ]\n\ + [ 4, 5, 6 ]\n\ + [ 4, 5, 6 ]\n\ + Hello from pointer!\n\ + pointer!\n\ + false\n\ + true\n\ + false\n\ 579\n\ 579\n\ 579\n\ diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index 35d51c0063..16e4d76b6d 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -20,11 +20,13 @@ try { const dylib = Deno.dlopen(libPath, { "print_something": { parameters: [], result: "void" }, - "print_buffer": { parameters: ["buffer", "usize"], result: "void" }, + "print_buffer": { parameters: ["pointer", "usize"], result: "void" }, "print_buffer2": { - parameters: ["buffer", "usize", "buffer", "usize"], + parameters: ["pointer", "usize", "pointer", "usize"], result: "void", }, + "return_buffer": { parameters: [], result: "pointer" }, + "is_null_ptr": { parameters: ["pointer"], result: "u8" }, "add_u32": { parameters: ["u32", "u32"], result: "u32" }, "add_i32": { parameters: ["i32", "i32"], result: "i32" }, "add_u64": { parameters: ["u64", "u64"], result: "u64" }, @@ -33,10 +35,10 @@ const dylib = Deno.dlopen(libPath, { "add_isize": { parameters: ["isize", "isize"], result: "isize" }, "add_f32": { parameters: ["f32", "f32"], result: "f32" }, "add_f64": { parameters: ["f64", "f64"], result: "f64" }, - "fill_buffer": { parameters: ["u8", "buffer", "usize"], result: "void" }, + "fill_buffer": { parameters: ["u8", "pointer", "usize"], result: "void" }, "sleep_blocking": { parameters: ["u64"], result: "void", nonblocking: true }, "nonblocking_buffer": { - parameters: ["buffer", "usize"], + parameters: ["pointer", "usize"], result: "void", nonblocking: true, }, @@ -47,6 +49,31 @@ const buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const buffer2 = new Uint8Array([9, 10]); dylib.symbols.print_buffer(buffer, buffer.length); dylib.symbols.print_buffer2(buffer, buffer.length, buffer2, buffer2.length); +const ptr = dylib.symbols.return_buffer(); +dylib.symbols.print_buffer(ptr, 8); +const ptrView = new Deno.UnsafePointerView(ptr); +const into = new Uint8Array(6); +const into2 = new Uint8Array(3); +const into2ptr = Deno.UnsafePointer.of(into2); +const into2ptrView = new Deno.UnsafePointerView(into2ptr); +const into3 = new Uint8Array(3); +ptrView.copyInto(into); +console.log([...into]); +ptrView.copyInto(into2, 3); +console.log([...into2]); +into2ptrView.copyInto(into3); +console.log([...into3]); +const string = new Uint8Array([ + ...new TextEncoder().encode("Hello from pointer!"), + 0, +]); +const stringPtr = Deno.UnsafePointer.of(string); +const stringPtrview = new Deno.UnsafePointerView(stringPtr); +console.log(stringPtrview.getCString()); +console.log(stringPtrview.getCString(11)); +console.log(Boolean(dylib.symbols.is_null_ptr(ptr))); +console.log(Boolean(dylib.symbols.is_null_ptr(null))); +console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into)))); console.log(dylib.symbols.add_u32(123, 456)); console.log(dylib.symbols.add_i32(123, 456)); console.log(dylib.symbols.add_u64(123, 456));