crates/mimir-driver/src/main.rs
//! `mimir-driver` — a rustc driver in the clippy-driver shape.
//!
//! Used as `RUSTC_WRAPPER` (cargo calls `mimir-driver <path/to/rustc> <args…>`)
//! or `RUSTC_WORKSPACE_WRAPPER`. It always runs the IN-PROCESS compiler of the
//! pinned nightly it is linked against (never the passed rustc), so every crate
//! of the build — deps, build scripts, proc macros — comes from ONE compiler and
//! all metadata is mutually loadable.
//!
//! ## Why `RUSTC_WRAPPER` (clippy uses `RUSTC_WORKSPACE_WRAPPER`)
//!
//! 1. Plan §2: deps and std are dumped once per `name@version` (the universe
//! ring) — only a wrapper that sees EVERY rustc call can dump them.
//! 2. Exact extern versions: each compile of a dep writes its sidecar
//! (`CARGO_PKG_VERSION` keyed by artifact stem), so a dependent resolves the
//! exact version of every crate it loads, two versions of one name included.
//! 3. One compiler for the whole graph (the driver's linked nightly), so every
//! rmeta is loadable by the driver.
//!
//! `cargo mimir-dump --workspace-wrapper` runs the clippy shape instead
//! (members only; deps compiled by the same pinned nightly's plain rustc;
//! versions from the cargo-metadata map + `--extern` argv).
//!
//! ## Behaviour
//!
//! * `MIMIR_OUT` unset, `--print`/`-vV` probes, build scripts and proc-macro
//! crates: plain rustc (default callbacks), untouched.
//! * normal lib/bin/test targets: rustc with an `after_analysis` hook that walks
//! the crate with `tcx` and writes `$MIMIR_OUT/<crate>@<version>-<hash>.{nodes,edges}.arrow`
//! + `.stats.json`. `Compilation::Continue` — rustc then emits its metadata as
//! usual. Walk failures (incl. panics) are caught and written to
//! `$MIMIR_OUT/.errors/`; rustc's exit code and diagnostics are never touched.
#![feature(rustc_private)]
extern crate rustc_ast;
extern crate rustc_attr_ir;
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;
mod walk;
use std::path::PathBuf;
use std::process::ExitCode;
use mimir_driver::{Invocation, Sidecar, ENV_INDEX, ENV_OUT, ENV_SCOPE};
use rustc_driver::{Callbacks, Compilation, TimePassesCallbacks};
use rustc_interface::interface;
use rustc_middle::ty::TyCtxt;
use rustc_session::EarlyDiagCtxt;
use rustc_session::config::ErrorOutputType;
struct DumpCallbacks {
inv: Invocation,
out: PathBuf,
}
impl Callbacks for DumpCallbacks {
/// MIR facts read `mir_promoted` after analysis: keep the bodies rustc
/// would steal before then (walk/mir.rs).
fn config(&mut self, config: &mut interface::Config) {
config.override_queries = Some(walk::mir::override_queries);
}
fn after_analysis<'tcx>(&mut self, _c: &interface::Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
if tcx.dcx().has_errors().is_none() {
walk::dump_guarded(tcx, &self.inv, &self.out);
}
Compilation::Continue
}
}
/// A crate that is compiled but NOT dumped (a dependency under
/// `MIMIR_DUMP=workspace`): only its table summaries are written, so the
/// crates that are dumped know what a call into it does with a table.
struct SummaryCallbacks {
inv: Invocation,
}
impl Callbacks for SummaryCallbacks {
fn after_analysis<'tcx>(&mut self, _c: &interface::Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
if tcx.dcx().has_errors().is_none() {
walk::table_summaries_guarded(tcx, &self.inv);
}
Compilation::Continue
}
}
fn main() -> ExitCode {
let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());
rustc_driver::init_rustc_env_logger(&early_dcx);
rustc_driver::install_ice_hook(
"https://git.gunnar.rs/nordisk/nornir/issues (mimir-driver)",
|_| (),
);
let mut args = rustc_driver::args::raw_args(&early_dcx);
// RUSTC_WRAPPER / RUSTC_WORKSPACE_WRAPPER mode: argv[1] is cargo's rustc.
// We drop it and compile in-process with OUR rustc (same nightly).
if args
.get(1)
.map(PathBuf::from)
.and_then(|p| p.file_stem().map(|s| s == "rustc"))
.unwrap_or(false)
{
args.remove(1);
}
let inv = Invocation::parse(&args[1..]);
// `cfg(mimir)` + the `mimir` tool, so `#[cfg_attr(mimir, mimir::table(…))]`
// exists under this driver and nowhere else (the clippy / kani shape).
// Only for real compiles of OUR sources — a workspace member or a path
// dependency: a registry / git crate cannot carry the annotation, and the
// toolchain's own crates (`--std`) never get a feature gate from here.
let ours = std::env::var_os("CARGO_MANIFEST_DIR").is_some_and(|d| {
let home = std::env::var_os("CARGO_HOME").map(PathBuf::from).or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cargo")));
!home.is_some_and(|h| PathBuf::from(&d).starts_with(h))
});
if ours && inv.has_input && !inv.has_print && !inv.is_build_script() && std::env::var(mimir_driver::ENV_STD).as_deref() != Ok("1") {
args.extend(["--cfg".to_string(), mimir_driver::CFG.to_string(), "--check-cfg".to_string(), format!("cfg({})", mimir_driver::CFG)]);
args.extend(["-Zcrate-attr=feature(register_tool)".to_string(), format!("-Zcrate-attr=register_tool({})", mimir_driver::CFG)]);
}
// Sidecar for EVERY real compile (proc macros included): the exact
// package/version of this artifact, keyed by its file stem.
if let (Some(dir), Some(key), Ok(package), Ok(version)) = (
std::env::var_os(ENV_INDEX),
inv.artifact_key(),
std::env::var("CARGO_PKG_NAME"),
std::env::var("CARGO_PKG_VERSION"),
) {
let _ = mimir_driver::write_sidecar(
&PathBuf::from(dir),
&key,
&Sidecar {
package,
version,
crate_name: inv.crate_name.clone().unwrap_or_default(),
primary: std::env::var_os("CARGO_PRIMARY_PACKAGE").is_some(),
member: std::env::var_os(mimir_driver::ENV_VERSIONS).and_then(|p| mimir_driver::VersionMap::load(std::path::Path::new(&p))).map(|m| {
m.is_member(
&std::env::var("CARGO_PKG_NAME").unwrap_or_default(),
&std::env::var("CARGO_PKG_VERSION").unwrap_or_default(),
std::env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from).as_deref(),
)
}),
manifest_dir: std::env::var("CARGO_MANIFEST_DIR").ok(),
},
);
}
// … and an EMPTY table sidecar of this driver's version: "compiled by
// this driver, summarises nothing" until the table pass says more. A
// dependent crate tells that apart from an artifact an older driver left.
if let (Some(dir), Some(key)) = (std::env::var_os(ENV_INDEX), inv.artifact_key()) {
let _ = mimir_driver::write_table_sidecar(&PathBuf::from(dir), &key, &mimir_driver::TableSidecar { v: mimir_driver::TABLE_PASS, ..Default::default() });
}
let out = mimir_driver::dump_out(std::env::var_os(ENV_OUT), std::env::var(ENV_SCOPE).ok().as_deref());
let scope_ok = match std::env::var(ENV_SCOPE).as_deref() {
Ok("workspace") => std::env::var_os("CARGO_PRIMARY_PACKAGE").is_some(),
_ => true,
};
let unit = out.is_some() && inv.has_input && !inv.has_print && !inv.is_proc_macro() && !inv.is_build_script() && inv.crate_name.is_some();
let would_dump = out.is_some()
&& scope_ok
&& inv.has_input
&& !inv.has_print
&& !inv.is_proc_macro()
&& !inv.is_build_script()
&& inv.crate_name.is_some();
// a DEPENDENCY unit whose dump the ring already holds (by its semantic
// key: crate, version, source, cfgs/features, code options, target):
// compiled and summarised, not walked again — a marker says so
let in_ring = would_dump
&& std::env::var_os("CARGO_PRIMARY_PACKAGE").is_none()
&& std::env::var_os(mimir_driver::ENV_RING_HAVE).is_some_and(|f| {
let semhash = mimir_driver::unit_semhash_of_env(&inv);
let hit = mimir_driver::ring_has(std::path::Path::new(&f), &semhash);
if let (true, Some(dir), Some(name)) = (hit, out.as_ref(), inv.crate_name.as_deref()) {
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default().replace(['/', ':'], "_");
let _ = std::fs::write(dir.join(format!("{name}@{version}-{}.ring", inv.short_hash())), &semhash);
}
hit
});
let dump = would_dump && !in_ring;
if dump {
let mut cb = DumpCallbacks { inv, out: out.unwrap() };
rustc_driver::catch_with_exit_code(|| rustc_driver::compiler_entrypoint(&args, &mut cb))
} else if unit {
let mut cb = SummaryCallbacks { inv };
rustc_driver::catch_with_exit_code(|| rustc_driver::compiler_entrypoint(&args, &mut cb))
} else {
let mut cb = TimePassesCallbacks::default();
rustc_driver::catch_with_exit_code(|| rustc_driver::compiler_entrypoint(&args, &mut cb))
}
}