Skip to content

Commit 9bd8671

Browse files
committed
Clean up Abomonated
1 parent 26e309d commit 9bd8671

File tree

2 files changed

+88
-48
lines changed

2 files changed

+88
-48
lines changed

src/abomonated.rs

Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11

2-
use std::mem::transmute;
32
use std::marker::PhantomData;
43
use std::ops::{Deref, DerefMut};
54

65
use super::{Abomonation, decode};
76

87
/// A type wrapping owned decoded abomonated data.
98
///
10-
/// This type ensures that decoding and pointer correction has already happened,
11-
/// and implements `Deref<Target=T>` using a pointer cast (transmute).
9+
/// This type ensures that decoding and pointer correction has already happened.
10+
/// It provides a way to move the deserialized data around, while keeping
11+
/// on-demand access to it via the `as_ref()` method.
12+
///
13+
/// As an extra convenience, `Deref<Target=T>` is also implemented if T does
14+
/// not contain references. Unfortunately, this trait cannot be safely
15+
/// implemented when T does contain references.
1216
///
1317
/// #Safety
1418
///
15-
/// The safety of this type, and in particular its `transute` implementation of
16-
/// the `Deref` trait, relies on the owned bytes not being externally mutated
17-
/// once provided. You could imagine a new type implementing `DerefMut` as required,
18-
/// but which also retains the ability (e.g. through `RefCell`) to mutate the bytes.
19-
/// This would be very bad, but seems hard to prevent in the type system. Please
20-
/// don't do this.
19+
/// The safety of this type, and in particular of access to the deserialized
20+
/// data, relies on the owned bytes not being externally mutated after the
21+
/// `Abomonated` is constructed. You could imagine a new type implementing
22+
/// `DerefMut` as required, but which also retains the ability (e.g. through
23+
/// `RefCell`) to mutate the bytes. This would be very bad, but seems hard to
24+
/// prevent in the type system. Please don't do this.
2125
///
2226
/// You must also use a type `S` whose bytes have a fixed location in memory.
2327
/// Otherwise moving an instance of `Abomonated<T, S>` may invalidate decoded
@@ -54,9 +58,10 @@ pub struct Abomonated<T, S: DerefMut<Target=[u8]>> {
5458
decoded: S,
5559
}
5660

