1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(ext/ffi): support marking symbols as optional (#18529)

This commit is contained in:
Dj 2023-04-04 00:02:21 +05:30 committed by GitHub
parent 51d3fb78ad
commit 62c5664697
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 6 deletions

View file

@ -271,6 +271,11 @@ declare namespace Deno {
*
* @default {false} */
callback?: boolean;
/** When `true`, dlopen will not fail if the symbol is not found.
* Instead, the symbol will be set to `null`.
*
* @default {false} */
optional?: boolean;
}
/** **UNSTABLE**: New API, yet to be vetted.
@ -282,6 +287,11 @@ declare namespace Deno {
name?: string;
/** The type of the foreign static value. */
type: Type;
/** When `true`, dlopen will not fail if the symbol is not found.
* Instead, the symbol will be set to `null`.
*
* @default {false} */
optional?: boolean;
}
/** **UNSTABLE**: New API, yet to be vetted.
@ -336,7 +346,9 @@ declare namespace Deno {
* @category FFI
*/
type StaticForeignLibraryInterface<T extends ForeignLibraryInterface> = {
[K in keyof T]: StaticForeignSymbol<T[K]>;
[K in keyof T]: T[K]["optional"] extends true
? StaticForeignSymbol<T[K]> | null
: StaticForeignSymbol<T[K]>;
};
const brand: unique symbol;

View file

@ -443,6 +443,12 @@ class DynamicLibrary {
continue;
}
// Symbol was marked as optional, and not found.
// In that case, we set its value to null in Rust-side.
if (symbols[symbol] === null) {
continue;
}
if (ReflectHas(symbols[symbol], "type")) {
const type = symbols[symbol].type;
if (type === "void") {
@ -456,6 +462,7 @@ class DynamicLibrary {
this.#rid,
name,
type,
symbols[symbol].optional,
);
ObjectDefineProperty(
this.symbols,

View file

@ -76,12 +76,19 @@ pub struct ForeignFunction {
#[serde(rename = "callback")]
#[serde(default = "default_callback")]
callback: bool,
#[serde(rename = "optional")]
#[serde(default = "default_optional")]
optional: bool,
}
fn default_callback() -> bool {
false
}
fn default_optional() -> bool {
false
}
// ForeignStatic's name and type fields are read and used by
// serde_v8 to determine which variant a ForeignSymbol is.
// They are not used beyond that and are thus marked with underscores.
@ -156,7 +163,7 @@ where
ForeignSymbol::ForeignStatic(_) => {
// No-op: Statics will be handled separately and are not part of the Rust-side resource.
}
ForeignSymbol::ForeignFunction(foreign_fn) => {
ForeignSymbol::ForeignFunction(foreign_fn) => 'register_symbol: {
let symbol = match &foreign_fn.name {
Some(symbol) => symbol,
None => &symbol_key,
@ -168,10 +175,18 @@ where
// SAFETY: The obtained T symbol is the size of a pointer.
match unsafe { resource.lib.symbol::<*const c_void>(symbol) } {
Ok(value) => Ok(value),
Err(err) => Err(generic_error(format!(
"Failed to register symbol {symbol}: {err}"
))),
Err(err) => if foreign_fn.optional {
let null: v8::Local<v8::Value> = v8::null(scope).into();
let func_key = v8::String::new(scope, &symbol_key).unwrap();
obj.set(scope, func_key.into(), null);
break 'register_symbol;
} else {
Err(generic_error(format!(
"Failed to register symbol {symbol}: {err}"
)))
},
}?;
let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
let cif = libffi::middle::Cif::new(
foreign_fn

View file

@ -20,10 +20,21 @@ pub fn op_ffi_get_static<'scope>(
rid: ResourceId,
name: String,
static_type: NativeType,
optional: bool,
) -> Result<serde_v8::Value<'scope>, AnyError> {
let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
let data_ptr = resource.get_static(name)?;
let data_ptr = match resource.get_static(name) {
Ok(data_ptr) => Ok(data_ptr),
Err(err) => {
if optional {
let null: v8::Local<v8::Value> = v8::null(scope).into();
return Ok(null.into());
} else {
Err(err)
}
}
}?;
Ok(match static_type {
NativeType::Void => {

View file

@ -46,6 +46,11 @@ const remote = Deno.dlopen(
parameters: ["bool"],
result: "bool",
},
method25: {
parameters: [],
result: "void",
optional: true,
},
static1: { type: "usize" },
static2: { type: "pointer" },
static3: { type: "usize" },
@ -61,6 +66,10 @@ const remote = Deno.dlopen(
static13: { type: "f32" },
static14: { type: "f64" },
static15: { type: "bool" },
static16: {
type: "bool",
optional: true,
},
},
);
@ -277,6 +286,9 @@ let r24_0: true = remote.symbols.method24(true);
let r42_1: number = remote.symbols.method24(true);
<boolean> remote.symbols.method24(Math.random() > 0.5);
// @ts-expect-error: Optional symbol; can be null.
remote.symbols.method25();
// @ts-expect-error: Invalid member type
const static1_wrong: null = remote.symbols.static1;
const static1_right: number | bigint = remote.symbols.static1;
@ -322,6 +334,9 @@ const static14_right: number = remote.symbols.static14;
// @ts-expect-error: Invalid member type
const static15_wrong: number = remote.symbols.static15;
const static15_right: boolean = remote.symbols.static15;
// @ts-expect-error: Invalid member type
const static16_wrong: boolean = remote.symbols.static16;
const static16_right: boolean | null = remote.symbols.static16;
// Adapted from https://stackoverflow.com/a/53808212/10873797
type Equal<T, U> = (<G>() => G extends T ? 1 : 2) extends

View file

@ -252,6 +252,7 @@ const dylib = Deno.dlopen(libPath, {
*/
"static_char": {
type: "pointer",
optional: true,
},
"hash": { parameters: ["buffer", "u32"], result: "u32" },
make_rect: {
@ -281,6 +282,22 @@ const dylib = Deno.dlopen(libPath, {
print_mixed: {
parameters: [{ struct: Mixed }],
result: "void",
optional: true,
},
non_existent_symbol: {
parameters: [],
result: "void",
optional: true,
},
non_existent_nonblocking_symbol: {
parameters: [],
result: "void",
nonblocking: true,
optional: true,
},
non_existent_static: {
type: "u32",
optional: true,
},
});
const { symbols } = dylib;