charon_driver/
main.rs

1//! The Charon driver, which calls Rustc with callbacks to compile some Rust
2//! crate to LLBC.
3#![feature(rustc_private)]
4#![expect(incomplete_features)]
5#![feature(box_patterns)]
6#![feature(deref_patterns)]
7#![feature(if_let_guard)]
8#![feature(iter_array_chunks)]
9#![feature(iterator_try_collect)]
10#![feature(let_chains)]
11
12extern crate rustc_abi;
13extern crate rustc_ast;
14extern crate rustc_ast_pretty;
15extern crate rustc_driver;
16extern crate rustc_error_messages;
17extern crate rustc_errors;
18extern crate rustc_hir;
19extern crate rustc_index;
20extern crate rustc_interface;
21extern crate rustc_middle;
22extern crate rustc_session;
23extern crate rustc_span;
24extern crate rustc_target;
25
26#[macro_use]
27extern crate charon_lib;
28
29mod driver;
30mod translate;
31
32use charon_lib::{
33    export, logger,
34    options::{self, CliOpts},
35    transform::{
36        Pass, PrintCtxPass, FINAL_CLEANUP_PASSES, INITIAL_CLEANUP_PASSES, LLBC_PASSES,
37        SHARED_FINALIZING_PASSES, ULLBC_PASSES,
38    },
39};
40use std::{env, fmt, panic};
41
42pub enum CharonFailure {
43    /// The usize is the number of errors.
44    CharonError(usize),
45    RustcError,
46    Panic,
47    Serialize,
48}
49
50impl fmt::Display for CharonFailure {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            CharonFailure::RustcError => write!(f, "Code failed to compile")?,
54            CharonFailure::CharonError(err_count) => write!(
55                f,
56                "Charon failed to translate this code ({err_count} errors)"
57            )?,
58            CharonFailure::Panic => write!(f, "Compilation panicked")?,
59            CharonFailure::Serialize => write!(f, "Could not serialize output file")?,
60        }
61        Ok(())
62    }
63}
64
65/// Calculate the list of passes we will run on the crate before outputting it.
66pub fn transformation_passes(options: &CliOpts) -> Vec<Pass> {
67    let mut passes: Vec<Pass> = vec![];
68
69    passes.push(Pass::NonBody(PrintCtxPass::new(
70        options.print_original_ullbc,
71        format!("# ULLBC after translation from MIR"),
72    )));
73
74    passes.extend(INITIAL_CLEANUP_PASSES);
75    passes.extend(ULLBC_PASSES);
76
77    if !options.ullbc {
78        // If we're reconstructing control-flow, print the ullbc here.
79        passes.push(Pass::NonBody(PrintCtxPass::new(
80            options.print_ullbc,
81            format!("# Final ULLBC before control-flow reconstruction"),
82        )));
83    }
84
85    if !options.ullbc {
86        passes.extend(LLBC_PASSES);
87    }
88    passes.extend(SHARED_FINALIZING_PASSES);
89
90    if options.ullbc {
91        // If we're not reconstructing control-flow, print the ullbc after finalizing passes.
92        passes.push(Pass::NonBody(PrintCtxPass::new(
93            options.print_ullbc,
94            format!("# Final ULLBC before serialization"),
95        )));
96    } else {
97        passes.push(Pass::NonBody(PrintCtxPass::new(
98            options.print_llbc,
99            format!("# Final LLBC before serialization"),
100        )));
101    }
102
103    // Run the final passes after pretty-printing so that we get some output even if check_generics
104    // fails.
105    passes.extend(FINAL_CLEANUP_PASSES);
106    passes
107}
108
109/// Run charon. Returns the number of warnings generated.
110fn run_charon(options: CliOpts) -> Result<usize, CharonFailure> {
111    // Run the driver machinery.
112    let Some(mut ctx) = driver::run_rustc_driver(&options)? else {
113        // We didn't run charon.
114        return Ok(0);
115    };
116
117    // The bulk of the translation is done, we no longer need to interact with rustc internals. We
118    // run several passes that simplify the items and cleanup the bodies.
119    for pass in transformation_passes(&options) {
120        trace!("# Starting pass {}", pass.name());
121        pass.run(&mut ctx);
122    }
123
124    let error_count = ctx.errors.borrow().error_count;
125
126    // # Final step: generate the files.
127    if !options.no_serialize {
128        let crate_data = export::CrateData::new(ctx);
129        let dest_file = match options.dest_file.clone() {
130            Some(f) => f,
131            None => {
132                let mut target_filename = options.dest_dir.clone().unwrap_or_default();
133                let crate_name = &crate_data.translated.crate_name;
134                let extension = if options.ullbc { "ullbc" } else { "llbc" };
135                target_filename.push(format!("{crate_name}.{extension}"));
136                target_filename
137            }
138        };
139        trace!("Target file: {:?}", dest_file);
140        crate_data
141            .serialize_to_file(&dest_file)
142            .map_err(|()| CharonFailure::Serialize)?;
143    }
144
145    if options.error_on_warnings && error_count != 0 {
146        return Err(CharonFailure::CharonError(error_count));
147    }
148
149    Ok(error_count)
150}
151
152fn main() {
153    // Initialize the logger
154    logger::initialize_logger();
155
156    // Retrieve the Charon options by deserializing them from the environment variable
157    // (cargo-charon serialized the arguments and stored them in a specific environment
158    // variable before calling cargo with RUSTC_WRAPPER=charon-driver).
159    let mut options: options::CliOpts = match env::var(options::CHARON_ARGS) {
160        Ok(opts) => serde_json::from_str(opts.as_str()).unwrap(),
161        Err(_) => Default::default(),
162    };
163    options.apply_preset();
164
165    // Catch any and all panics coming from charon to display a clear error.
166    let res = panic::catch_unwind(move || run_charon(options))
167        .map_err(|_| CharonFailure::Panic)
168        .and_then(|x| x);
169
170    match res {
171        Ok(warn_count) => {
172            if warn_count != 0 {
173                let msg = format!("The extraction generated {} warnings", warn_count);
174                eprintln!("warning: {}", msg);
175            }
176        }
177        Err(err) => {
178            log::error!("{err}");
179            let exit_code = match err {
180                CharonFailure::CharonError(_) | CharonFailure::Serialize => 1,
181                CharonFailure::RustcError => 2,
182                // This is a real panic, exit with the standard rust panic error code.
183                CharonFailure::Panic => 101,
184            };
185            std::process::exit(exit_code);
186        }
187    }
188}