char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somdeslop: consolidate public type and registry test utils7325e8c

main
39.5 KiB955 linesraw
1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3use std::ffi::c_void;
4use std::path::{Path, PathBuf};
5use std::rc::Rc;
6use std::sync::Arc;
7
8use avril_caps::{Capabilities, PathTarget};
9use avril_core::serde_v8::ToJsBuffer;
10use avril_core::{Extension, Op, PublicModule, v8};
11use futures::channel::oneshot;
12use libffi::middle::{Arg, Cif, CodePtr, Ret, Type};
13
14#[cfg(not(all(target_pointer_width = "64", target_endian = "little")))]
15compile_error!("avril-ffi currently requires a 64-bit little-endian target");
16
17// Resource IDs cross into JS as Numbers, so they must stay exact safe integers.
18const MAX_ID: u64 = 9_007_199_254_740_991;
19
20#[derive(Clone, Copy, PartialEq, Eq, Debug)]
21enum Kind {
22    Void,
23    Bool,
24    I8,
25    U8,
26    I16,
27    U16,
28    I32,
29    U32,
30    I64,
31    U64,
32    Isize,
33    Usize,
34    F32,
35    F64,
36    Pointer,
37    Buffer,
38    Function,
39}
40
41impl Kind {
42    fn parse(name: &str) -> Result<Self, String> {
43        Ok(match name {
44            "void" => Self::Void,
45            "bool" => Self::Bool,
46            "i8" => Self::I8,
47            "u8" => Self::U8,
48            "i16" => Self::I16,
49            "u16" => Self::U16,
50            "i32" => Self::I32,
51            "u32" => Self::U32,
52            "i64" => Self::I64,
53            "u64" => Self::U64,
54            "isize" => Self::Isize,
55            "usize" => Self::Usize,
56            "f32" => Self::F32,
57            "f64" => Self::F64,
58            "pointer" => Self::Pointer,
59            "buffer" => Self::Buffer,
60            "function" => Self::Function,
61            _ => return Err(format!("Unknown native type: {name}")),
62        })
63    }
64
65    fn ffi_type(self) -> Type {
66        match self {
67            Self::Void => Type::void(),
68            Self::Bool | Self::U8 => Type::u8(),
69            Self::I8 => Type::i8(),
70            Self::U16 => Type::u16(),
71            Self::I16 => Type::i16(),
72            Self::U32 => Type::u32(),
73            Self::I32 => Type::i32(),
74            Self::U64 => Type::u64(),
75            Self::I64 => Type::i64(),
76            Self::Usize => Type::usize(),
77            Self::Isize => Type::isize(),
78            Self::F32 => Type::f32(),
79            Self::F64 => Type::f64(),
80            Self::Pointer | Self::Buffer | Self::Function => Type::pointer(),
81        }
82    }
83}
84
85#[derive(Clone, Debug)]
86struct Def {
87    params: Vec<Kind>,
88    result: Kind,
89}
90
91#[derive(serde::Deserialize)]
92struct DefWire {
93    symbol: String,
94    params: Vec<String>,
95    result: String,
96    nonblocking: bool,
97}
98
99struct SymbolState {
100    address: usize,
101    def: Def,
102}
103
104struct LibraryState {
105    _library: libloading::Library,
106    symbols: Vec<SymbolState>,
107}
108
109#[derive(Clone, Debug)]
110enum Value {
111    Void,
112    Bool(bool),
113    I64(i64),
114    U64(u64),
115    F32(f32),
116    F64(f64),
117    Ptr(u64),
118}
119
120struct Completed {
121    outcome: Result<Value, String>,
122    buffers: Vec<Vec<u8>>,
123    originals: Vec<Vec<u8>>,
124    targets: Vec<v8::Global<v8::Value>>,
125}
126
127struct PendingTargets {
128    targets: Vec<v8::Global<v8::Value>>,
129    originals: Vec<Vec<u8>>,
130}
131
132type CallReceiver = oneshot::Receiver<Result<(Value, Vec<Vec<u8>>), String>>;
133
134struct Registry {
135    caps: Arc<Capabilities>,
136    libraries: RefCell<HashMap<u64, Arc<LibraryState>>>,
137    next_library: Cell<u64>,
138    receivers: RefCell<HashMap<u64, CallReceiver>>,
139    targets: RefCell<HashMap<u64, PendingTargets>>,
140    completed: Rc<RefCell<HashMap<u64, Completed>>>,
141    next_call: Cell<u64>,
142}
143
144enum CallError {
145    Type(String),
146    Error(String),
147}
148
149impl CallError {
150    fn type_(message: impl Into<String>) -> Self {
151        Self::Type(message.into())
152    }
153    fn error(message: impl Into<String>) -> Self {
154        Self::Error(message.into())
155    }
156}
157
158type CallResult<T> = Result<T, CallError>;
159
160fn throw<T>(scope: &mut v8::PinScope, error: &CallError) -> Option<T> {
161    let (message, type_error) = match error {
162        CallError::Type(message) => (message, true),
163        CallError::Error(message) => (message, false),
164    };
165    let message = v8::String::new(scope, message)?;
166    let exception = if type_error {
167        v8::Exception::type_error(scope, message)
168    } else {
169        v8::Exception::error(scope, message)
170    };
171    scope.throw_exception(exception);
172    None
173}
174
175fn bigint_u64(scope: &mut v8::PinScope, value: v8::Local<v8::Value>) -> CallResult<u64> {
176    if !value.is_big_int() {
177        return Err(CallError::type_("Expected a bigint"));
178    }
179    let bigint = value.to_big_int(scope).unwrap();
180    let (value, lossless) = bigint.u64_value();
181    if lossless {
182        return Ok(value);
183    }
184    Err(CallError::type_("Bigint is out of range"))
185}
186
187fn bigint_i64(scope: &mut v8::PinScope, value: v8::Local<v8::Value>) -> CallResult<i64> {
188    if !value.is_big_int() {
189        return Err(CallError::type_("Expected a bigint"));
190    }
191    let bigint = value.to_big_int(scope).unwrap();
192    let (value, lossless) = bigint.i64_value();
193    if lossless {
194        return Ok(value);
195    }
196    Err(CallError::type_("Bigint is out of range"))
197}
198
199fn number(scope: &mut v8::PinScope, value: v8::Local<v8::Value>) -> CallResult<f64> {
200    if !value.is_number() {
201        return Err(CallError::type_("Expected a number"));
202    }
203    value
204        .number_value(scope)
205        .ok_or_else(|| CallError::type_("Expected a number"))
206}
207
208fn integer(
209    scope: &mut v8::PinScope,
210    value: v8::Local<v8::Value>,
211    min: f64,
212    max: f64,
213) -> CallResult<i64> {
214    if value.is_big_int() {
215        let value = bigint_i64(scope, value)?;
216        if (value as f64) < min || (value as f64) > max {
217            return Err(CallError::Type("Integer argument is out of range".into()));
218        }
219        return Ok(value);
220    }
221    let value = number(scope, value)?;
222    if value.fract() != 0.0 || value < min || value > max {
223        return Err(CallError::Type("Integer argument is out of range".into()));
224    }
225    Ok(value as i64)
226}
227
228fn view_bytes(
229    scope: &mut v8::PinScope,
230    value: v8::Local<v8::Value>,
231) -> CallResult<(*mut u8, usize)> {
232    if let Ok(view) = v8::Local::<v8::ArrayBufferView>::try_from(value) {
233        return Ok((view.data() as *mut u8, view.byte_length()));
234    }
235    if let Ok(buffer) = v8::Local::<v8::ArrayBuffer>::try_from(value) {
236        let pointer = buffer
237            .data()
238            .map(|pointer| pointer.as_ptr() as *mut u8)
239            .unwrap_or(std::ptr::null_mut());
240        return Ok((pointer, buffer.byte_length()));
241    }
242    let _ = scope;
243    Err(CallError::type_(
244        "Expected an ArrayBuffer or an ArrayBuffer view",
245    ))
246}
247
248// One 8-byte slot per argument; libffi reads only the bytes of the slot's type.
249#[derive(Clone, Copy)]
250struct Slot(u64);
251
252impl Slot {
253    fn from_i64(value: i64) -> Self {
254        Self(value as u64)
255    }
256    fn from_u64(value: u64) -> Self {
257        Self(value)
258    }
259    fn from_f32(value: f32) -> Self {
260        Self(f32::to_bits(value) as u64)
261    }
262    fn from_f64(value: f64) -> Self {
263        Self(f64::to_bits(value))
264    }
265    fn from_ptr(value: *mut c_void) -> Self {
266        Self(value as usize as u64)
267    }
268}
269
270fn convert_arg(
271    scope: &mut v8::PinScope,
272    value: v8::Local<v8::Value>,
273    kind: Kind,
274    nonblocking: bool,
275    buffers: &mut Vec<Vec<u8>>,
276) -> CallResult<Slot> {
277    Ok(match kind {
278        Kind::Bool => {
279            if !value.is_boolean() {
280                return Err(CallError::type_("Expected a boolean"));
281            }
282            Slot::from_u64(value.boolean_value(scope) as u64)
283        }
284        Kind::I8 => Slot::from_i64(integer(scope, value, i8::MIN as f64, i8::MAX as f64)?),
285        Kind::U8 => Slot::from_i64(integer(scope, value, 0.0, u8::MAX as f64)?),
286        Kind::I16 => Slot::from_i64(integer(scope, value, i16::MIN as f64, i16::MAX as f64)?),
287        Kind::U16 => Slot::from_i64(integer(scope, value, 0.0, u16::MAX as f64)?),
288        Kind::I32 => Slot::from_i64(integer(scope, value, i32::MIN as f64, i32::MAX as f64)?),
289        Kind::U32 => Slot::from_i64(integer(scope, value, 0.0, u32::MAX as f64)?),
290        Kind::I64 | Kind::Isize => {
291            if value.is_big_int() {
292                Slot::from_i64(bigint_i64(scope, value)?)
293            } else {
294                Slot::from_i64(integer(
295                    scope,
296                    value,
297                    -9_007_199_254_740_992.0,
298                    9_007_199_254_740_992.0,
299                )?)
300            }
301        }
302        Kind::U64 | Kind::Usize => {
303            if value.is_big_int() {
304                Slot::from_u64(bigint_u64(scope, value)?)
305            } else {
306                Slot::from_i64(integer(scope, value, 0.0, 9_007_199_254_740_992.0)?)
307            }
308        }
309        Kind::F32 => Slot::from_f32(number(scope, value)? as f32),
310        Kind::F64 => Slot::from_f64(number(scope, value)?),
311        Kind::Pointer | Kind::Function => {
312            if value.is_null_or_undefined() {
313                Slot::from_ptr(std::ptr::null_mut())
314            } else if value.is_big_int() {
315                Slot::from_u64(bigint_u64(scope, value)?)
316            } else if let Ok((pointer, _)) = view_bytes(scope, value) {
317                if nonblocking {
318                    return Err(CallError::type_(
319                        "A pointer into a view cannot be used in a nonblocking call; pass an UnsafePointer allocation",
320                    ));
321                }
322                Slot::from_ptr(pointer as *mut c_void)
323            } else {
324                return Err(CallError::type_("Expected a pointer"));
325            }
326        }
327        Kind::Buffer => {
328            let (pointer, length) = view_bytes(scope, value)?;
329            if nonblocking {
330                let copy = if length == 0 {
331                    Vec::new()
332                } else {
333                    // SAFETY: pointer/length describe this view's live bytes; we copy them out now.
334                    unsafe { std::slice::from_raw_parts(pointer, length) }.to_vec()
335                };
336                buffers.push(copy);
337                let copy = buffers.last_mut().unwrap();
338                Slot::from_ptr(copy.as_mut_ptr() as *mut c_void)
339            } else {
340                Slot::from_ptr(pointer as *mut c_void)
341            }
342        }
343        Kind::Void => return Err(CallError::type_("void is not a parameter type")),
344    })
345}
346
347fn call_native(address: usize, def: &Def, slots: &mut [Slot]) -> Value {
348    let cif = Cif::new(
349        def.params.iter().map(|kind| kind.ffi_type()),
350        def.result.ffi_type(),
351    );
352    let args: Vec<Arg> = slots.iter().map(|slot| Arg::new(&slot.0)).collect();
353    let function = CodePtr::from_ptr(address as *const c_void);
354    // SAFETY: the caller checked address against def when the library (or the
355    // UnsafeFnPointer's creator) was opened; slot bytes match def's types.
356    unsafe {
357        match def.result {
358            Kind::Void => {
359                cif.call_return_into(function, &args, Ret::void());
360                Value::Void
361            }
362            Kind::Bool => Value::Bool(cif.call::<u8>(function, &args) != 0),
363            Kind::I8 => Value::I64(cif.call::<i8>(function, &args) as i64),
364            Kind::U8 => Value::U64(cif.call::<u8>(function, &args) as u64),
365            Kind::I16 => Value::I64(cif.call::<i16>(function, &args) as i64),
366            Kind::U16 => Value::U64(cif.call::<u16>(function, &args) as u64),
367            Kind::I32 => Value::I64(cif.call::<i32>(function, &args) as i64),
368            Kind::U32 => Value::U64(cif.call::<u32>(function, &args) as u64),
369            Kind::I64 | Kind::Isize => Value::I64(cif.call::<i64>(function, &args)),
370            Kind::U64 | Kind::Usize => Value::U64(cif.call::<u64>(function, &args)),
371            Kind::F32 => Value::F32(cif.call::<f32>(function, &args)),
372            Kind::F64 => Value::F64(cif.call::<f64>(function, &args)),
373            Kind::Pointer | Kind::Function => {
374                Value::Ptr(cif.call::<*mut c_void>(function, &args) as usize as u64)
375            }
376            Kind::Buffer => Value::Ptr(cif.call::<*mut c_void>(function, &args) as usize as u64),
377        }
378    }
379}
380
381fn value_to_v8<'s>(
382    scope: &mut v8::PinScope<'s, '_>,
383    value: &Value,
384    result: Kind,
385) -> Option<v8::Local<'s, v8::Value>> {
386    Some(match (value, result) {
387        (Value::Void, _) => v8::undefined(scope).into(),
388        (Value::Bool(value), _) => v8::Boolean::new(scope, *value).into(),
389        (Value::I64(value), Kind::I64 | Kind::Isize) => {
390            v8::BigInt::new_from_i64(scope, *value).into()
391        }
392        (Value::U64(value), Kind::U64 | Kind::Usize) => {
393            v8::BigInt::new_from_u64(scope, *value).into()
394        }
395        (Value::I64(value), _) => v8::Number::new(scope, *value as f64).into(),
396        (Value::U64(value), _) => v8::Number::new(scope, *value as f64).into(),
397        (Value::F32(value), _) => v8::Number::new(scope, *value as f64).into(),
398        (Value::F64(value), _) => v8::Number::new(scope, *value).into(),
399        (Value::Ptr(value), _) => v8::BigInt::new_from_u64(scope, *value).into(),
400    })
401}
402
403fn parse_def(wire: DefWire) -> Result<(String, Def), String> {
404    let params = wire
405        .params
406        .iter()
407        .map(|name| Kind::parse(name))
408        .collect::<Result<Vec<_>, _>>()?;
409    if params.contains(&Kind::Void) {
410        return Err("void is not a parameter type".into());
411    }
412    let result = Kind::parse(&wire.result)?;
413    if result == Kind::Buffer {
414        return Err("buffer is not a result type".into());
415    }
416    let _ = wire.nonblocking;
417    Ok((wire.symbol, Def { params, result }))
418}
419
420fn resolve_library(caps: &Capabilities, specifier: &str) -> Result<libloading::Library, CallError> {
421    // A bare name goes through the loader's search path; only unrestricted
422    // ffi authority covers names whose resolution we cannot check beforehand.
423    let bare = !specifier.contains(['/', '\\']) && Path::new(specifier).components().count() == 1;
424    if let Ok(url) = url::Url::parse(specifier)
425        && url.scheme() == "file"
426    {
427        let path = url
428            .to_file_path()
429            .map_err(|_| CallError::error("Invalid file URL"))?;
430        return load_checked(caps, &path);
431    }
432    if bare && !specifier.starts_with('.') {
433        if !caps.unrestricted_ffi() {
434            return Err(CallError::error(format!(
435                "PermissionDenied: ffi {specifier}"
436            )));
437        }
438        // SAFETY: loading a library runs its initializers; unrestricted ffi
439        // authority is exactly permission to run native code in-process.
440        return unsafe { libloading::Library::new(specifier) }
441            .map_err(|error| CallError::error(error.to_string()));
442    }
443    let path = if Path::new(specifier).is_absolute() {
444        PathBuf::from(specifier)
445    } else {
446        caps.cwd().join(specifier)
447    };
448    load_checked(caps, &path)
449}
450
451fn load_checked(caps: &Capabilities, path: &Path) -> Result<libloading::Library, CallError> {
452    let target =
453        PathTarget::for_executable(path).map_err(|error| CallError::error(error.to_string()))?;
454    caps.check_ffi(&target)
455        .map_err(|error| CallError::error(error.to_string()))?;
456    // SAFETY: see resolve_library.
457    unsafe { libloading::Library::new(target.as_path()) }
458        .map_err(|error| CallError::error(error.to_string()))
459}
460
461fn id<T>(scope: &mut v8::PinScope, value: v8::Local<v8::Value>) -> CallResult<T>
462where
463    T: TryFrom<u64>,
464{
465    let number = number(scope, value).map_err(|_| CallError::type_("Expected an id"))?;
466    T::try_from(number as u64).map_err(|_| CallError::type_("Id is out of range"))
467}
468
469fn snapshot_args<'s>(
470    scope: &mut v8::PinScope<'s, '_>,
471    values: &v8::Local<'s, v8::Array>,
472) -> CallResult<Vec<v8::Local<'s, v8::Value>>> {
473    (0..values.length())
474        .map(|index| values.get_index(scope, index))
475        .collect::<Option<Vec<_>>>()
476        .ok_or_else(|| CallError::type_("Could not read the arguments array"))
477}
478
479fn authorize_any(registry: &Registry) -> CallResult<()> {
480    registry
481        .caps
482        .check_ffi_any()
483        .map_err(|error| CallError::error(error.to_string()))
484}
485
486pub fn init(caps: Arc<Capabilities>) -> Extension {
487    let registry = Rc::new(Registry {
488        caps,
489        libraries: RefCell::new(HashMap::new()),
490        next_library: Cell::new(1),
491        receivers: RefCell::new(HashMap::new()),
492        targets: RefCell::new(HashMap::new()),
493        completed: Rc::new(RefCell::new(HashMap::new())),
494        next_call: Cell::new(1),
495    });
496
497    let dlopen_registry = registry.clone();
498    let close_registry = registry.clone();
499    let call_registry = registry.clone();
500    let begin_registry = registry.clone();
501    let await_registry = registry.clone();
502    let finish_registry = registry.clone();
503    let ptr_registry = registry.clone();
504    let address_registry = registry.clone();
505    let read_registry = registry.clone();
506    let write_registry = registry.clone();
507    let bytes_registry = registry.clone();
508    let write_bytes_registry = registry.clone();
509    let cstring_registry = registry.clone();
510
511    Extension {
512        name: "ext:ffi",
513        dependencies: &[],
514        ops: vec![
515            Op::raw("op_ffi_dlopen", move |scope, args| {
516                let specifier = args.get(0).to_rust_string_lossy(scope);
517                let wires: Vec<DefWire> = match avril_core::serde_v8::from_v8(scope, args.get(1)) {
518                    Ok(wires) => wires,
519                    Err(error) => {
520                        return throw(scope, &CallError::type_(error.to_string()));
521                    }
522                };
523                let library = match resolve_library(&dlopen_registry.caps, &specifier) {
524                    Ok(library) => library,
525                    Err(error) => return throw(scope, &error),
526                };
527                let mut symbols = Vec::new();
528                for wire in wires {
529                    let (name, def) = match parse_def(wire) {
530                        Ok(parsed) => parsed,
531                        Err(error) => return throw(scope, &CallError::type_(error)),
532                    };
533                    // SAFETY: we only record the symbol's address; it is
534                    // invoked later through libffi with def's signature.
535                    let address = unsafe {
536                        match library.get::<*const c_void>(name.as_bytes()) {
537                            Ok(symbol) => match symbol.try_as_raw_ptr() {
538                                Some(address) if !address.is_null() => address as usize,
539                                Some(_) => {
540                                    return throw(
541                                        scope,
542                                        &CallError::error("Symbol resolved to a null address"),
543                                    );
544                                }
545                                None => {
546                                    return throw(
547                                        scope,
548                                        &CallError::error("Could not resolve symbol"),
549                                    );
550                                }
551                            },
552                            Err(error) => {
553                                return throw(scope, &CallError::error(error.to_string()));
554                            }
555                        }
556                    };
557                    symbols.push(SymbolState { address, def });
558                }
559                let library_id = dlopen_registry.next_library.get();
560                if library_id > MAX_ID {
561                    return throw(scope, &CallError::error("FFI library IDs exhausted"));
562                }
563                dlopen_registry.next_library.set(library_id + 1);
564                dlopen_registry.libraries.borrow_mut().insert(
565                    library_id,
566                    Arc::new(LibraryState { _library: library, symbols }),
567                );
568                Some(v8::Number::new(scope, library_id as f64).into())
569            }),
570            Op::raw("op_ffi_close", move |scope, args| {
571                if let Ok(library_id) = id::<u64>(scope, args.get(0)) {
572                    close_registry.libraries.borrow_mut().remove(&library_id);
573                }
574                Some(v8::undefined(scope).into())
575            }),
576            Op::raw("op_ffi_call", move |scope, args| {
577                let result = (|| -> CallResult<v8::Local<v8::Value>> {
578                    let library_id = id::<u64>(scope, args.get(0))?;
579                    let index = id::<usize>(scope, args.get(1))?;
580                    let library = call_registry
581                        .libraries
582                        .borrow()
583                        .get(&library_id)
584                        .cloned()
585                        .ok_or_else(|| CallError::error("Library is closed"))?;
586                    let symbol = library
587                        .symbols
588                        .get(index)
589                        .ok_or_else(|| CallError::error("Unknown symbol"))?;
590                    let values = v8::Local::<v8::Array>::try_from(args.get(2))
591                        .map_err(|_| CallError::type_("Expected an arguments array"))?;
592                    if values.length() as usize != symbol.def.params.len() {
593                        return Err(CallError::type_("Wrong number of arguments"));
594                    }
595                    // Read every element before converting any: an accessor
596                    // could run JS, and no view address may be captured yet.
597                    let elements = snapshot_args(scope, &values)?;
598                    let mut buffers = Vec::new();
599                    let mut slots = Vec::new();
600                    for (value, kind) in elements.iter().zip(&symbol.def.params) {
601                        slots.push(convert_arg(scope, *value, *kind, false, &mut buffers)?);
602                    }
603                    let value = call_native(symbol.address, &symbol.def, &mut slots);
604                    value_to_v8(scope, &value, symbol.def.result)
605                        .ok_or_else(|| CallError::error("Could not convert the result"))
606                })();
607                match result {
608                    Ok(value) => Some(value),
609                    Err(error) => throw(scope, &error),
610                }
611            }),
612            Op::raw("op_ffi_call_ptr", move |scope, args| {
613                let result = (|| -> CallResult<v8::Local<v8::Value>> {
614                    authorize_any(&ptr_registry)?;
615                    let address = bigint_u64(scope, args.get(0))? as usize;
616                    let wire: DefWire = avril_core::serde_v8::from_v8(scope, args.get(1))
617                        .map_err(|error| CallError::type_(error.to_string()))?;
618                    let (_, def) = parse_def(wire).map_err(CallError::type_)?;
619                    let values = v8::Local::<v8::Array>::try_from(args.get(2))
620                        .map_err(|_| CallError::type_("Expected an arguments array"))?;
621                    if values.length() as usize != def.params.len() {
622                        return Err(CallError::type_("Wrong number of arguments"));
623                    }
624                    let elements = snapshot_args(scope, &values)?;
625                    let mut buffers = Vec::new();
626                    let mut slots = Vec::new();
627                    for (value, kind) in elements.iter().zip(&def.params) {
628                        slots.push(convert_arg(scope, *value, *kind, false, &mut buffers)?);
629                    }
630                    let value = call_native(address, &def, &mut slots);
631                    value_to_v8(scope, &value, def.result)
632                        .ok_or_else(|| CallError::error("Could not convert the result"))
633                })();
634                match result {
635                    Ok(value) => Some(value),
636                    Err(error) => throw(scope, &error),
637                }
638            }),
639            Op::raw("op_ffi_call_async_begin", move |scope, args| {
640                let result = (|| -> CallResult<u64> {
641                    let library_id = id::<u64>(scope, args.get(0))?;
642                    let index = id::<usize>(scope, args.get(1))?;
643                    let library = begin_registry
644                        .libraries
645                        .borrow()
646                        .get(&library_id)
647                        .cloned()
648                        .ok_or_else(|| CallError::error("Library is closed"))?;
649                    let symbol = library
650                        .symbols
651                        .get(index)
652                        .ok_or_else(|| CallError::error("Unknown symbol"))?;
653                    let values = v8::Local::<v8::Array>::try_from(args.get(2))
654                        .map_err(|_| CallError::type_("Expected an arguments array"))?;
655                    if values.length() as usize != symbol.def.params.len() {
656                        return Err(CallError::type_("Wrong number of arguments"));
657                    }
658                    let elements = snapshot_args(scope, &values)?;
659                    let mut buffers: Vec<Vec<u8>> = Vec::new();
660                    let mut targets = Vec::new();
661                    let mut originals = Vec::new();
662                    let mut slots = Vec::new();
663                    for (value, kind) in elements.iter().zip(&symbol.def.params) {
664                        if *kind == Kind::Buffer {
665                            targets.push(v8::Global::new(scope, *value));
666                        }
667                        slots.push(convert_arg(scope, *value, *kind, true, &mut buffers)?);
668                        if *kind == Kind::Buffer {
669                            originals.push(buffers.last().unwrap().clone());
670                        }
671                    }
672                    let call_id = begin_registry.next_call.get();
673                    if call_id > MAX_ID {
674                        return Err(CallError::error("FFI call IDs exhausted"));
675                    }
676                    begin_registry.next_call.set(call_id + 1);
677                    let (sender, receiver) = oneshot::channel();
678                    begin_registry
679                        .receivers
680                        .borrow_mut()
681                        .insert(call_id, receiver);
682                    begin_registry
683                        .targets
684                        .borrow_mut()
685                        .insert(call_id, PendingTargets { targets, originals });
686                    let address = symbol.address;
687                    let def = symbol.def.clone();
688                    let library = library.clone();
689                    let spawned =
690                        std::thread::Builder::new()
691                            .name("avril:ffi".into())
692                            .spawn(move || {
693                                // Keeps the library loaded for the call's duration.
694                                let _library = &library;
695                                let mut slots = slots;
696                                let outcome =
697                                    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
698                                        (call_native(address, &def, &mut slots), buffers)
699                                    }));
700                                let result = match outcome {
701                                    Ok((value, buffers)) => Ok((value, buffers)),
702                                    Err(_) => Err("Native call panicked".to_string()),
703                                };
704                                let _ = sender.send(result);
705                            });
706                    if let Err(error) = spawned {
707                        begin_registry.receivers.borrow_mut().remove(&call_id);
708                        begin_registry.targets.borrow_mut().remove(&call_id);
709                        return Err(CallError::error(error.to_string()));
710                    }
711                    Ok(call_id)
712                })();
713                match result {
714                    Ok(call_id) => Some(v8::Number::new(scope, call_id as f64).into()),
715                    Err(error) => throw(scope, &error),
716                }
717            }),
718            Op::asynchronous("op_ffi_await", move |call_id: u64| {
719                let receiver = await_registry.receivers.borrow_mut().remove(&call_id);
720                let pending = await_registry.targets.borrow_mut().remove(&call_id);
721                let (targets, originals) = match pending {
722                    Some(pending) => (pending.targets, pending.originals),
723                    None => (Vec::new(), Vec::new()),
724                };
725                let completed = await_registry.completed.clone();
726                async move {
727                    let outcome = match receiver {
728                        Some(receiver) => match receiver.await {
729                            Ok(Ok((value, buffers))) => Ok((value, buffers)),
730                            Ok(Err(error)) => Err(error),
731                            Err(_) => Err("Native call was dropped".to_string()),
732                        },
733                        None => Err("Unknown call".to_string()),
734                    };
735                    let (outcome, buffers) = match outcome {
736                        Ok((value, buffers)) => (Ok(value), buffers),
737                        Err(error) => (Err(error), Vec::new()),
738                    };
739                    completed.borrow_mut().insert(
740                        call_id,
741                        Completed {
742                            outcome,
743                            buffers,
744                            originals,
745                            targets,
746                        },
747                    );
748                    Ok::<_, std::io::Error>(call_id)
749                }
750            }),
751            Op::raw("op_ffi_call_async_finish", move |scope, args| {
752                let result = (|| -> CallResult<v8::Local<v8::Value>> {
753                    let call_id = id::<u64>(scope, args.get(0))?;
754                    let result_kind = Kind::parse(&args.get(1).to_rust_string_lossy(scope))
755                        .map_err(CallError::type_)?;
756                    let completed = finish_registry
757                        .completed
758                        .borrow_mut()
759                        .remove(&call_id)
760                        .ok_or_else(|| CallError::error("Call is not complete"))?;
761                    for ((target, bytes), original) in completed
762                        .targets
763                        .iter()
764                        .zip(&completed.buffers)
765                        .zip(&completed.originals)
766                    {
767                        let target = v8::Local::new(scope, target);
768                        let (pointer, length) = view_bytes(scope, target)?;
769                        if length != bytes.len() {
770                            return Err(CallError::error(
771                                "Buffer changed size during a nonblocking call",
772                            ));
773                        }
774                        // Only bytes the native call changed are written
775                        // back, so JS writes to other bytes during the call
776                        // are not clobbered. SAFETY: pointer/length describe
777                        // the target view, kept alive via this Global.
778                        for (index, (byte, before)) in bytes.iter().zip(original).enumerate() {
779                            if byte != before {
780                                unsafe { *pointer.add(index) = *byte };
781                            }
782                        }
783                    }
784                    let value = completed.outcome.map_err(CallError::error)?;
785                    value_to_v8(scope, &value, result_kind)
786                        .ok_or_else(|| CallError::error("Could not convert the result"))
787                })();
788                match result {
789                    Ok(value) => Some(value),
790                    Err(error) => throw(scope, &error),
791                }
792            }),
793            Op::raw("op_ffi_address_of", move |scope, args| {
794                if let Err(error) = authorize_any(&address_registry) {
795                    return throw(scope, &error);
796                }
797                match view_bytes(scope, args.get(0)) {
798                    Ok((pointer, _)) => {
799                        Some(v8::BigInt::new_from_u64(scope, pointer as usize as u64).into())
800                    }
801                    Err(error) => throw(scope, &error),
802                }
803            }),
804            Op::raw("op_ffi_read", move |scope, args| {
805                let result = (|| -> CallResult<v8::Local<v8::Value>> {
806                    authorize_any(&read_registry)?;
807                    let address = bigint_u64(scope, args.get(0))? as usize;
808                    let kind = Kind::parse(&args.get(1).to_rust_string_lossy(scope))
809                        .map_err(CallError::type_)?;
810                    let pointer = address as *const u8;
811                    // SAFETY: the caller asserts address is readable for kind;
812                    // that is the documented unsafety of pointer reads.
813                    let value = unsafe {
814                        match kind {
815                            Kind::Bool => Value::Bool(*pointer != 0),
816                            Kind::I8 => Value::I64(pointer.cast::<i8>().read_unaligned() as i64),
817                            Kind::U8 => Value::U64(*pointer as u64),
818                            Kind::I16 => Value::I64(pointer.cast::<i16>().read_unaligned() as i64),
819                            Kind::U16 => Value::U64(pointer.cast::<u16>().read_unaligned() as u64),
820                            Kind::I32 => Value::I64(pointer.cast::<i32>().read_unaligned() as i64),
821                            Kind::U32 => Value::U64(pointer.cast::<u32>().read_unaligned() as u64),
822                            Kind::I64 | Kind::Isize => {
823                                Value::I64(pointer.cast::<i64>().read_unaligned())
824                            }
825                            Kind::U64 | Kind::Usize => {
826                                Value::U64(pointer.cast::<u64>().read_unaligned())
827                            }
828                            Kind::F32 => Value::F32(pointer.cast::<f32>().read_unaligned()),
829                            Kind::F64 => Value::F64(pointer.cast::<f64>().read_unaligned()),
830                            Kind::Pointer | Kind::Function => {
831                                Value::Ptr(pointer.cast::<usize>().read_unaligned() as u64)
832                            }
833                            _ => {
834                                return Err(CallError::type_(
835                                    "Type cannot be read through a pointer",
836                                ));
837                            }
838                        }
839                    };
840                    value_to_v8(scope, &value, kind)
841                        .ok_or_else(|| CallError::error("Could not convert the result"))
842                })();
843                match result {
844                    Ok(value) => Some(value),
845                    Err(error) => throw(scope, &error),
846                }
847            }),
848            Op::raw("op_ffi_write", move |scope, args| {
849                let result = (|| -> CallResult<v8::Local<v8::Value>> {
850                    authorize_any(&write_registry)?;
851                    let address = bigint_u64(scope, args.get(0))? as usize;
852                    let kind = Kind::parse(&args.get(1).to_rust_string_lossy(scope))
853                        .map_err(CallError::type_)?;
854                    let mut buffers = Vec::new();
855                    let slot = convert_arg(scope, args.get(2), kind, false, &mut buffers)?;
856                    let size = match kind {
857                        Kind::Bool | Kind::I8 | Kind::U8 => 1,
858                        Kind::I16 | Kind::U16 => 2,
859                        Kind::I32 | Kind::U32 | Kind::F32 => 4,
860                        Kind::I64
861                        | Kind::U64
862                        | Kind::Isize
863                        | Kind::Usize
864                        | Kind::F64
865                        | Kind::Pointer
866                        | Kind::Function => 8,
867                        _ => {
868                            return Err(CallError::type_(
869                                "Type cannot be written through a pointer",
870                            ));
871                        }
872                    };
873                    // SAFETY: as op_ffi_read, with the caller's writable assert.
874                    unsafe {
875                        std::ptr::copy_nonoverlapping(
876                            (&slot.0 as *const u64).cast::<u8>(),
877                            address as *mut u8,
878                            size,
879                        );
880                    }
881                    Ok(v8::undefined(scope).into())
882                })();
883                match result {
884                    Ok(value) => Some(value),
885                    Err(error) => throw(scope, &error),
886                }
887            }),
888            Op::raw("op_ffi_read_bytes", move |scope, args| {
889                let result = (|| -> CallResult<v8::Local<v8::Value>> {
890                    authorize_any(&bytes_registry)?;
891                    let address = bigint_u64(scope, args.get(0))? as usize;
892                    let length = id::<usize>(scope, args.get(1))?;
893                    let bytes = if length == 0 {
894                        Vec::new()
895                    } else {
896                        // SAFETY: the caller asserts address..address+length is readable.
897                        unsafe { std::slice::from_raw_parts(address as *const u8, length) }.to_vec()
898                    };
899                    avril_core::serde_v8::to_v8(scope, ToJsBuffer::from(bytes))
900                        .map_err(|error| CallError::error(error.to_string()))
901                })();
902                match result {
903                    Ok(value) => Some(value),
904                    Err(error) => throw(scope, &error),
905                }
906            }),
907            Op::raw("op_ffi_write_bytes", move |scope, args| {
908                let result = (|| -> CallResult<v8::Local<v8::Value>> {
909                    authorize_any(&write_bytes_registry)?;
910                    let address = bigint_u64(scope, args.get(0))? as usize;
911                    let (pointer, length) = view_bytes(scope, args.get(1))?;
912                    // SAFETY: source is a live view; the caller asserts the
913                    // destination is writable for length bytes. Ranges may
914                    // overlap, hence ptr::copy.
915                    if length > 0 {
916                        unsafe { std::ptr::copy(pointer, address as *mut u8, length) };
917                    }
918                    Ok(v8::undefined(scope).into())
919                })();
920                match result {
921                    Ok(value) => Some(value),
922                    Err(error) => throw(scope, &error),
923                }
924            }),
925            Op::raw("op_ffi_read_cstring", move |scope, args| {
926                let result = (|| -> CallResult<v8::Local<v8::Value>> {
927                    authorize_any(&cstring_registry)?;
928                    let address = bigint_u64(scope, args.get(0))? as usize;
929                    let max = id::<usize>(scope, args.get(1))?;
930                    let mut bytes = Vec::new();
931                    for offset in 0..max {
932                        // SAFETY: the caller asserts a NUL-terminated string
933                        // at address; we stop at max bytes regardless.
934                        let byte = unsafe { *(address as *const u8).add(offset) };
935                        if byte == 0 {
936                            break;
937                        }
938                        bytes.push(byte);
939                    }
940                    avril_core::serde_v8::to_v8(scope, ToJsBuffer::from(bytes))
941                        .map_err(|error| CallError::error(error.to_string()))
942                })();
943                match result {
944                    Ok(value) => Some(value),
945                    Err(error) => throw(scope, &error),
946                }
947            }),
948        ],
949        modules: const { &[PUBLIC_MODULES[0].module] },
950        entry_point: Some("ext:ffi/main.js"),
951    }
952}
953
954pub const PUBLIC_MODULES: &[PublicModule] =
955    &[avril_core::public_module!("avril:ffi", "ext:ffi", "main")];