From 42d09703b4d11935225e4c707a86da65afc5e667 Mon Sep 17 00:00:00 2001 From: Jacherr Date: Fri, 8 Mar 2024 18:32:29 +0000 Subject: [PATCH] Add support for ICE reporting in lintcheck --- lintcheck/src/main.rs | 96 +++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 664429981..f6e1abacc 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -19,12 +19,12 @@ use std::env::consts::EXE_SUFFIX; use std::fmt::{self, Write as _}; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, ExitStatus}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; use std::{env, fs, thread}; -use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel}; +use cargo_metadata::diagnostic::Diagnostic; use cargo_metadata::Message; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -90,16 +90,43 @@ struct Crate { options: Option>, } +/// A single emitted output from clippy being executed on a crate. It may either be a +/// `ClippyWarning`, or a `RustcIce` caused by a panic within clippy. A crate may have many +/// `ClippyWarning`s but a maximum of one `RustcIce` (at which point clippy halts execution). +#[derive(Debug)] +enum ClippyCheckOutput { + ClippyWarning(ClippyWarning), + RustcIce(RustcIce), +} + +#[derive(Debug)] +struct RustcIce { + pub crate_name: String, + pub ice_content: String, +} +impl RustcIce { + pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option { + if status.code().unwrap_or(0) == 101 + /* ice exit status */ + { + Some(Self { + crate_name: crate_name.to_owned(), + ice_content: stderr.to_owned(), + }) + } else { + None + } + } +} + /// A single warning that clippy issued while checking a `Crate` #[derive(Debug)] struct ClippyWarning { - crate_name: String, file: String, line: usize, column: usize, lint_type: String, message: String, - is_ice: bool, } #[allow(unused)] @@ -124,13 +151,11 @@ impl ClippyWarning { }; Some(Self { - crate_name: crate_name.to_owned(), file, line: span.line_start, column: span.column_start, lint_type, message: diag.message, - is_ice: diag.level == DiagnosticLevel::Ice, }) } @@ -311,7 +336,7 @@ impl Crate { config: &LintcheckConfig, lint_filter: &[String], server: &Option, - ) -> Vec { + ) -> Vec { // advance the atomic index by one let index = target_dir_index.fetch_add(1, Ordering::SeqCst); // "loop" the index within 0..thread_limit @@ -335,9 +360,9 @@ impl Crate { let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir"); let mut cargo_clippy_args = if config.fix { - vec!["--fix", "--"] + vec!["--quiet", "--fix", "--"] } else { - vec!["--", "--message-format=json", "--"] + vec!["--quiet", "--message-format=json", "--"] }; let mut clippy_args = Vec::<&str>::new(); @@ -428,14 +453,21 @@ impl Crate { } // get all clippy warnings and ICEs - let warnings: Vec = Message::parse_stream(stdout.as_bytes()) + let mut entries: Vec = Message::parse_stream(stdout.as_bytes()) .filter_map(|msg| match msg { Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message, &self.name, &self.version), _ => None, }) + .map(ClippyCheckOutput::ClippyWarning) .collect(); - warnings + if let Some(ice) = RustcIce::from_stderr_and_status(&self.name, *status, &stderr) { + entries.push(ClippyCheckOutput::RustcIce(ice)); + } else if !status.success() { + println!("non-ICE bad exit status for {} {}: {}", self.name, self.version, stderr); + } + + entries } } @@ -635,7 +667,7 @@ fn main() { LintcheckServer::spawn(recursive_options) }); - let mut clippy_warnings: Vec = crates + let mut clippy_entries: Vec = crates .par_iter() .flat_map(|krate| { krate.run_clippy_lints( @@ -651,7 +683,9 @@ fn main() { .collect(); if let Some(server) = server { - clippy_warnings.extend(server.warnings()); + let server_clippy_entries = server.warnings().map(ClippyCheckOutput::ClippyWarning); + + clippy_entries.extend(server_clippy_entries); } // if we are in --fix mode, don't change the log files, terminate here @@ -659,20 +693,21 @@ fn main() { return; } + // split up warnings and ices + let mut warnings: Vec = vec![]; + let mut raw_ices: Vec = vec![]; + for entry in clippy_entries { + if let ClippyCheckOutput::ClippyWarning(x) = entry { + warnings.push(x); + } else if let ClippyCheckOutput::RustcIce(x) = entry { + raw_ices.push(x); + } + } + // generate some stats - let (stats_formatted, new_stats) = gather_stats(&clippy_warnings); + let (stats_formatted, new_stats) = gather_stats(&warnings); - // grab crashes/ICEs, save the crate name and the ice message - let ices: Vec<(&String, &String)> = clippy_warnings - .iter() - .filter(|warning| warning.is_ice) - .map(|w| (&w.crate_name, &w.message)) - .collect(); - - let mut all_msgs: Vec = clippy_warnings - .iter() - .map(|warn| warn.to_output(config.markdown)) - .collect(); + let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.markdown)).collect(); all_msgs.sort(); all_msgs.push("\n\n### Stats:\n\n".into()); all_msgs.push(stats_formatted); @@ -686,11 +721,18 @@ fn main() { } write!(text, "{}", all_msgs.join("")).unwrap(); text.push_str("\n\n### ICEs:\n"); - for (cratename, msg) in &ices { - let _: fmt::Result = write!(text, "{cratename}: '{msg}'"); + for ice in &raw_ices { + let _: fmt::Result = write!( + text, + "{}:\n{}\n========================================\n\n", + ice.crate_name, ice.ice_content + ); } println!("Writing logs to {}", config.lintcheck_results_path.display()); + if !raw_ices.is_empty() { + println!("WARNING: at least one ICE reported, check log file"); + } fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap(); fs::write(&config.lintcheck_results_path, text).unwrap();