Skip to content

Commit 7e42b45

Browse files
committed
Replace MemCase's unsound impl of Deref/AsMut with a borrow() method
This code compiled fine, but raised a segfault on the last println: ```rust use epserde::deser::Deserialize; use epserde::prelude::Flags; use epserde::prelude::MemCase; use epserde::ser::Serialize; use std::fs::File; use std::io::BufWriter; fn main() { let v = vec![0u64, 10, 20, 30, 40]; let path = std::path::PathBuf::from("/tmp/test.vector"); println!("Writing vector..."); let mut writer = BufWriter::new(File::create(&path).expect("Could not create temp file")); unsafe { v.serialize(&mut writer) }.expect("Could not write vector"); drop(v); println!("mmapping vector..."); let v = unsafe { <Vec<u64>>::mmap(&path, Flags::RANDOM_ACCESS) }.expect("Could not mmap vector"); let v2: &[u64] = *v; println!("Vector value:"); println!("{:?}", v2); println!("Dropping vector..."); drop(v); println!("Vector value:"); println!("{:?}", v2); } ``` This is because the `Deref` implementation of `MemCase<&[u64]>` effectively yields a `&&'static [u64]` which can itself be dereferenced to a `&'static [u64]`, which outlives the `MemCase`. There does not seem to be a way to implement `Deref` or `AsRef` (or `std::borrow::Borrow`) without yielding some sort of `'static` here. So as far as I can tell, we have to take this ergonomic blow in order to avoid segfaults that are very easy for users to trigger unknowingly.
1 parent 7054f93 commit 7e42b45

File tree

4 files changed

+71
-72
lines changed

4 files changed

+71
-72
lines changed

README.md

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,12 @@ These are the main limitations you should be aware of before choosing to use
7676
structure you will need to couple permanently the deserialized structure with
7777
its serialized support, which is obtained by putting it in a [`MemCase`] using
7878
the convenience methods [`Deserialize::load_mem`], [`Deserialize::load_mmap`],
79-
and [`Deserialize::mmap`]. A [`MemCase`] will deref to its contained type, so it
80-
can be used transparently as long as fields and methods are concerned, but if
81-
your original type is `T` the field of the new structure will have to be of type
82-
`MemCase<DeserType<'static, T>>`, not `T`.
79+
and [`Deserialize::mmap`]. A [`MemCase`] provides a method that yields references
80+
to the deserialized type associated to its contained type.
8381

8482
- No validation or padding cleaning is performed on zero-copy types. If you plan
8583
to serialize data and distribute it, you must take care of these issues.
86-
84+
8785
## Pros
8886

8987
- Almost instant deserialization with minimal allocation provided that you
@@ -140,7 +138,7 @@ let t: DeserType<'_, [usize; 1000]> =
140138
assert_eq!(s, *t);
141139

142140
// This is a traditional deserialization instead
143-
let t: [usize; 1000] =
141+
let t: [usize; 1000] =
144142
unsafe { <[usize; 1000]>::deserialize_full(
145143
&mut std::fs::File::open(&file)?
146144
)? };
@@ -149,25 +147,19 @@ assert_eq!(s, t);
149147
// In this case we map the data structure into memory
150148
//
151149
// Note: requires the `mmap` feature.
152-
let u: MemCase<&[usize; 1000]> =
153-
unsafe { <[usize; 1000]>::mmap(&file, Flags::empty())? };
154-
155-
assert_eq!(s, **u);
156-
157-
// When using a MemCase, the lifetime of the derived deserialization type is 'static
158-
let u: MemCase<DeserType<'static, [usize; 1000]>> =
150+
let u: MemCase<[usize; 1000]> =
159151
unsafe { <[usize; 1000]>::mmap(&file, Flags::empty())? };
160152

