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}