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