Skip to content

Commit 83be1e4

Browse files
committed
make the Views type miri-safe
and add more comments
1 parent bca9180 commit 83be1e4

File tree

1 file changed

+109
-6
lines changed

1 file changed

+109
-6
lines changed

src/views.rs

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,82 @@ use orx_concurrent_vec::ConcurrentVec;
77

88
use crate::Database;
99

10+
/// A `Views` struct is associated with some specific database type
11+
/// (a `DatabaseImpl<U>` for some existential `U`). It contains functions
12+
/// to downcast from that type to `dyn DbView` for various traits `DbView`.
13+
/// None of these types are known at compilation time, they are all checked
14+
/// dynamically through `TypeId` magic.
15+
///
16+
/// You can think of the struct as looking like:
17+
///
18+
/// ```rust,ignore
19+
/// struct Views<ghost Db> {
20+
/// source_type_id: TypeId, // `TypeId` for `Db`
21+
/// view_casters: Arc<ConcurrentVec<exists<DbView> {
22+
/// ViewCaster<Db, DbView>
23+
/// }>>,
24+
/// }
25+
/// ```
1026
#[derive(Clone)]
1127
pub struct Views {
1228
source_type_id: TypeId,
1329
view_casters: Arc<ConcurrentVec<ViewCaster>>,
1430
}
1531

32+
/// A ViewCaster contains a trait object that can cast from the
33+
/// (ghost) `Db` type of `Views` to some (ghost) `DbView` type.
34+
///
35+
/// You can think of the struct as looking like:
36+
///
37+
/// ```rust,ignore
38+
/// struct ViewCaster<ghost Db, ghost DbView> {
39+
/// target_type_id: TypeId, // TypeId of DbView
40+
/// type_name: &'static str, // type name of DbView
41+
/// cast_to: OpaqueBoxDyn, // a `Box<dyn CastTo<DbView>>` that expects a `Db`
42+
/// free_box: Box<dyn Free>, // the same box as above, but upcast to `dyn Free`
43+
/// }
44+
/// ```
45+
///
46+
/// As you can see, we have to work very hard to manage things
47+
/// in a way that miri is happy with. What is going on here?
48+
///
49+
/// * The `cast_to` is the cast object, but we can't actually name its type, so
50+
/// we transmute it into some opaque bytes. We can transmute it back once we
51+
/// are in a function monormophized over some function `T` that has the same type-id
52+
/// as `target_type_id`.
53+
/// * The problem is that dropping `cast_to` has no effect and we need
54+
/// to free the box! To do that, we *also* upcast the box to a `Box<dyn Free>`.
55+
/// This trait has no purpose but to carry a destructor.
1656
struct ViewCaster {
57+
/// The id of the target type `DbView` that we can cast to.
1758
target_type_id: TypeId,
59+
60+
/// The name of the target type `DbView` that we can cast to.
1861
type_name: &'static str,
19-
func: fn(&Dummy) -> &Dummy,
62+
63+
/// A "type-obscured" `Box<dyn CastTo<DbView>>`, where `DbView`
64+
/// is the type whose id is encoded in `target_type_id`.
65+
cast_to: OpaqueBoxDyn,
66+
67+
/// An upcasted version of `cast_to`; the only purpose of this field is
68+
/// to be dropped in the destructor, see `ViewCaster` comment.
69+
#[allow(dead_code)]
70+
free_box: Box<dyn Free>,
2071
}
2172

73+
type OpaqueBoxDyn = [u8; std::mem::size_of::<Box<dyn CastTo<Dummy>>>()];
74+
75+
trait CastTo<DbView: ?Sized>: Free {
76+
/// # Safety requirement
77+
///
78+
/// `db` must have a data pointer whose type is the `Db` type for `Self`
79+
unsafe fn cast<'db>(&self, db: &'db dyn Database) -> &'db DbView;
80+
81+
fn into_box_free(self: Box<Self>) -> Box<dyn Free>;
82+
}
83+
84+
trait Free: Send + Sync {}
85+
2286
#[allow(dead_code)]
2387
enum Dummy {}
2488

@@ -45,10 +109,21 @@ impl Views {
45109
return;
46110
}
47111

112+
let cast_to: Box<dyn CastTo<DbView>> = Box::new(func);
113+
let cast_to: OpaqueBoxDyn =
114+
unsafe { std::mem::transmute::<Box<dyn CastTo<DbView>>, OpaqueBoxDyn>(cast_to) };
115+
116+
// Create a second copy of `cast_to` (which is now `Copy`) and upcast it to a `Box<dyn Any>`.
117+
// We will drop this box to run the destructor.
118+
let free_box: Box<dyn Free> = unsafe {
119+
std::mem::transmute::<OpaqueBoxDyn, Box<dyn CastTo<DbView>>>(cast_to).into_box_free()
120+
};
121+
48122
self.view_casters.push(ViewCaster {
49123
target_type_id,
50124
type_name: std::any::type_name::<DbView>(),
51-
func: unsafe { std::mem::transmute::<fn(&Db) -> &DbView, fn(&Dummy) -> &Dummy>(func) },
125+
cast_to,
126+
free_box,
52127
});
53128
}
54129

@@ -73,8 +148,12 @@ impl Views {
73148
// While the compiler doesn't know what `X` is at this point, we know it's the
74149
// same as the true type of `db_data_ptr`, and the memory representation for `()`
75150
// and `&X` are the same (since `X` is `Sized`).
76-
let func: fn(&()) -> &DbView = unsafe { std::mem::transmute(caster.func) };
77-
return Some(func(data_ptr(db)));
151+
let cast_to: &OpaqueBoxDyn = &caster.cast_to;
152+
unsafe {
153+
let cast_to =
154+
std::mem::transmute::<&OpaqueBoxDyn, &Box<dyn CastTo<DbView>>>(cast_to);
155+
return Some(cast_to.cast(db));
156+
};
78157
}
79158
}
80159

@@ -98,8 +177,32 @@ impl std::fmt::Debug for ViewCaster {
98177

99178
/// Given a wide pointer `T`, extracts the data pointer (typed as `()`).
100179
/// This is safe because `()` gives no access to any data and has no validity requirements in particular.
101-
fn data_ptr<T: ?Sized>(t: &T) -> &() {
180+
unsafe fn data_ptr<T: ?Sized, U>(t: &T) -> &U {
102181
let t: *const T = t;
103-
let u: *const () = t as *const ();
182+
let u: *const U = t as *const U;
104183
unsafe { &*u }
105184
}
185+
186+
impl<Db, DbView> CastTo<DbView> for fn(&Db) -> &DbView
187+
where
188+
Db: Database,
189+
DbView: ?Sized + Any,
190+
{
191+
unsafe fn cast<'db>(&self, db: &'db dyn Database) -> &'db DbView {
192+
// This tests the safety requirement:
193+
debug_assert_eq!(db.type_id(), TypeId::of::<Db>());
194+
195+
// SAFETY:
196+
//
197+
// Caller guarantees that the input is of type `Db`
198+
// (we test it in the debug-assertion above).
199+
let db = unsafe { data_ptr::<dyn Database, Db>(db) };
200+
(*self)(db)
201+
}
202+
203+
fn into_box_free(self: Box<Self>) -> Box<dyn Free> {
204+
self
205+
}
206+
}
207+
208+
impl<T: Send + Sync> Free for T {}

0 commit comments

Comments
 (0)