summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi/src/ffi/rustbuffer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi/src/ffi/rustbuffer.rs')
-rw-r--r--third_party/rust/uniffi/src/ffi/rustbuffer.rs353
1 files changed, 353 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/src/ffi/rustbuffer.rs b/third_party/rust/uniffi/src/ffi/rustbuffer.rs
new file mode 100644
index 0000000000..63af586fb6
--- /dev/null
+++ b/third_party/rust/uniffi/src/ffi/rustbuffer.rs
@@ -0,0 +1,353 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::ffi::{call_with_output, ForeignBytes, RustCallStatus};
+
+/// Support for passing an allocated-by-Rust buffer of bytes over the FFI.
+///
+/// We can pass a `Vec<u8>` to foreign language code by decomposing it into
+/// its raw parts (buffer pointer, length, and capacity) and passing those
+/// around as a struct. Naturally, this can be tremendously unsafe! So here
+/// are the details:
+///
+/// * `RustBuffer` structs must only ever be constructed from a `Vec<u8>`,
+/// either explicitly via `RustBuffer::from_vec` or indirectly by calling
+/// one of the `RustBuffer::new*` constructors.
+///
+/// * `RustBuffer` structs do not implement `Drop`, since they are intended
+/// to be passed to foreign-language code outside of the control of Rust's
+/// ownership system. To avoid memory leaks they *must* passed back into
+/// Rust and either explicitly destroyed using `RustBuffer::destroy`, or
+/// converted back to a `Vec<u8>` using `RustBuffer::destroy_into_vec`
+/// (which will then be dropped via Rust's usual ownership-tracking system).
+///
+/// Foreign-language code should not construct `RustBuffer` structs other than
+/// by receiving them from a call into the Rust code, and should not modify them
+/// apart from the following safe operations:
+///
+/// * Writing bytes into the buffer pointed to by `data`, without writing
+/// beyond the indicated `capacity`.
+///
+/// * Adjusting the `len` property to indicate the amount of data written,
+/// while ensuring that 0 <= `len` <= `capacity`.
+///
+/// * As a special case, constructing a `RustBuffer` with zero capacity, zero
+/// length, and a null `data` pointer to indicate an empty buffer.
+///
+/// In particular, it is not safe for foreign-language code to construct a `RustBuffer`
+/// that points to its own allocated memory; use the `ForeignBytes` struct to
+/// pass a view of foreign-owned memory in to Rust code.
+///
+/// Implementation note: all the fields of this struct are private, so you can't
+/// manually construct instances that don't come from a `Vec<u8>`. If you've got
+/// a `RustBuffer` then it either came from a public constructor (all of which
+/// are safe) or it came from foreign-language code (which should have in turn
+/// received it by calling some Rust function, and should be respecting the
+/// invariants listed above).
+///
+/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified
+/// to retain unallocated capacity rather than truncating to the occupied length.
+#[repr(C)]
+pub struct RustBuffer {
+ /// The allocated capacity of the underlying `Vec<u8>`.
+ /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
+ capacity: i32,
+ /// The occupied length of the underlying `Vec<u8>`.
+ /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
+ len: i32,
+ /// The pointer to the allocated buffer of the `Vec<u8>`.
+ data: *mut u8,
+}
+
+impl RustBuffer {
+ /// Creates an empty `RustBuffer`.
+ ///
+ /// The buffer will not allocate.
+ /// The resulting vector will not be automatically dropped; you must
+ /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
+ pub fn new() -> Self {
+ Self::from_vec(Vec::new())
+ }
+
+ /// Creates a `RustBuffer` from its constituent fields.
+ ///
+ /// This is intended mainly as an internal convenience function and should not
+ /// be used outside of this module.
+ ///
+ /// # Safety
+ ///
+ /// You must ensure that the raw parts uphold the documented invariants of this class.
+ pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self {
+ Self {
+ capacity,
+ len,
+ data,
+ }
+ }
+
+ /// Get the current length of the buffer, as a `usize`.
+ ///
+ /// This is mostly a helper function to convert the `i32` length field
+ /// into a `usize`, which is what Rust code usually expects.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called on an invalid struct obtained from foreign-language code,
+ /// in which the `len` field is negative.
+ pub fn len(&self) -> usize {
+ self.len
+ .try_into()
+ .expect("buffer length negative or overflowed")
+ }
+
+ /// Returns true if the length of the buffer is 0.
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ /// Creates a `RustBuffer` zero-filed to the requested size.
+ ///
+ /// The resulting vector will not be automatically dropped; you must
+ /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the requested size is too large to fit in an `i32`, and
+ /// hence would risk incompatibility with some foreign-language code.
+ pub fn new_with_size(size: usize) -> Self {
+ assert!(
+ size < i32::MAX as usize,
+ "RustBuffer requested size too large"
+ );
+ Self::from_vec(vec![0u8; size])
+ }
+
+ /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`.
+ ///
+ /// The resulting vector will not be automatically dropped; you must
+ /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the vector's length or capacity are too large to fit in an `i32`,
+ /// and hence would risk incompatibility with some foreign-language code.
+ pub fn from_vec(v: Vec<u8>) -> Self {
+ let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32.");
+ let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32.");
+ let mut v = std::mem::ManuallyDrop::new(v);
+ unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) }
+ }
+
+ /// Converts this `RustBuffer` back into an owned `Vec<u8>`.
+ ///
+ /// This restores ownership of the underlying buffer to Rust, meaning it will
+ /// be dropped when the `Vec<u8>` is dropped. The `RustBuffer` *must* have been
+ /// previously obtained from a valid `Vec<u8>` owned by this Rust code.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called on an invalid struct obtained from foreign-language code,
+ /// which does not respect the invairiants on `len` and `capacity`.
+ pub fn destroy_into_vec(self) -> Vec<u8> {
+ // Rust will never give us a null `data` pointer for a `Vec`, but
+ // foreign-language code can use it to cheaply pass an empty buffer.
+ if self.data.is_null() {
+ assert!(self.capacity == 0, "null RustBuffer had non-zero capacity");
+ assert!(self.len == 0, "null RustBuffer had non-zero length");
+ vec![]
+ } else {
+ let capacity: usize = self
+ .capacity
+ .try_into()
+ .expect("buffer capacity negative or overflowed");
+ let len: usize = self
+ .len
+ .try_into()
+ .expect("buffer length negative or overflowed");
+ assert!(len <= capacity, "RustBuffer length exceeds capacity");
+ unsafe { Vec::from_raw_parts(self.data, len, capacity) }
+ }
+ }
+
+ /// Reclaim memory stored in this `RustBuffer`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called on an invalid struct obtained from foreign-language code,
+ /// which does not respect the invairiants on `len` and `capacity`.
+ pub fn destroy(self) {
+ drop(self.destroy_into_vec());
+ }
+}
+
+impl Default for RustBuffer {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+// extern "C" functions for the RustBuffer functionality.
+//
+// These are used in two ways:
+// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer
+// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings.
+//
+// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL
+// hash This creates a separate set of functions for each UniFFIed component, which is needed
+// in the case where we create multiple dylib artifacts since each dylib will have its own
+// allocator.
+
+/// This helper allocates a new byte buffer owned by the Rust code, and returns it
+/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually
+/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined
+/// below, or by passing ownership of the buffer back into Rust code.
+#[no_mangle]
+pub extern "C" fn uniffi_rustbuffer_alloc(
+ size: i32,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ RustBuffer::new_with_size(size.max(0) as usize)
+ })
+}
+
+/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned
+/// by the Rust code, and returns it as a `RustBuffer` struct. Callers must eventually
+/// free the resulting buffer, either by explicitly calling the destructor defined below,
+/// or by passing ownership of the buffer back into Rust code.
+///
+/// # Safety
+/// This function will dereference a provided pointer in order to copy bytes from it, so
+/// make sure the `ForeignBytes` struct contains a valid pointer and length.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_from_bytes(
+ bytes: ForeignBytes,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ let bytes = bytes.as_slice();
+ RustBuffer::from_vec(bytes.to_vec())
+ })
+}
+
+/// Free a byte buffer that had previously been passed to the foreign language code.
+///
+/// # Safety
+/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
+/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
+/// corrupting the allocator state.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
+ call_with_output(call_status, || RustBuffer::destroy(buf))
+}
+
+/// Reserve additional capacity in a byte buffer that had previously been passed to the
+/// foreign language code.
+///
+/// The first argument *must* be a uniquely-owned `RustBuffer` previously
+/// obtained from a call into the Rust code that returned a buffer. Its underlying data pointer
+/// will be reallocated if necessary and returned in a new `RustBuffer` struct.
+///
+/// The second argument must be the minimum number of *additional* bytes to reserve
+/// capacity for in the buffer; it is likely to reserve additional capacity in practice
+/// due to amortized growth strategy of Rust vectors.
+///
+/// # Safety
+/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
+/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
+/// corrupting the allocator state.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_reserve(
+ buf: RustBuffer,
+ additional: i32,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ let additional: usize = additional
+ .try_into()
+ .expect("additional buffer length negative or overflowed");
+ let mut v = buf.destroy_into_vec();
+ v.reserve(additional);
+ RustBuffer::from_vec(v)
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[test]
+ fn test_rustbuffer_from_vec() {
+ let rbuf = RustBuffer::from_vec(vec![1u8, 2, 3]);
+ assert_eq!(rbuf.len(), 3);
+ assert_eq!(rbuf.destroy_into_vec(), vec![1u8, 2, 3]);
+ }
+
+ #[test]
+ fn test_rustbuffer_empty() {
+ let rbuf = RustBuffer::new();
+ assert_eq!(rbuf.len(), 0);
+ // Rust will never give us a null pointer, even for an empty buffer.
+ assert!(!rbuf.data.is_null());
+ assert_eq!(rbuf.destroy_into_vec(), Vec::<u8>::new());
+ }
+
+ #[test]
+ fn test_rustbuffer_new_with_size() {
+ let rbuf = RustBuffer::new_with_size(5);
+ assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8, 0, 0, 0, 0]);
+
+ let rbuf = RustBuffer::new_with_size(0);
+ assert!(!rbuf.data.is_null());
+ assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
+ }
+
+ #[test]
+ fn test_rustbuffer_null_means_empty() {
+ // This is how foreign-language code might cheaply indicate an empty buffer.
+ let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 0) };
+ assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_null_must_have_no_capacity() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 1) };
+ rbuf.destroy_into_vec();
+ }
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_null_must_have_zero_length() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 12, 0) };
+ rbuf.destroy_into_vec();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_provided_capacity_must_be_non_negative() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let mut v = vec![0u8, 1, 2];
+ let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) };
+ rbuf.destroy_into_vec();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_provided_len_must_be_non_negative() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let mut v = vec![0u8, 1, 2];
+ let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) };
+ rbuf.destroy_into_vec();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_provided_len_must_not_exceed_capacity() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let mut v = vec![0u8, 1, 2];
+ let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, 2) };
+ rbuf.destroy_into_vec();
+ }
+}