57-
impl<'bytes, T, S> Abomonated<T, S>
58-
where S: DerefMut<Target=[u8]> + 'bytes,
59-
T: Abomonation<'bytes>,
61+
impl<'s, 't, T, S> Abomonated<T, S>
62+
where S: DerefMut<Target=[u8]> + 's,
63+
T: Abomonation<'t>,
64+
's: 't
6065
{
6166
/// Attempts to create decoded data from owned mutable bytes.
6267
///
@@ -95,59 +100,65 @@ impl<'bytes, T, S> Abomonated<T, S>
95100
/// The type `S` must have its bytes at a fixed location, which will
96101
/// not change if the `bytes: S` instance is moved. Good examples are
97102
/// `Vec<u8>` whereas bad examples are `[u8; 16]`.
98-
pub unsafe fn new(bytes: S) -> Option<Self> {
99-
// FIXME: Our API specification for `T` and `S` interacts with the API
100-
// specification of `decode()` in such an unfortunate way that
101-
// `decode::<T>(bytes.borrow_mut())` borrows `bytes` forever.
103+
pub unsafe fn new(mut bytes: S) -> Option<Self> {
104+
// Fix type `T`'s inner pointers. Will return `None` on failure.
102105
//
103-
// This is not what we want here, because it prevents moving
104-
// `bytes` into the `Abomonated` at the end of the function.
106+
// FIXME: `slice::from_raw_parts_mut` is used to work around the borrow
107+
// checker marking `bytes` as forever borrowed if `&mut bytes` is
108+
// directly passed as input to `decode()`. But that is itself a
109+
// byproduct of the API contract specified by the `where` clause
110+
// above, which allows S to be `&'t mut [u8]` (and therefore
111+
// require such a perpetual borrow) in the worst case.
105112
//
106-
// I could not find a way to redesign the API of either
107-
// `Abomonated` or `decode()` so that this doesn't happen.
108-
// Therefore, I will have to work around it the unsafe way...
113+
// A different API contract might allow us to achieve the same
114+
// result without resorting to such evil unsafe tricks.
109115
//
110-
let bytes_uc = std::cell::UnsafeCell::new(bytes);
111-
112-
{
113-
// Create an `&'bytes mut [u8]` slice from `bytes_uc`, without
114-
// borrowing `bytes_uc` in the process (`get()` returns `*mut _`)
115-
//
116-
// This is safe because `UnsafeCell<T>` can alias with `&mut T`.
117-
//
118-
let bytes: &'bytes mut [u8] = (*bytes_uc.get()).deref_mut();
116+
decode::<T>(std::slice::from_raw_parts_mut(bytes.as_mut_ptr(),
117+
bytes.len()))?;
119118

120-
// Fix type `T`'s inner pointers (will exit early on failure)
121-
//
122-
// This borrows the `bytes` slice for its entire `'bytes` lifetime!
123-
// But it's okay, we'll just throw that slice away at the end of the
124-
// scope, without letting any reference to `bytes_uc` leak.
125-
//
126-
// As we have not borrowed `bytes_uc`, we can then keep using it.
127-
//
128-
decode::<T>(bytes)?;
129-
}
130-
131-
// Get back the original `bytes` and return the Abomonated structure
119+
// Build the Abomonated structure
132120
Some(Abomonated {
133121
phantom: PhantomData,
134-
decoded: bytes_uc.into_inner(),
122+
decoded: bytes,
135123
})
136124
}
137125
}
138126

139-
impl<T, S: DerefMut<Target=[u8]>> Abomonated<T, S> {
127+
impl<'t, T, S> Abomonated<T, S>
128+
where S: DerefMut<Target=[u8]>,
129+
T: Abomonation<'t>
130+
{
131+
/// Get a read-only view on the deserialized bytes
140132
pub fn as_bytes(&self) -> &[u8] {
141133
&self.decoded
142134
}
143-
}
144135

136+
/// Get a read-only view on the deserialized data
137+
//
138+
// NOTE: This method can be safely used even if type T contains references,
139+
// because it makes sure that the borrow of `self` lasts long enough
140+
// to encompass the lifetime of these references.
141+
//
142+
// Otherwise, it would be possible to extract an `&'static Something`
143+
// from a short-lived borrow of a `Box<[u8]>`, then drop the `Box`,
144+
// and end up with a dangling reference.
145+
//
146+
pub fn as_ref<'a: 't>(&'a self) -> &'a T {
147+
unsafe { &*(self.decoded.as_ptr() as *const T) }
148+
}
149+
}
145150

146-
impl<T, S: DerefMut<Target=[u8]>> Deref for Abomonated<T, S> {
151+
// NOTE: The lifetime constraint that was applied to `as_ref()` cannot be
152+
// applied to a `Deref` implementation. Therefore, `Deref` can only be
153+
// used on types T which do not contain references, as enforced by the
154+
// higher-ranked trait bound below.
155+
impl<T, S> Deref for Abomonated<T, S>
156+
where S: DerefMut<Target=[u8]>,
157+
for<'t> T: Abomonation<'t>,
158+
{
147159
type Target = T;
148160
#[inline]
149161
fn deref(&self) -> &T {
150-
let result: &T = unsafe { transmute(self.decoded.get_unchecked(0)) };
151-
result
162+
self.as_ref()
152163
}
153164
}

tests/tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,32 @@ fn test_net_types() {
157157
let (t, r) = unsafe { decode::<SocketAddr>(&mut bytes) }.unwrap(); assert_eq!(*t, socket_addr4);
158158
let (t, _r) = unsafe { decode::<SocketAddr>(r) }.unwrap(); assert_eq!(*t, socket_addr6);
159159
}
160+
161+
#[test]
162+
fn test_abomonated_owned() {
163+
use abomonation::abomonated::Abomonated;
164+
165+
let s_owned = "This is an owned string".to_owned();
166+
167+
let mut bytes = Vec::new();
168+
unsafe { encode::<String, _>(&s_owned, &mut bytes).unwrap(); }
169+
170+
let abo = unsafe { Abomonated::<String, _>::new(bytes).unwrap() };
171+
assert_eq!(abo.as_ref(), &s_owned);
172+
assert_eq!(&*abo, &s_owned);
173+
}
174+
175+
#[test]
176+
fn test_abomonated_ref() {
177+
use abomonation::abomonated::Abomonated;
178+
179+
let s_owned = "This is an owned string".to_owned();
180+
let s_borrow = &s_owned[11..16]; // "owned"
181+
182+
let mut bytes = Vec::new();
183+
unsafe { encode::<&str, _>(&s_borrow, &mut bytes).unwrap(); }
184+
185+
let abo = unsafe { Abomonated::<&str, _>::new(bytes).unwrap() };
186+
assert_eq!(abo.as_ref(), &s_borrow);
187+
// NOTE: Cannot use Deref here because &str contains a reference
188+
}

0 commit comments

Comments
 (0)