1use std::marker::PhantomData;
4use std::mem::ManuallyDrop;
5
6#[macro_export]
8macro_rules! external {
9 ($type:ty, $name:literal) => {
10 impl $crate::Externalizable for $type {
11 fn external_marker() -> ::core::primitive::usize {
12 static mut DEFINITION: $crate::ExternalDefinition =
15 $crate::ExternalDefinition::new($name);
16 let ptr = ::std::hint::black_box(::std::ptr::addr_of_mut!(DEFINITION));
20 ptr as ::core::primitive::usize
21 }
22
23 fn external_name() -> &'static ::core::primitive::str {
24 $name
25 }
26 }
27 };
28}
29
30pub trait Externalizable {
31 fn external_marker() -> usize;
32 fn external_name() -> &'static str;
33}
34
35#[doc(hidden)]
36pub struct ExternalDefinition {
37 #[allow(unused, reason = "used for documentation and debugging")]
38 pub name: &'static str,
39}
40
41impl ExternalDefinition {
42 #[doc(hidden)]
43 pub const fn new(name: &'static str) -> Self {
44 Self { name }
45 }
46}
47
48#[repr(C)]
49struct ExternalWithMarker<T> {
50 marker: usize,
51 external: T,
52}
53
54#[repr(transparent)]
57pub struct ExternalPointer<E: Externalizable> {
58 ptr: *mut ManuallyDrop<ExternalWithMarker<E>>,
59 _type: std::marker::PhantomData<E>,
60}
61
62impl<E: Externalizable> ExternalPointer<E> {
63 pub fn new(external: E) -> Self {
64 let marker = E::external_marker();
65 let new =
66 Box::new(ManuallyDrop::new(ExternalWithMarker { marker, external }));
67 ExternalPointer {
68 ptr: Box::into_raw(new),
69 _type: PhantomData,
70 }
71 }
72
73 pub fn into_raw(self) -> *const std::ffi::c_void {
74 self.ptr as _
75 }
76
77 pub fn from_raw(ptr: *const std::ffi::c_void) -> Self {
79 ExternalPointer {
80 ptr: ptr as _,
81 _type: PhantomData,
82 }
83 }
84
85 fn validate_pointer(&self) -> *mut ExternalWithMarker<E> {
89 let expected_marker = E::external_marker();
90 if self.ptr.is_null()
93 || self.ptr.align_offset(std::mem::align_of::<usize>()) != 0
94 || unsafe { std::ptr::read::<usize>(self.ptr as _) } != expected_marker
95 {
96 panic!(
97 "Detected an invalid v8::External (expected {})",
98 E::external_name()
99 );
100 }
101 self.ptr as _
102 }
103
104 pub unsafe fn unsafely_deref(&self) -> &E {
113 unsafe {
114 let validated_ptr = self.validate_pointer();
115 let external = std::ptr::addr_of!((*validated_ptr).external);
116 &*external
117 }
118 }
119
120 pub unsafe fn unsafely_take(self) -> E {
129 unsafe {
130 let validated_ptr = self.validate_pointer();
131 let marker = std::ptr::addr_of_mut!((*validated_ptr).marker);
132 assert_ne!(std::ptr::replace(marker, 0), 0);
134 std::ptr::write(marker, 0);
135 let external =
136 std::ptr::read(std::ptr::addr_of!((*validated_ptr).external));
137 _ = Box::<ManuallyDrop<ExternalWithMarker<E>>>::from_raw(self.ptr);
139 external
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 struct External1(u32);
149 external!(External1, "external 1");
150
151 struct External2(());
152 external!(External2, "external 2");
153
154 struct External1b(());
156 external!(External1b, "external 1");
157
158 struct DeallocOnPanic<E: Externalizable>(Option<ExternalPointer<E>>);
160
161 impl<E: Externalizable> DeallocOnPanic<E> {
162 pub fn new(external: &ExternalPointer<E>) -> Self {
163 Self(Some(ExternalPointer {
164 ptr: external.ptr,
165 _type: PhantomData,
166 }))
167 }
168 }
169
170 impl<E: Externalizable> Drop for DeallocOnPanic<E> {
171 fn drop(&mut self) {
172 unsafe {
173 self.0.take().unwrap().unsafely_take();
174 }
175 }
176 }
177
178 #[test]
179 pub fn test_external() {
180 let external = ExternalPointer::new(External1(1));
181 assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
182 let ptr = external.into_raw();
183
184 let external = ExternalPointer::<External1>::from_raw(ptr);
185 assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
186 assert_eq!(unsafe { external.unsafely_take() }.0, 1);
187 }
188
189 #[test]
192 pub fn test_external_markers() {
193 let m1 = External1::external_marker();
194 let m2 = External2::external_marker();
195 let m1b = External1b::external_marker();
196
197 assert_ne!(m1, m2);
198 assert_ne!(m1, m1b);
199 }
200
201 #[test]
205 #[should_panic]
206 pub fn test_external_incompatible_same_name() {
207 let external = ExternalPointer::new(External1(1));
208 let _dealloc = DeallocOnPanic::new(&external);
209 assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
210 let ptr = external.into_raw();
211
212 let external = ExternalPointer::<External1b>::from_raw(ptr);
213 unsafe {
214 external.unsafely_deref();
215 }
216 }
217
218 #[cfg(not(miri))]
220 #[test]
221 #[should_panic]
222 pub fn test_external_deref_after_take() {
223 let external = ExternalPointer::new(External1(1));
224 let ptr = external.into_raw();
225
226 let external = ExternalPointer::<External1>::from_raw(ptr);
228 unsafe {
229 external.unsafely_take();
230 }
231
232 let external = ExternalPointer::<External1>::from_raw(ptr);
234 unsafe {
235 external.unsafely_deref();
236 }
237 }
238
239 #[test]
240 #[should_panic]
241 pub fn test_external_incompatible_deref() {
242 let external = ExternalPointer::new(External1(1));
243 let _dealloc = DeallocOnPanic::new(&external);
244 assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
245 let ptr = external.into_raw();
246
247 let external = ExternalPointer::<External2>::from_raw(ptr);
248 unsafe {
249 external.unsafely_deref();
250 }
251 }
252
253 #[test]
254 #[should_panic]
255 pub fn test_external_incompatible_take() {
256 let external = ExternalPointer::new(External1(1));
257 let _dealloc = DeallocOnPanic::new(&external);
258 assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
259 let ptr = external.into_raw();
260
261 let external = ExternalPointer::<External2>::from_raw(ptr);
262 unsafe {
263 external.unsafely_take();
264 }
265 }
266}