#![feature(rustc_private)]
#![deny(clippy::expect_used)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
#![deny(unused_extern_crates)]
extern crate rustc_driver;
extern crate rustc_interface;
extern crate rustc_lint;
extern crate rustc_session;
extern crate rustc_span;
use anyhow::{bail, ensure, Result};
use dylint_internal::{env, parse_path_filename, rustup::is_rustc};
use std::{
collections::BTreeSet,
ffi::{CString, OsStr},
path::{Path, PathBuf},
};
pub const DYLINT_VERSION: &str = "0.1.0";
type DylintVersionFunc = unsafe fn() -> *mut std::os::raw::c_char;
type RegisterLintsFunc =
unsafe fn(sess: &rustc_session::Session, store: &mut rustc_lint::LintStore);
struct LoadedLibrary {
path: PathBuf,
lib: libloading::Library,
}
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
struct Lint {
name: &'static str,
level: rustc_lint::Level,
desc: &'static str,
}
impl From<&rustc_lint::Lint> for Lint {
fn from(lint: &rustc_lint::Lint) -> Self {
Self {
name: lint.name,
level: lint.default_level,
desc: lint.desc,
}
}
}
impl LoadedLibrary {
fn register_lints(
&self,
sess: &rustc_session::Session,
lint_store: &mut rustc_lint::LintStore,
) {
(|| unsafe {
if let Ok(func) = self.lib.get::<DylintVersionFunc>(b"dylint_version") {
let dylint_version = CString::from_raw(func()).into_string()?;
ensure!(
dylint_version == DYLINT_VERSION,
"`{}` has dylint version `{}`, but `{}` was expected",
self.path.to_string_lossy(),
dylint_version,
DYLINT_VERSION
);
} else {
bail!(
"could not find `dylint_version` in `{}`",
self.path.to_string_lossy()
);
}
if let Ok(func) = self.lib.get::<RegisterLintsFunc>(b"register_lints") {
func(sess, lint_store);
} else {
bail!(
"could not find `register_lints` in `{}`",
self.path.to_string_lossy()
);
}
Ok(())
})()
.unwrap_or_else(|err| {
sess.err(err.to_string());
});
}
}
struct Callbacks {
loaded_libs: Vec<LoadedLibrary>,
}
impl Callbacks {
fn new(paths: Vec<PathBuf>) -> Self {
let mut loaded_libs = Vec::new();
for path in paths {
unsafe {
let lib = libloading::Library::new(&path).unwrap_or_else(|err| {
rustc_session::early_error(
rustc_session::config::ErrorOutputType::default(),
&format!(
"could not load library `{}`: {}",
path.to_string_lossy(),
err
),
);
});
loaded_libs.push(LoadedLibrary { path, lib });
}
}
Self { loaded_libs }
}
}
#[rustversion::before(2022-07-14)]
fn zero_mir_opt_level(config: &mut rustc_interface::Config) {
trait Zeroable {
fn zero(&mut self);
}
impl Zeroable for usize {
fn zero(&mut self) {
*self = 0;
}
}
impl Zeroable for Option<usize> {
fn zero(&mut self) {
*self = Some(0);
}
}
config.opts.debugging_opts.mir_opt_level.zero();
}
#[rustversion::since(2022-07-14)]
fn zero_mir_opt_level(config: &mut rustc_interface::Config) {
config.opts.unstable_opts.mir_opt_level = Some(0);
}
impl rustc_driver::Callbacks for Callbacks {
fn config(&mut self, config: &mut rustc_interface::Config) {
let previous = config.register_lints.take();
let loaded_libs = self.loaded_libs.split_off(0);
config.register_lints = Some(Box::new(move |sess, lint_store| {
if let Some(previous) = &previous {
previous(sess, lint_store);
}
let mut before = BTreeSet::<Lint>::new();
if list_enabled() {
lint_store.get_lints().iter().for_each(|&lint| {
before.insert(lint.into());
});
}
for loaded_lib in &loaded_libs {
if let Some(path) = loaded_lib.path.to_str() {
sess.parse_sess
.file_depinfo
.lock()
.insert(rustc_span::Symbol::intern(path));
}
loaded_lib.register_lints(sess, lint_store);
}
if list_enabled() {
let mut after = BTreeSet::<Lint>::new();
lint_store.get_lints().iter().for_each(|&lint| {
after.insert(lint.into());
});
list_lints(&before, &after);
std::process::exit(0);
}
}));
zero_mir_opt_level(config);
}
}
#[must_use]
fn list_enabled() -> bool {
env::var(env::DYLINT_LIST).map_or(false, |value| value != "0")
}
fn list_lints(before: &BTreeSet<Lint>, after: &BTreeSet<Lint>) {
let difference: Vec<Lint> = after.difference(before).cloned().collect();
let name_width = difference
.iter()
.map(|lint| lint.name.len())
.max()
.unwrap_or_default();
let level_width = difference
.iter()
.map(|lint| lint.level.as_str().len())
.max()
.unwrap_or_default();
for Lint { name, level, desc } in difference {
println!(
" {:<name_width$} {:<level_width$} {}",
name.to_lowercase(),
level.as_str(),
desc,
name_width = name_width,
level_width = level_width
);
}
}
pub fn dylint_driver<T: AsRef<OsStr>>(args: &[T]) -> Result<()> {
if args.len() <= 1 || args.iter().any(|arg| arg.as_ref() == "-V") {
println!("{} {}", env!("RUSTUP_TOOLCHAIN"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
run(&args[1..])
}
pub fn run<T: AsRef<OsStr>>(args: &[T]) -> Result<()> {
let sysroot = sysroot().ok();
let rustflags = rustflags();
let paths = paths();
let rustc_args = rustc_args(args, &sysroot, &rustflags, &paths)?;
let mut callbacks = Callbacks::new(paths);
log::debug!("{:?}", rustc_args);
rustc_driver::RunCompiler::new(&rustc_args, &mut callbacks)
.run()
.map_err(|_| std::process::exit(1))
}
fn sysroot() -> Result<PathBuf> {
let rustup_home = env::var(env::RUSTUP_HOME)?;
let rustup_toolchain = env::var(env::RUSTUP_TOOLCHAIN)?;
Ok(PathBuf::from(rustup_home)
.join("toolchains")
.join(rustup_toolchain))
}
fn rustflags() -> Vec<String> {
env::var(env::DYLINT_RUSTFLAGS).map_or_else(
|_| Vec::new(),
|rustflags| rustflags.split_whitespace().map(String::from).collect(),
)
}
fn paths() -> Vec<PathBuf> {
(|| -> Result<_> {
let dylint_libs = env::var(env::DYLINT_LIBS)?;
serde_json::from_str(&dylint_libs).map_err(Into::into)
})()
.unwrap_or_default()
}
fn rustc_args<T: AsRef<OsStr>, U: AsRef<str>, V: AsRef<Path>>(
args: &[T],
sysroot: &Option<PathBuf>,
rustflags: &[U],
paths: &[V],
) -> Result<Vec<String>> {
let mut args = args.iter().peekable();
let mut rustc_args = Vec::new();
let first_arg = args.peek();
if first_arg.map_or(true, |arg| !is_rustc(arg)) {
rustc_args.push("rustc".to_owned());
}
if let Some(arg) = first_arg {
if is_rustc(arg) {
rustc_args.push(arg.as_ref().to_string_lossy().to_string());
let _ = args.next();
}
}
if let Some(sysroot) = sysroot {
rustc_args.extend(vec![
"--sysroot".to_owned(),
sysroot.to_string_lossy().to_string(),
]);
}
for path in paths {
if let Some((name, _)) = parse_path_filename(path.as_ref()) {
rustc_args.push(format!(r#"--cfg=dylint_lib="{name}""#));
} else {
bail!("could not parse `{}`", path.as_ref().to_string_lossy());
}
}
rustc_args.extend(args.map(|s| s.as_ref().to_string_lossy().to_string()));
rustc_args.extend(
rustflags
.iter()
.map(|rustflag| rustflag.as_ref().to_owned()),
);
Ok(rustc_args)
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn no_rustc() {
assert_eq!(
rustc_args(
&["--crate-name", "name"],
&None,
&[] as &[&str],
&[] as &[&Path]
)
.unwrap(),
vec!["rustc", "--crate-name", "name"]
);
}
#[test]
fn plain_rustc() {
assert_eq!(
rustc_args(
&["rustc", "--crate-name", "name"],
&None,
&[] as &[&str],
&[] as &[&Path]
)
.unwrap(),
vec!["rustc", "--crate-name", "name"]
);
}
#[test]
fn qualified_rustc() {
assert_eq!(
rustc_args(
&["/bin/rustc", "--crate-name", "name"],
&None,
&[] as &[&str],
&[] as &[&Path]
)
.unwrap(),
vec!["/bin/rustc", "--crate-name", "name"]
);
}
}