jyafn_ext/
lib.rs

1//! This crate is intended to help extension authors. It exposes a minimal version of
2//! `jyafn` and many convenience macros to generate all the boilerplate involved.
3
4mod io;
5mod layout;
6mod outcome;
7mod resource;
8
9/// Reexporting from the `paste` crate. This is neeede because we have to programatically
10/// generate new identifiers to be used as symbols in the final shared object.
11pub use paste::paste;
12/// We need JSON support to zip JSON values around the FFI boundary.
13pub use serde_json;
14
15pub use io::{Input, OutputBuilder, InputReader};
16pub use layout::{Layout, Struct, ISOFORMAT};
17pub use outcome::Outcome;
18pub use resource::{Method, Resource};
19
20/// Generates the boilerplate code for a `jyafn` extension.
21///
22/// # Usage
23///
24/// This macro accepts a list of comman-separated types, each of which has to implement
25/// the [`Resource`] trait, like so
26/// ```
27/// extension! {
28///     Foo, Bar, Baz
29/// }
30/// ```
31/// Optionally, you may define an init function, which takes no arguments and returns
32/// `Result<(), String>`, like so
33/// ```
34/// extension! {
35///     init = my_init;
36///     Foo, Bar, Baz
37/// }
38///
39/// fn my_init() -> Result<(), String> { /* ... */}
40/// ```
41#[macro_export]
42macro_rules! extension {
43    ($($ty:ty),*) => {
44        fn noop() -> Result<(), String> { Ok (()) }
45
46        $crate::extension! {
47            init = noop;
48            $($ty),*
49        }
50    };
51    (init = $init_fn:ident; $($ty:ty),*) => {
52        use std::ffi::{c_char, CString};
53        use $crate::Outcome;
54
55        /// Creates a C-style string out of a `String` in a way that doesn't produce errors. This
56        /// function substitutes nul characters by the ` ` (space) character. This avoids an
57        /// allocation.
58        ///
59        /// This method **leaks** the string. So, don't forget to guarantee that somene somewhere
60        /// is freeing it.
61        ///
62        /// # Note
63        ///
64        /// Yes, I know! It's a pretty lousy implementation that is even... O(n^2) (!!). You can
65        /// do better than I in 10mins.
66        pub(crate) fn make_safe_c_str(s: String) -> CString {
67            let mut v = s.into_bytes();
68            loop {
69                match std::ffi::CString::new(v) {
70                    Ok(c_str) => return c_str,
71                    Err(err) => {
72                        let nul_position = err.nul_position();
73                        v = err.into_vec();
74                        v[nul_position] = b' ';
75                    }
76                }
77            }
78        }
79
80
81        /// # Safety
82        ///
83        /// Expecting a valid pointer from input.
84        #[no_mangle]
85        pub unsafe extern "C" fn outcome_get_err(outcome: *mut Outcome) -> *const c_char {
86            let outcome = &*outcome;
87
88            match outcome {
89                Outcome::Ok(_) => std::ptr::null(),
90                Outcome::Err(err) => err.as_ptr(),
91            }
92        }
93
94        /// # Safety
95        ///
96        /// Expecting a valid pointer from input.
97        #[no_mangle]
98        pub unsafe extern "C" fn outcome_get_ok(outcome: *mut Outcome) -> *mut () {
99            let outcome = &*outcome;
100
101            match outcome {
102                Outcome::Ok(ptr) => *ptr,
103                Outcome::Err(_) => std::ptr::null_mut(),
104            }
105        }
106
107        /// # Safety
108        ///
109        /// Expecting a valid pointer from input.
110        #[no_mangle]
111        pub unsafe extern "C" fn outcome_drop(outcome: *mut Outcome) {
112            let _ = Box::from_raw(outcome);
113        }
114
115        /// # Safety
116        ///
117        /// Expecting a valid pointer from input.
118        #[no_mangle]
119        pub unsafe extern "C" fn dump_get_len(dump: *const Vec<u8>) -> usize {
120            let dump = &*dump;
121            dump.len()
122        }
123
124        /// # Safety
125        ///
126        /// Expecting a valid pointer from input.
127        #[no_mangle]
128        pub unsafe extern "C" fn dump_get_ptr(dump: *const Vec<u8>) -> *const u8 {
129            let dump = &*dump;
130            dump.as_ptr()
131        }
132
133        /// # Safety
134        ///
135        /// Expecting a valid pointer from input.
136        #[no_mangle]
137        pub unsafe extern "C" fn dump_drop(dump: *mut Vec<u8>) {
138            let _ = Box::from_raw(dump);
139        }
140
141        #[no_mangle]
142        pub unsafe extern "C" fn string_drop(method: *mut c_char) {
143            let _ = CString::from_raw(method);
144        }
145
146        #[no_mangle]
147        pub extern "C" fn extension_init() -> *const c_char {
148            fn safe_extension_init() -> Result<$crate::serde_json::Value, String> {
149                $init_fn()?;
150
151                let manifest = $crate::serde_json::json!({
152                    "metadata": {
153                        "name": env!("CARGO_PKG_NAME"),
154                        "version": env!("CARGO_PKG_VERSION"),
155                        "about": env!("CARGO_PKG_DESCRIPTION"),
156                        "authors": env!("CARGO_PKG_AUTHORS"),
157                        "license": env!("CARGO_PKG_LICENSE"),
158                    },
159                    "outcome": {
160                        "fn_get_err": "outcome_get_err",
161                        "fn_get_ok": "outcome_get_ok",
162                        "fn_drop": "outcome_drop"
163                    },
164                    "dumped": {
165                        "fn_get_ptr": "dump_get_ptr",
166                        "fn_get_len": "dump_get_len",
167                        "fn_drop": "dump_drop"
168                    },
169                    "string": {
170                        "fn_drop": "string_drop"
171                    },
172                    "resources": {$(
173                        stringify!($ty): {
174                            "fn_from_bytes": stringify!($ty).to_string() + "_from_bytes",
175                            "fn_dump": stringify!($ty).to_string() + "_dump",
176                            "fn_size": stringify!($ty).to_string() + "_size",
177                            "fn_get_method_def": stringify!($ty).to_string() + "_get_method",
178                            "fn_drop": stringify!($ty).to_string() + "_drop"
179                        },
180                    )*}
181                });
182
183                Ok(manifest)
184            }
185
186            let outcome = std::panic::catch_unwind(|| {
187                match safe_extension_init() {
188                    Ok(manifest) => manifest,
189                    Err(err) => {
190                        $crate::serde_json::json!({"error": err})
191                    }
192                }
193            }).unwrap_or_else(|_| {
194                $crate::serde_json::json!({
195                    "error": "extension initialization panicked. See stderr"
196                })
197            });
198
199            match CString::new(outcome.to_string()) {
200                Ok(s) => s.into_raw(),
201                Err(_) => std::ptr::null(),
202            }
203        }
204
205        $(
206            $crate::resource! { $ty }
207        )*
208    };
209}
210
211/// Declares a single resource for this extension, given a type. This writes all the
212/// boilerplate code thar corresponds to the extension side of the API.
213#[macro_export]
214macro_rules! resource {
215    ($ty:ty) => {
216        $crate::paste! {
217
218            #[allow(unused)]
219            fn [<$ty _test_is_a_resource>]() where $ty: $crate::Resource {}
220
221            #[no_mangle]
222            pub unsafe extern "C" fn [<$ty _size>](raw: *mut $ty) -> usize {
223                std::panic::catch_unwind(|| (&*raw).size())
224                    .unwrap_or_else(|_| {
225                        eprintln!(
226                            "calling `size` on resource {:?} panicked. Size will be set to zero. See stderr.",
227                            stringify!($ty)
228                        );
229                        0
230                    })
231            }
232
233            #[no_mangle]
234            pub unsafe extern "C" fn [<$ty _dump>](raw: *mut $ty) -> *mut $crate::Outcome {
235                std::panic::catch_unwind(|| {
236                    let boxed = Box::new($crate::Outcome::from((&*raw).dump()));
237                    Box::leak(boxed) as *mut _
238                }).unwrap_or_else(|_| {
239                    eprintln!(
240                        "calling `dump` on resource {:?} panicked. Will return null. See stderr.",
241                        stringify!($ty)
242                    );
243                    std::ptr::null_mut()
244                })
245            }
246
247            #[no_mangle]
248            pub unsafe extern "C" fn [<$ty _from_bytes>](
249                bytes_ptr: *const u8,
250                bytes_len: usize,
251            ) -> *mut $crate::Outcome {
252                std::panic::catch_unwind(|| {
253                    let bytes = std::slice::from_raw_parts(bytes_ptr, bytes_len);
254                    let boxed = Box::new($crate::Outcome::from($ty::from_bytes(bytes)));
255                    Box::leak(boxed) as *mut _
256                }).unwrap_or_else(|_| {
257                    eprintln!(
258                        "calling `dump` on resource {:?} panicked. Will return null. See stderr.",
259                        stringify!($ty)
260                    );
261                    std::ptr::null_mut()
262                })
263            }
264
265            #[no_mangle]
266            pub unsafe extern "C" fn [<$ty _get_method>](
267                raw: *mut $ty,
268                name: *const c_char,
269            ) -> *const c_char {
270                std::panic::catch_unwind(|| {
271                    let name = std::ffi::CStr::from_ptr(name);
272                    let method = (&*raw).get_method(&name.to_string_lossy());
273
274                    if let Some(method) = method {
275                        CString::new(
276                            $crate::serde_json::to_string_pretty(&method)
277                                .expect("can always serialize method as json")
278                        )
279                        .expect("json representation does not contain nul chars")
280                        .into_raw()
281                    } else {
282                        std::ptr::null()
283                    }
284                }).unwrap_or_else(|_| {
285                    eprintln!(
286                        "calling `get_method` on resource {:?} panicked. See stderr.",
287                        stringify!($ty)
288                    );
289                    std::ptr::null()
290                })
291            }
292
293            #[no_mangle]
294            pub unsafe extern "C" fn [<$ty _drop>](raw: *mut $ty) {
295                std::panic::catch_unwind(|| {
296                    let _ = Box::from_raw(raw);
297                }).unwrap_or_else(|_| {
298                    eprintln!(
299                        "calling `drop` on resource {:?} panicked. See stderr.",
300                        stringify!($ty)
301                    );
302                })
303            }
304        }
305    };
306}
307
308/// A safe convenience macro for method call. This macro does three things for you:
309/// 1. Converts the raw pointer to a reference.
310/// 2. Converts the pointers into slices correctly.
311/// 3. Treats possible panics, converting them to errors. Panics are always unwanted, but
312///    panicking through an FFI boundary is UB. Therefore, this treatment is always necessary.
313///
314/// # Usage
315///
316/// ```
317/// impl MyResource {
318///     fn something_safe(
319///         &self,
320///         input: Input,
321///         output: OutputBuilder,
322///     ) -> Result<(), String> {   // or anything else implementing `ToString`...
323///         // ...
324///         todo!()
325///     }
326///
327///     method!(something_safe)  // can only call from inside an impl block!
328///                              // This is for type safety reasons
329/// }
330///
331/// ```
332#[macro_export]
333macro_rules! method {
334    ($safe_interface:ident) => {
335        $crate::paste! {
336            #[allow(non_snake_case)]
337            pub unsafe extern "C" fn [<raw_method__ $safe_interface>](
338                resource_ptr: *const (),
339                input_ptr: *const u8,
340                input_slots: u64,
341                output_ptr: *mut u8,
342                output_slots: u64,
343            ) -> *mut u8 {
344                match std::panic::catch_unwind(|| {
345                    unsafe {
346                        // Safety: all this stuff came from jyafn code. The jyafn code should
347                        // provide valid parameters. Plus, it's the responsibility of the
348                        // implmementer guarantee that the types match.
349
350                        let resource: &Self = &*(resource_ptr as *const _);
351
352                        Self::$safe_interface(
353                            resource,
354                            $crate::Input::new(input_ptr, input_slots as usize),
355                            $crate::OutputBuilder::new(output_ptr, output_slots as usize),
356                        )
357                    }
358                }) {
359                    Ok(Ok(())) => std::ptr::null_mut(),
360                    Ok(Err(err)) => {
361                        make_safe_c_str(err).into_raw() as *mut u8
362                    }
363                    // DON'T forget the nul character when working with bytes directly!
364                    Err(_) => {
365                        make_safe_c_str(format!(
366                            "method {:?} panicked. See stderr",
367                            stringify!($safe_interface),
368                        )).into_raw() as *mut u8
369                    }
370                }
371            }
372        }
373    };
374}
375
376/// A convenience macro to get references to methods created with [`method`].
377#[macro_export]
378macro_rules! get_method_ptr {
379    ($safe_interface:ident) => {
380        $crate::paste!(Self::[<raw_method__ $safe_interface>]) as usize
381    }
382}
383
384/// This macro provides a standard implementation for the [`Resource::get_method`]
385/// function from a list of methods.
386///
387/// # Usage
388///
389/// ```
390/// impl Resource for MyResource {
391///     // ...
392///
393///     fn get_method(&self, method: &str) -> Option<Method> {
394///         declare_methods! {
395///             // This the the variable containing the method name.
396///             match method:
397///                 // Use the layout notation to declare the method (an yes, you can use
398///                 // `self` anywhere in the declaration)
399///                 foo_method(x: scalar, y: [datetime; self.size]) -> [datetime; self.size];
400///         }
401///     }
402/// }
403/// ```
404#[macro_export]
405macro_rules! declare_methods {
406    ($( $safe_interface:ident ($($key:tt : $ty:tt),*) -> $output:tt; )*) => {
407        $crate::declare_methods! {
408            match method:  $( $safe_interface ($($key : $ty),*) -> $output; )*
409        }
410    };
411    ( match $method:ident : $( $safe_interface:ident ($($key:tt : $ty:tt),*) -> $output:tt; )*) => {
412        Some(match $method {
413            $(
414                stringify!($safe_interface) => $crate::Method {
415                    fn_ptr: $crate::get_method_ptr!($safe_interface),
416                    input_layout: $crate::r#struct!($($key : $ty),*),
417                    output_layout: $crate::layout!($output),
418                },
419            )*
420            _ => return None,
421        })
422    };
423}