charon_driver/
driver.rs

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