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::errors::ErrorCtx;
5use charon_lib::options::{self, CliOpts};
6use charon_lib::transform::TransformCtx;
7use charon_lib::utils::arg_value;
8use itertools::Itertools;
9use rustc_driver::{Callbacks, Compilation};
10use rustc_interface::Config;
11use rustc_interface::interface::Compiler;
12use rustc_middle::ty::{InstanceKind, TyCtxt};
13use rustc_middle::util::Providers;
14use rustc_session::config::{OutputType, OutputTypes};
15use rustc_span::ErrorGuaranteed;
16use std::num::NonZero;
17use std::path::PathBuf;
18use std::process::Command;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::{env, fmt};
21
22/// Helper that runs the compiler and catches its fatal errors.
23fn run_compiler_with_callbacks(
24    args: Vec<String>,
25    callbacks: &mut (dyn Callbacks + Send),
26) -> Result<(), CharonFailure> {
27    rustc_driver::catch_fatal_errors(|| rustc_driver::run_compiler(&args, callbacks))
28        .map_err(|_| CharonFailure::RustcError)
29}
30
31/// Tweak options to get usable MIR even for foreign crates.
32fn set_mir_options(config: &mut Config) {
33    config.opts.unstable_opts.always_encode_mir = true;
34    config.opts.unstable_opts.mir_opt_level = Some(0);
35    config.opts.unstable_opts.mir_preserve_ub = true;
36    let disabled_mir_passes = ["CheckAlignment", "CheckNull"];
37    for pass in disabled_mir_passes {
38        config
39            .opts
40            .unstable_opts
41            .mir_enable_passes
42            .push((pass.to_owned(), false));
43    }
44}
45
46/// Enable rustc's parallel front-end.
47fn set_parallel_frontend(config: &mut Config) {
48    if config.opts.jobs.frontend.is_none() {
49        // Match rustc's `--jobs-frontend=0` behavior.
50        const RUSTC_MAX_THREADS_CAP: usize = u8::MAX as usize;
51        let threads = std::thread::available_parallelism()
52            .map_or(1, |n| n.get())
53            .min(RUSTC_MAX_THREADS_CAP);
54        config.opts.jobs.frontend = NonZero::new(threads).filter(|n| n.get() > 1);
55    }
56}
57
58// We use a static to be able to pass data to `override_queries`.
59static SKIP_BORROWCK: AtomicBool = AtomicBool::new(false);
60fn set_skip_borrowck() {
61    SKIP_BORROWCK.store(true, Ordering::SeqCst);
62}
63fn skip_borrowck_if_set(providers: &mut Providers) {
64    if SKIP_BORROWCK.load(Ordering::SeqCst) {
65        providers.queries.mir_borrowck = |tcx, _def_id| {
66            // Empty result, which is what is used for custom_mir bodies.
67            Ok(tcx.arena.alloc(Default::default()))
68        }
69    }
70}
71
72fn setup_compiler(
73    config: &mut Config,
74    options: &CliOpts,
75    do_translate: bool,
76    emit_artifacts: bool,
77    codegen: bool,
78) {
79    if do_translate {
80        if options.skip_borrowck {
81            // We use a static to be able to pass data to `override_queries`.
82            set_skip_borrowck();
83        }
84
85        config.override_queries = Some(|_sess, providers| {
86            skip_borrowck_if_set(providers);
87
88            // TODO: catch the MIR in-flight to avoid stealing issues?
89            // providers.mir_built = |tcx, def_id| {
90            //     let mir = (rustc_interface::DEFAULT_QUERY_PROVIDERS.mir_built)(tcx, def_id);
91            //     let mut mir = mir.steal();
92            //     // use the mir
93            //     tcx.alloc_steal_mir(mir)
94            // };
95        });
96
97        config.opts.unstable_opts.no_codegen = !codegen;
98        if !emit_artifacts {
99            config.opts.output_types = OutputTypes::new(&[(OutputType::Object, None)]);
100        }
101        set_parallel_frontend(config);
102    }
103    set_mir_options(config);
104}
105
106/// Run a couple of rustc queries that don't involve MIR (so that they don't steal it). Returns
107/// whether rustc reported errors. This lets us avoid running Charon translation on crates rustc
108/// already rejects.
109fn precheck_rustc_errors(tcx: TyCtxt<'_>) -> bool {
110    type QueryResult = Result<(), ErrorGuaranteed>;
111
112    tcx.par_hir_for_each_module(|module| {
113        tcx.ensure_ok().check_mod_attrs(module);
114        tcx.ensure_ok().check_mod_unstable_api_usage(module);
115    });
116
117    let _: QueryResult = tcx.ensure_result().check_type_wf(());
118    for &trait_def_id in tcx.all_local_trait_impls(()).keys() {
119        let _: QueryResult = tcx.ensure_result().coherent_trait(trait_def_id);
120    }
121    let _: QueryResult = tcx.ensure_result().crate_inherent_impls_validity_check(());
122    let _: QueryResult = tcx.ensure_result().crate_inherent_impls_overlap_check(());
123
124    tcx.par_hir_body_owners(|def_id| {
125        let def_kind = tcx.def_kind(def_id);
126        if !matches!(def_kind, rustc_hir::def::DefKind::AnonConst)
127            && !tcx.is_typeck_child(def_id.to_def_id())
128        {
129            tcx.ensure_ok().typeck(def_id);
130        }
131    });
132
133    tcx.dcx().has_errors().is_some()
134}
135
136/// Run rustc checks that normally happen close to codegen, so that we get all the post-mono errors
137/// etc.
138fn check_late_rustc_errors(tcx: TyCtxt<'_>) {
139    tcx.par_hir_body_owners(|def_id| {
140        let _ = tcx.instance_mir(InstanceKind::Item(def_id.to_def_id()));
141    });
142
143    if tcx.dcx().err_count() == 0 {
144        let _ = tcx.collect_and_partition_mono_items(());
145    }
146}
147
148/// `cargo miri setup` sets up a sysroot containing a standard library built with
149/// `-Zalways-encode-mir`.
150fn setup_miri_sysroot(target: &str) -> Option<PathBuf> {
151    if let Some(root) = env::var_os("CHARON_MIRI_SYSROOTS")
152        && let sysroot = PathBuf::from(root)
153        && sysroot.join("lib").join("rustlib").join(target).is_dir()
154    {
155        return Some(sysroot);
156    }
157
158    let mut cmd = Command::new("cargo");
159    cmd.arg("miri")
160        .arg("setup")
161        .arg(format!("--target={target}"))
162        .arg("--print-sysroot")
163        .env_remove("RUSTC_WORKSPACE_WRAPPER")
164        .env_remove("RUSTC_WRAPPER");
165
166    let output = match cmd.output() {
167        Ok(output) => output,
168        Err(err) => {
169            eprintln!(
170                "warning: failed to run `cargo miri setup` for target `{target}`; \
171                falling back to rustc's default sysroot: {err}"
172            );
173            return None;
174        }
175    };
176
177    if !output.status.success() {
178        let stderr = String::from_utf8_lossy(&output.stderr);
179        eprintln!(
180            "warning: `cargo miri setup` failed for target `{target}`; \
181            falling back to rustc's default sysroot: {}",
182            stderr.trim()
183        );
184        return None;
185    }
186
187    let stdout = String::from_utf8_lossy(&output.stdout);
188    let sysroot = stdout.lines().map(str::trim).find(|line| !line.is_empty());
189    match sysroot {
190        Some(sysroot) => Some(PathBuf::from(sysroot)),
191        None => {
192            eprintln!(
193                "warning: `cargo miri setup --print-sysroot` printed no sysroot for target \
194                `{target}`; falling back to rustc's default sysroot"
195            );
196            None
197        }
198    }
199}
200
201/// Run the rustc driver with our custom hooks. Returns `None` if the crate was not compiled with
202/// charon (e.g. because it was a dependency). Otherwise returns the translated crate, ready for
203/// post-processing transformations.
204pub fn run_rustc_driver() -> Result<Option<(TransformCtx, CliOpts)>, CharonFailure> {
205    // Retreive the command-line arguments pased to `charon_driver`. The first arg is the path to
206    // the current executable, we skip it.
207    let mut compiler_args: Vec<String> = env::args().skip(1).collect();
208    // We use `RUSTC_WORKSPACE_WRAPPER` to break cargo's caching; that value ends up as our first
209    // argument.
210    if compiler_args
211        .first()
212        .is_some_and(|arg| arg.starts_with("charon-dont-cache-this-"))
213    {
214        compiler_args.remove(0);
215    }
216    trace!(
217        "charon-driver called with args: {}",
218        compiler_args.iter().format(" ")
219    );
220
221    // When called using cargo, we tell cargo to use `charon-driver` by setting the `RUSTC_WRAPPER`
222    // env var. This uses `charon-driver` for all the crates being compiled.
223    // We may however not want to be calling charon on all crates; `CARGO_PRIMARY_PACKAGE` tells us
224    // whether the crate was specifically selected or is a dependency.
225    let is_workspace_dependency =
226        env::var("CHARON_USING_CARGO").is_ok() && env::var("CARGO_PRIMARY_PACKAGE").is_err();
227    // Let rustc emit artifacts (metadata, binaries) normally if invoked by `cargo` or when
228    // explicitly requested.
229    let emit_artifacts =
230        env::var("CHARON_USING_CARGO").is_ok() || env::var("CHARON_EMIT_ARTIFACTS").is_ok();
231    // Let rustc emit codegen artifacts, more specifically.
232    let mut codegen = emit_artifacts;
233    // Determines if we are being invoked to build a crate for the "target" architecture, in
234    // contrast to the "host" architecture. Host crates are for build scripts and proc macros and
235    // still need to be built like normal; target crates need to be processed by Charon.
236    //
237    // Currently, we detect this by checking for "--target=", which is never set for host crates.
238    // This matches what Miri does, which hopefully makes it reliable enough. This relies on us
239    // always invoking cargo itself with `--target`, which `charon` ensures.
240    let target = arg_value(&compiler_args, "--target");
241    // Whether this is the crate we want to translate.
242    let is_selected_crate = !is_workspace_dependency && target.is_some();
243
244    let mut error_ctx = ErrorCtx::new();
245
246    // Retrieve the Charon options by deserializing them from the environment variable
247    // (cargo-charon serialized the arguments and stored them in a specific environment
248    // variable before calling cargo with `RUSTC_WRAPPER=charon-driver`).
249    let mut options = match env::var(options::CHARON_ARGS)
250        .ok()
251        .map(|opts| serde_json::from_str::<options::CliOpts>(&opts).unwrap())
252    {
253        Some(options) => options,
254        None if !is_selected_crate => Default::default(),
255        None => {
256            register_error!(
257                error_ctx,
258                no_crate,
259                "environment variable `CHARON_ARGS` not set; \
260                don't call `charon-driver` directly, call `charon rustc` instead"
261            );
262            return Err(CharonFailure::CharonError(1));
263        }
264    };
265
266    if options.sysroot.as_deref() == Some("default") {
267        // Do nothing
268    } else if let Some(sysroot) = options.sysroot.as_ref()
269        && sysroot != "miri"
270    {
271        compiler_args.push(format!("--sysroot={sysroot}"));
272    } else if let Some(target) = target
273        && let Some(sysroot) = setup_miri_sysroot(target)
274    {
275        // In the default case, or `--sysroot=miri`, we ask Miri to build a full-mir syroot for us.
276        compiler_args.push(format!("--sysroot={}", sysroot.display()));
277        // The Miri sysroot doesn't support codegen.
278        codegen = false;
279    }
280
281    let output = if !is_selected_crate {
282        trace!("Skipping charon; running compiler normally instead.");
283        // Run the compiler normally.
284        run_compiler_with_callbacks(compiler_args, &mut RunCompilerNormallyCallbacks)?;
285        None
286    } else {
287        options.apply_preset();
288
289        error_ctx.continue_on_failure = !options.abort_on_error;
290        error_ctx.error_on_warnings = options.error_on_warnings;
291
292        for extra_flag in options.rustc_args.iter().cloned() {
293            compiler_args.push(extra_flag);
294        }
295
296        // Call the Rust compiler with our custom callback.
297        let mut callback = CharonCallbacks {
298            options: &options,
299            emit_artifacts,
300            codegen,
301            error_ctx: Some(error_ctx),
302            transform_ctx: None,
303        };
304        run_compiler_with_callbacks(compiler_args, &mut callback)?;
305        // If `transform_ctx` is not set here, there was a fatal error.
306        let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
307        Some((ctx, options))
308    };
309    Ok(output)
310}
311
312/// The callbacks for Charon
313pub struct CharonCallbacks<'a> {
314    options: &'a CliOpts,
315    /// Whether rustc should emit the artifacts (metadata, binaries) it normally would. This is
316    /// needed under cargo so later crate invocations can consume earlier selected crates.
317    emit_artifacts: bool,
318    /// Whether to let rustc run codegen as it normally would.
319    codegen: bool,
320    /// Context for errors; `take()`n by translation.
321    error_ctx: Option<ErrorCtx>,
322    /// This is to be filled during the extraction; it contains the translated crate. `None` at the
323    /// start or if we couldn't translate anything.
324    transform_ctx: Option<TransformCtx>,
325}
326impl<'a> Callbacks for CharonCallbacks<'a> {
327    fn config(&mut self, config: &mut Config) {
328        setup_compiler(
329            config,
330            self.options,
331            true,
332            self.emit_artifacts,
333            self.codegen,
334        );
335    }
336
337    /// The MIR is modified in place: borrow-checking requires the "promoted" MIR, which causes the
338    /// "built" MIR (which results from the conversion to HIR to MIR) to become unaccessible.
339    /// Because we require built MIR at the moment, we hook ourselves before MIR-based analysis
340    /// passes.
341    fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
342        // Set up our own `DefId` debug routine.
343        rustc_hir::def_id::DEF_ID_DEBUG
344            .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
345
346        if precheck_rustc_errors(tcx) {
347            return Compilation::Continue;
348        }
349
350        self.transform_ctx = translate_crate::translate(
351            tcx,
352            self.options,
353            self.error_ctx.take().unwrap(),
354            compiler.sess.opts.sysroot.path().to_owned(),
355        )
356        .ok();
357
358        Compilation::Continue
359    }
360    fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
361        if !self.emit_artifacts {
362            check_late_rustc_errors(tcx);
363        }
364        Compilation::Continue
365    }
366}
367
368/// Dummy callbacks used to run the compiler normally when we shouldn't be analyzing the crate.
369pub struct RunCompilerNormallyCallbacks;
370
371impl Callbacks for RunCompilerNormallyCallbacks {
372    fn config(&mut self, config: &mut Config) {
373        setup_compiler(config, &Default::default(), false, true, true);
374    }
375}
376
377/// Custom `DefId` debug routine that doesn't print unstable values like ids and hashes.
378fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379    rustc_middle::ty::tls::with_opt(|opt_tcx| {
380        if let Some(tcx) = opt_tcx {
381            let crate_name = if def_id.is_local() {
382                tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
383            } else {
384                tcx.cstore_untracked().crate_name(def_id.krate)
385            };
386            write!(
387                f,
388                "{}{}",
389                crate_name,
390                tcx.def_path(def_id).to_string_no_crate_verbose()
391            )?;
392        } else {
393            write!(f, "<can't access `tcx` to print `DefId` path>")?;
394        }
395        Ok(())
396    })
397}