161-
assert_eq!(s, **u);
153+
assert_eq!(s, **u.borrow());
162154
# Ok(())
163155
# }
164156
```
165157

166158
Note how we serialize an array, but we deserialize a reference. The reference
167159
points inside `b`, so there is no copy performed. The call to
168160
[`deserialize_full`] creates a new array instead. The third call maps the data
169-
structure into memory and returns a [`MemCase`] that can be used transparently
170-
as a reference to the array; moreover, the [`MemCase`] can be passed to other
161+
structure into memory and returns a [`MemCase`] that can be used to get
162+
a reference to the array; moreover, the [`MemCase`] can be passed to other
171163
functions or stored in a structure field, as it contains both the structure and
172164
the memory-mapped region that supports it.
173165

@@ -205,14 +197,14 @@ let t: DeserType<'_, Vec<usize>> =
205197
assert_eq!(s, *t);
206198

207199
// This is a traditional deserialization instead
208-
let t: Vec<usize> =
200+
let t: Vec<usize> =
209201
unsafe { <Vec<usize>>::load_full(&file)? };
210202
assert_eq!(s, t);
211203

212204
// In this case we map the data structure into memory
213-
let u: MemCase<DeserType<'static, Vec<usize>>> =
205+
let u: MemCase<Vec<usize>> =
214206
unsafe { <Vec<usize>>::mmap(&file, Flags::empty())? };
215-
assert_eq!(s, **u);
207+
assert_eq!(s, **u.borrow());
216208
# Ok(())
217209
# }
218210
```
@@ -264,14 +256,14 @@ let t: DeserType<'_, Vec<Data>> =
264256
assert_eq!(s, *t);
265257

266258
// This is a traditional deserialization instead
267-
let t: Vec<Data> =
259+
let t: Vec<Data> =
268260
unsafe { <Vec<Data>>::load_full(&file)? };
269261
assert_eq!(s, t);
270262

271263
// In this case we map the data structure into memory
272-
let u: MemCase<DeserType<'static, Vec<Data>>> =
264+
let u: MemCase<Vec<Data>> =
273265
unsafe { <Vec<Data>>::mmap(&file, Flags::empty())? };
274-
assert_eq!(s, **u);
266+
assert_eq!(s, **u.borrow());
275267
# Ok(())
276268
# }
277269
```
@@ -309,20 +301,21 @@ unsafe { s.store(&file) };
309301
let b = std::fs::read(&file)?;
310302

311303
// The type of t will be inferred--it is shown here only for clarity
312-
let t: MyStruct<&[isize]> =
304+
let t: MyStruct<&[isize]> =
313305
unsafe { <MyStruct<Vec<isize>>>::deserialize_eps(b.as_ref())? };
314306

315307
assert_eq!(s.id, t.id);
316308
assert_eq!(s.data, Vec::from(t.data));
317309

318310
// This is a traditional deserialization instead
319-
let t: MyStruct<Vec<isize>> =
311+
let t: MyStruct<Vec<isize>> =
320312
unsafe { <MyStruct<Vec<isize>>>::load_full(&file)? };
321313
assert_eq!(s, t);
322314

323315
// In this case we map the data structure into memory
324-
let u: MemCase<MyStruct<&[isize]>> =
316+
let u: MemCase<MyStruct<Vec<isize>>> =
325317
unsafe { <MyStruct<Vec<isize>>>::mmap(&file, Flags::empty())? };
318+
let u: &MyStruct<&[isize]> = u.borrow();
326319
assert_eq!(s.id, u.id);
327320
assert_eq!(s.data, u.data.as_ref());
328321
# Ok(())
@@ -371,8 +364,9 @@ let t = unsafe { MyStruct::deserialize_eps(b.as_ref())? };
371364
assert_eq!(s.sum(), t.sum());
372365

373366
let t = unsafe { <MyStruct>::mmap(&file, Flags::empty())? };
367+
let t: &MyStructParam<&[isize]> = t.borrow();
374368

375-
// t works transparently as a MyStructParam<&[isize]>
369+
// t works transparently as a &MyStructParam<&[isize]>
376370
assert_eq!(s.id, t.id);
377371
assert_eq!(s.data, t.data.as_ref());
378372
assert_eq!(s.sum(), t.sum());
@@ -407,7 +401,7 @@ unsafe { s.store(&file) };
407401
let b = std::fs::read(&file)?;
408402

409403
// The type of t is unchanged
410-
let t: MyStruct<Vec<isize>> =
404+
let t: MyStruct<Vec<isize>> =
411405
unsafe { <MyStruct<Vec<isize>>>::deserialize_eps(b.as_ref())? };
412406
# Ok(())
413407
# }
@@ -445,7 +439,7 @@ unsafe { s.store(&file) };
445439
let b = std::fs::read(&file)?;
446440

447441
// The type of t is unchanged
448-
let t: &MyStruct<i32> =
442+
let t: &MyStruct<i32> =
449443
unsafe { <MyStruct<i32>>::deserialize_eps(b.as_ref())? };
450444
# Ok(())
451445
# }
@@ -480,7 +474,7 @@ let e = Enum::B(vec![0, 1, 2, 3]);
480474
let mut file = std::env::temp_dir();
481475
file.push("serialized7");
482476
unsafe { e.store(&file) };
483-
// Deserializing using just Enum will fail, as the type parameter
477+
// Deserializing using just Enum will fail, as the type parameter
484478
// by default is Vec<usize>
485479
assert!(unsafe { <Enum>::load_full(&file) }.is_err());
486480
# Ok(())
@@ -513,7 +507,7 @@ let t: &[i32] = unsafe { <Vec<i32>>::deserialize_eps(b.as_ref())? };
513507
let t: Vec<i32> = unsafe { <Vec<i32>>::deserialize_full(
514508
&mut std::fs::File::open(&file)?
515509
)? };
516-
let t: MemCase<&[i32]> = unsafe { <Vec<i32>>::mmap(&file, Flags::empty())? };
510+
let t: MemCase<Vec<i32>> = unsafe { <Vec<i32>>::mmap(&file, Flags::empty())? };
517511

518512
// Within a structure
519513
#[derive(Epserde, Debug, PartialEq, Eq, Default, Clone)]
@@ -532,7 +526,7 @@ let t: Data<&[i32]> = unsafe { <Data<Vec<i32>>>::deserialize_eps(b.as_ref())? };
532526
let t: Data<Vec<i32>> = unsafe { <Data<Vec<i32>>>::deserialize_full(
533527
&mut std::fs::File::open(&file)?
534528
)? };
535-
let t: MemCase<Data<&[i32]>> = unsafe { <Data<Vec<i32>>>::mmap(&file, Flags::empty())? };
529+
let t: MemCase<Data<Vec<i32>>> = unsafe { <Data<Vec<i32>>>::mmap(&file, Flags::empty())? };
536530
# Ok(())
537531
# }
538532
```
@@ -565,7 +559,7 @@ let t: &[i32] = unsafe { <Vec<i32>>::deserialize_eps(b.as_ref())? };
565559
let t: Vec<i32> = unsafe { <Vec<i32>>::deserialize_full(
566560
&mut std::fs::File::open(&file)?
567561
)? };
568-
let t: MemCase<&[i32]> = unsafe { <Vec<i32>>::mmap(&file, Flags::empty())? };
562+
let t: MemCase<Vec<i32>> = unsafe { <Vec<i32>>::mmap(&file, Flags::empty())? };
569563

