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