1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
use holochain_wasmer_common::*;
use std::mem;

/// Attempt to extract the length at the given guest_ptr.
//. Note that the guest_ptr could point at garbage and the "length prefix" would be garbage and
//. then some arbitrary memory would be referenced so not erroring does not imply safety.
pub fn length_prefix_at_guest_ptr(guest_ptr: GuestPtr) -> Len {
    let len_bytes: &[u8] =
        unsafe { std::slice::from_raw_parts(guest_ptr as *const u8, std::mem::size_of::<Len>()) };
    u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]])
}

#[no_mangle]
/// Allocate a length __plus a length prefix__ in bytes that won't be dropped by the allocator.
/// Return the pointer to it so a length prefix + bytes can be written to the allocation.
pub extern "C" fn __allocate(len: Len) -> GuestPtr {
    let dummy: Vec<u8> = Vec::with_capacity((len + std::mem::size_of::<Len>() as Len) as usize);
    let ptr = dummy.as_ptr() as GuestPtr;
    let _ = mem::ManuallyDrop::new(dummy);
    ptr
}

/// Free a length-prefixed allocation.
/// Needed because we leak memory every time we call `__allocate` and `write_bytes`.
#[no_mangle]
pub extern "C" fn __deallocate(guest_ptr: GuestPtr) {
    // Failing to deallocate when requested is unrecoverable.
    let len = length_prefix_at_guest_ptr(guest_ptr) + std::mem::size_of::<Len>() as Len;
    let _: Vec<u8> =
        unsafe { Vec::from_raw_parts(guest_ptr as *mut u8, len as usize, len as usize) };
}

/// Attempt to consume bytes out of a length-prefixed allocation at the given pointer position.
///
/// Consume in this context means take ownership of previously forgotten data.
///
/// This needs to work for bytes written into the guest from the host and for bytes written with
/// the write_bytes() function within the guest.
pub fn consume_bytes(guest_ptr: GuestPtr) -> Vec<u8> {
    // The Vec safety requirements are much stricter than a simple slice:
    //
    // - the pointer must have been generated with Vec/String on the same allocator
    //   - yes: the guest always creates its own GuestPtr for vector allocations, note that if we
    //     change the GuestPtr at all (e.g. to try and offset the length prefix) this will break,
    //     we must use the _exact_ GuestPtr previously allocated
    // - the pointer and T need the same size and alignment
    //   - yes: we can cast GuestPtr to *mut u8
    // - length needs to be compatible with capacity
    //   - yes: we can set length and capacity equal
    // - capacity needs to match capacity at the time of the pointer creation
    //   - yes: this is trickier, what we _want_ is a vector of just the payload bytes without the
    //     length prefix, but if we try to change either the capacity or the pointer to offset the
    //     prefix directly then we end up with MemoryOutOfBounds exceptions down the line
    // - @see https://doc.rust-lang.org/std/vec/struct.Vec.html#safety
    //
    // For example, this did _not_ work and leads to memory related panics down the line:
    // let v: Vec<u8> = Vec::from_raw_parts(
    //     (guest_ptr + std::mem::size_of::<Len>() as Len) as *mut u8,
    //     len as usize,
    //     (len + std::mem::size_of::<Len>() as Len) as usize,
    // );

    // We need the same length used to allocate the vector originally, so it includes the prefix
    let len = length_prefix_at_guest_ptr(guest_ptr) + std::mem::size_of::<Len>() as Len;
    let mut v: Vec<u8> = unsafe {
        Vec::from_raw_parts(
            // must match the pointer produced by the original allocation exactly
            guest_ptr as *mut u8,
            // this is the full length of the allocation as we want all the bytes
            len as usize,
            // must match the capacity set during the original allocation exactly
            len as usize,
        )
    };

    // This leads to an additional allocation for a new vector starting after the length prefix
    // the old vector will be dropped and cleaned up by the allocator after this call
    // the split off bytes will take ownership moving forward.
    //
    // Note that we could have tried to do something with std::slice::from_raw_parts() in this
    // function but we'd still need a new allocation at the point of slice.to_vec() and then
    // we'd need to manually free whatever the slice was pointing at.
    v.split_off(std::mem::size_of::<Len>())
}

/// Attempt to write a slice of bytes into a length prefixed allocation.
///
/// This is identical to the following:
/// - host has some slice of bytes
/// - host calls __allocate with the slice length
/// - guest returns GuestPtr to the host
/// - host writes a length prefix and the slice bytes into the guest at GuestPtr location
/// - host hands the GuestPtr back to the guest
///
/// In this case everything happens within the guest and a GuestPtr is returned if successful.
///
/// This also leaks the written bytes, exactly like the above process.
///
/// This facilitates the guest handing a GuestPtr back to the host as the _return_ value of guest
/// functions so that the host can read the _output_ of guest logic from a length-prefixed pointer.
///
/// A good host will call __deallocate with the GuestPtr produced here once it has read the bytes
/// out of the guest, otherwise the bytes will be permanently leaked for the lifetime of the guest.
pub fn write_bytes(slice: &[u8]) -> GuestPtr {
    let len_bytes = slice.len().to_le_bytes();

    let v: Vec<u8> = len_bytes.iter().chain(slice.iter()).cloned().collect();
    let ptr: GuestPtr = v.as_ptr() as GuestPtr;
    let _ = mem::ManuallyDrop::new(v);
    ptr
}