570564
// Within a structure
571565
#[derive(Epserde, Debug, PartialEq, Eq, Default, Clone)]
@@ -584,7 +578,7 @@ let t: Data<&[i32]> = unsafe { <Data<Vec<i32>>>::deserialize_eps(b.as_ref())? };
584578
let t: Data<Vec<i32>> = unsafe { <Data<Vec<i32>>>::deserialize_full(
585579
&mut std::fs::File::open(&file)?
586580
)? };
587-
let t: MemCase<Data<&[i32]>> = unsafe { <Data<Vec<i32>>>::mmap(&file, Flags::empty())? };
581+
let t: MemCase<Data<Vec<i32>>> = unsafe { <Data<Vec<i32>>>::mmap(&file, Flags::empty())? };
588582
# Ok(())
589583
# }
590584
```

epserde/src/deser/mem_case.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
55
*/
66

7+
use crate::DeserializeInner;
78
use bitflags::bitflags;
8-
use core::{mem::size_of, ops::Deref};
9+
use core::{fmt, mem::size_of};
910
use maligned::A64;
1011
use mem_dbg::{MemDbg, MemSize};
1112

@@ -111,36 +112,42 @@ impl MemBackend {
111112
/// wrapped type, using the no-op [`None`](`MemBackend#variant.None`) variant
112113
/// of [`MemBackend`], so a structure can be [encased](MemCase::encase)
113114
/// almost transparently.
114-
#[derive(Debug, MemDbg, MemSize)]
115-
pub struct MemCase<S>(pub(crate) S, pub(crate) MemBackend);
115+
#[derive(MemDbg, MemSize)]
116+
pub struct MemCase<'a, S: DeserializeInner>(
117+
pub(crate) <S as DeserializeInner>::DeserType<'a>,
118+
pub(crate) MemBackend,
119+
);
116120

