#![feature(rustc_private)]
extern crate rustc_abi;
extern crate rustc_driver;
extern crate rustc_hir;
extern crate rustc_index;
extern crate rustc_interface;
extern crate rustc_middle;
extern crate rustc_mir_dataflow;
extern crate rustc_span;
mod extract;
mod fold;
mod read;
mod sinks;
mod state;
mod summary;
mod value;
use std::{
hash::{DefaultHasher, Hash, Hasher},
path::{Path, PathBuf},
process::Command,
};
use panicgraph::{Artifact, BuildConfig, StdMode};
use rustc_driver::{Callbacks, Compilation};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_middle::ty::TyCtxt;
use crate::extract::Extractor;
const OUT_DIR: &str = "PANICGRAPH_OUT";
const PROFILE: &str = "PANICGRAPH_PROFILE";
const STD_MODE: &str = "PANICGRAPH_STD_MODE";
const MIR_OPT_LEVEL: &str = "PANICGRAPH_MIR_OPT_LEVEL";
const LIBRARY_WORKSPACE: &str = "PANICGRAPH_LIBRARY_WORKSPACE";
struct PanicGraph;
impl Callbacks for PanicGraph {
fn after_analysis(
&mut self,
_compiler: &rustc_interface::interface::Compiler,
tcx: TyCtxt<'_>,
) -> Compilation {
if let Err(err) = emit(tcx) {
eprintln!("panicgraph: could not write artifact: {err}");
}
Compilation::Continue
}
}
fn emit(tcx: TyCtxt<'_>) -> std::io::Result<()> {
let Some(dir) = std::env::var_os(OUT_DIR) else {
return Ok(());
};
if std::env::var_os("CARGO_PRIMARY_PACKAGE").is_none() {
return Ok(());
}
let dir = PathBuf::from(dir);
std::fs::create_dir_all(&dir)?;
let krate = tcx.crate_name(LOCAL_CRATE).to_string();
let source = tcx
.sess
.io
.input
.opt_path()
.and_then(|input| std::path::absolute(input).ok());
let test = tcx.sess.opts.test;
let path = dir.join(artifact_name(tcx, source.as_deref(), test));
let extraction = Extractor::new(tcx).run();
let artifact = Artifact {
krate,
source: source.map(|at| at.to_string_lossy().into_owned()),
test,
config: build_config(tcx),
bodies: extraction.bodies,
reified: extraction.reified,
coerced: extraction.coerced,
};
let json = serde_json::to_vec(&artifact).map_err(std::io::Error::other)?;
if let Err(err) = std::fs::write(&path, json) {
match std::fs::remove_file(&path) {
Ok(()) => {}
Err(stuck) if stuck.kind() == std::io::ErrorKind::NotFound => {}
Err(stuck) => {
eprintln!(
"panicgraph: could not remove the stale artifact {}: \
{stuck}",
path.display()
);
}
}
return Err(err);
}
Ok(())
}
fn artifact_name(tcx: TyCtxt<'_>, source: Option<&Path>, test: bool) -> String {
let Some(source) = source else {
let stamp = tcx.stable_crate_id(LOCAL_CRATE).as_u64();
return format!("{}-{stamp:016x}.json", tcx.crate_name(LOCAL_CRATE));
};
let mut hasher = DefaultHasher::new();
source.hash(&mut hasher);
test.hash(&mut hasher);
for kind in tcx.crate_types() {
format!("{kind:?}").hash(&mut hasher);
}
format!("target-{:016x}.json", hasher.finish())
}
fn build_config(tcx: TyCtxt<'_>) -> BuildConfig {
let debug_assertions = tcx.sess.opts.debug_assertions;
let raw = std::env::var(STD_MODE).unwrap_or_default();
let std_mode = StdMode::from_name(&raw).unwrap_or_else(|| {
if !raw.is_empty() {
eprintln!(
"panicgraph: {STD_MODE} is `{raw}`, which names no standard \
library mode; reading it as shipped"
);
}
StdMode::Shipped
});
BuildConfig {
rustc: tcx.sess.cfg_version.to_owned(),
profile: std::env::var(PROFILE)
.unwrap_or_else(|_| "unknown".to_owned()),
debug_assertions,
overflow_checks: tcx
.sess
.opts
.cg
.overflow_checks
.unwrap_or(debug_assertions),
std_mode,
mir_opt_level: mir_opt_level(),
}
}
fn mir_opt_level() -> Option<u8> {
std::env::var(MIR_OPT_LEVEL).ok()?.parse().ok()
}
fn sysroot() -> Option<String> {
let out = Command::new("rustc").arg("--print=sysroot").output().ok()?;
if !out.status.success() {
return None;
}
let text = String::from_utf8(out.stdout).ok()?;
Some(text.trim().to_owned())
}
fn main() -> std::process::ExitCode {
let mut args: Vec<String> = std::env::args().collect();
if args.len() > 1 && args[1].ends_with("rustc") {
args.remove(1);
}
if let Some(root) = sysroot() {
args.push(format!("--sysroot={root}"));
}
let probe = args
.iter()
.any(|arg| arg == "-")
.then(empty_source)
.flatten();
if let Some(path) = &probe {
for arg in &mut args {
if arg == "-" {
path.to_string_lossy().as_ref().clone_into(arg);
}
}
}
args.push("-Zalways-encode-mir".to_owned());
if let Some(level) = mir_opt_level() {
args.push(format!("-Zmir-opt-level={level}"));
}
if std::env::var_os(LIBRARY_WORKSPACE).is_some() {
args.push("-Zforce-unstable-if-unmarked".to_owned());
}
if args.iter().any(|arg| arg == "--test") {
args.push("-Zinline-mir=no".to_owned());
}
if std::env::var_os("CARGO_PRIMARY_PACKAGE").is_some() {
args.push("-Zcross-crate-inline-threshold=never".to_owned());
}
let code = rustc_driver::catch_with_exit_code(|| {
rustc_driver::run_compiler(&args, &mut PanicGraph);
});
if let Some(path) = &probe {
let _ = std::fs::remove_file(path);
}
code
}
fn empty_source() -> Option<PathBuf> {
let path = std::env::temp_dir()
.join(format!("panicgraph-probe-{}.rs", std::process::id()));
std::fs::write(&path, b"").ok()?;
Some(path)
}