include/
lib.rs

1extern crate proc_macro;
2
3use c2rust_transpile::RustEdition;
4use proc_macro::TokenStream;
5use std::path::{Path, PathBuf};
6
7use anyhow::Context;
8use rand::Rng;
9
10use c2rust_transpile::{transpile_single_to_buffer, CompileCmd, ReplaceMode, TranspilerConfig};
11
12#[proc_macro]
13pub fn c(input: TokenStream) -> TokenStream {
14    let c_code = input.to_string();
15
16    // need to detect something that has runtim different between rust  Edition2021 Edition2024
17
18    // don't want to add syn yet
19    assert!(
20        c_code.starts_with("r#\""),
21        "C code must be passed as rust raw string literal starting with r#\""
22    );
23    assert!(
24        c_code.ends_with("\"#"),
25        "C code must be passed as rust raw string literal"
26    );
27
28    let transplied_code =
29        transpile_code(&c_code[3..c_code.len() - 2]).expect("transpilation failure");
30
31    transplied_code.parse().expect("rust parse output error")
32}
33
34fn temp_build_dir() -> std::io::Result<PathBuf> {
35    let tmp = std::env::temp_dir();
36    let random: u64 = rand::rng().random();
37
38    let build_path = tmp.join("include-c2rust").join(random.to_string());
39    std::fs::create_dir_all(&build_path)?;
40    Ok(build_path)
41}
42
43fn transpile_code(c_code: &str) -> anyhow::Result<String> {
44    let build_dir = temp_build_dir().context("unable to make tmp dir")?;
45    eprintln!("output dir: {}", build_dir.display());
46
47    let c_file_path = build_dir.join("code.c");
48    std::fs::write(&c_file_path, c_code).expect("Unable to write c code to temporary file.");
49    let transpile_config = make_transpile_config();
50    let cc_db = compile_commands(&build_dir, &c_file_path);
51    eprintln!("ccdb {cc_db:?}");
52
53    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
54    let _guard = LOCK.lock().unwrap();
55    // something this function calls isn't re-entrant
56    // add a mutex guard to prevent it from causing flaky tests
57    // TODO remove this and fix the code at the location below
58    //
59    // ast_exporter: for the -p option: may only occur zero or one times!
60    // [Parse Error]
61    // include-d771570fb872ba35: c2rust-ast-exporter/src/AstExporter.cpp:2815: Outputs process(int, const char**, int*): Assertion `0 && "Failed to parse command line options"' failed.
62    // error: test failed, to rerun pass `--lib`
63
64    let (out, _, _) = transpile_single_to_buffer(&transpile_config, c_file_path, &cc_db, &[])
65        .map_err(|()| std::io::Error::other("transpilation failure"))?;
66
67    Ok(out)
68}
69
70fn compile_commands(build_dir: &Path, source_file: &Path) -> PathBuf {
71    let cc_db_file = build_dir.join("compile_commands.json");
72
73    let absolute_path = std::fs::canonicalize(source_file)
74        .unwrap_or_else(|_| panic!("Could not canonicalize {}", source_file.display()));
75
76    let compile_commands = [CompileCmd {
77        directory: PathBuf::from("."),
78        file: absolute_path.clone(),
79        arguments: vec![
80            "clang".to_string(),
81            absolute_path.to_str().unwrap().to_owned(),
82        ],
83        command: None,
84        output: None,
85    }];
86
87    let json_content = serde_json::to_string(&compile_commands).unwrap();
88    std::fs::write(&cc_db_file, &json_content)
89        .expect("Failed to write to temporary compile_commands.json");
90
91    cc_db_file
92}
93
94fn make_transpile_config() -> TranspilerConfig {
95    // note: lots of massaging had to be done with these args to get things to work
96    // don't expect things to work properly if you change the options
97    TranspilerConfig {
98        verbose: true,
99        incremental_relooper: true,
100        fail_on_multiple: true,
101        use_c_multiple_info: true,
102        simplify_structures: true,
103        panic_on_translator_failure: true,
104        emit_modules: true, // must be true, otherwise we emit #![allow(...)] attributes in the middle of a file
105        fail_on_error: true,
106        replace_unsupported_decls: ReplaceMode::None, // not sure about this one
107        translate_valist: false,                      // must be false, otherwise we require nightly
108        overwrite_existing: true,
109        reduce_type_annotations: true,
110        reorganize_definitions: false,
111        emit_no_std: false,
112        output_dir: None, // broken if we specify output dir
113        disable_refactoring: false,
114        preserve_unused_functions: true,
115        // below are things we don't care about
116        dump_untyped_context: false,
117        dump_typed_context: false,
118        pretty_typed_context: false,
119        dump_function_cfgs: false,
120        json_function_cfgs: false,
121        dump_cfg_liveness: false,
122        dump_structures: false,
123        debug_ast_exporter: false,
124        filter: None,
125        debug_relooper_labels: false,
126        prefix_function_names: None,
127        translate_asm: false,
128        use_c_loop_info: false,
129        enabled_warnings: std::collections::HashSet::new(),
130        translate_const_macros: false,
131        translate_fn_macros: false,
132        log_level: None,
133        max_rust_edition: RustEdition::Edition2024,
134        emit_build_files: false,
135        binaries: vec![],
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_compile() {
145        let rust_translation = transpile_code("int foo() {return 1;}").unwrap();
146
147        insta::assert_snapshot!(rust_translation, @r###"
148        #[unsafe(no_mangle)]
149        pub unsafe extern "C" fn foo() -> core::ffi::c_int {
150            unsafe {
151                return 1 as core::ffi::c_int;
152            }
153        }
154        "###);
155    }
156
157    #[test]
158    fn test_compile_access_ptr() {
159        let rust_translation = transpile_code("int access_ptr(int* p) { return *p; }").unwrap();
160
161        insta::assert_snapshot!(rust_translation, @r###"
162    #[unsafe(no_mangle)]
163    pub unsafe extern "C" fn access_ptr(mut p: *mut core::ffi::c_int) -> core::ffi::c_int {
164        unsafe {
165            return *p;
166        }
167    }
168    "###);
169    }
170}