LCOV - code coverage report
Current view: top level - driver/src - lib.rs (source / functions) Hit Total Coverage
Test: unnamed Lines: 69 291 23.7 %
Date: 2024-10-23 19:20:50 Functions: 5 34 14.7 %

          Line data    Source code
       1             : #![feature(rustc_private)]
       2             : #![deny(clippy::expect_used)]
       3             : #![deny(clippy::unwrap_used)]
       4             : #![deny(clippy::panic)]
       5             : #![deny(unused_extern_crates)]
       6             : 
       7             : extern crate rustc_driver;
       8             : extern crate rustc_interface;
       9             : extern crate rustc_lint;
      10             : extern crate rustc_session;
      11             : extern crate rustc_span;
      12             : 
      13             : use anyhow::{bail, ensure, Result};
      14             : use dylint_internal::{env, parse_path_filename, rustup::is_rustc};
      15             : use std::{
      16             :     collections::BTreeSet,
      17             :     ffi::{CString, OsStr},
      18             :     path::{Path, PathBuf},
      19             : };
      20             : 
      21             : pub const DYLINT_VERSION: &str = "0.1.0";
      22             : 
      23             : type DylintVersionFunc = unsafe fn() -> *mut std::os::raw::c_char;
      24             : 
      25             : type RegisterLintsFunc =
      26             :     unsafe fn(sess: &rustc_session::Session, store: &mut rustc_lint::LintStore);
      27             : 
      28             : /// A library that has been loaded but whose lints have not necessarily been registered.
      29             : struct LoadedLibrary {
      30             :     path: PathBuf,
      31             :     lib: libloading::Library,
      32             : }
      33             : 
      34             : #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
      35             : struct Lint {
      36             :     name: &'static str,
      37             :     level: rustc_lint::Level,
      38             :     desc: &'static str,
      39             : }
      40             : 
      41             : impl From<&rustc_lint::Lint> for Lint {
      42           0 :     fn from(lint: &rustc_lint::Lint) -> Self {
      43           0 :         Self {
      44           0 :             name: lint.name,
      45           0 :             level: lint.default_level,
      46           0 :             desc: lint.desc,
      47           0 :         }
      48           0 :     }
      49             : }
      50             : 
      51             : impl LoadedLibrary {
      52           0 :     fn register_lints(
      53           0 :         &self,
      54           0 :         sess: &rustc_session::Session,
      55           0 :         lint_store: &mut rustc_lint::LintStore,
      56           0 :     ) {
      57           0 :         (|| unsafe {
      58           0 :             if let Ok(func) = self.lib.get::<DylintVersionFunc>(b"dylint_version") {
      59           0 :                 let dylint_version = CString::from_raw(func()).into_string()?;
      60           0 :                 ensure!(
      61           0 :                     dylint_version == DYLINT_VERSION,
      62           0 :                     "`{}` has dylint version `{}`, but `{}` was expected",
      63           0 :                     self.path.to_string_lossy(),
      64             :                     dylint_version,
      65             :                     DYLINT_VERSION
      66             :                 );
      67             :             } else {
      68           0 :                 bail!(
      69           0 :                     "could not find `dylint_version` in `{}`",
      70           0 :                     self.path.to_string_lossy()
      71           0 :                 );
      72             :             }
      73           0 :             if let Ok(func) = self.lib.get::<RegisterLintsFunc>(b"register_lints") {
      74           0 :                 func(sess, lint_store);
      75           0 :             } else {
      76           0 :                 bail!(
      77           0 :                     "could not find `register_lints` in `{}`",
      78           0 :                     self.path.to_string_lossy()
      79           0 :                 );
      80             :             }
      81           0 :             Ok(())
      82           0 :         })()
      83           0 :         .unwrap_or_else(|err| {
      84           0 :             session_err(sess, &err);
      85           0 :         });
      86           0 :     }
      87             : }
      88             : 
      89             : #[rustversion::before(2023-12-18)]
      90             : fn session_err(sess: &rustc_session::Session, err: &impl ToString) -> rustc_span::ErrorGuaranteed {
      91             :     sess.diagnostic().err(err.to_string())
      92             : }
      93             : 
      94             : #[rustversion::since(2023-12-18)]
      95           0 : fn session_err(sess: &rustc_session::Session, err: &impl ToString) -> rustc_span::ErrorGuaranteed {
      96           0 :     sess.dcx().err(err.to_string())
      97           0 : }
      98             : 
      99             : struct Callbacks {
     100             :     loaded_libs: Vec<LoadedLibrary>,
     101             : }
     102             : 
     103             : // smoelius: Use of thread local storage was added to Clippy by:
     104             : // https://github.com/rust-lang/rust-clippy/commit/3c06e0b1ce003912f8fe0536d3a7fe22558e38cf
     105             : // This results in a segfault after the Clippy library has been unloaded; see the following issue
     106             : // for an explanation as to why: https://github.com/nagisa/rust_libloading/issues/5
     107             : // The workaround I've chosen is:
     108             : // https://github.com/nagisa/rust_libloading/issues/5#issuecomment-244195096
     109             : impl Callbacks {
     110             :     // smoelius: Load the libraries when `Callbacks` is created and not later (e.g., in `config`)
     111             :     // to ensure that the libraries live long enough.
     112           0 :     fn new(paths: Vec<PathBuf>) -> Self {
     113           0 :         let mut loaded_libs = Vec::new();
     114           0 :         for path in paths {
     115           0 :             unsafe {
     116           0 :                 // smoelius: `libloading` does not define `RTLD_NODELETE`.
     117           0 :                 #[cfg(unix)]
     118           0 :                 let result = libloading::os::unix::Library::open(
     119           0 :                     Some(&path),
     120           0 :                     libloading::os::unix::RTLD_LAZY
     121           0 :                         | libloading::os::unix::RTLD_LOCAL
     122           0 :                         | libc::RTLD_NODELETE,
     123           0 :                 )
     124           0 :                 .map(Into::into);
     125           0 : 
     126           0 :                 #[cfg(not(unix))]
     127           0 :                 let result = libloading::Library::new(&path);
     128           0 : 
     129           0 :                 let lib = result.unwrap_or_else(|err| {
     130           0 :                     // smoelius: rust-lang/rust#111633 changed the type of `early_error`'s `msg`
     131           0 :                     // argument from `&str` to `impl Into<DiagnosticMessage>`.
     132           0 :                     // smoelius: And rust-lang/rust#111748 made it that `msg` is borrowed for
     133           0 :                     // `'static`. Since the program is about to exit, it's probably fine to leak the
     134           0 :                     // string.
     135           0 :                     let msg = format!(
     136           0 :                         "could not load library `{}`: {}",
     137           0 :                         path.to_string_lossy(),
     138           0 :                         err
     139           0 :                     );
     140           0 :                     early_error(msg);
     141           0 :                 });
     142           0 : 
     143           0 :                 loaded_libs.push(LoadedLibrary { path, lib });
     144           0 :             }
     145             :         }
     146           0 :         Self { loaded_libs }
     147           0 :     }
     148             : }
     149             : 
     150             : #[rustversion::before(2023-06-28)]
     151             : fn early_error(msg: String) -> ! {
     152             :     rustc_session::early_error(
     153             :         rustc_session::config::ErrorOutputType::default(),
     154             :         Box::leak(msg.into_boxed_str()) as &str,
     155             :     )
     156             : }
     157             : 
     158             : #[rustversion::since(2023-06-28)]
     159             : extern crate rustc_errors;
     160             : 
     161             : #[rustversion::all(since(2023-06-28), before(2023-12-18))]
     162             : fn early_error(msg: impl Into<rustc_errors::DiagnosticMessage>) -> ! {
     163             :     let handler =
     164             :         rustc_session::EarlyErrorHandler::new(rustc_session::config::ErrorOutputType::default());
     165             :     handler.early_error(msg)
     166             : }
     167             : 
     168             : #[rustversion::all(since(2023-12-18), before(2023-12-23))]
     169             : fn early_error(msg: impl Into<rustc_errors::DiagnosticMessage>) -> ! {
     170             :     let handler =
     171             :         rustc_session::EarlyDiagCtxt::new(rustc_session::config::ErrorOutputType::default());
     172             :     handler.early_error(msg)
     173             : }
     174             : 
     175             : #[rustversion::all(since(2023-12-23), before(2024-03-05))]
     176             : fn early_error(msg: impl Into<rustc_errors::DiagnosticMessage>) -> ! {
     177             :     let handler =
     178             :         rustc_session::EarlyDiagCtxt::new(rustc_session::config::ErrorOutputType::default());
     179             :     handler.early_fatal(msg)
     180             : }
     181             : 
     182             : #[rustversion::since(2024-03-05)]
     183           0 : fn early_error(msg: impl Into<rustc_errors::DiagMessage>) -> ! {
     184           0 :     let handler =
     185           0 :         rustc_session::EarlyDiagCtxt::new(rustc_session::config::ErrorOutputType::default());
     186           0 :     handler.early_fatal(msg)
     187             : }
     188             : 
     189             : trait ParseSess {
     190             :     fn parse_sess(&self) -> &rustc_session::parse::ParseSess;
     191             : }
     192             : 
     193             : impl ParseSess for rustc_session::Session {
     194             :     #[rustversion::before(2024-03-05)]
     195             :     fn parse_sess(&self) -> &rustc_session::parse::ParseSess {
     196             :         &self.parse_sess
     197             :     }
     198             : 
     199             :     #[rustversion::since(2024-03-05)]
     200           0 :     fn parse_sess(&self) -> &rustc_session::parse::ParseSess {
     201           0 :         &self.psess
     202           0 :     }
     203             : }
     204             : 
     205             : #[rustversion::before(2022-07-14)]
     206             : fn zero_mir_opt_level(config: &mut rustc_interface::Config) {
     207             :     trait Zeroable {
     208             :         fn zero(&mut self);
     209             :     }
     210             : 
     211             :     impl Zeroable for usize {
     212             :         fn zero(&mut self) {
     213             :             *self = 0;
     214             :         }
     215             :     }
     216             : 
     217             :     impl Zeroable for Option<usize> {
     218             :         fn zero(&mut self) {
     219             :             *self = Some(0);
     220             :         }
     221             :     }
     222             : 
     223             :     // smoelius: `Zeroable` is a hack to make the next line compile for different Rust versions:
     224             :     // https://github.com/rust-lang/rust-clippy/commit/0941fc0bb5d655cdd0816f862af8cfe70556dad6
     225             :     config.opts.debugging_opts.mir_opt_level.zero();
     226             : }
     227             : 
     228             : // smoelius: Relevant PR and merge commit:
     229             : // - https://github.com/rust-lang/rust/pull/98975
     230             : // - https://github.com/rust-lang/rust/commit/0ed9c64c3e63acac9bd77abce62501696c390450
     231             : #[rustversion::since(2022-07-14)]
     232           0 : fn zero_mir_opt_level(config: &mut rustc_interface::Config) {
     233           0 :     config.opts.unstable_opts.mir_opt_level = Some(0);
     234           0 : }
     235             : 
     236             : impl rustc_driver::Callbacks for Callbacks {
     237           0 :     fn config(&mut self, config: &mut rustc_interface::Config) {
     238           0 :         let previous = config.register_lints.take();
     239           0 :         let loaded_libs = self.loaded_libs.split_off(0);
     240           0 :         config.register_lints = Some(Box::new(move |sess, lint_store| {
     241           0 :             if let Some(previous) = &previous {
     242           0 :                 previous(sess, lint_store);
     243           0 :             }
     244             : 
     245           0 :             let dylint_libs = env::var(env::DYLINT_LIBS).ok();
     246           0 :             let dylint_metadata = env::var(env::DYLINT_METADATA).ok();
     247           0 :             let dylint_no_deps = env::var(env::DYLINT_NO_DEPS).ok();
     248           0 :             let dylint_no_deps_enabled =
     249           0 :                 dylint_no_deps.as_ref().map_or(false, |value| value != "0");
     250           0 :             let cargo_primary_package_is_set = env::var(env::CARGO_PRIMARY_PACKAGE).is_ok();
     251           0 : 
     252           0 :             sess.parse_sess().env_depinfo.lock().insert((
     253           0 :                 rustc_span::Symbol::intern(env::DYLINT_LIBS),
     254           0 :                 dylint_libs.as_deref().map(rustc_span::Symbol::intern),
     255           0 :             ));
     256           0 :             sess.parse_sess().env_depinfo.lock().insert((
     257           0 :                 rustc_span::Symbol::intern(env::DYLINT_METADATA),
     258           0 :                 dylint_metadata.as_deref().map(rustc_span::Symbol::intern),
     259           0 :             ));
     260           0 :             sess.parse_sess().env_depinfo.lock().insert((
     261           0 :                 rustc_span::Symbol::intern(env::DYLINT_NO_DEPS),
     262           0 :                 dylint_no_deps.as_deref().map(rustc_span::Symbol::intern),
     263           0 :             ));
     264           0 : 
     265           0 :             if dylint_no_deps_enabled && !cargo_primary_package_is_set {
     266           0 :                 return;
     267           0 :             }
     268           0 : 
     269           0 :             let mut before = BTreeSet::<Lint>::new();
     270           0 :             if list_enabled() {
     271           0 :                 lint_store.get_lints().iter().for_each(|&lint| {
     272           0 :                     before.insert(lint.into());
     273           0 :                 });
     274           0 :             }
     275           0 :             for loaded_lib in &loaded_libs {
     276           0 :                 if let Some(path) = loaded_lib.path.to_str() {
     277           0 :                     sess.parse_sess()
     278           0 :                         .file_depinfo
     279           0 :                         .lock()
     280           0 :                         .insert(rustc_span::Symbol::intern(path));
     281           0 :                 }
     282           0 :                 loaded_lib.register_lints(sess, lint_store);
     283             :             }
     284           0 :             if list_enabled() {
     285           0 :                 let mut after = BTreeSet::<Lint>::new();
     286           0 :                 lint_store.get_lints().iter().for_each(|&lint| {
     287           0 :                     after.insert(lint.into());
     288           0 :                 });
     289           0 :                 list_lints(&before, &after);
     290           0 :                 std::process::exit(0);
     291           0 :             }
     292           0 :         }));
     293           0 : 
     294           0 :         // smoelius: Choose to be compatible with Clippy:
     295           0 :         // https://github.com/rust-lang/rust-clippy/commit/7bae5bd828e98af9d245b77118c075a7f1a036b9
     296           0 :         zero_mir_opt_level(config);
     297           0 :     }
     298             : }
     299             : 
     300             : #[must_use]
     301           0 : fn list_enabled() -> bool {
     302           0 :     env::var(env::DYLINT_LIST).map_or(false, |value| value != "0")
     303           0 : }
     304             : 
     305           0 : fn list_lints(before: &BTreeSet<Lint>, after: &BTreeSet<Lint>) {
     306           0 :     let difference: Vec<Lint> = after.difference(before).cloned().collect();
     307           0 : 
     308           0 :     let name_width = difference
     309           0 :         .iter()
     310           0 :         .map(|lint| lint.name.len())
     311           0 :         .max()
     312           0 :         .unwrap_or_default();
     313           0 : 
     314           0 :     let level_width = difference
     315           0 :         .iter()
     316           0 :         .map(|lint| lint.level.as_str().len())
     317           0 :         .max()
     318           0 :         .unwrap_or_default();
     319             : 
     320           0 :     for Lint { name, level, desc } in difference {
     321           0 :         println!(
     322           0 :             "    {:<name_width$}    {:<level_width$}    {}",
     323           0 :             name.to_lowercase(),
     324           0 :             level.as_str(),
     325           0 :             desc,
     326           0 :             name_width = name_width,
     327           0 :             level_width = level_width
     328           0 :         );
     329           0 :     }
     330           0 : }
     331             : 
     332           0 : pub fn dylint_driver<T: AsRef<OsStr>>(args: &[T]) -> Result<()> {
     333           0 :     if args.len() <= 1 || args.iter().any(|arg| arg.as_ref() == "-V") {
     334           0 :         println!("{} {}", env!("RUSTUP_TOOLCHAIN"), env!("CARGO_PKG_VERSION"));
     335           0 :         return Ok(());
     336           0 :     }
     337           0 : 
     338           0 :     run(&args[1..])
     339           0 : }
     340             : 
     341           0 : pub fn run<T: AsRef<OsStr>>(args: &[T]) -> Result<()> {
     342           0 :     let sysroot = sysroot().ok();
     343           0 :     let rustflags = rustflags();
     344           0 :     let paths = paths();
     345             : 
     346           0 :     let rustc_args = rustc_args(args, &sysroot, &rustflags, &paths)?;
     347             : 
     348           0 :     let mut callbacks = Callbacks::new(paths);
     349           0 : 
     350           0 :     // smoelius: I am not sure that this should be here. `RUST_LOG=debug cargo test` fails because
     351           0 :     // of the log messages.
     352           0 :     log::debug!("{:?}", rustc_args);
     353             : 
     354           0 :     rustc_driver::RunCompiler::new(&rustc_args, &mut callbacks)
     355           0 :         .run()
     356           0 :         .map_err(|_| std::process::exit(1))
     357           0 : }
     358             : 
     359           0 : fn sysroot() -> Result<PathBuf> {
     360           0 :     let rustup_home = env::var(env::RUSTUP_HOME)?;
     361           0 :     let rustup_toolchain = env::var(env::RUSTUP_TOOLCHAIN)?;
     362           0 :     Ok(PathBuf::from(rustup_home)
     363           0 :         .join("toolchains")
     364           0 :         .join(rustup_toolchain))
     365           0 : }
     366             : 
     367           0 : fn rustflags() -> Vec<String> {
     368           0 :     env::var(env::DYLINT_RUSTFLAGS).map_or_else(
     369           0 :         |_| Vec::new(),
     370           0 :         |rustflags| rustflags.split_whitespace().map(String::from).collect(),
     371           0 :     )
     372           0 : }
     373             : 
     374           0 : fn paths() -> Vec<PathBuf> {
     375           0 :     (|| -> Result<_> {
     376           0 :         let dylint_libs = env::var(env::DYLINT_LIBS)?;
     377           0 :         serde_json::from_str(&dylint_libs).map_err(Into::into)
     378           0 :     })()
     379           0 :     .unwrap_or_default()
     380           0 : }
     381             : 
     382           3 : fn rustc_args<T: AsRef<OsStr>, U: AsRef<str>, V: AsRef<Path>>(
     383           3 :     args: &[T],
     384           3 :     sysroot: &Option<PathBuf>,
     385           3 :     rustflags: &[U],
     386           3 :     paths: &[V],
     387           3 : ) -> Result<Vec<String>> {
     388           3 :     let mut args = args.iter().peekable();
     389           3 :     let mut rustc_args = Vec::new();
     390           3 : 
     391           3 :     let first_arg = args.peek();
     392           3 :     if first_arg.map_or(true, |arg| !is_rustc(arg)) {
     393           1 :         rustc_args.push("rustc".to_owned());
     394           2 :     }
     395           3 :     if let Some(arg) = first_arg {
     396           3 :         if is_rustc(arg) {
     397           2 :             rustc_args.push(arg.as_ref().to_string_lossy().to_string());
     398           2 :             let _ = args.next();
     399           2 :         }
     400           0 :     }
     401           3 :     if let Some(sysroot) = sysroot {
     402           0 :         rustc_args.extend([
     403           0 :             "--sysroot".to_owned(),
     404           0 :             sysroot.to_string_lossy().to_string(),
     405           0 :         ]);
     406           3 :     }
     407           3 :     for path in paths {
     408           0 :         if let Some((name, _)) = parse_path_filename(path.as_ref()) {
     409           0 :             rustc_args.push(format!(r#"--cfg=dylint_lib="{name}""#));
     410           0 :         } else {
     411           0 :             bail!("could not parse `{}`", path.as_ref().to_string_lossy());
     412             :         }
     413             :     }
     414           6 :     rustc_args.extend(args.map(|s| s.as_ref().to_string_lossy().to_string()));
     415           3 :     rustc_args.extend(
     416           3 :         rustflags
     417           3 :             .iter()
     418           3 :             .map(|rustflag| rustflag.as_ref().to_owned()),
     419           3 :     );
     420           3 : 
     421           3 :     Ok(rustc_args)
     422           3 : }
     423             : 
     424             : #[expect(clippy::unwrap_used)]
     425             : #[cfg(test)]
     426             : mod test {
     427             :     use super::*;
     428             :     use rustc_version::{version_meta, Channel};
     429             : 
     430             :     #[test]
     431           1 :     fn channel_is_nightly() {
     432           1 :         assert!(matches!(version_meta().unwrap().channel, Channel::Nightly));
     433           1 :     }
     434             : 
     435             :     #[test]
     436           1 :     fn no_rustc() {
     437           1 :         assert_eq!(
     438           1 :             rustc_args(
     439           1 :                 &["--crate-name", "name"],
     440           1 :                 &None,
     441           1 :                 &[] as &[&str],
     442           1 :                 &[] as &[&Path]
     443           1 :             )
     444           1 :             .unwrap(),
     445           1 :             vec!["rustc", "--crate-name", "name"]
     446           1 :         );
     447           1 :     }
     448             : 
     449             :     #[test]
     450           1 :     fn plain_rustc() {
     451           1 :         assert_eq!(
     452           1 :             rustc_args(
     453           1 :                 &["rustc", "--crate-name", "name"],
     454           1 :                 &None,
     455           1 :                 &[] as &[&str],
     456           1 :                 &[] as &[&Path]
     457           1 :             )
     458           1 :             .unwrap(),
     459           1 :             vec!["rustc", "--crate-name", "name"]
     460           1 :         );
     461           1 :     }
     462             : 
     463             :     #[test]
     464           1 :     fn qualified_rustc() {
     465           1 :         assert_eq!(
     466           1 :             rustc_args(
     467           1 :                 &["/bin/rustc", "--crate-name", "name"],
     468           1 :                 &None,
     469           1 :                 &[] as &[&str],
     470           1 :                 &[] as &[&Path]
     471           1 :             )
     472           1 :             .unwrap(),
     473           1 :             vec!["/bin/rustc", "--crate-name", "name"]
     474           1 :         );
     475           1 :     }
     476             : }

Generated by: LCOV version 1.14