Skip to main content

rusteron_code_gen/
lib.rs

1#![allow(improper_ctypes_definitions)]
2#![allow(non_upper_case_globals)]
3#![allow(non_camel_case_types)]
4#![allow(non_snake_case)]
5#![allow(clippy::all)]
6#![allow(unused_unsafe)]
7#![allow(unused_variables)]
8#![doc = include_str!("../README.md")]
9
10mod common;
11mod generator;
12mod parser;
13pub mod test_logger;
14
15pub use common::*;
16pub use generator::*;
17pub use parser::*;
18
19use proc_macro2::TokenStream;
20use std::fs::OpenOptions;
21use std::io::Write;
22use std::path::Path;
23use std::process::{Command, Stdio};
24
25pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
26pub const COMMON_CODE: &str = include_str!("./common.rs");
27
28pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
29    let path = Path::new(file_path);
30    if let Some(parent) = path.parent() {
31        if !parent.as_os_str().is_empty() {
32            std::fs::create_dir_all(parent)?;
33        }
34    }
35
36    let mut file = OpenOptions::new()
37        .create(true)
38        .write(true)
39        .append(true)
40        .open(path)?;
41
42    writeln!(file, "\n{}", code)?;
43
44    Ok(())
45}
46
47#[allow(dead_code)]
48pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
49    let mut rustfmt = Command::new("rustfmt")
50        .stdin(Stdio::piped())
51        .stdout(Stdio::piped())
52        .spawn()?;
53
54    if let Some(mut stdin) = rustfmt.stdin.take() {
55        stdin.write_all(code.as_bytes())?;
56    }
57
58    let output = rustfmt.wait_with_output()?;
59    let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
60
61    Ok(formatted_code)
62}
63
64#[allow(dead_code)]
65pub fn format_token_stream(tokens: TokenStream) -> String {
66    let code = tokens.to_string();
67
68    match format_with_rustfmt(&code) {
69        Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
70        _ => code.replace("{", "{\n"), // Fallback to unformatted code in case of error
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use crate::generator::MEDIA_DRIVER_BINDINGS;
77    use crate::parser::parse_bindings;
78    use crate::{
79        append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS,
80        CLIENT_BINDINGS, CUSTOM_AERON_CODE,
81    };
82    use proc_macro2::TokenStream;
83    use std::fs;
84
85    // valgrind can give false positives, so we don't want to run on tests which are 100% rust
86    // and do not have any chance of any undefined behaviour i.e. parsing rs and generating code
87    fn running_under_valgrind() -> bool {
88        std::env::var_os("RUSTERON_VALGRIND").is_some()
89    }
90
91    #[test]
92    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
93    fn client() {
94        if running_under_valgrind() {
95            return;
96        }
97
98        let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
99            .join("bindings")
100            .join("client.rs");
101        let mut bindings = parse_bindings(&path);
102        assert_eq!(
103            "AeronImageFragmentAssembler",
104            bindings
105                .wrappers
106                .get("aeron_image_fragment_assembler_t")
107                .unwrap()
108                .class_name
109        );
110        assert_eq!(
111            0,
112            bindings.methods.len(),
113            "expected all methods to have been matched {:#?}",
114            bindings.methods
115        );
116
117        let file = write_to_file(TokenStream::new(), true, "client.rs");
118        let bindings_copy = bindings.clone();
119        for handler in bindings.handlers.iter_mut() {
120            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
121            let _ = crate::generate_handlers(handler, &bindings_copy);
122        }
123        for (p, w) in bindings.wrappers.values().enumerate() {
124            let code = crate::generate_rust_code(
125                w,
126                &bindings.wrappers,
127                p == 0,
128                true,
129                true,
130                &bindings.handlers,
131            );
132            write_to_file(code, false, "client.rs");
133        }
134        let bindings_copy = bindings.clone();
135        for handler in bindings.handlers.iter_mut() {
136            let code = crate::generate_handlers(handler, &bindings_copy);
137            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
138        }
139
140        let t = trybuild::TestCases::new();
141        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
142        append_to_file(&file, CLIENT_BINDINGS).unwrap();
143        append_to_file(&file, "}").unwrap();
144        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
145        append_to_file(&file, "\npub fn main() {}\n").unwrap();
146        t.pass(file)
147    }
148
149    #[test]
150    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
151    fn media_driver() {
152        if running_under_valgrind() {
153            return;
154        }
155
156        let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
157            .join("bindings")
158            .join("media-driver.rs");
159        let mut bindings = parse_bindings(&path);
160        assert_eq!(
161            "AeronImageFragmentAssembler",
162            bindings
163                .wrappers
164                .get("aeron_image_fragment_assembler_t")
165                .unwrap()
166                .class_name
167        );
168
169        let file = write_to_file(TokenStream::new(), true, "md.rs");
170
171        let bindings_copy = bindings.clone();
172        for handler in bindings.handlers.iter_mut() {
173            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
174            let _ = crate::generate_handlers(handler, &bindings_copy);
175        }
176        for (p, w) in bindings
177            .wrappers
178            .values()
179            .filter(|w| !w.type_name.contains("_t_") && w.type_name != "in_addr")
180            .enumerate()
181        {
182            let code = crate::generate_rust_code(
183                w,
184                &bindings.wrappers,
185                p == 0,
186                true,
187                true,
188                &bindings.handlers,
189            );
190            write_to_file(code, false, "md.rs");
191        }
192        let bindings_copy = bindings.clone();
193        for handler in bindings.handlers.iter_mut() {
194            let code = crate::generate_handlers(handler, &bindings_copy);
195            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
196        }
197        let t = trybuild::TestCases::new();
198        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
199        append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
200        append_to_file(&file, "}").unwrap();
201        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
202        append_to_file(&file, "\npub fn main() {}\n").unwrap();
203        t.pass(&file)
204    }
205
206    #[test]
207    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
208    fn archive() {
209        if running_under_valgrind() {
210            return;
211        }
212
213        let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
214            .join("bindings")
215            .join("archive.rs");
216        let mut bindings = parse_bindings(&path);
217        assert_eq!(
218            "AeronImageFragmentAssembler",
219            bindings
220                .wrappers
221                .get("aeron_image_fragment_assembler_t")
222                .unwrap()
223                .class_name
224        );
225
226        let file = write_to_file(TokenStream::new(), true, "archive.rs");
227        let bindings_copy = bindings.clone();
228        for handler in bindings.handlers.iter_mut() {
229            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
230            let _ = crate::generate_handlers(handler, &bindings_copy);
231        }
232        for (p, w) in bindings.wrappers.values().enumerate() {
233            let code = crate::generate_rust_code(
234                w,
235                &bindings.wrappers,
236                p == 0,
237                true,
238                true,
239                &bindings.handlers,
240            );
241            write_to_file(code, false, "archive.rs");
242        }
243        let bindings_copy = bindings.clone();
244        for handler in bindings.handlers.iter_mut() {
245            let code = crate::generate_handlers(handler, &bindings_copy);
246            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
247        }
248        let t = trybuild::TestCases::new();
249        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
250        append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
251        append_to_file(&file, "}").unwrap();
252        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
253        append_to_file(&file, "\npub fn main() {}\n").unwrap();
254        t.pass(file)
255    }
256
257    fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
258        let src = format_token_stream(rust_code);
259        let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
260            .join(format!("../target/{}", name));
261        let path = path.to_str().unwrap();
262        if delete {
263            let _ = fs::remove_file(path);
264        }
265        append_to_file(path, &src).unwrap();
266        path.to_string()
267    }
268}
269
270#[cfg(test)]
271mod test {
272    use crate::ManagedCResource;
273
274    use std::sync::atomic::{AtomicBool, Ordering};
275    use std::sync::Arc;
276
277    fn make_resource(val: i32) -> *mut i32 {
278        Box::into_raw(Box::new(val))
279    }
280
281    unsafe fn reclaim_resource(ptr: *mut i32) {
282        if !ptr.is_null() {
283            let _ = Box::from_raw(ptr);
284        }
285    }
286
287    #[test]
288    fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
289        let flag = Arc::new(AtomicBool::new(false));
290        let flag_clone = flag.clone();
291        let resource_ptr = make_resource(10);
292
293        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
294            flag_clone.store(true, Ordering::SeqCst);
295            // Set the resource to null to simulate cleanup.
296            unsafe {
297                reclaim_resource(*res);
298                *res = std::ptr::null_mut();
299            }
300            0
301        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
302
303        {
304            let _resource = ManagedCResource::new(
305                |res: *mut *mut i32| {
306                    unsafe {
307                        *res = resource_ptr;
308                    }
309                    0
310                },
311                cleanup,
312                false,
313                None,
314            );
315            assert!(_resource.is_ok())
316        }
317        assert!(flag.load(Ordering::SeqCst));
318    }
319
320    #[test]
321    fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
322        let flag = Arc::new(AtomicBool::new(false));
323        let flag_clone = flag.clone();
324        let resource_ptr = make_resource(20);
325
326        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
327            flag_clone.store(true, Ordering::SeqCst);
328            unsafe {
329                *res = std::ptr::null_mut();
330            }
331            0
332        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
333
334        {
335            let _resource = ManagedCResource::new(
336                |res: *mut *mut i32| {
337                    unsafe {
338                        *res = resource_ptr;
339                    }
340                    0
341                },
342                cleanup,
343                true,
344                None,
345            );
346            assert!(_resource.is_ok())
347        }
348        assert!(flag.load(Ordering::SeqCst));
349    }
350
351    #[test]
352    fn test_drop_does_not_call_cleanup_if_already_closed() {
353        let flag = Arc::new(AtomicBool::new(false));
354        let flag_clone = flag.clone();
355        let resource_ptr = make_resource(30);
356
357        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
358            flag_clone.store(true, Ordering::SeqCst);
359            unsafe {
360                reclaim_resource(*res);
361                *res = std::ptr::null_mut();
362            }
363            0
364        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
365
366        let mut resource = ManagedCResource::new(
367            |res: *mut *mut i32| {
368                unsafe {
369                    *res = resource_ptr;
370                }
371                0
372            },
373            cleanup,
374            false,
375            None,
376        );
377        assert!(resource.is_ok());
378
379        if let Ok(ref mut resource) = &mut resource {
380            assert!(resource.close().is_ok())
381        }
382
383        // Reset the flag to ensure drop does not call cleanup a second time.
384        flag.store(false, Ordering::SeqCst);
385        drop(resource);
386        assert!(!flag.load(Ordering::SeqCst));
387    }
388
389    #[test]
390    fn test_drop_does_not_call_cleanup_if_check_for_is_closed_returns_true() {
391        let flag = Arc::new(AtomicBool::new(false));
392        let flag_clone = flag.clone();
393        let resource_ptr = make_resource(60);
394
395        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
396            flag_clone.store(true, Ordering::SeqCst);
397            unsafe {
398                reclaim_resource(*res);
399                *res = std::ptr::null_mut();
400            }
401            0
402        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
403
404        let check_fn = Some(|_res: *mut i32| -> bool { true } as fn(_) -> bool);
405
406        {
407            let _resource = ManagedCResource::new(
408                |res: *mut *mut i32| {
409                    unsafe {
410                        *res = resource_ptr;
411                    }
412                    0
413                },
414                cleanup,
415                false,
416                check_fn,
417            );
418            assert!(_resource.is_ok());
419        }
420        assert!(!flag.load(Ordering::SeqCst));
421        unsafe {
422            reclaim_resource(resource_ptr);
423        }
424    }
425
426    #[test]
427    fn test_drop_does_call_cleanup_if_check_for_is_closed_returns_false() {
428        let flag = Arc::new(AtomicBool::new(false));
429        let flag_clone = flag.clone();
430        let resource_ptr = make_resource(60);
431
432        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
433            flag_clone.store(true, Ordering::SeqCst);
434            unsafe {
435                reclaim_resource(*res);
436                *res = std::ptr::null_mut();
437            }
438            0
439        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
440
441        let check_fn = Some(|_res: *mut i32| -> bool { false } as fn(*mut i32) -> bool);
442
443        {
444            let _resource = ManagedCResource::new(
445                |res: *mut *mut i32| {
446                    unsafe {
447                        *res = resource_ptr;
448                    }
449                    0
450                },
451                cleanup,
452                false,
453                check_fn,
454            );
455            assert!(_resource.is_ok())
456        }
457        assert!(flag.load(Ordering::SeqCst));
458    }
459}