Skip to main content

code_native/
borrowed.rs

1//! Optional, synchronous metadata-only dispatch (ABI 2, extension version 1).
2//! No pointer obtained here may be retained after dispatch or passed to a
3//! CodeValue constructor/release function. Hosts may decline this extension;
4//! modules must retain their ordinary code_module_dispatch implementation.
5use core::{ffi::c_void, marker::PhantomData};
6#[path = "../vendor/borrowed_abi.rs"]
7mod abi;
8pub use abi::{CodeBorrowedApi, CodeValueInfo};
9
10/// A read-only view, valid only for the synchronous call that supplied it.
11#[derive(Clone, Copy)]
12pub struct BorrowedValue<'a> {
13    api: &'a CodeBorrowedApi,
14    value: *const c_void,
15    _borrow: PhantomData<&'a c_void>,
16}
17
18impl<'a> BorrowedValue<'a> {
19    /// # Safety
20    /// The API must have a valid version prefix. For version 1, the full API,
21    /// value, all returned fields/bytes and callbacks must remain valid and
22    /// immutable for 'a, within this dispatch, on the calling thread. String
23    /// bytes must be valid UTF-8, as required by CodeValueInfo's contract.
24    pub unsafe fn from_raw(api: *const CodeBorrowedApi, value: *const c_void) -> Option<Self> {
25        if api.is_null() || value.is_null() {
26            return None;
27        }
28        // Read only the stable prefix before assuming this version's layout.
29        if api.cast::<u32>().read() != 1 {
30            return None;
31        }
32        let api = &*api;
33        Some(Self {
34            api,
35            value,
36            _borrow: PhantomData,
37        })
38    }
39
40    pub fn info(self) -> CodeValueInfo {
41        // SAFETY: guaranteed by from_raw, including pointers returned by field.
42        unsafe { (self.api.inspect)(self.value) }
43    }
44
45    pub fn field(self, key: &str) -> Option<Self> {
46        // SAFETY: callback borrows the key only during this call.
47        let value = unsafe { (self.api.field)(self.value, key.as_ptr(), key.len() as u64) };
48        if value.is_null() {
49            None
50        } else {
51            Some(Self { value, ..self })
52        }
53    }
54
55    pub fn text(self) -> Option<&'a str> {
56        let info = self.info();
57        if info.tag != 1 {
58            return None;
59        }
60        if info.length == 0 {
61            return Some("");
62        }
63        // SAFETY: from_raw's contract covers the host-owned byte slice.
64        let bytes = unsafe { core::slice::from_raw_parts(info.bytes, info.length as usize) };
65        // SAFETY: the view explicitly promises UTF-8, unlike raw CodeValue
66        // C strings. Revalidating a Rust host's text here defeats the borrow.
67        Some(unsafe { core::str::from_utf8_unchecked(bytes) })
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    unsafe extern "C" fn inspect(_: *const c_void) -> CodeValueInfo {
75        CodeValueInfo {
76            tag: 3,
77            length: 0,
78            bytes: core::ptr::null(),
79        }
80    }
81    unsafe extern "C" fn field(_: *const c_void, _: *const u8, _: u64) -> *const c_void {
82        core::ptr::null()
83    }
84    #[test]
85    fn unsupported_versions_and_missing_views_decline_before_reading_values() {
86        let version = 9_u32;
87        // SAFETY: only the version is read; the opaque pointer is never used.
88        unsafe {
89            assert!(BorrowedValue::from_raw(
90                (&version as *const u32).cast(),
91                core::ptr::NonNull::<u8>::dangling().as_ptr().cast()
92            )
93            .is_none());
94            assert!(BorrowedValue::from_raw(core::ptr::null(), core::ptr::null()).is_none());
95            let api = CodeBorrowedApi {
96                version: 1,
97                inspect,
98                field,
99            };
100            let opaque = 0_u8;
101            let view = BorrowedValue::from_raw(&api, (&opaque as *const u8).cast()).unwrap();
102            assert_eq!(view.info().tag, 3);
103            assert!(view.field("").is_none());
104            assert!(view.text().is_none());
105        }
106    }
107}