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