Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use std::io::Write; // for bytes.write_all; push_all is unstable and extend is s
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::num::*;
use std::sync::Arc;

pub mod abomonated;

Expand Down Expand Up @@ -534,6 +535,36 @@ impl<T: Abomonation> Abomonation for Box<T> {
}
}

impl<T: Abomonation + 'static + Sized + Unpin> Abomonation for Arc<T> {
#[inline]
unsafe fn entomb<W: std::io::Write>(&self, write: &mut W) -> std::io::Result<()> {
/* Since the value T has not yet been seen by abomonate (it is behind a pointer)
* we need to fully encode it.
*/
encode(self.as_ref(), write)
}
#[inline]
unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> {
use std::ptr;
/* The idea here is to construct a new Arc<T> from the entombed bytes.
* The state of this ArcVal upon entry of this function contains only an invalid
* pointer to an ArcInner that we need to dispose of without trying to run
* its destructor (which would panic).
*/
let (value, bytes) = decode::<T>(bytes)?;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frankmcsherry Here value contains a reference to a T. Since T: 'static this structure cannot contain non-static references (but it could contain boxes). My understanding is that T can only directly reference bytes by unsafely reinterpreting a reference as a box or equivalently by transmuting a lifetime to 'static. Is my assumption correct that such behaviour violates the contract of Abomonation?

// value is just a reference to the first part of old bytes, so move it into a new Arc
let arc = Arc::new(ptr::read(value));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we lift the bytes from the underlying slice and move them into the ArcInner, i.e. we move the value of type T to a new memory location. Since it cannot reference bytes by the above argument, the result should be a correctly self-contained Arc<T>.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be the problematic moment. Something like String can still reference bytes even after a ptr::read. The read will only grab the 24 bytes of the String type, but it may contain a pointer in to bytes (and very likely does, as this is how String implements Abomonation).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, that’s evil! So the only correct implementation would be to require T: Clone and clone the referenced value returned by decode.

// now swap the fresh arc into its place ...
let garbage = std::mem::replace(self, arc);
// ... and forget about the old one
mem::forget(garbage);
Some(bytes)
}
fn extent(&self) -> usize {
std::mem::size_of::<T>() + self.as_ref().extent()
}
}

// This method currently enables undefined behavior, by exposing padding bytes.
#[inline] unsafe fn typed_to_bytes<T>(slice: &[T]) -> &[u8] {
std::slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len() * mem::size_of::<T>())
Expand Down
24 changes: 24 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,27 @@ fn test_net_types() {
let (t, r) = unsafe { decode::<SocketAddr>(&mut bytes) }.unwrap(); assert!(*t == socket_addr4);
let (t, _r) = unsafe { decode::<SocketAddr>(r) }.unwrap(); assert!(*t == socket_addr6);
}

#[test]
fn test_ref_types() {
use std::sync::Arc;
let value = Arc::new("hello".to_owned());

let mut bytes = Vec::new();
unsafe { abomonation::encode(&value, &mut bytes).unwrap() };
// abomonated bytes end with "hello"
assert_eq!(&bytes[bytes.len() - 5..], b"hello");

let mut reference = Vec::new();
unsafe { abomonation::encode(&"hello".to_owned(), &mut reference).unwrap() };
// abomonating an Arc is like abomonating a pointer and then the contained value
assert_eq!(&bytes[std::mem::size_of::<usize>() + 2..], &reference[2..]);

// modify the bytes to see that deserialization uses them and not the pointer
let pos = bytes.len() - 4;
bytes[pos] = b'a';

let (value2, bytes) = unsafe { abomonation::decode::<Arc<String>>(&mut bytes).unwrap() };
assert_eq!(value2.as_ref(), "hallo");
assert!(bytes.is_empty());
}