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::options::CliOpts;
5use charon_lib::transform::TransformCtx;
6use rustc_driver::{Callbacks, Compilation};
7use rustc_interface::Config;
8use rustc_interface::interface::Compiler;
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            // Empty result, which is what is used for custom_mir bodies.
57            Ok(tcx.arena.alloc(Default::default()))
58        }
59    }
60}
61
62fn setup_compiler(config: &mut Config, options: &CliOpts, do_translate: bool) {
63    if do_translate {
64        if options.skip_borrowck {
65            // We use a static to be able to pass data to `override_queries`.
66            set_skip_borrowck();
67        }
68
69        config.override_queries = Some(|_sess, providers| {
70            skip_borrowck_if_set(providers);
71
72            // TODO: catch the MIR in-flight to avoid stealing issues?
73            // providers.mir_built = |tcx, def_id| {
74            //     let mir = (rustc_interface::DEFAULT_QUERY_PROVIDERS.mir_built)(tcx, def_id);
75            //     let mut mir = mir.steal();
76            //     // use the mir
77            //     tcx.alloc_steal_mir(mir)
78            // };
79        });
80
81        set_no_codegen(config);
82        if options.use_polonius {
83            config.opts.unstable_opts.polonius = Polonius::Legacy;
84        }
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(options: &CliOpts) -> Result<Option<TransformCtx>, 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
97    // When called using cargo, we tell cargo to use `charon-driver` by setting the `RUSTC_WRAPPER`
98    // env var. This uses `charon-driver` for all the crates being compiled.
99    // We may however not want to be calling charon on all crates; `CARGO_PRIMARY_PACKAGE` tells us
100    // whether the crate was specifically selected or is a dependency.
101    let is_workspace_dependency =
102        env::var("CHARON_USING_CARGO").is_ok() && !env::var("CARGO_PRIMARY_PACKAGE").is_ok();
103    // Determines if we are being invoked to build a crate for the "target" architecture, in
104    // contrast to the "host" architecture. Host crates are for build scripts and proc macros and
105    // still need to be built like normal; target crates need to be processed by Charon.
106    //
107    // Currently, we detect this by checking for "--target=", which is never set for host crates.
108    // This matches what Miri does, which hopefully makes it reliable enough. This relies on us
109    // always invoking cargo itself with `--target`, which `charon` ensures.
110    let is_target = arg_values(&compiler_args, "--target").next().is_some();
111    // Whether this is the crate we want to translate.
112    let is_selected_crate = !is_workspace_dependency && is_target;
113
114    let output = if !is_selected_crate {
115        trace!("Skipping charon; running compiler normally instead.");
116        // Run the compiler normally.
117        run_compiler_with_callbacks(compiler_args, &mut RunCompilerNormallyCallbacks { options })?;
118        None
119    } else {
120        for extra_flag in options.rustc_args.iter().cloned() {
121            compiler_args.push(extra_flag);
122        }
123
124        // Call the Rust compiler with our custom callback.
125        let mut callback = CharonCallbacks {
126            options,
127            transform_ctx: None,
128        };
129        run_compiler_with_callbacks(compiler_args, &mut callback)?;
130        // If `transform_ctx` is not set here, there was a fatal error.
131        let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
132        Some(ctx)
133    };
134    Ok(output)
135}
136
137/// The callbacks for Charon
138pub struct CharonCallbacks<'a> {
139    options: &'a CliOpts,
140    /// This is to be filled during the extraction; it contains the translated crate.
141    transform_ctx: Option<TransformCtx>,
142}
143impl<'a> Callbacks for CharonCallbacks<'a> {
144    fn config(&mut self, config: &mut Config) {
145        setup_compiler(config, self.options, true);
146    }
147
148    /// The MIR is modified in place: borrow-checking requires the "promoted" MIR, which causes the
149    /// "built" MIR (which results from the conversion to HIR to MIR) to become unaccessible.
150    /// Because we require built MIR at the moment, we hook ourselves before MIR-based analysis
151    /// passes.
152    fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
153        // Set up our own `DefId` debug routine.
154        rustc_hir::def_id::DEF_ID_DEBUG
155            .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
156
157        let transform_ctx = translate_crate::translate(
158            &self.options,
159            tcx,
160            compiler.sess.opts.sysroot.path().to_owned(),
161        );
162        self.transform_ctx = Some(transform_ctx);
163        Compilation::Continue
164    }
165    fn after_analysis<'tcx>(&mut self, _: &Compiler, _: TyCtxt<'tcx>) -> Compilation {
166        // Don't continue to codegen etc.
167        Compilation::Stop
168    }
169}
170
171/// Dummy callbacks used to run the compiler normally when we shouldn't be analyzing the crate.
172pub struct RunCompilerNormallyCallbacks<'a> {
173    options: &'a CliOpts,
174}
175impl<'a> Callbacks for RunCompilerNormallyCallbacks<'a> {
176    fn config(&mut self, config: &mut Config) {
177        setup_compiler(config, self.options, false);
178    }
179}
180
181/// Returns the values of the command-line options that match `find_arg`. The options are built-in
182/// to be of the form `--arg=value` or `--arg value`.
183fn arg_values<'a, T: Deref<Target = str>>(
184    args: &'a [T],
185    needle: &'a str,
186) -> impl Iterator<Item = &'a str> {
187    struct ArgFilter<'a, T> {
188        args: std::slice::Iter<'a, T>,
189        needle: &'a str,
190    }
191    impl<'a, T: Deref<Target = str>> Iterator for ArgFilter<'a, T> {
192        type Item = &'a str;
193        fn next(&mut self) -> Option<Self::Item> {
194            while let Some(arg) = self.args.next() {
195                let mut split_arg = arg.splitn(2, '=');
196                if split_arg.next() == Some(self.needle) {
197                    return match split_arg.next() {
198                        // `--arg=value` form
199                        arg @ Some(_) => arg,
200                        // `--arg value` form
201                        None => self.args.next().map(|x| x.deref()),
202                    };
203                }
204            }
205            None
206        }
207    }
208    ArgFilter {
209        args: args.iter(),
210        needle,
211    }
212}
213
214/// Custom `DefId` debug routine that doesn't print unstable values like ids and hashes.
215fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216    rustc_middle::ty::tls::with_opt(|opt_tcx| {
217        if let Some(tcx) = opt_tcx {
218            let crate_name = if def_id.is_local() {
219                tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
220            } else {
221                tcx.cstore_untracked().crate_name(def_id.krate)
222            };
223            write!(
224                f,
225                "{}{}",
226                crate_name,
227                tcx.def_path(def_id).to_string_no_crate_verbose()
228            )?;
229        } else {
230            write!(f, "<can't access `tcx` to print `DefId` path>")?;
231        }
232        Ok(())
233    })
234}