Skip to main content

charon_driver/
driver.rs

1//! Run the rustc compiler with our custom options and hooks.
2use crate::CharonFailure;
3use crate::translate::translate_crate;
4use charon_lib::common::arg_value;
5use charon_lib::errors::ErrorCtx;
6use charon_lib::options::{self, CliOpts};
7use charon_lib::transform::TransformCtx;
8use itertools::Itertools;
9use rustc_driver::{Callbacks, Compilation};
10use rustc_interface::Config;
11use rustc_interface::interface::Compiler;
12use rustc_middle::ty::TyCtxt;
13use rustc_middle::util::Providers;
14use rustc_session::config::{OutputType, OutputTypes};
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::{env, fmt};
17
18/// Helper that runs the compiler and catches its fatal errors.
19fn run_compiler_with_callbacks(
20    args: Vec<String>,
21    callbacks: &mut (dyn Callbacks + Send),
22) -> Result<(), CharonFailure> {
23    rustc_driver::catch_fatal_errors(|| rustc_driver::run_compiler(&args, callbacks))
24        .map_err(|_| CharonFailure::RustcError)
25}
26
27/// Tweak options to get usable MIR even for foreign crates.
28fn set_mir_options(config: &mut Config) {
29    config.opts.unstable_opts.always_encode_mir = true;
30    config.opts.unstable_opts.mir_opt_level = Some(0);
31    config.opts.unstable_opts.mir_emit_retag = true;
32    config.opts.unstable_opts.mir_preserve_ub = true;
33    let disabled_mir_passes = ["CheckAlignment"];
34    for pass in disabled_mir_passes {
35        config
36            .opts
37            .unstable_opts
38            .mir_enable_passes
39            .push((pass.to_owned(), false));
40    }
41}
42
43/// Don't even try to codegen. This avoids errors due to checking if the output filename is
44/// available (despite the fact that we won't emit it because we stop compilation early).
45fn set_no_codegen(config: &mut Config) {
46    config.opts.unstable_opts.no_codegen = true;
47    // Only emit metadata.
48    config.opts.output_types = OutputTypes::new(&[(OutputType::Metadata, None)]);
49}
50
51// We use a static to be able to pass data to `override_queries`.
52static SKIP_BORROWCK: AtomicBool = AtomicBool::new(false);
53fn set_skip_borrowck() {
54    SKIP_BORROWCK.store(true, Ordering::SeqCst);
55}
56fn skip_borrowck_if_set(providers: &mut Providers) {
57    if SKIP_BORROWCK.load(Ordering::SeqCst) {
58        providers.queries.mir_borrowck = |tcx, _def_id| {
59            // Empty result, which is what is used for custom_mir bodies.
60            Ok(tcx.arena.alloc(Default::default()))
61        }
62    }
63}
64
65fn setup_compiler(config: &mut Config, options: &CliOpts, do_translate: bool) {
66    if do_translate {
67        if options.skip_borrowck {
68            // We use a static to be able to pass data to `override_queries`.
69            set_skip_borrowck();
70        }
71
72        config.override_queries = Some(|_sess, providers| {
73            skip_borrowck_if_set(providers);
74
75            // TODO: catch the MIR in-flight to avoid stealing issues?
76            // providers.mir_built = |tcx, def_id| {
77            //     let mir = (rustc_interface::DEFAULT_QUERY_PROVIDERS.mir_built)(tcx, def_id);
78            //     let mut mir = mir.steal();
79            //     // use the mir
80            //     tcx.alloc_steal_mir(mir)
81            // };
82        });
83
84        set_no_codegen(config);
85    }
86    set_mir_options(config);
87}
88
89/// Run the rustc driver with our custom hooks. Returns `None` if the crate was not compiled with
90/// charon (e.g. because it was a dependency). Otherwise returns the translated crate, ready for
91/// post-processing transformations.
92pub fn run_rustc_driver() -> Result<Option<(TransformCtx, CliOpts)>, CharonFailure> {
93    // Retreive the command-line arguments pased to `charon_driver`. The first arg is the path to
94    // the current executable, we skip it.
95    let mut compiler_args: Vec<String> = env::args().skip(1).collect();
96    trace!(
97        "charon-driver called with args: {}",
98        compiler_args.iter().format(" ")
99    );
100
101    // When called using cargo, we tell cargo to use `charon-driver` by setting the `RUSTC_WRAPPER`
102    // env var. This uses `charon-driver` for all the crates being compiled.
103    // We may however not want to be calling charon on all crates; `CARGO_PRIMARY_PACKAGE` tells us
104    // whether the crate was specifically selected or is a dependency.
105    let is_workspace_dependency =
106        env::var("CHARON_USING_CARGO").is_ok() && env::var("CARGO_PRIMARY_PACKAGE").is_err();
107    // Determines if we are being invoked to build a crate for the "target" architecture, in
108    // contrast to the "host" architecture. Host crates are for build scripts and proc macros and
109    // still need to be built like normal; target crates need to be processed by Charon.
110    //
111    // Currently, we detect this by checking for "--target=", which is never set for host crates.
112    // This matches what Miri does, which hopefully makes it reliable enough. This relies on us
113    // always invoking cargo itself with `--target`, which `charon` ensures.
114    let is_target = arg_value(&compiler_args, "--target").is_some();
115    // Whether this is the crate we want to translate.
116    let is_selected_crate = !is_workspace_dependency && is_target;
117
118    let output = if !is_selected_crate {
119        trace!("Skipping charon; running compiler normally instead.");
120        // Run the compiler normally.
121        run_compiler_with_callbacks(compiler_args, &mut RunCompilerNormallyCallbacks)?;
122        None
123    } else {
124        let mut error_ctx = ErrorCtx::new();
125
126        // Retrieve the Charon options by deserializing them from the environment variable
127        // (cargo-charon serialized the arguments and stored them in a specific environment
128        // variable before calling cargo with RUSTC_WRAPPER=charon-driver).
129        let mut options: options::CliOpts = match env::var(options::CHARON_ARGS) {
130            Ok(opts) => serde_json::from_str(opts.as_str()).unwrap(),
131            Err(_) => {
132                register_error!(
133                    error_ctx,
134                    no_crate,
135                    "environment variable `CHARON_ARGS` not set; \
136                    don't call `charon-driver` directly, call `charon rustc` instead"
137                );
138                return Err(CharonFailure::CharonError(1));
139            }
140        };
141        options.apply_preset();
142
143        error_ctx.continue_on_failure = !options.abort_on_error;
144        error_ctx.error_on_warnings = options.error_on_warnings;
145
146        for extra_flag in options.rustc_args.iter().cloned() {
147            compiler_args.push(extra_flag);
148        }
149
150        // Call the Rust compiler with our custom callback.
151        let mut callback = CharonCallbacks {
152            options: &options,
153            error_ctx: Some(error_ctx),
154            transform_ctx: None,
155        };
156        run_compiler_with_callbacks(compiler_args, &mut callback)?;
157        // If `transform_ctx` is not set here, there was a fatal error.
158        let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
159        Some((ctx, options))
160    };
161    Ok(output)
162}
163
164/// The callbacks for Charon
165pub struct CharonCallbacks<'a> {
166    options: &'a CliOpts,
167    /// Context for errors; `take()`n by translation.
168    error_ctx: Option<ErrorCtx>,
169    /// This is to be filled during the extraction; it contains the translated crate. `None` at the
170    /// start or if we couldn't translate anything.
171    transform_ctx: Option<TransformCtx>,
172}
173impl<'a> Callbacks for CharonCallbacks<'a> {
174    fn config(&mut self, config: &mut Config) {
175        setup_compiler(config, self.options, true);
176    }
177
178    /// The MIR is modified in place: borrow-checking requires the "promoted" MIR, which causes the
179    /// "built" MIR (which results from the conversion to HIR to MIR) to become unaccessible.
180    /// Because we require built MIR at the moment, we hook ourselves before MIR-based analysis
181    /// passes.
182    fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
183        // Set up our own `DefId` debug routine.
184        rustc_hir::def_id::DEF_ID_DEBUG
185            .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
186
187        self.transform_ctx = translate_crate::translate(
188            tcx,
189            self.options,
190            self.error_ctx.take().unwrap(),
191            compiler.sess.opts.sysroot.path().to_owned(),
192        )
193        .ok();
194
195        Compilation::Continue
196    }
197    fn after_analysis<'tcx>(&mut self, _: &Compiler, _: TyCtxt<'tcx>) -> Compilation {
198        // Don't continue to codegen etc.
199        Compilation::Stop
200    }
201}
202
203/// Dummy callbacks used to run the compiler normally when we shouldn't be analyzing the crate.
204pub struct RunCompilerNormallyCallbacks;
205
206impl Callbacks for RunCompilerNormallyCallbacks {
207    fn config(&mut self, config: &mut Config) {
208        setup_compiler(config, &Default::default(), false);
209    }
210}
211
212/// Custom `DefId` debug routine that doesn't print unstable values like ids and hashes.
213fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214    rustc_middle::ty::tls::with_opt(|opt_tcx| {
215        if let Some(tcx) = opt_tcx {
216            let crate_name = if def_id.is_local() {
217                tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
218            } else {
219                tcx.cstore_untracked().crate_name(def_id.krate)
220            };
221            write!(
222                f,
223                "{}{}",
224                crate_name,
225                tcx.def_path(def_id).to_string_no_crate_verbose()
226            )?;
227        } else {
228            write!(f, "<can't access `tcx` to print `DefId` path>")?;
229        }
230        Ok(())
231    })
232}