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