summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tsc/dts/lib.deno.unstable.d.ts14
-rw-r--r--ext/ffi/00_ffi.js7
-rw-r--r--ext/ffi/dlfcn.rs23
-rw-r--r--ext/ffi/static.rs13
-rw-r--r--test_ffi/tests/ffi_types.ts15
-rw-r--r--test_ffi/tests/test.js17
6 files changed, 83 insertions, 6 deletions
diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts
index b82a80382..4bc0fed45 100644
--- a/cli/tsc/dts/lib.deno.unstable.d.ts
+++ b/cli/tsc/dts/lib.deno.unstable.d.ts
@@ -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;
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js
index ea75df65c..a8f05dfca 100644
--- a/ext/ffi/00_ffi.js
+++ b/ext/ffi/00_ffi.js
@@ -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,
diff --git a/ext/ffi/dlfcn.rs b/ext/ffi/dlfcn.rs
index 4f58248e6..3af9177bf 100644
--- a/ext/ffi/dlfcn.rs
+++ b/ext/ffi/dlfcn.rs
@@ -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
diff --git a/ext/ffi/static.rs b/ext/ffi/static.rs
index 2f32f03fd..ae095d5fc 100644
--- a/ext/ffi/static.rs
+++ b/ext/ffi/static.rs
@@ -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 => {
diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts
index c04c13d79..ff2a4fd5d 100644
--- a/test_ffi/tests/ffi_types.ts
+++ b/test_ffi/tests/ffi_types.ts
@@ -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
diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js
index 5e193e99f..f4fc96a1b 100644
--- a/test_ffi/tests/test.js
+++ b/test_ffi/tests/test.js
@@ -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;