1#![allow(clippy::too_many_arguments)]
2
3mod diagnostics;
4
5pub mod build_files;
6pub mod c_ast;
7pub mod cfg;
8mod compile_cmds;
9pub mod convert_type;
10pub mod renamer;
11pub mod rust_ast;
12pub mod translator;
13pub mod with_stmts;
14
15use std::collections::HashSet;
16use std::fs::{self, File};
17use std::io::prelude::*;
18use std::path::{Path, PathBuf};
19use std::process::Command;
20use std::{env, io};
21
22use crate::compile_cmds::CompileCmd;
23use failure::Error;
24use itertools::Itertools;
25use log::{info, warn};
26use regex::Regex;
27use serde_derive::Serialize;
28pub use tempfile::TempDir;
29
30use crate::c_ast::Printer;
31use crate::c_ast::*;
32pub use crate::diagnostics::Diagnostic;
33use c2rust_ast_exporter as ast_exporter;
34
35use crate::build_files::{emit_build_files, get_build_dir, CrateConfig};
36use crate::compile_cmds::get_compile_commands;
37use crate::convert_type::RESERVED_NAMES;
38pub use crate::translator::ReplaceMode;
39use std::prelude::v1::Vec;
40
41type PragmaVec = Vec<(&'static str, Vec<&'static str>)>;
42type PragmaSet = indexmap::IndexSet<(&'static str, &'static str)>;
43type CrateSet = indexmap::IndexSet<ExternCrate>;
44type TranspileResult = Result<(PathBuf, PragmaVec, CrateSet), ()>;
45
46#[derive(Default, Debug)]
47pub enum TranslateMacros {
48 None,
50
51 #[default]
53 Conservative,
54
55 Experimental,
60}
61
62#[derive(Debug)]
64pub struct TranspilerConfig {
65 pub dump_untyped_context: bool,
67 pub dump_typed_context: bool,
68 pub pretty_typed_context: bool,
69 pub dump_function_cfgs: bool,
70 pub json_function_cfgs: bool,
71 pub dump_cfg_liveness: bool,
72 pub dump_structures: bool,
73 pub verbose: bool,
74 pub debug_ast_exporter: bool,
75 pub emit_c_decl_map: bool,
76
77 pub incremental_relooper: bool,
79 pub fail_on_multiple: bool,
80 pub filter: Option<Regex>,
81 pub debug_relooper_labels: bool,
82 pub cross_checks: bool,
83 pub cross_check_backend: String,
84 pub cross_check_configs: Vec<String>,
85 pub prefix_function_names: Option<String>,
86 pub translate_asm: bool,
87 pub use_c_loop_info: bool,
88 pub use_c_multiple_info: bool,
89 pub simplify_structures: bool,
90 pub panic_on_translator_failure: bool,
91 pub emit_modules: bool,
92 pub fail_on_error: bool,
93 pub replace_unsupported_decls: ReplaceMode,
94 pub translate_valist: bool,
95 pub overwrite_existing: bool,
96 pub reduce_type_annotations: bool,
97 pub reorganize_definitions: bool,
98 pub enabled_warnings: HashSet<Diagnostic>,
99 pub emit_no_std: bool,
100 pub output_dir: Option<PathBuf>,
101 pub translate_const_macros: TranslateMacros,
102 pub translate_fn_macros: TranslateMacros,
103 pub disable_rustfmt: bool,
104 pub disable_refactoring: bool,
105 pub preserve_unused_functions: bool,
106 pub log_level: log::LevelFilter,
107
108 pub emit_build_files: bool,
111
112 pub binaries: Vec<String>,
115
116 pub c2rust_dir: Option<PathBuf>,
117}
118
119impl TranspilerConfig {
120 fn binary_name_from_path(file: &Path) -> String {
121 let file = Path::new(file.file_stem().unwrap());
122 get_module_name(file, false, false, false).unwrap()
123 }
124
125 fn is_binary(&self, file: &Path) -> bool {
126 let module_name = Self::binary_name_from_path(file);
127 self.binaries.contains(&module_name)
128 }
129
130 fn check_if_all_binaries_used(
131 &self,
132 transpiled_modules: impl IntoIterator<Item = impl AsRef<Path>>,
133 ) -> bool {
134 let module_names = transpiled_modules
135 .into_iter()
136 .map(|module| Self::binary_name_from_path(module.as_ref()))
137 .collect::<HashSet<_>>();
138 let mut ok = true;
139 for binary in &self.binaries {
140 if !module_names.contains(binary) {
141 ok = false;
142 warn!("binary not used: {binary}");
143 }
144 }
145 if !ok {
146 let module_names = module_names.iter().format(", ");
147 info!("candidate modules for binaries are: {module_names}");
148 }
149 ok
150 }
151
152 fn crate_name(&self) -> String {
153 self.output_dir
154 .as_ref()
155 .and_then(|dir| dir.file_name())
156 .map(|fname| str_to_ident_checked(fname.to_string_lossy().as_ref(), true))
157 .unwrap_or_else(|| "c2rust_out".into())
158 }
159}
160
161#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
162pub enum ExternCrate {
163 C2RustBitfields,
164 C2RustAsmCasts,
165 F128,
166 NumTraits,
167 Memoffset,
168 Libc,
169}
170
171#[derive(Serialize)]
172struct ExternCrateDetails {
173 name: &'static str,
174 ident: String,
175 macro_use: bool,
176 version: &'static str,
177 path: Option<PathBuf>,
178}
179
180impl ExternCrateDetails {
181 pub fn new(
182 name: &'static str,
183 version: &'static str,
184 macro_use: bool,
185 path: Option<PathBuf>,
186 ) -> Self {
187 Self {
188 name,
189 ident: name.replace('-', "_"),
190 macro_use,
191 version,
192 path,
193 }
194 }
195
196 pub fn external(name: &'static str, version: &'static str, macro_use: bool) -> Self {
198 Self::new(name, version, macro_use, None)
199 }
200
201 pub fn internal(name: &'static str, macro_use: bool, c2rust_dir: Option<&Path>) -> Self {
203 Self::new(
204 name,
205 env!("CARGO_PKG_VERSION"),
206 macro_use,
207 c2rust_dir.map(|dir| dir.join(name)),
208 )
209 }
210}
211
212impl ExternCrate {
213 fn with_details(&self, c2rust_dir: Option<&Path>) -> ExternCrateDetails {
214 use ExternCrate::*;
215 match self {
216 C2RustBitfields => ExternCrateDetails::internal("c2rust-bitfields", true, c2rust_dir),
217 C2RustAsmCasts => ExternCrateDetails::internal("c2rust-asm-casts", true, c2rust_dir),
218 F128 => ExternCrateDetails::external("f128", "0.2", false),
219 NumTraits => ExternCrateDetails::external("num-traits", "0.2", true),
220 Memoffset => ExternCrateDetails::external("memoffset", "0.5", true),
221 Libc => ExternCrateDetails::external("libc", "0.2", false),
222 }
223 }
224}
225
226fn char_to_ident(c: char) -> char {
227 if c.is_alphanumeric() {
228 c
229 } else {
230 '_'
231 }
232}
233
234fn str_to_ident(s: &str) -> String {
235 s.chars().map(char_to_ident).collect()
236}
237
238fn str_to_ident_checked(s: &str, check_reserved: bool) -> String {
242 let s = str_to_ident(s);
243
244 if check_reserved && RESERVED_NAMES.contains(&s.as_str()) {
246 format!("r#{}", s)
247 } else {
248 s
249 }
250}
251
252fn get_module_name(
253 file: &Path,
254 check_reserved: bool,
255 keep_extension: bool,
256 full_path: bool,
257) -> Option<String> {
258 let is_rs = file.extension().map(|ext| ext == "rs").unwrap_or(false);
259 let fname = if is_rs {
260 file.file_stem()
261 } else {
262 file.file_name()
263 };
264 let fname = fname.unwrap().to_str().unwrap();
265 let mut name = str_to_ident_checked(fname, check_reserved);
266 if keep_extension && is_rs {
267 name.push_str(".rs");
268 }
269 let file = if full_path {
270 file.with_file_name(name)
271 } else {
272 Path::new(&name).to_path_buf()
273 };
274 file.to_str().map(String::from)
275}
276
277pub fn create_temp_compile_commands(sources: &[PathBuf]) -> (TempDir, PathBuf) {
278 let temp_dir = tempfile::Builder::new()
284 .prefix("c2rust-")
285 .tempdir()
286 .expect("Failed to create temporary directory for compile_commands.json");
287 let temp_path = temp_dir.path().join("compile_commands.json");
288
289 let compile_commands: Vec<CompileCmd> = sources
290 .iter()
291 .map(|source_file| {
292 let absolute_path = fs::canonicalize(source_file)
293 .unwrap_or_else(|_| panic!("Could not canonicalize {}", source_file.display()));
294
295 CompileCmd {
296 directory: PathBuf::from("."),
297 file: absolute_path.clone(),
298 arguments: vec![
299 "clang".to_string(),
300 absolute_path.to_str().unwrap().to_owned(),
301 ],
302 command: None,
303 output: None,
304 }
305 })
306 .collect();
307
308 let json_content = serde_json::to_string(&compile_commands).unwrap();
309 let mut file =
310 File::create(&temp_path).expect("Failed to create temporary compile_commands.json");
311 file.write_all(json_content.as_bytes())
312 .expect("Failed to write to temporary compile_commands.json");
313 (temp_dir, temp_path)
314}
315
316pub fn transpile(tcfg: TranspilerConfig, cc_db: &Path, extra_clang_args: &[&str]) {
319 diagnostics::init(tcfg.enabled_warnings.clone(), tcfg.log_level);
320
321 let build_dir = get_build_dir(&tcfg, cc_db);
322
323 let lcmds = get_compile_commands(cc_db, &tcfg.filter).unwrap_or_else(|_| {
324 panic!(
325 "Could not parse compile commands from {}",
326 cc_db.to_string_lossy()
327 )
328 });
329
330 let clang_args: Vec<String> = get_extra_args_macos();
332 let mut clang_args: Vec<&str> = clang_args.iter().map(AsRef::as_ref).collect();
333 clang_args.extend_from_slice(extra_clang_args);
334
335 let mut top_level_ccfg = None;
336 let mut workspace_members = vec![];
337 let mut num_transpiled_files = 0;
338 let mut transpiled_modules = Vec::new();
339
340 for lcmd in &lcmds {
341 let cmds = &lcmd.cmd_inputs;
342 let lcmd_name = lcmd
343 .output
344 .as_ref()
345 .map(|output| {
346 let output_path = Path::new(output);
347 output_path
348 .file_stem()
349 .unwrap()
350 .to_str()
351 .unwrap()
352 .to_owned()
353 })
354 .unwrap_or_else(|| tcfg.crate_name());
355 let build_dir = if lcmd.top_level {
356 build_dir.to_path_buf()
357 } else {
358 build_dir.join(&lcmd_name)
359 };
360
361 let mut ancestor_path = cmds
364 .first()
365 .map(|cmd| {
366 let mut dir = cmd.abs_file();
367 dir.pop(); dir
369 })
370 .unwrap_or_else(PathBuf::new);
371 if cmds.len() > 1 {
372 for cmd in &cmds[1..] {
373 let cmd_path = cmd.abs_file();
374 ancestor_path = ancestor_path
375 .ancestors()
376 .find(|a| cmd_path.starts_with(a))
377 .map(ToOwned::to_owned)
378 .unwrap_or_else(PathBuf::new);
379 }
380 }
381
382 let results = cmds
383 .iter()
384 .map(|cmd| {
385 transpile_single(
386 &tcfg,
387 &cmd.abs_file(),
388 &ancestor_path,
389 &build_dir,
390 cc_db,
391 &clang_args,
392 )
393 })
394 .collect::<Vec<TranspileResult>>();
395 let mut modules = vec![];
396 let mut modules_skipped = false;
397 let mut pragmas = PragmaSet::new();
398 let mut crates = CrateSet::new();
399 for res in results {
400 match res {
401 Ok((module, pragma_vec, crate_set)) => {
402 modules.push(module);
403 crates.extend(crate_set);
404
405 num_transpiled_files += 1;
406 for (key, vals) in pragma_vec {
407 for val in vals {
408 pragmas.insert((key, val));
409 }
410 }
411 }
412 Err(_) => {
413 modules_skipped = true;
414 }
415 }
416 }
417 pragmas.sort();
418 crates.sort();
419
420 transpiled_modules.extend(modules.iter().cloned());
421
422 if tcfg.emit_build_files {
423 if modules_skipped {
424 warn!("Can't emit build files after incremental transpiler run; skipped.");
426 return;
427 }
428
429 let ccfg = CrateConfig {
430 crate_name: lcmd_name.clone(),
431 modules,
432 pragmas,
433 crates,
434 link_cmd: lcmd,
435 };
436 if lcmd.top_level {
437 top_level_ccfg = Some(ccfg);
438 } else {
439 let crate_file = emit_build_files(&tcfg, &build_dir, Some(ccfg), None);
440 reorganize_definitions(&tcfg, &build_dir, crate_file)
441 .unwrap_or_else(|e| warn!("Reorganizing definitions failed: {}", e));
442 workspace_members.push(lcmd_name);
443 }
444 }
445 }
446
447 if num_transpiled_files == 0 {
448 warn!("No C files found in compile_commands.json; nothing to do.");
449 return;
450 }
451
452 if tcfg.emit_build_files {
453 let crate_file =
454 emit_build_files(&tcfg, &build_dir, top_level_ccfg, Some(workspace_members));
455 reorganize_definitions(&tcfg, &build_dir, crate_file)
456 .unwrap_or_else(|e| warn!("Reorganizing definitions failed: {}", e));
457 }
458
459 tcfg.check_if_all_binaries_used(&transpiled_modules);
460}
461
462fn get_extra_args_macos() -> Vec<String> {
471 let mut args = vec![];
472 if cfg!(target_os = "macos") {
473 let usr_incl = Path::new("/usr/include");
474 if !usr_incl.exists() {
475 let output = Command::new("xcrun")
476 .args(["--show-sdk-path"])
477 .output()
478 .expect("failed to run `xcrun` subcommand");
479 let mut sdk_path = String::from_utf8(output.stdout).unwrap();
480 let olen = sdk_path.len();
481 sdk_path.truncate(olen - 1);
482 sdk_path.push_str("/usr/include");
483
484 args.push("-isystem".to_owned());
485 args.push(sdk_path);
486 }
487
488 args.push("-fno-blocks".to_owned());
490 }
491 args
492}
493
494fn invoke_refactor(build_dir: &Path) -> Result<(), Error> {
495 let status = Command::new("cargo")
497 .args(["check"])
498 .env("RUSTFLAGS", "-Awarnings")
499 .current_dir(build_dir)
500 .status()?;
501 if !status.success() {
502 return Err(failure::format_err!("Crate does not compile."));
503 }
504
505 let refactor = env::current_exe()
507 .expect("Cannot get current executable path")
508 .with_file_name("c2rust-refactor");
509 let args = [
510 "--cargo",
511 "--rewrite-mode",
512 "inplace",
513 "rename_unnamed",
514 ";",
515 "reorganize_definitions",
516 ];
517 let status = Command::new(&refactor)
518 .args(args)
519 .current_dir(build_dir)
520 .status()
521 .map_err(|e| {
522 let refactor = refactor.display();
523 failure::format_err!("unable to run {refactor}: {e}\nNote that c2rust-refactor must be installed separately from c2rust and c2rust-transpile.")
524 })?;
525 if status.success() {
526 Ok(())
527 } else {
528 Err(failure::format_err!(
529 "Refactoring failed. Please fix errors above and re-run:\n c2rust refactor {}",
530 args.join(" "),
531 ))
532 }
533}
534
535fn reorganize_definitions(
536 tcfg: &TranspilerConfig,
537 build_dir: &Path,
538 crate_file: Option<PathBuf>,
539) -> Result<(), Error> {
540 if crate_file.is_none() || tcfg.disable_refactoring || !tcfg.reorganize_definitions {
542 return Ok(());
543 }
544
545 invoke_refactor(build_dir)?;
546
547 if !tcfg.disable_rustfmt {
548 let status = Command::new("cargo")
550 .args(["fmt"])
551 .current_dir(build_dir)
552 .status()?;
553 if !status.success() {
554 warn!("cargo fmt failed, code may not be well-formatted");
555 }
556 }
557
558 Ok(())
559}
560
561fn transpile_single(
562 tcfg: &TranspilerConfig,
563 input_path: &Path,
564 ancestor_path: &Path,
565 build_dir: &Path,
566 cc_db: &Path,
567 extra_clang_args: &[&str],
568) -> TranspileResult {
569 let output_path = get_output_path(tcfg, input_path, ancestor_path, build_dir);
570 if output_path.exists() && !tcfg.overwrite_existing {
571 warn!("Skipping existing file {}", output_path.display());
572 return Err(());
573 }
574
575 let file = input_path.file_name().unwrap().to_str().unwrap();
576 if !input_path.exists() {
577 warn!(
578 "Input C file {} does not exist, skipping!",
579 input_path.display()
580 );
581 return Err(());
582 }
583
584 if tcfg.verbose {
585 println!("Additional Clang arguments: {}", extra_clang_args.join(" "));
586 }
587
588 let untyped_context = match ast_exporter::get_untyped_ast(
590 input_path,
591 cc_db,
592 extra_clang_args,
593 tcfg.debug_ast_exporter,
594 ) {
595 Err(e) => {
596 warn!(
597 "Error: {}. Skipping {}; is it well-formed C?",
598 e,
599 input_path.display()
600 );
601 return Err(());
602 }
603 Ok(cxt) => cxt,
604 };
605
606 println!("Transpiling {}", file);
607
608 if tcfg.dump_untyped_context {
609 println!("CBOR Clang AST");
610 println!("{:#?}", untyped_context);
611 }
612
613 let typed_context = {
615 let conv = ConversionContext::new(input_path, &untyped_context);
616 if conv.invalid_clang_ast && tcfg.fail_on_error {
617 panic!("Clang AST was invalid");
618 }
619 conv.into_typed_context()
620 };
621
622 if tcfg.dump_typed_context {
623 println!("Clang AST");
624 println!("{:#?}", typed_context);
625 }
626
627 if tcfg.pretty_typed_context {
628 println!("Pretty-printed Clang AST");
629 println!("{:#?}", Printer::new(io::stdout()).print(&typed_context));
630 }
631
632 let (translated_string, maybe_decl_map, pragmas, crates) =
634 translator::translate(typed_context, tcfg, input_path);
635
636 if let Some(decl_map) = maybe_decl_map {
637 let decl_map_path = output_path.with_extension("c_decls.json");
638 let file = match File::create(&decl_map_path) {
639 Ok(file) => file,
640 Err(e) => panic!(
641 "Unable to open file {} for writing: {}",
642 output_path.display(),
643 e
644 ),
645 };
646
647 match serde_json::ser::to_writer(file, &decl_map) {
648 Ok(()) => (),
649 Err(e) => panic!(
650 "Unable to write C declaration map to file {}: {}",
651 output_path.display(),
652 e
653 ),
654 };
655 }
656
657 let mut file = match File::create(&output_path) {
658 Ok(file) => file,
659 Err(e) => panic!(
660 "Unable to open file {} for writing: {}",
661 output_path.display(),
662 e
663 ),
664 };
665
666 match file.write_all(translated_string.as_bytes()) {
667 Ok(()) => (),
668 Err(e) => panic!(
669 "Unable to write translation to file {}: {}",
670 output_path.display(),
671 e
672 ),
673 };
674
675 if !tcfg.disable_rustfmt {
676 rustfmt(&output_path, build_dir);
677 }
678
679 Ok((output_path, pragmas, crates))
680}
681
682fn get_output_path(
683 tcfg: &TranspilerConfig,
684 input_path: &Path,
685 ancestor_path: &Path,
686 build_dir: &Path,
687) -> PathBuf {
688 let file_name = input_path
691 .file_name()
692 .unwrap()
693 .to_str()
694 .unwrap()
695 .replace('-', "_");
696
697 let mut input_path = input_path.with_file_name(file_name);
698 input_path.set_extension("rs");
699
700 if tcfg.output_dir.is_some() {
701 let path_buf = input_path
702 .strip_prefix(ancestor_path)
703 .expect("Couldn't strip common ancestor path");
704
705 let mut output_path = build_dir.to_path_buf();
707 output_path.push("src");
708 for elem in path_buf.iter() {
709 let path = Path::new(elem);
710 let name = get_module_name(path, false, true, false).unwrap();
711 output_path.push(name);
712 }
713
714 let parent = output_path.parent().unwrap();
716 if !parent.exists() {
717 fs::create_dir_all(parent).unwrap_or_else(|_| {
718 panic!("couldn't create source directory: {}", parent.display())
719 });
720 }
721 output_path
722 } else {
723 input_path
724 }
725}
726
727fn rustfmt(output_path: &Path, build_dir: &Path) {
728 let edition = "2021";
729
730 let status = Command::new("rustfmt")
731 .args(["--edition", edition])
732 .arg(output_path)
733 .current_dir(build_dir)
734 .status();
735
736 if !status.map_or(false, |status| status.success()) {
737 warn!("rustfmt failed, code may not be well-formatted");
738 }
739}