1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 08:33:43 -05:00

feat(ext/ffi): Replace pointer integers with v8::External objects (#16889)

This commit is contained in:
Aapo Alasuutari 2023-02-22 19:32:38 +02:00 committed by GitHub
parent 2bd7482295
commit b56b8c8a75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 834 additions and 373 deletions

View file

@ -2824,6 +2824,30 @@ itest!(unstable_ffi_15 {
exit_code: 70,
});
itest!(unstable_ffi_16 {
args: "run run/ffi/unstable_ffi_16.js",
output: "run/ffi/unstable_ffi_16.js.out",
exit_code: 70,
});
itest!(unstable_ffi_17 {
args: "run run/ffi/unstable_ffi_17.js",
output: "run/ffi/unstable_ffi_17.js.out",
exit_code: 70,
});
itest!(unstable_ffi_18 {
args: "run run/ffi/unstable_ffi_18.js",
output: "run/ffi/unstable_ffi_18.js.out",
exit_code: 70,
});
itest!(unstable_ffi_19 {
args: "run run/ffi/unstable_ffi_19.js",
output: "run/ffi/unstable_ffi_19.js.out",
exit_code: 70,
});
itest!(future_check2 {
args: "run --check run/future_check.ts",
output: "run/future_check2.out",

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_i16(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_i16(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_u32(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_u32(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_i32(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_i32(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_u64(0n, 0, new Uint32Array(2));
Deno[Deno.internal].core.ops.op_ffi_read_u64(null, 0, new Uint32Array(2));

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_f32(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_f32(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_f64(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_f64(null, 0);

View file

@ -0,0 +1 @@
Deno[Deno.internal].core.ops.op_ffi_ptr_value(null, new Uint32Array(2));

View file

@ -0,0 +1 @@
Unstable API 'Deno.UnsafePointer#value'. The --unstable flag must be provided.

View file

@ -0,0 +1 @@
Deno[Deno.internal].core.ops.op_ffi_get_buf(null, 0, 0);

View file

@ -0,0 +1 @@
Unstable API 'Deno.UnsafePointerView#getArrayBuffer'. The --unstable flag must be provided.

View file

@ -0,0 +1 @@
Deno[Deno.internal].core.ops.op_ffi_ptr_create(null);

View file

@ -0,0 +1 @@
Unstable API 'Deno.UnsafePointer#create'. The --unstable flag must be provided.

View file

@ -0,0 +1 @@
Deno[Deno.internal].core.ops.op_ffi_ptr_equals(null, null);

View file

@ -0,0 +1 @@
Unstable API 'Deno.UnsafePointer#equals'. The --unstable flag must be provided.

View file

@ -1,4 +1,4 @@
Deno[Deno.internal].core.ops.op_ffi_call_ptr(0n, {
Deno[Deno.internal].core.ops.op_ffi_call_ptr(null, {
name: null,
parameters: [],
result: "void",

View file

@ -1,4 +1,4 @@
Deno[Deno.internal].core.opAsync("op_ffi_call_ptr_nonblocking", 0n, {
Deno[Deno.internal].core.opAsync("op_ffi_call_ptr_nonblocking", null, {
name: null,
parameters: [],
result: "void",

View file

@ -1 +1,6 @@
Deno[Deno.internal].core.ops.op_ffi_buf_copy_into(0n, 0, new Uint8Array(0), 0);
Deno[Deno.internal].core.ops.op_ffi_buf_copy_into(
null,
0,
new Uint8Array(0),
0,
);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_cstr_read(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_cstr_read(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_u8(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_u8(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_i8(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_i8(null, 0);

View file

@ -1 +1 @@
Deno[Deno.internal].core.ops.op_ffi_read_u16(0n, 0);
Deno[Deno.internal].core.ops.op_ffi_read_u16(null, 0);

View file

@ -29,7 +29,8 @@ Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() {
Deno.dlopen("/usr/lib/libc.so.6", {});
}, Deno.errors.PermissionDenied);
const fnptr = new Deno.UnsafeFnPointer(
0n,
// @ts-expect-error: Not NonNullable but null check is after premissions check.
null,
{
parameters: ["u32", "pointer"],
result: "void",
@ -41,7 +42,10 @@ Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() {
assertThrows(() => {
Deno.UnsafePointer.of(new Uint8Array(0));
}, Deno.errors.PermissionDenied);
const ptrView = new Deno.UnsafePointerView(0n);
const ptrView = new Deno.UnsafePointerView(
// @ts-expect-error: Not NonNullable but null check is after premissions check.
null,
);
assertThrows(() => {
ptrView.copyInto(new Uint8Array(0));
}, Deno.errors.PermissionDenied);

View file

@ -131,10 +131,10 @@ declare namespace Deno {
*/
type ToNativeTypeMap =
& Record<NativeNumberType, number>
& Record<NativeBigIntType, PointerValue>
& Record<NativeBigIntType, number | bigint>
& Record<NativeBooleanType, boolean>
& Record<NativePointerType, PointerValue | null>
& Record<NativeFunctionType, PointerValue | null>
& Record<NativePointerType, PointerValue>
& Record<NativeFunctionType, PointerValue>
& Record<NativeBufferType, BufferSource | null>;
/** **UNSTABLE**: New API, yet to be vetted.
@ -191,7 +191,7 @@ declare namespace Deno {
*/
type FromNativeTypeMap =
& Record<NativeNumberType, number>
& Record<NativeBigIntType, PointerValue>
& Record<NativeBigIntType, number | bigint>
& Record<NativeBooleanType, boolean>
& Record<NativePointerType, PointerValue>
& Record<NativeBufferType, PointerValue>
@ -340,6 +340,9 @@ declare namespace Deno {
[K in keyof T]: StaticForeignSymbol<T[K]>;
};
const brand: unique symbol;
type PointerObject = { [brand]: unknown };
/** **UNSTABLE**: New API, yet to be vetted.
*
* Pointer type depends on the architecture and actual pointer value.
@ -350,7 +353,7 @@ declare namespace Deno {
*
* @category FFI
*/
export type PointerValue = number | bigint;
export type PointerValue = null | PointerObject;
/** **UNSTABLE**: New API, yet to be vetted.
*
@ -360,8 +363,16 @@ declare namespace Deno {
* @category FFI
*/
export class UnsafePointer {
/** Create a pointer from a numeric value. This is one is <i>really</i> dangerous! */
static create(value: number | bigint): PointerValue;
/** Returns `true` if the two pointers point to the same address. */
static equals(a: PointerValue, b: PointerValue): boolean;
/** Return the direct memory pointer to the typed array in memory. */
static of(value: Deno.UnsafeCallback | BufferSource): PointerValue;
/** Return a new pointer offset from the original by `offset` bytes. */
static offset(value: NonNullable<PointerValue>, offset: number): PointerValue
/** Get the numeric value of a pointer */
static value(value: PointerValue): number | bigint;
}
/** **UNSTABLE**: New API, yet to be vetted.
@ -374,9 +385,9 @@ declare namespace Deno {
* @category FFI
*/
export class UnsafePointerView {
constructor(pointer: PointerValue);
constructor(pointer: NonNullable<PointerValue>);
pointer: PointerValue;
pointer: NonNullable<PointerValue>;
/** Gets a boolean at the specified byte offset from the pointer. */
getBool(offset?: number): boolean;
@ -400,29 +411,31 @@ declare namespace Deno {
getInt32(offset?: number): number;
/** Gets an unsigned 64-bit integer at the specified byte offset from the
* pointer. */
getBigUint64(offset?: number): PointerValue;
getBigUint64(offset?: number): number | bigint;
/** Gets a signed 64-bit integer at the specified byte offset from the
* pointer. */
getBigInt64(offset?: number): PointerValue;
getBigInt64(offset?: number): 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 pointer at the specified byte offset from the pointer */
getPointer(offset?: number): PointerValue;
/** Gets a C string (`null` terminated string) at the specified byte offset
* from the pointer. */
getCString(offset?: number): string;
/** Gets a C string (`null` terminated string) at the specified byte offset
* from the specified pointer. */
static getCString(pointer: PointerValue, offset?: number): string;
static getCString(pointer: NonNullable<PointerValue>, offset?: number): string;
/** Gets an `ArrayBuffer` of length `byteLength` at the specified byte
* offset from the pointer. */
getArrayBuffer(byteLength: number, offset?: number): ArrayBuffer;
/** Gets an `ArrayBuffer` of length `byteLength` at the specified byte
* offset from the specified pointer. */
static getArrayBuffer(
pointer: PointerValue,
pointer: NonNullable<PointerValue>,
byteLength: number,
offset?: number,
): ArrayBuffer;
@ -438,7 +451,7 @@ declare namespace Deno {
*
* Also takes optional byte offset from the pointer. */
static copyInto(
pointer: PointerValue,
pointer: NonNullable<PointerValue>,
destination: BufferSource,
offset?: number,
): void;
@ -453,11 +466,11 @@ declare namespace Deno {
*/
export class UnsafeFnPointer<Fn extends ForeignFunction> {
/** The pointer to the function. */
pointer: PointerValue;
pointer: NonNullable<PointerValue>;
/** The definition of the function. */
definition: Fn;
constructor(pointer: PointerValue, definition: Const<Fn>);
constructor(pointer: NonNullable<PointerValue>, definition: Const<Fn>);
/** Call the foreign function. */
call: FromForeignFunction<Fn>;
@ -516,7 +529,7 @@ declare namespace Deno {
);
/** The pointer to the unsafe callback. */
pointer: PointerValue;
pointer: NonNullable<PointerValue>;
/** The definition of the unsafe callback. */
definition: Definition;
/** The callback function. */

View file

@ -118,6 +118,13 @@ class UnsafePointerView {
);
}
getPointer(offset = 0) {
return ops.op_ffi_read_ptr(
this.pointer,
offset,
);
}
getCString(offset = 0) {
return ops.op_ffi_cstr_read(
this.pointer,
@ -170,11 +177,33 @@ class UnsafePointerView {
const OUT_BUFFER = new Uint32Array(2);
const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer);
class UnsafePointer {
static create(value) {
return ops.op_ffi_ptr_create(value);
}
static equals(a, b) {
if (a === null || b === null) {
return a === b;
}
return ops.op_ffi_ptr_equals(a, b);
}
static of(value) {
if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) {
return value.pointer;
}
ops.op_ffi_ptr_of(value, OUT_BUFFER);
return ops.op_ffi_ptr_of(value);
}
static offset(value, offset) {
return ops.op_ffi_ptr_offset(value, offset);
}
static value(value) {
if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) {
value = value.pointer;
}
ops.op_ffi_ptr_value(value, OUT_BUFFER);
const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1];
if (NumberIsSafeInteger(result)) {
return result;
@ -240,8 +269,7 @@ class UnsafeFnPointer {
}
function isReturnedAsBigInt(type) {
return type === "buffer" || type === "pointer" || type === "function" ||
type === "u64" || type === "i64" ||
return type === "u64" || type === "i64" ||
type === "usize" || type === "isize";
}

View file

@ -14,9 +14,11 @@ use deno_core::error::AnyError;
use deno_core::op;
use deno_core::serde_json::Value;
use deno_core::serde_v8;
use deno_core::serde_v8::ExternalPointer;
use deno_core::v8;
use deno_core::ResourceId;
use libffi::middle::Arg;
use serde::Serialize;
use std::cell::RefCell;
use std::ffi::c_void;
use std::future::Future;
@ -28,14 +30,13 @@ unsafe fn ffi_call_rtype_struct(
fn_ptr: &libffi::middle::CodePtr,
call_args: Vec<Arg>,
out_buffer: *mut u8,
) -> NativeValue {
) {
libffi::raw::ffi_call(
cif.as_raw_ptr(),
Some(*fn_ptr.as_safe_fun()),
out_buffer as *mut c_void,
call_args.as_ptr() as *mut *mut c_void,
);
NativeValue { void_value: () }
}
// A one-off synchronous FFI call.
@ -174,16 +175,25 @@ where
pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args),
}
}
NativeType::Struct(_) => ffi_call_rtype_struct(
&symbol.cif,
&symbol.ptr,
call_args,
out_buffer.unwrap().0,
),
NativeType::Struct(_) => NativeValue {
void_value: ffi_call_rtype_struct(
&symbol.cif,
&symbol.ptr,
call_args,
out_buffer.unwrap().0,
),
},
})
}
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum FfiValue {
Value(Value),
External(ExternalPointer),
}
fn ffi_call(
call_args: Vec<NativeValue>,
cif: &libffi::middle::Cif,
@ -191,7 +201,7 @@ fn ffi_call(
parameter_types: &[NativeType],
result_type: NativeType,
out_buffer: Option<OutBuffer>,
) -> Result<NativeValue, AnyError> {
) -> Result<FfiValue, AnyError> {
let call_args: Vec<Arg> = call_args
.iter()
.enumerate()
@ -205,55 +215,57 @@ fn ffi_call(
// types of symbol.
unsafe {
Ok(match result_type {
NativeType::Void => NativeValue {
void_value: cif.call::<()>(fun_ptr, &call_args),
},
NativeType::Bool => NativeValue {
bool_value: cif.call::<bool>(fun_ptr, &call_args),
},
NativeType::U8 => NativeValue {
u8_value: cif.call::<u8>(fun_ptr, &call_args),
},
NativeType::I8 => NativeValue {
i8_value: cif.call::<i8>(fun_ptr, &call_args),
},
NativeType::U16 => NativeValue {
u16_value: cif.call::<u16>(fun_ptr, &call_args),
},
NativeType::I16 => NativeValue {
i16_value: cif.call::<i16>(fun_ptr, &call_args),
},
NativeType::U32 => NativeValue {
u32_value: cif.call::<u32>(fun_ptr, &call_args),
},
NativeType::I32 => NativeValue {
i32_value: cif.call::<i32>(fun_ptr, &call_args),
},
NativeType::U64 => NativeValue {
u64_value: cif.call::<u64>(fun_ptr, &call_args),
},
NativeType::I64 => NativeValue {
i64_value: cif.call::<i64>(fun_ptr, &call_args),
},
NativeType::USize => NativeValue {
usize_value: cif.call::<usize>(fun_ptr, &call_args),
},
NativeType::ISize => NativeValue {
isize_value: cif.call::<isize>(fun_ptr, &call_args),
},
NativeType::F32 => NativeValue {
f32_value: cif.call::<f32>(fun_ptr, &call_args),
},
NativeType::F64 => NativeValue {
f64_value: cif.call::<f64>(fun_ptr, &call_args),
},
NativeType::Void => {
cif.call::<()>(fun_ptr, &call_args);
FfiValue::Value(Value::from(()))
}
NativeType::Bool => {
FfiValue::Value(Value::from(cif.call::<bool>(fun_ptr, &call_args)))
}
NativeType::U8 => {
FfiValue::Value(Value::from(cif.call::<u8>(fun_ptr, &call_args)))
}
NativeType::I8 => {
FfiValue::Value(Value::from(cif.call::<i8>(fun_ptr, &call_args)))
}
NativeType::U16 => {
FfiValue::Value(Value::from(cif.call::<u16>(fun_ptr, &call_args)))
}
NativeType::I16 => {
FfiValue::Value(Value::from(cif.call::<i16>(fun_ptr, &call_args)))
}
NativeType::U32 => {
FfiValue::Value(Value::from(cif.call::<u32>(fun_ptr, &call_args)))
}
NativeType::I32 => {
FfiValue::Value(Value::from(cif.call::<i32>(fun_ptr, &call_args)))
}
NativeType::U64 => {
FfiValue::Value(Value::from(cif.call::<u64>(fun_ptr, &call_args)))
}
NativeType::I64 => {
FfiValue::Value(Value::from(cif.call::<i64>(fun_ptr, &call_args)))
}
NativeType::USize => {
FfiValue::Value(Value::from(cif.call::<usize>(fun_ptr, &call_args)))
}
NativeType::ISize => {
FfiValue::Value(Value::from(cif.call::<isize>(fun_ptr, &call_args)))
}
NativeType::F32 => {
FfiValue::Value(Value::from(cif.call::<f32>(fun_ptr, &call_args)))
}
NativeType::F64 => {
FfiValue::Value(Value::from(cif.call::<f64>(fun_ptr, &call_args)))
}
NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
NativeValue {
pointer: cif.call::<*mut c_void>(fun_ptr, &call_args),
}
FfiValue::External(ExternalPointer::from(
cif.call::<*mut c_void>(fun_ptr, &call_args),
))
}
NativeType::Struct(_) => {
ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0)
ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0);
FfiValue::Value(Value::Null)
}
})
}
@ -263,11 +275,11 @@ fn ffi_call(
pub fn op_ffi_call_ptr_nonblocking<'scope, FP>(
scope: &mut v8::HandleScope<'scope>,
state: Rc<RefCell<deno_core::OpState>>,
pointer: usize,
pointer: *mut c_void,
def: ForeignFunction,
parameters: serde_v8::Value<'scope>,
out_buffer: Option<serde_v8::Value<'scope>>,
) -> Result<impl Future<Output = Result<Value, AnyError>>, AnyError>
) -> Result<impl Future<Output = Result<FfiValue, AnyError>>, AnyError>
where
FP: FfiPermissions + 'static,
{
@ -280,7 +292,6 @@ where
let symbol = PtrSymbol::new(pointer, &def)?;
let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
let def_result = def.result.clone();
let out_buffer = out_buffer
.map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap());
@ -303,7 +314,7 @@ where
.await
.map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
Ok(unsafe { result.to_value(def_result) })
Ok(result)
})
}
@ -316,7 +327,8 @@ pub fn op_ffi_call_nonblocking<'scope>(
symbol: String,
parameters: serde_v8::Value<'scope>,
out_buffer: Option<serde_v8::Value<'scope>>,
) -> Result<impl Future<Output = Result<Value, AnyError>> + 'static, AnyError> {
) -> Result<impl Future<Output = Result<FfiValue, AnyError>> + 'static, AnyError>
{
let symbol = {
let state = state.borrow();
let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
@ -332,7 +344,6 @@ pub fn op_ffi_call_nonblocking<'scope>(
.map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap());
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
let result_type = symbol.result_type.clone();
let join_handle = tokio::task::spawn_blocking(move || {
let Symbol {
cif,
@ -356,7 +367,7 @@ pub fn op_ffi_call_nonblocking<'scope>(
.await
.map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
Ok(unsafe { result.to_value(result_type) })
Ok(result)
})
}
@ -364,11 +375,11 @@ pub fn op_ffi_call_nonblocking<'scope>(
pub fn op_ffi_call_ptr<FP, 'scope>(
scope: &mut v8::HandleScope<'scope>,
state: Rc<RefCell<deno_core::OpState>>,
pointer: usize,
pointer: *mut c_void,
def: ForeignFunction,
parameters: serde_v8::Value<'scope>,
out_buffer: Option<serde_v8::Value<'scope>>,
) -> Result<serde_v8::Value<'scope>, AnyError>
) -> Result<FfiValue, AnyError>
where
FP: FfiPermissions + 'static,
{
@ -395,6 +406,5 @@ where
out_buffer_ptr,
)?;
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
let result = unsafe { result.to_v8(scope, def.result) };
Ok(result)
}

View file

@ -40,7 +40,10 @@ pub struct PtrSymbol {
}
impl PtrSymbol {
pub fn new(fn_ptr: usize, def: &ForeignFunction) -> Result<Self, AnyError> {
pub fn new(
fn_ptr: *mut c_void,
def: &ForeignFunction,
) -> Result<Self, AnyError> {
let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
let cif = libffi::middle::Cif::new(
def
@ -236,11 +239,11 @@ unsafe fn do_ffi_callback(
}
}
NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
let result = *((*val) as *const usize);
if result > MAX_SAFE_INTEGER as usize {
v8::BigInt::new_from_u64(scope, result as u64).into()
let result = *((*val) as *const *mut c_void);
if result.is_null() {
v8::null(scope).into()
} else {
v8::Number::new(scope, result as f64).into()
v8::External::new(scope, result).into()
}
}
NativeType::Struct(_) => {
@ -353,34 +356,43 @@ unsafe fn do_ffi_callback(
};
*(result as *mut f64) = value;
}
NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
let pointer = if let Ok(value) =
NativeType::Buffer => {
let pointer: *mut u8 = if let Ok(value) =
v8::Local::<v8::ArrayBufferView>::try_from(value)
{
let byte_offset = value.byte_offset();
let backing_store = value
let pointer = value
.buffer(scope)
.expect("Unable to deserialize result parameter.")
.get_backing_store();
&backing_store[byte_offset..] as *const _ as *const u8
} else if let Ok(value) = v8::Local::<v8::BigInt>::try_from(value) {
value.u64_value().0 as usize as *const u8
.data();
if let Some(non_null) = pointer {
// SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset
// is within the buffer backing store.
unsafe { non_null.as_ptr().add(byte_offset) as *mut u8 }
} else {
ptr::null_mut()
}
} else if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(value) {
let backing_store = value.get_backing_store();
&backing_store[..] as *const _ as *const u8
} else if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) {
value.value() as usize as *const u8
} else if value.is_null() {
ptr::null()
let pointer = value.data();
if let Some(non_null) = pointer {
non_null.as_ptr() as *mut u8
} else {
ptr::null_mut()
}
} else {
// Fallthrough: Probably someone returned a number but this could
// also be eg. a string. This is essentially UB.
value
.integer_value(scope)
.expect("Unable to deserialize result parameter.") as usize
as *const u8
ptr::null_mut()
};
*(result as *mut *const u8) = pointer;
*(result as *mut *mut u8) = pointer;
}
NativeType::Pointer | NativeType::Function => {
let pointer: *mut c_void =
if let Ok(external) = v8::Local::<v8::External>::try_from(value) {
external.value()
} else {
// TODO(@aapoalas): Start throwing errors into JS about invalid callback return values.
ptr::null_mut()
};
*(result as *mut *mut c_void) = pointer;
}
NativeType::I8 => {
let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) {
@ -591,7 +603,7 @@ where
let closure = libffi::middle::Closure::new(cif, deno_ffi_callback, unsafe {
info.as_ref().unwrap()
});
let ptr = *closure.code_ptr() as usize;
let ptr = *closure.code_ptr() as *mut c_void;
let resource = UnsafeCallbackResource {
cancel: CancelHandle::new_rc(),
closure,
@ -600,11 +612,7 @@ where
let rid = state.resource_table.add(resource);
let rid_local = v8::Integer::new_from_unsigned(scope, rid);
let ptr_local: v8::Local<v8::Value> = if ptr > MAX_SAFE_INTEGER as usize {
v8::BigInt::new_from_u64(scope, ptr as u64).into()
} else {
v8::Number::new(scope, ptr as f64).into()
};
let ptr_local: v8::Local<v8::Value> = v8::External::new(scope, ptr).into();
let array = v8::Array::new(scope, 2);
array.set_index(scope, 0, rid_local.into());
array.set_index(scope, 1, ptr_local);

View file

@ -38,13 +38,13 @@ impl Resource for DynamicLibraryResource {
}
impl DynamicLibraryResource {
pub fn get_static(&self, symbol: String) -> Result<*const c_void, AnyError> {
pub fn get_static(&self, symbol: String) -> Result<*mut c_void, AnyError> {
// By default, Err returned by this function does not tell
// which symbol wasn't exported. So we'll modify the error
// message to include the name of symbol.
//
// SAFETY: The obtained T symbol is the size of a pointer.
match unsafe { self.lib.symbol::<*const c_void>(&symbol) } {
match unsafe { self.lib.symbol::<*mut c_void>(&symbol) } {
Ok(value) => Ok(Ok(value)),
Err(err) => Err(generic_error(format!(
"Failed to register symbol {symbol}: {err}"
@ -56,13 +56,7 @@ impl DynamicLibraryResource {
pub fn needs_unwrap(rv: &NativeType) -> bool {
matches!(
rv,
NativeType::Function
| NativeType::Pointer
| NativeType::Buffer
| NativeType::I64
| NativeType::ISize
| NativeType::U64
| NativeType::USize
NativeType::I64 | NativeType::ISize | NativeType::U64 | NativeType::USize
)
}
@ -257,26 +251,20 @@ fn make_sync_fn<'s>(
match needs_unwrap {
Some(v) => {
let view: v8::Local<v8::ArrayBufferView> = v.try_into().unwrap();
let backing_store =
view.buffer(scope).unwrap().get_backing_store();
let pointer =
view.buffer(scope).unwrap().data().unwrap().as_ptr() as *mut u8;
if is_i64(&symbol.result_type) {
// SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
// it points to a fixed continuous slice of bytes on the heap.
let bs = unsafe {
&mut *(&backing_store[..] as *const _ as *mut [u8]
as *mut i64)
};
let bs = unsafe { &mut *(pointer as *mut i64) };
// SAFETY: We already checked that type == I64
let value = unsafe { result.i64_value };
*bs = value;
} else {
// SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
// it points to a fixed continuous slice of bytes on the heap.
let bs = unsafe {
&mut *(&backing_store[..] as *const _ as *mut [u8]
as *mut u64)
};
let bs = unsafe { &mut *(pointer as *mut u64) };
// SAFETY: We checked that type == U64
let value = unsafe { result.u64_value };
*bs = value;

View file

@ -5,7 +5,6 @@ use crate::MAX_SAFE_INTEGER;
use crate::MIN_SAFE_INTEGER;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::serde_json::Value;
use deno_core::serde_v8;
use deno_core::v8;
use libffi::middle::Arg;
@ -80,33 +79,6 @@ impl NativeValue {
}
}
// SAFETY: native_type must correspond to the type of value represented by the union field
pub unsafe fn to_value(&self, native_type: NativeType) -> Value {
match native_type {
NativeType::Void => Value::Null,
NativeType::Bool => Value::from(self.bool_value),
NativeType::U8 => Value::from(self.u8_value),
NativeType::I8 => Value::from(self.i8_value),
NativeType::U16 => Value::from(self.u16_value),
NativeType::I16 => Value::from(self.i16_value),
NativeType::U32 => Value::from(self.u32_value),
NativeType::I32 => Value::from(self.i32_value),
NativeType::U64 => Value::from(self.u64_value),
NativeType::I64 => Value::from(self.i64_value),
NativeType::USize => Value::from(self.usize_value),
NativeType::ISize => Value::from(self.isize_value),
NativeType::F32 => Value::from(self.f32_value),
NativeType::F64 => Value::from(self.f64_value),
NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
Value::from(self.pointer as usize)
}
NativeType::Struct(_) => {
// Return value is written to out_buffer
Value::Null
}
}
}
// SAFETY: native_type must correspond to the type of value represented by the union field
#[inline]
pub unsafe fn to_v8<'scope>(
@ -206,13 +178,11 @@ impl NativeValue {
local_value.into()
}
NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
let value = self.pointer as u64;
let local_value: v8::Local<v8::Value> =
if value > MAX_SAFE_INTEGER as u64 {
v8::BigInt::new_from_u64(scope, value).into()
} else {
v8::Number::new(scope, value as f64).into()
};
let local_value: v8::Local<v8::Value> = if self.pointer.is_null() {
v8::null(scope).into()
} else {
v8::External::new(scope, self.pointer).into()
};
local_value.into()
}
NativeType::Struct(_) => {
@ -396,22 +366,16 @@ pub fn ffi_parse_f64_arg(
#[inline]
pub fn ffi_parse_pointer_arg(
scope: &mut v8::HandleScope,
_scope: &mut v8::HandleScope,
arg: v8::Local<v8::Value>,
) -> Result<NativeValue, AnyError> {
// Order of checking:
// 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
// 2. Number: Common and supported by Fast API.
// 3. Null: Very uncommon / can be represented by a 0.
let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
value.u64_value().0 as usize as *mut c_void
} else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
value.integer_value(scope).unwrap() as usize as *mut c_void
let pointer = if let Ok(value) = v8::Local::<v8::External>::try_from(arg) {
value.value()
} else if arg.is_null() {
ptr::null_mut()
} else {
return Err(type_error(
"Invalid FFI pointer type, expected null, integer or BigInt",
"Invalid FFI pointer type, expected null, or External",
));
};
Ok(NativeValue { pointer })
@ -502,22 +466,16 @@ pub fn ffi_parse_struct_arg(
#[inline]
pub fn ffi_parse_function_arg(
scope: &mut v8::HandleScope,
_scope: &mut v8::HandleScope,
arg: v8::Local<v8::Value>,
) -> Result<NativeValue, AnyError> {
// Order of checking:
// 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
// 2. Number: Common and supported by Fast API, optimise this case as second.
// 3. Null: Very uncommon / can be represented by a 0.
let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
value.u64_value().0 as usize as *mut c_void
} else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
value.integer_value(scope).unwrap() as usize as *mut c_void
let pointer = if let Ok(value) = v8::Local::<v8::External>::try_from(arg) {
value.value()
} else if arg.is_null() {
ptr::null_mut()
} else {
return Err(type_error(
"Invalid FFI function type, expected null, integer, or BigInt",
"Invalid FFI function type, expected null, or External",
));
};
Ok(NativeValue { pointer })

View file

@ -91,7 +91,11 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
op_ffi_call_nonblocking::decl(),
op_ffi_call_ptr::decl::<P>(),
op_ffi_call_ptr_nonblocking::decl::<P>(),
op_ffi_ptr_create::decl::<P>(),
op_ffi_ptr_equals::decl::<P>(),
op_ffi_ptr_of::decl::<P>(),
op_ffi_ptr_offset::decl::<P>(),
op_ffi_ptr_value::decl::<P>(),
op_ffi_get_buf::decl::<P>(),
op_ffi_buf_copy_into::decl::<P>(),
op_ffi_cstr_read::decl::<P>(),
@ -106,6 +110,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
op_ffi_read_i64::decl::<P>(),
op_ffi_read_f32::decl::<P>(),
op_ffi_read_f64::decl::<P>(),
op_ffi_read_ptr::decl::<P>(),
op_ffi_unsafe_callback_create::decl::<P>(),
op_ffi_unsafe_callback_ref::decl(),
op_ffi_unsafe_callback_unref::decl(),

View file

@ -13,16 +13,90 @@ use std::ffi::c_void;
use std::ffi::CStr;
use std::ptr;
#[op(fast)]
fn op_ffi_ptr_create<FP>(
state: &mut deno_core::OpState,
ptr_number: usize,
) -> Result<*mut c_void, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#create");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
Ok(ptr_number as *mut c_void)
}
#[op(fast)]
pub fn op_ffi_ptr_equals<FP>(
state: &mut deno_core::OpState,
a: *const c_void,
b: *const c_void,
) -> Result<bool, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#equals");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
Ok(a == b)
}
#[op(fast)]
pub fn op_ffi_ptr_of<FP>(
state: &mut deno_core::OpState,
buf: *const u8,
) -> Result<*mut c_void, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#of");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
Ok(buf as *mut c_void)
}
#[op(fast)]
fn op_ffi_ptr_offset<FP>(
state: &mut deno_core::OpState,
ptr: *mut c_void,
offset: isize,
) -> Result<*mut c_void, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#offset");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
if ptr.is_null() {
return Err(type_error("Invalid pointer to offset, pointer is null"));
}
// SAFETY: Pointer and offset are user provided.
Ok(unsafe { ptr.offset(offset) })
}
unsafe extern "C" fn noop_deleter_callback(
_data: *mut c_void,
_byte_length: usize,
_deleter_data: *mut c_void,
) {
}
#[op(fast)]
fn op_ffi_ptr_value<FP>(
state: &mut deno_core::OpState,
ptr: *mut c_void,
out: &mut [u32],
) -> Result<(), AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#of");
check_unstable(state, "Deno.UnsafePointer#value");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
@ -35,47 +109,35 @@ where
// SAFETY: Out buffer was asserted to be at least large enough to hold a usize, and properly aligned.
let out = unsafe { &mut *outptr };
*out = buf as usize;
*out = ptr as usize;
Ok(())
}
unsafe extern "C" fn noop_deleter_callback(
_data: *mut c_void,
_byte_length: usize,
_deleter_data: *mut c_void,
) {
}
#[op(v8)]
pub fn op_ffi_get_buf<FP, 'scope>(
scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
len: usize,
) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointerView#arrayBuffer");
check_unstable(state, "Deno.UnsafePointerView#getArrayBuffer");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *mut c_void;
if ptr.is_null() {
return Err(type_error("Invalid FFI pointer value, got nullptr"));
return Err(type_error("Invalid ArrayBuffer pointer, pointer is null"));
}
// SAFETY: Offset is user defined.
let ptr = unsafe { ptr.add(offset) };
// SAFETY: Trust the user to have provided a real pointer, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion.
// SAFETY: Trust the user to have provided a real pointer, offset, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion.
let backing_store = unsafe {
v8::ArrayBuffer::new_backing_store_from_ptr(
ptr,
ptr.offset(offset),
len,
noop_deleter_callback,
std::ptr::null_mut(),
@ -90,8 +152,8 @@ where
#[op(fast)]
pub fn op_ffi_buf_copy_into<FP>(
state: &mut deno_core::OpState,
src: usize,
offset: usize,
src: *mut c_void,
offset: isize,
dst: &mut [u8],
len: usize,
) -> Result<(), AnyError>
@ -103,19 +165,20 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
if dst.len() < len {
if src.is_null() {
Err(type_error("Invalid ArrayBuffer pointer, pointer is null"))
} else if dst.len() < len {
Err(range_error(
"Destination length is smaller than source length",
))
} else {
let src = src as *const c_void;
// SAFETY: Offset is user defined.
let src = unsafe { src.add(offset) as *const u8 };
// SAFETY: src is user defined.
// SAFETY: src and offset are user defined.
// dest is properly aligned and is valid for writes of len * size_of::<T>() bytes.
unsafe { ptr::copy::<u8>(src, dst.as_mut_ptr(), len) };
unsafe {
ptr::copy::<u8>(src.offset(offset) as *const u8, dst.as_mut_ptr(), len)
};
Ok(())
}
}
@ -124,8 +187,8 @@ where
pub fn op_ffi_cstr_read<FP, 'scope>(
scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
@ -135,17 +198,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid CString pointer, pointer is null"));
}
// SAFETY: Offset is user defined.
let ptr = unsafe { ptr.add(offset) };
// SAFETY: Pointer is user provided.
let cstr = unsafe { CStr::from_ptr(ptr as *const c_char) }.to_bytes();
let cstr =
// SAFETY: Pointer and offset are user provided.
unsafe { CStr::from_ptr(ptr.offset(offset) as *const c_char) }.to_bytes();
let value: v8::Local<v8::Value> =
v8::String::new_from_utf8(scope, cstr, v8::NewStringType::Normal)
.ok_or_else(|| {
@ -158,8 +217,8 @@ where
#[op(fast)]
pub fn op_ffi_read_bool<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<bool, AnyError>
where
FP: FfiPermissions + 'static,
@ -169,21 +228,19 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid bool pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<bool>(ptr.add(offset) as *const bool) })
Ok(unsafe { ptr::read_unaligned::<bool>(ptr.offset(offset) as *const bool) })
}
#[op(fast)]
pub fn op_ffi_read_u8<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<u32, AnyError>
where
FP: FfiPermissions + 'static,
@ -193,21 +250,21 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid u8 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<u8>(ptr.add(offset) as *const u8) as u32 })
Ok(unsafe {
ptr::read_unaligned::<u8>(ptr.offset(offset) as *const u8) as u32
})
}
#[op(fast)]
pub fn op_ffi_read_i8<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<i32, AnyError>
where
FP: FfiPermissions + 'static,
@ -217,21 +274,21 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid i8 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<i8>(ptr.add(offset) as *const i8) as i32 })
Ok(unsafe {
ptr::read_unaligned::<i8>(ptr.offset(offset) as *const i8) as i32
})
}
#[op(fast)]
pub fn op_ffi_read_u16<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<u32, AnyError>
where
FP: FfiPermissions + 'static,
@ -241,23 +298,21 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid u16 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe {
ptr::read_unaligned::<u16>(ptr.add(offset) as *const u16) as u32
ptr::read_unaligned::<u16>(ptr.offset(offset) as *const u16) as u32
})
}
#[op(fast)]
pub fn op_ffi_read_i16<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<i32, AnyError>
where
FP: FfiPermissions + 'static,
@ -267,23 +322,21 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid i16 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe {
ptr::read_unaligned::<i16>(ptr.add(offset) as *const i16) as i32
ptr::read_unaligned::<i16>(ptr.offset(offset) as *const i16) as i32
})
}
#[op(fast)]
pub fn op_ffi_read_u32<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<u32, AnyError>
where
FP: FfiPermissions + 'static,
@ -293,21 +346,19 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid u32 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<u32>(ptr.add(offset) as *const u32) })
Ok(unsafe { ptr::read_unaligned::<u32>(ptr.offset(offset) as *const u32) })
}
#[op(fast)]
pub fn op_ffi_read_i32<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<i32, AnyError>
where
FP: FfiPermissions + 'static,
@ -317,21 +368,19 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid i32 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<i32>(ptr.add(offset) as *const i32) })
Ok(unsafe { ptr::read_unaligned::<i32>(ptr.offset(offset) as *const i32) })
}
#[op]
pub fn op_ffi_read_u64<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
out: &mut [u32],
) -> Result<(), AnyError>
where
@ -349,15 +398,13 @@ where
);
assert_eq!((outptr as usize % std::mem::size_of::<u64>()), 0);
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid u64 pointer, pointer is null"));
}
let value =
// SAFETY: ptr and offset are user provided.
unsafe { ptr::read_unaligned::<u64>(ptr.add(offset) as *const u64) };
unsafe { ptr::read_unaligned::<u64>(ptr.offset(offset) as *const u64) };
// SAFETY: Length and alignment of out slice were asserted to be correct.
unsafe { *outptr = value };
@ -367,8 +414,8 @@ where
#[op(fast)]
pub fn op_ffi_read_i64<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
out: &mut [u32],
) -> Result<(), AnyError>
where
@ -386,15 +433,13 @@ where
);
assert_eq!((outptr as usize % std::mem::size_of::<i64>()), 0);
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid i64 pointer, pointer is null"));
}
let value =
// SAFETY: ptr and offset are user provided.
unsafe { ptr::read_unaligned::<i64>(ptr.add(offset) as *const i64) };
unsafe { ptr::read_unaligned::<i64>(ptr.offset(offset) as *const i64) };
// SAFETY: Length and alignment of out slice were asserted to be correct.
unsafe { *outptr = value };
Ok(())
@ -403,8 +448,8 @@ where
#[op(fast)]
pub fn op_ffi_read_f32<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<f32, AnyError>
where
FP: FfiPermissions + 'static,
@ -414,21 +459,19 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid f32 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<f32>(ptr.add(offset) as *const f32) })
Ok(unsafe { ptr::read_unaligned::<f32>(ptr.offset(offset) as *const f32) })
}
#[op(fast)]
pub fn op_ffi_read_f64<FP>(
state: &mut deno_core::OpState,
ptr: usize,
offset: usize,
ptr: *mut c_void,
offset: isize,
) -> Result<f64, AnyError>
where
FP: FfiPermissions + 'static,
@ -438,12 +481,34 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
let ptr = ptr as *const c_void;
if ptr.is_null() {
return Err(type_error("Invalid f64 pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe { ptr::read_unaligned::<f64>(ptr.add(offset) as *const f64) })
Ok(unsafe { ptr::read_unaligned::<f64>(ptr.offset(offset) as *const f64) })
}
#[op(fast)]
pub fn op_ffi_read_ptr<FP>(
state: &mut deno_core::OpState,
ptr: *mut c_void,
offset: isize,
) -> Result<*mut c_void, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointerView#getPointer");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
if ptr.is_null() {
return Err(type_error("Invalid pointer pointer, pointer is null"));
}
// SAFETY: ptr and offset are user provided.
Ok(unsafe {
ptr::read_unaligned::<*mut c_void>(ptr.offset(offset) as *const *mut c_void)
})
}

View file

@ -10,6 +10,7 @@ use deno_core::op;
use deno_core::serde_v8;
use deno_core::v8;
use deno_core::ResourceId;
use std::ffi::c_void;
use std::ptr;
#[op(v8)]
@ -134,13 +135,9 @@ pub fn op_ffi_get_static<'scope>(
number.into()
}
NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
let result = data_ptr as u64;
let integer: v8::Local<v8::Value> = if result > MAX_SAFE_INTEGER as u64 {
v8::BigInt::new_from_u64(scope, result).into()
} else {
v8::Number::new(scope, result as f64).into()
};
integer.into()
let external: v8::Local<v8::Value> =
v8::External::new(scope, data_ptr as *mut c_void).into();
external.into()
}
NativeType::Struct(_) => {
return Err(type_error("Invalid FFI static type 'struct'"));

View file

@ -48,6 +48,9 @@ pub(crate) fn make_template(sym: &Symbol, trampoline: &Trampoline) -> Template {
let ret = if needs_unwrap(&sym.result_type) {
params.push(fast_api::Type::TypedArray(fast_api::CType::Int32));
fast_api::Type::Void
} else if sym.result_type == NativeType::Buffer {
// Buffer can be used as a return type and converts differently than in parameters.
fast_api::Type::Pointer
} else {
fast_api::Type::from(&sym.result_type)
};
@ -71,9 +74,9 @@ impl Trampoline {
}
pub(crate) struct Template {
args: Box<[fast_api::Type]>,
ret: fast_api::CType,
symbol_ptr: *const c_void,
pub args: Box<[fast_api::Type]>,
pub ret: fast_api::CType,
pub symbol_ptr: *const c_void,
}
impl fast_api::FastFunction for Template {
@ -106,9 +109,8 @@ impl From<&NativeType> for fast_api::Type {
NativeType::I64 => fast_api::Type::Int64,
NativeType::U64 => fast_api::Type::Uint64,
NativeType::ISize => fast_api::Type::Int64,
NativeType::USize | NativeType::Pointer | NativeType::Function => {
fast_api::Type::Uint64
}
NativeType::USize => fast_api::Type::Uint64,
NativeType::Pointer | NativeType::Function => fast_api::Type::Pointer,
NativeType::Buffer => fast_api::Type::TypedArray(fast_api::CType::Uint8),
NativeType::Struct(_) => {
fast_api::Type::TypedArray(fast_api::CType::Uint8)

View file

@ -418,13 +418,14 @@ pub(crate) fn generate(
fn q_fast_ty(v: &FastValue) -> Quote {
match v {
FastValue::Void => q!({ () }),
FastValue::Bool => q!({ bool }),
FastValue::U32 => q!({ u32 }),
FastValue::I32 => q!({ i32 }),
FastValue::U64 => q!({ u64 }),
FastValue::I64 => q!({ i64 }),
FastValue::F32 => q!({ f32 }),
FastValue::F64 => q!({ f64 }),
FastValue::Bool => q!({ bool }),
FastValue::Pointer => q!({ *mut ::std::ffi::c_void }),
FastValue::V8Value => q!({ v8::Local<v8::Value> }),
FastValue::Uint8Array
| FastValue::Uint32Array
@ -436,13 +437,14 @@ fn q_fast_ty(v: &FastValue) -> Quote {
fn q_fast_ty_variant(v: &FastValue) -> Quote {
match v {
FastValue::Void => q!({ Void }),
FastValue::Bool => q!({ Bool }),
FastValue::U32 => q!({ Uint32 }),
FastValue::I32 => q!({ Int32 }),
FastValue::U64 => q!({ Uint64 }),
FastValue::I64 => q!({ Int64 }),
FastValue::F32 => q!({ Float32 }),
FastValue::F64 => q!({ Float64 }),
FastValue::Bool => q!({ Bool }),
FastValue::Pointer => q!({ Pointer }),
FastValue::V8Value => q!({ V8Value }),
FastValue::Uint8Array => q!({ TypedArray(CType::Uint8) }),
FastValue::Uint32Array => q!({ TypedArray(CType::Uint32) }),

View file

@ -474,6 +474,13 @@ fn codegen_arg(
let #ident = #blk;
};
}
// Fast path for `*const c_void` and `*mut c_void`
if is_ptr_cvoid(&**ty) {
let blk = codegen_cvoid_ptr(core, idx);
return quote! {
let #ident = #blk;
};
}
// Otherwise deserialize it via serde_v8
quote! {
let #ident = args.get(#idx as i32);
@ -560,6 +567,19 @@ fn codegen_u8_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 {
}}
}
fn codegen_cvoid_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 {
quote! {{
let value = args.get(#idx as i32);
if value.is_null() {
std::ptr::null_mut()
} else if let Ok(b) = #core::v8::Local::<#core::v8::External>::try_from(value) {
b.value()
} else {
return #core::_ops::throw_type_error(scope, format!("Expected External at position {}", #idx));
}
}}
}
fn codegen_u32_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 {
quote! {
if let Ok(view) = #core::v8::Local::<#core::v8::Uint32Array>::try_from(args.get(#idx as i32)) {
@ -626,6 +646,15 @@ fn codegen_sync_ret(
quote! {
rv.set_uint32(result as u32);
}
} else if is_ptr_cvoid(output) || is_ptr_cvoid_rv(output) {
quote! {
if result.is_null() {
// External canot contain a null pointer, null pointers are instead represented as null.
rv.set_null();
} else {
rv.set(v8::External::new(scope, result as *mut ::std::ffi::c_void).into());
}
}
} else {
quote! {
match #core::serde_v8::to_v8(scope, result) {
@ -723,6 +752,15 @@ fn is_ptr_u8(ty: impl ToTokens) -> bool {
tokens(ty) == "* const u8"
}
fn is_ptr_cvoid(ty: impl ToTokens) -> bool {
tokens(&ty) == "* const c_void" || tokens(&ty) == "* mut c_void"
}
fn is_ptr_cvoid_rv(ty: impl ToTokens) -> bool {
tokens(&ty).contains("Result < * const c_void")
|| tokens(&ty).contains("Result < * mut c_void")
}
fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool {
tokens(&ty).contains("Option < & mut FastApiCallbackOptions")
}

View file

@ -45,6 +45,7 @@ enum TransformKind {
SliceU8(bool),
SliceF64(bool),
PtrU8,
PtrVoid,
WasmMemory,
}
@ -90,6 +91,13 @@ impl Transform {
index,
}
}
fn void_ptr(index: usize) -> Self {
Transform {
kind: TransformKind::PtrVoid,
index,
}
}
}
#[derive(Debug, PartialEq)]
@ -195,19 +203,25 @@ impl Transform {
.as_ptr();
})
}
TransformKind::PtrVoid => {
*ty = parse_quote! { *mut ::std::ffi::c_void };
q!(Vars {}, {})
}
}
}
}
fn get_fast_scalar(s: &str) -> Option<FastValue> {
match s {
"bool" => Some(FastValue::Bool),
"u32" => Some(FastValue::U32),
"i32" => Some(FastValue::I32),
"u64" => Some(FastValue::U64),
"i64" => Some(FastValue::I64),
"f32" => Some(FastValue::F32),
"f64" => Some(FastValue::F64),
"bool" => Some(FastValue::Bool),
"* const c_void" | "* mut c_void" => Some(FastValue::Pointer),
"ResourceId" => Some(FastValue::U32),
_ => None,
}
@ -226,13 +240,14 @@ fn can_return_fast(v: &FastValue) -> bool {
#[derive(Debug, PartialEq, Clone)]
pub(crate) enum FastValue {
Void,
Bool,
U32,
I32,
U64,
I64,
F32,
F64,
Bool,
Pointer,
V8Value,
Uint8Array,
Uint32Array,
@ -414,6 +429,31 @@ impl Optimizer {
{
self.fast_result = Some(FastValue::Void);
}
Some(GenericArgument::Type(Type::Ptr(TypePtr {
mutability: Some(_),
elem,
..
}))) => {
match &**elem {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
// Is `T` a c_void?
let segment = single_segment(segments)?;
match segment {
PathSegment { ident, .. } if ident == "c_void" => {
self.fast_result = Some(FastValue::Pointer);
return Ok(());
}
_ => {
return Err(BailoutReason::FastUnsupportedParamType)
}
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
@ -430,6 +470,29 @@ impl Optimizer {
}
};
}
Type::Ptr(TypePtr {
mutability: Some(_),
elem,
..
}) => {
match &**elem {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
// Is `T` a c_void?
let segment = single_segment(segments)?;
match segment {
PathSegment { ident, .. } if ident == "c_void" => {
self.fast_result = Some(FastValue::Pointer);
return Ok(());
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
};
@ -684,6 +747,31 @@ impl Optimizer {
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
},
// *const T
Type::Ptr(TypePtr {
elem,
mutability: Some(_),
..
}) => match &**elem {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
let segment = single_segment(segments)?;
match segment {
// Is `T` a c_void?
PathSegment { ident, .. } if ident == "c_void" => {
self.fast_parameters.push(FastValue::Pointer);
assert!(self
.transforms
.insert(index, Transform::void_ptr(index))
.is_none());
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
},
_ => return Err(BailoutReason::FastUnsupportedParamType),
},
_ => return Err(BailoutReason::FastUnsupportedParamType),

View file

@ -0,0 +1,11 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: false
has_rc_opstate: false
has_fast_callback_option: false
needs_fast_callback_option: true
fast_result: Some(Void)
fast_parameters: [V8Value, Pointer, Uint32Array]
transforms: {0: Transform { kind: PtrVoid, index: 0 }, 1: Transform { kind: SliceU32(true), index: 1 }}
is_async: false
fast_compatible: true

View file

@ -0,0 +1,127 @@
#[allow(non_camel_case_types)]
///Auto-generated by `deno_ops`, i.e: `#[op]`
///
///Use `op_ffi_ptr_value::decl()` to get an op-declaration
///you can include in a `deno_core::Extension`.
pub struct op_ffi_ptr_value;
#[doc(hidden)]
impl op_ffi_ptr_value {
pub fn name() -> &'static str {
stringify!(op_ffi_ptr_value)
}
pub fn v8_fn_ptr<'scope>() -> deno_core::v8::FunctionCallback {
use deno_core::v8::MapFnTo;
Self::v8_func.map_fn_to()
}
pub fn decl<'scope>() -> deno_core::OpDecl {
deno_core::OpDecl {
name: Self::name(),
v8_fn_ptr: Self::v8_fn_ptr(),
enabled: true,
fast_fn: Some(
Box::new(op_ffi_ptr_value_fast {
_phantom: ::std::marker::PhantomData,
}),
),
is_async: false,
is_unstable: false,
is_v8: false,
argc: 2usize,
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn call(ptr: *mut c_void, out: &mut [u32]) {}
pub fn v8_func<'scope>(
scope: &mut deno_core::v8::HandleScope<'scope>,
args: deno_core::v8::FunctionCallbackArguments,
mut rv: deno_core::v8::ReturnValue,
) {
let ctx = unsafe {
&*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
as *const deno_core::_ops::OpCtx)
};
let arg_0 = {
let value = args.get(0usize as i32);
if value.is_null() {
std::ptr::null_mut()
} else if let Ok(b)
= deno_core::v8::Local::<deno_core::v8::External>::try_from(value) {
b.value()
} else {
return deno_core::_ops::throw_type_error(
scope,
format!("Expected External at position {}", 0usize),
);
}
};
let arg_1 = if let Ok(view)
= deno_core::v8::Local::<
deno_core::v8::Uint32Array,
>::try_from(args.get(1usize as i32)) {
let (offset, len) = (view.byte_offset(), view.byte_length());
let buffer = match view.buffer(scope) {
Some(v) => v,
None => {
return deno_core::_ops::throw_type_error(
scope,
format!("Expected Uint32Array at position {}", 1usize),
);
}
};
if let Some(data) = buffer.data() {
let store = data.cast::<u8>().as_ptr();
unsafe {
::std::slice::from_raw_parts_mut(
store.add(offset) as *mut u32,
len / 4,
)
}
} else {
&mut []
}
} else {
return deno_core::_ops::throw_type_error(
scope,
format!("Expected Uint32Array at position {}", 1usize),
);
};
let result = Self::call(arg_0, arg_1);
let op_state = ::std::cell::RefCell::borrow(&*ctx.state);
op_state.tracker.track_sync(ctx.id);
}
}
struct op_ffi_ptr_value_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for op_ffi_ptr_value_fast {
fn function(&self) -> *const ::std::ffi::c_void {
op_ffi_ptr_value_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, Pointer, TypedArray(CType::Uint32), CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Void
}
}
fn op_ffi_ptr_value_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
ptr: *mut ::std::ffi::c_void,
out: *const deno_core::v8::fast_api::FastApiTypedArray<u32>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> () {
use deno_core::v8;
use deno_core::_ops;
let out = match unsafe { &*out }.get_storage_if_aligned() {
Some(v) => v,
None => {
unsafe { &mut *fast_api_callback_options }.fallback = true;
return Default::default();
}
};
let result = op_ffi_ptr_value::call(ptr, out);
result
}

View file

@ -0,0 +1,3 @@
pub fn op_ffi_ptr_value(ptr: *mut c_void, out: &mut [u32]) {
// ...
}

View file

@ -22,6 +22,7 @@ pub enum Error {
ExpectedObject,
ExpectedBuffer,
ExpectedDetachable,
ExpectedExternal,
ExpectedUtf8,
ExpectedLatin1,

View file

@ -20,6 +20,7 @@ pub use magic::bytestring::ByteString;
pub use magic::detached_buffer::DetachedBuffer;
pub use magic::string_or_buffer::StringOrBuffer;
pub use magic::u16string::U16String;
pub use magic::ExternalPointer;
pub use magic::Global;
pub use magic::Value;
pub use ser::to_v8;

View file

@ -0,0 +1,56 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::ffi::c_void;
use super::transl8::impl_magic;
use super::transl8::FromV8;
use super::transl8::ToV8;
pub struct ExternalPointer(*mut c_void);
// SAFETY: Nonblocking FFI is user controller and we must trust user to have it right.
unsafe impl Send for ExternalPointer {}
// SAFETY: Nonblocking FFI is user controller and we must trust user to have it right.
unsafe impl Sync for ExternalPointer {}
impl_magic!(ExternalPointer);
impl ToV8 for ExternalPointer {
fn to_v8<'a>(
&mut self,
scope: &mut v8::HandleScope<'a>,
) -> Result<v8::Local<'a, v8::Value>, crate::Error> {
if self.0.is_null() {
Ok(v8::null(scope).into())
} else {
Ok(v8::External::new(scope, self.0).into())
}
}
}
impl FromV8 for ExternalPointer {
fn from_v8(
_scope: &mut v8::HandleScope,
value: v8::Local<v8::Value>,
) -> Result<Self, crate::Error> {
if value.is_null() {
Ok(ExternalPointer(std::ptr::null_mut()))
} else if let Ok(external) = v8::Local::<v8::External>::try_from(value) {
Ok(ExternalPointer(external.value()))
} else {
Err(crate::Error::ExpectedExternal)
}
}
}
impl From<*mut c_void> for ExternalPointer {
fn from(value: *mut c_void) -> Self {
ExternalPointer(value)
}
}
impl From<*const c_void> for ExternalPointer {
fn from(value: *const c_void) -> Self {
ExternalPointer(value as *mut c_void)
}
}

View file

@ -2,6 +2,7 @@
pub mod buffer;
pub mod bytestring;
pub mod detached_buffer;
mod external_pointer;
mod global;
pub(super) mod rawbytes;
pub mod string_or_buffer;
@ -9,5 +10,6 @@ pub mod transl8;
pub mod u16string;
pub mod v8slice;
mod value;
pub use external_pointer::ExternalPointer;
pub use global::Global;
pub use value::Value;

View file

@ -16,6 +16,7 @@ use crate::magic::transl8::ToV8;
use crate::magic::transl8::MAGIC_FIELD;
use crate::ByteString;
use crate::DetachedBuffer;
use crate::ExternalPointer;
use crate::StringOrBuffer;
use crate::U16String;
use crate::ZeroCopyBuf;
@ -269,6 +270,7 @@ impl<'a, 'b, 'c, T: MagicType + ToV8> ser::SerializeStruct
// Dispatches between magic and regular struct serializers
pub enum StructSerializers<'a, 'b, 'c> {
ExternalPointer(MagicalSerializer<'a, 'b, 'c, magic::ExternalPointer>),
Magic(MagicalSerializer<'a, 'b, 'c, magic::Value<'a>>),
ZeroCopyBuf(MagicalSerializer<'a, 'b, 'c, ZeroCopyBuf>),
MagicDetached(MagicalSerializer<'a, 'b, 'c, DetachedBuffer>),
@ -288,6 +290,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> {
value: &T,
) -> Result<()> {
match self {
StructSerializers::ExternalPointer(s) => s.serialize_field(key, value),
StructSerializers::Magic(s) => s.serialize_field(key, value),
StructSerializers::ZeroCopyBuf(s) => s.serialize_field(key, value),
StructSerializers::MagicDetached(s) => s.serialize_field(key, value),
@ -302,6 +305,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> {
fn end(self) -> JsResult<'a> {
match self {
StructSerializers::ExternalPointer(s) => s.end(),
StructSerializers::Magic(s) => s.end(),
StructSerializers::ZeroCopyBuf(s) => s.end(),
StructSerializers::MagicDetached(s) => s.end(),
@ -385,8 +389,8 @@ macro_rules! forward_to {
};
}
const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1;
const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER;
pub(crate) const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1;
pub(crate) const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER;
impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> {
type Ok = v8::Local<'a, v8::Value>;
@ -564,6 +568,10 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> {
len: usize,
) -> Result<Self::SerializeStruct> {
match name {
magic::ExternalPointer::MAGIC_NAME => {
let m = MagicalSerializer::<ExternalPointer>::new(self.scope);
Ok(StructSerializers::ExternalPointer(m))
}
ByteString::MAGIC_NAME => {
let m = MagicalSerializer::<ByteString>::new(self.scope);
Ok(StructSerializers::MagicByteString(m))

View file

@ -140,30 +140,31 @@ remote.symbols.method14(0);
remote.symbols.method15("foo");
// @ts-expect-error: Invalid argument
remote.symbols.method15(new Uint16Array(1));
remote.symbols.method15(0n);
remote.symbols.method15(null);
remote.symbols.method15({} as Deno.PointerValue);
const result = remote.symbols.method16();
// @ts-expect-error: Invalid argument
let r_0: string = result;
let r_1: Deno.PointerValue = result;
let r_1: number | bigint = result;
const result2 = remote.symbols.method17();
// @ts-expect-error: Invalid argument
result2.then((_0: string) => {});
result2.then((_1: Deno.PointerValue) => {});
result2.then((_1: number | bigint) => {});
const result3 = remote.symbols.method18();
// @ts-expect-error: Invalid argument
let r3_0: Deno.BufferSource = result3;
let r3_1: Deno.UnsafePointer = result3;
let r3_1: null | Deno.UnsafePointer = result3;
const result4 = remote.symbols.method19();
// @ts-expect-error: Invalid argument
result4.then((_0: Deno.BufferSource) => {});
result4.then((_1: Deno.UnsafePointer) => {});
result4.then((_1: null | Deno.UnsafePointer) => {});
const fnptr = new Deno.UnsafeFnPointer(
0n,
{} as NonNullable<Deno.PointerValue>,
{
parameters: ["u32", "pointer"],
result: "void",
@ -210,7 +211,7 @@ const unsafe_callback_right1 = new Deno.UnsafeCallback(
parameters: ["u8", "u32", "pointer"],
result: "void",
},
(_1: number, _2: number, _3: Deno.PointerValue) => {},
(_1: number, _2: number, _3: null | Deno.PointerValue) => {},
);
const unsafe_callback_right2 = new Deno.UnsafeCallback(
{
@ -232,14 +233,14 @@ const unsafe_callback_right4 = new Deno.UnsafeCallback(
parameters: ["u8", "u32", "pointer"],
result: "u8",
},
(_1: number, _2: number, _3: Deno.PointerValue) => 3,
(_1: number, _2: number, _3: null | Deno.PointerValue) => 3,
);
const unsafe_callback_right5 = new Deno.UnsafeCallback(
{
parameters: ["u8", "i32", "pointer"],
result: "void",
},
(_1: number, _2: number, _3: Deno.PointerValue) => {},
(_1: number, _2: number, _3: null | Deno.PointerValue) => {},
);
// @ts-expect-error: Must pass callback
@ -255,9 +256,9 @@ remote.symbols.method23(new Uint32Array(1));
remote.symbols.method23(new Uint8Array(1));
// @ts-expect-error: Cannot pass pointer values as buffer.
remote.symbols.method23(0);
remote.symbols.method23({});
// @ts-expect-error: Cannot pass pointer values as buffer.
remote.symbols.method23(0n);
remote.symbols.method23({});
remote.symbols.method23(null);
// @ts-expect-error: Cannot pass number as bool.
@ -278,16 +279,16 @@ let r42_1: number = remote.symbols.method24(true);
// @ts-expect-error: Invalid member type
const static1_wrong: null = remote.symbols.static1;
const static1_right: Deno.PointerValue = remote.symbols.static1;
const static1_right: number | bigint = remote.symbols.static1;
// @ts-expect-error: Invalid member type
const static2_wrong: null = remote.symbols.static2;
const static2_right: Deno.UnsafePointer = remote.symbols.static2;
const static2_right: null | Deno.UnsafePointer = remote.symbols.static2;
// @ts-expect-error: Invalid member type
const static3_wrong: null = remote.symbols.static3;
const static3_right: Deno.PointerValue = remote.symbols.static3;
const static3_right: number | bigint = remote.symbols.static3;
// @ts-expect-error: Invalid member type
const static4_wrong: null = remote.symbols.static4;
const static4_right: Deno.PointerValue = remote.symbols.static4;
const static4_right: number | bigint = remote.symbols.static4;
// @ts-expect-error: Invalid member type
const static5_wrong: null = remote.symbols.static5;
const static5_right: number = remote.symbols.static5;
@ -299,7 +300,7 @@ const static7_wrong: null = remote.symbols.static7;
const static7_right: number = remote.symbols.static7;
// @ts-expect-error: Invalid member type
const static8_wrong: null = remote.symbols.static8;
const static8_right: Deno.PointerValue = remote.symbols.static8;
const static8_right: number | bigint = remote.symbols.static8;
// @ts-expect-error: Invalid member type
const static9_wrong: null = remote.symbols.static9;
const static9_right: number = remote.symbols.static9;
@ -311,7 +312,7 @@ const static11_wrong: null = remote.symbols.static11;
const static11_right: number = remote.symbols.static11;
// @ts-expect-error: Invalid member type
const static12_wrong: null = remote.symbols.static12;
const static12_right: Deno.PointerValue = remote.symbols.static12;
const static12_right: number | bigint = remote.symbols.static12;
// @ts-expect-error: Invalid member type
const static13_wrong: null = remote.symbols.static13;
const static13_right: number = remote.symbols.static13;
@ -376,8 +377,8 @@ type __Tests__ = [
symbols: {
pushBuf: (
buf: BufferSource | null,
ptr: Deno.PointerValue | null,
func: Deno.PointerValue | null,
ptr: Deno.PointerValue,
func: Deno.PointerValue,
) => Deno.PointerValue;
};
close(): void;
@ -395,8 +396,8 @@ type __Tests__ = [
{
symbols: {
foo: (
...args: (Deno.PointerValue | null)[]
) => Deno.PointerValue;
...args: (number | Deno.PointerValue | null)[]
) => number | bigint;
};
close(): void;
},

View file

@ -97,12 +97,8 @@ fn basic() {
-9007199254740992n\n\
579.9119873046875\n\
579.912\n\
After sleep_blocking\n\
true\n\
Before\n\
true\n\
After\n\
true\n\
logCallback\n\
1 -1 2 -2 3 -3 4 -4 0.5 -0.5 1 2 3 4 5 6 7 8\n\
u8: 8\n\
@ -119,23 +115,12 @@ fn basic() {
78\n\
STORED_FUNCTION cleared\n\
STORED_FUNCTION_2 cleared\n\
Thread safe call counter: 0\n\
logCallback\n\
Thread safe call counter: 1\n\
u8: 8\n\
Static u32: 42\n\
Static i64: -1242464576485\n\
Static ptr: true\n\
Static ptr value: 42\n\
Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\
Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\
Rect { x: 20.0, y: 20.0, w: 100.0, h: 200.0 }\n\
Mixed { u8: 3, f32: 12.515, rect: Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }, usize: 12456789, array: [8, 32] }\n\
arrayBuffer.byteLength: 4\n\
uint32Array.length: 1\n\
uint32Array[0]: 42\n\
uint32Array[0] after mutation: 55\n\
Static ptr value after mutation: 55\n\
2264956937\n\
2264956937\n\
Correct number of resources\n";

View file

@ -1,12 +1,15 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file
// Run using cargo test or `--v8-options=--allow-natives-syntax`
// Run using cargo test or `--v8-flags=--allow-natives-syntax`
import { assertEquals, assertInstanceOf, assertNotEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts";
import {
assertThrows,
assert,
assertNotEquals,
assertInstanceOf,
assertEquals,
assertFalse,
} from "../../test_util/std/testing/asserts.ts";
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
@ -368,8 +371,8 @@ assertEquals(isNullBufferDeopt(externalOneBuffer), false, "isNullBufferDeopt(ext
// Due to ops macro using `Local<ArrayBuffer>->Data()` to get the pointer for the slice that is then used to get
// the pointer of an ArrayBuffer / TypedArray, the same effect can be seen where a zero byte length buffer returns
// a null pointer as its pointer value.
assertEquals(Deno.UnsafePointer.of(externalZeroBuffer), 0, "Deno.UnsafePointer.of(externalZeroBuffer) !== 0");
assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), 0, "Deno.UnsafePointer.of(externalOneBuffer) !== 0");
assertEquals(Deno.UnsafePointer.of(externalZeroBuffer), null, "Deno.UnsafePointer.of(externalZeroBuffer) !== null");
assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), null, "Deno.UnsafePointer.of(externalOneBuffer) === null");
const addU32Ptr = dylib.symbols.get_add_u32_ptr();
const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, {
@ -486,16 +489,15 @@ await promise;
let start = performance.now();
dylib.symbols.sleep_blocking(100);
console.log("After sleep_blocking");
console.log(performance.now() - start >= 100);
assert(performance.now() - start >= 100);
start = performance.now();
const promise_2 = dylib.symbols.sleep_nonblocking(100).then(() => {
console.log("After");
console.log(performance.now() - start >= 100);
assert(performance.now() - start >= 100);
});
console.log("Before");
console.log(performance.now() - start < 100);
assert(performance.now() - start < 100);
// Await to make sure `sleep_nonblocking` calls and logs before we proceed
await promise_2;
@ -532,7 +534,7 @@ const returnU8Callback = new Deno.UnsafeCallback(
);
const returnBufferCallback = new Deno.UnsafeCallback({
parameters: [],
result: "pointer",
result: "buffer",
}, () => {
return buffer;
});
@ -617,27 +619,29 @@ const addToFooCallback = new Deno.UnsafeCallback({
}, () => counter++);
// Test thread safe callbacks
console.log("Thread safe call counter:", counter);
assertEquals(counter, 0);
addToFooCallback.ref();
await dylib.symbols.call_fn_ptr_thread_safe(addToFooCallback.pointer);
addToFooCallback.unref();
logCallback.ref();
await dylib.symbols.call_fn_ptr_thread_safe(logCallback.pointer);
logCallback.unref();
console.log("Thread safe call counter:", counter);
assertEquals(counter, 1);
returnU8Callback.ref();
await dylib.symbols.call_fn_ptr_return_u8_thread_safe(returnU8Callback.pointer);
// Purposefully do not unref returnU8Callback: Instead use it to test close() unrefing.
// Test statics
console.log("Static u32:", dylib.symbols.static_u32);
console.log("Static i64:", dylib.symbols.static_i64);
console.log(
"Static ptr:",
typeof dylib.symbols.static_ptr === "number",
assertEquals(dylib.symbols.static_u32, 42);
assertEquals(dylib.symbols.static_i64, -1242464576485);
assert(
typeof dylib.symbols.static_ptr === "object"
);
assertEquals(
Object.keys(dylib.symbols.static_ptr).length, 0
);
const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr);
console.log("Static ptr value:", view.getUint32());
assertEquals(view.getUint32(), 42);
// Test struct returning
const rect_sync = dylib.symbols.make_rect(10, 20, 100, 200);
@ -656,7 +660,7 @@ assertEquals(rect_async.length, 4 * 8);
assertEquals(Array.from(new Float64Array(rect_async.buffer)), [10, 20, 100, 200]);
// Test complex, mixed struct returning and passing
const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, 12456789, new Uint32Array([8, 32]));
const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, Deno.UnsafePointer.create(12456789), new Uint32Array([8, 32]));
assertEquals(mixedStruct.length, 56);
assertEquals(Array.from(mixedStruct.subarray(0, 4)), [3, 0, 0, 0]);
assertEquals(new Float32Array(mixedStruct.buffer, 4, 1)[0], 12.515000343322754);
@ -681,12 +685,31 @@ cb.close();
const arrayBuffer = view.getArrayBuffer(4);
const uint32Array = new Uint32Array(arrayBuffer);
console.log("arrayBuffer.byteLength:", arrayBuffer.byteLength);
console.log("uint32Array.length:", uint32Array.length);
console.log("uint32Array[0]:", uint32Array[0]);
assertEquals(arrayBuffer.byteLength, 4);
assertEquals(uint32Array.length, 1);
assertEquals(uint32Array[0], 42);
uint32Array[0] = 55; // MUTATES!
console.log("uint32Array[0] after mutation:", uint32Array[0]);
console.log("Static ptr value after mutation:", view.getUint32());
assertEquals(uint32Array[0], 55);
assertEquals(view.getUint32(), 55);
{
// Test UnsafePointer APIs
assertEquals(Deno.UnsafePointer.create(0), null);
const createdPointer = Deno.UnsafePointer.create(1);
assertNotEquals(createdPointer, null);
assertEquals(typeof createdPointer, "object");
assertEquals(Deno.UnsafePointer.value(null), 0);
assertEquals(Deno.UnsafePointer.value(createdPointer), 1);
assert(Deno.UnsafePointer.equals(null, null));
assertFalse(Deno.UnsafePointer.equals(null, createdPointer));
assertFalse(Deno.UnsafePointer.equals(Deno.UnsafePointer.create(2), createdPointer));
// Do not allow offsetting from null, `create` function should be used instead.
assertThrows(() => Deno.UnsafePointer.offset(null, 5));
const offsetPointer = Deno.UnsafePointer.offset(createdPointer, 5);
assertEquals(Deno.UnsafePointer.value(offsetPointer), 6);
assertEquals(Deno.UnsafePointer.offset(offsetPointer, -6), null);
}
// Test non-UTF-8 characters