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