117-
impl<S> MemCase<S> {
118-
/// Encases a data structure in a [`MemCase`] with no backend.
119-
pub fn encase(s: S) -> MemCase<S> {
120-
MemCase(s, MemBackend::None)
121+
impl<'a, S: DeserializeInner> fmt::Debug for MemCase<'a, S>
122+
where
123+
<S as DeserializeInner>::DeserType<'a>: fmt::Debug,
124+
{
125+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126+
f.debug_tuple("MemBackend")
127+
.field(&self.0)
128+
.field(&self.1)
129+
.finish()
121130
}
122131
}
123132

124-
unsafe impl<S: Send> Send for MemCase<S> {}
125-
unsafe impl<S: Sync> Sync for MemCase<S> {}
126-
127-
impl<S> Deref for MemCase<S> {
128-
type Target = S;
129-
#[inline(always)]
130-
fn deref(&self) -> &Self::Target {
131-
&self.0
133+
impl<'a, S: DeserializeInner> MemCase<'a, S> {
134+
/// Encases a data structure in a [`MemCase`] with no backend.
135+
pub fn encase(s: <S as DeserializeInner>::DeserType<'a>) -> Self {
136+
MemCase(s, MemBackend::None)
132137
}
133-
}
134138

135-
impl<S> AsRef<S> for MemCase<S> {
136-
#[inline(always)]
137-
fn as_ref(&self) -> &S {
138-
&self.0
139+
pub fn borrow<'this>(&'this self) -> &'this <S as DeserializeInner>::DeserType<'this> {
140+
// SAFETY: 'a outlives 'this, and <S as DeserializeInner>::DeserType is required to be
141+
// covariant (ie. it's a normal structure and not, say, a closure with 'this as argument)
142+
// TODO: document in DeserializeInner that its DeserType must be covariant
143+
unsafe {
144+
core::mem::transmute::<
145+
&'this <S as DeserializeInner>::DeserType<'a>,
146+
&'this <S as DeserializeInner>::DeserType<'this>,
147+
>(&self.0)
148+
}
139149
}
140150
}
141151

142-
impl<S: Send + Sync> From<S> for MemCase<S> {
143-
fn from(s: S) -> Self {
144-
MemCase::encase(s)
145-
}
146-
}
152+
unsafe impl<'a, S: DeserializeInner + Send> Send for MemCase<'a, S> {}
153+
unsafe impl<'a, S: DeserializeInner + Sync> Sync for MemCase<'a, S> {}

epserde/src/deser/mod.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ pub trait Deserialize: DeserializeInner {
9999
/// # Safety
100100
///
101101
/// See the [trait documentation](Deserialize).
102-
unsafe fn load_mem<'a>(
103-
path: impl AsRef<Path>,
104-
) -> anyhow::Result<MemCase<<Self as DeserializeInner>::DeserType<'a>>> {
102+
unsafe fn load_mem<'a>(path: impl AsRef<Path>) -> anyhow::Result<MemCase<'a, Self>> {
105103
let align_to = align_of::<MemoryAlignment>();
106104
if align_of::<Self>() > align_to {
107105
return Err(Error::AlignmentError.into());
@@ -111,8 +109,7 @@ pub trait Deserialize: DeserializeInner {
111109
// Round up to u128 size
112110
let capacity = file_len + crate::pad_align_to(file_len, align_to);
113111

114-
let mut uninit: MaybeUninit<MemCase<<Self as DeserializeInner>::DeserType<'_>>> =
115-
MaybeUninit::uninit();
112+
let mut uninit: MaybeUninit<MemCase<'_, Self>> = MaybeUninit::uninit();
116113
let ptr = uninit.as_mut_ptr();
117114

118115
// SAFETY: the entire vector will be filled with data read from the file,
@@ -170,13 +167,12 @@ pub trait Deserialize: DeserializeInner {
170167
unsafe fn load_mmap<'a>(
171168
path: impl AsRef<Path>,
172169
flags: Flags,
173-
) -> anyhow::Result<MemCase<<Self as DeserializeInner>::DeserType<'a>>> {
170+
) -> anyhow::Result<MemCase<'a, Self>> {
174171
let file_len = path.as_ref().metadata()?.len() as usize;
175172
let mut file = std::fs::File::open(path)?;
176173
let capacity = file_len + crate::pad_align_to(file_len, 16);
177174

178-
let mut uninit: MaybeUninit<MemCase<<Self as DeserializeInner>::DeserType<'_>>> =
179-
MaybeUninit::uninit();
175+
let mut uninit: MaybeUninit<MemCase<'_, Self>> = MaybeUninit::uninit();
180176
let ptr = uninit.as_mut_ptr();
181177

182178
let mut mmap = mmap_rs::MmapOptions::new(capacity)?
@@ -217,15 +213,11 @@ pub trait Deserialize: DeserializeInner {
217213
///
218214
/// See the [trait documentation](Deserialize) and [mmap's `with_file`'s documentation](mmap_rs::MmapOptions::with_file).
219215
#[cfg(feature = "mmap")]
220-
unsafe fn mmap<'a>(
221-
path: impl AsRef<Path>,
222-
flags: Flags,
223-
) -> anyhow::Result<MemCase<<Self as DeserializeInner>::DeserType<'a>>> {
216+
unsafe fn mmap<'a>(path: impl AsRef<Path>, flags: Flags) -> anyhow::Result<MemCase<'a, Self>> {
224217
let file_len = path.as_ref().metadata()?.len();
225218
let file = std::fs::File::open(path)?;
226219

227-
let mut uninit: MaybeUninit<MemCase<<Self as DeserializeInner>::DeserType<'_>>> =
228-
MaybeUninit::uninit();
220+
let mut uninit: MaybeUninit<MemCase<'_, Self>> = MaybeUninit::uninit();
229221
let ptr = uninit.as_mut_ptr();
230222

231223
let mmap = unsafe {

epserde/tests/test_memcase.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,21 @@ fn test_mem_case() {
3737
unsafe { person.store("test.bin").unwrap() };
3838

3939
let res = unsafe { Person::load_mem("test.bin").unwrap() };
40+
let res = res.borrow();
4041
assert_eq!(person.test, res.test);
4142
assert_eq!(person.a, res.a);
4243
assert_eq!(person.b.a, res.b.a);
4344
assert_eq!(person.b.b, res.b.b);
4445

4546
let res = unsafe { Person::load_mmap("test.bin", Flags::empty()).unwrap() };
47+
let res = res.borrow();
4648
assert_eq!(person.test, res.test);
4749
assert_eq!(person.a, res.a);
4850
assert_eq!(person.b.a, res.b.a);
4951
assert_eq!(person.b.b, res.b.b);
5052

5153
let res = unsafe { Person::load_mem("test.bin").unwrap() };
54+
let res = res.borrow();
5255
assert_eq!(person.test, res.test);
5356
assert_eq!(person.a, res.a);
5457
assert_eq!(person.b.a, res.b.a);
@@ -61,18 +64,21 @@ fn test_mem_case() {
6164
assert_eq!(person.b.b, res.b.b);
6265

6366
let res = unsafe { Person::mmap("test.bin", Flags::empty()).unwrap() };
67+
let res = res.borrow();
6468
assert_eq!(person.test, res.test);
6569
assert_eq!(person.a, res.a);
6670
assert_eq!(person.b.a, res.b.a);
6771
assert_eq!(person.b.b, res.b.b);
6872

6973
let res = unsafe { Person::mmap("test.bin", Flags::TRANSPARENT_HUGE_PAGES).unwrap() };
74+
let res = res.borrow();
7075
assert_eq!(person.test, res.test);
7176
assert_eq!(person.a, res.a);
7277
assert_eq!(person.b.a, res.b.a);
7378
assert_eq!(person.b.b, res.b.b);
7479

7580
let res = unsafe { Person::mmap("test.bin", Flags::empty()).unwrap() };
81+
let res = res.borrow();
7682
assert_eq!(person.test, res.test);
7783
assert_eq!(person.a, res.a);
7884
assert_eq!(person.b.a, res.b.a);

0 commit comments

Comments
 (0)