This commit is contained in:
nora 2022-12-17 22:17:34 +01:00
parent a9e488f3e3
commit 0f05ef625a
11 changed files with 153 additions and 70 deletions

View file

@ -1,6 +1,7 @@
use anyhow::{Context, Result};
use rustfix::diagnostics::Diagnostic;
use std::{collections::HashSet, fmt::Display, path::PathBuf};
use serde::Deserialize;
use std::{collections::HashSet, fmt::Display, path::PathBuf, process::Command};
use crate::Options;
@ -35,9 +36,16 @@ impl Build {
}
pub fn build(&self) -> Result<BuildResult> {
if self.no_verify {
return Ok(BuildResult {
reproduces_issue: false,
no_verify: true,
});
}
let reproduces_issue = match &self.mode {
BuildMode::Cargo => {
let mut cmd = std::process::Command::new("cargo");
let mut cmd = Command::new("cargo");
cmd.arg("build");
let output =
@ -47,12 +55,12 @@ impl Build {
output.contains("internal compiler error")
}
BuildMode::Script(script_path) => {
let mut cmd = std::process::Command::new(script_path);
let mut cmd = Command::new(script_path);
cmd.output().context("spawning script")?.status.success()
}
BuildMode::Rustc => {
let mut cmd = std::process::Command::new("rustc");
let mut cmd = Command::new("rustc");
cmd.args(["--edition", "2018"]);
cmd.arg(&self.input_path);
@ -71,11 +79,26 @@ impl Build {
}
pub fn get_suggestions(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
match self.mode {
let diags = match self.mode {
BuildMode::Cargo => {
todo!();
let mut cmd = Command::new("cargo");
cmd.args(["build", "--message-format=json"]);
let cmd_output = cmd.output()?;
let output = String::from_utf8(cmd_output.stdout.clone())?;
let messages = serde_json::Deserializer::from_str(&output)
.into_iter::<CargoJsonCompileMessage>()
.collect::<Result<Vec<_>, _>>()?;
let diags = messages
.into_iter()
.filter(|msg| msg.reason == "compiler-message")
.flat_map(|msg| msg.message)
.collect();
diags
}
BuildMode::Script(_) => todo!(),
BuildMode::Rustc => {
let mut cmd = std::process::Command::new("rustc");
cmd.args(["--edition", "2018", "--error-format=json"]);
@ -84,18 +107,26 @@ impl Build {
let output = cmd.output()?.stderr;
let output = String::from_utf8(output)?;
let diags = serde_json::Deserializer::from_str(&output).into_iter::<Diagnostic>().collect::<Result<_, _>>()?;
let diags = serde_json::Deserializer::from_str(&output)
.into_iter::<Diagnostic>()
.collect::<Result<_, _>>()?;
let suggestions = rustfix::get_suggestions_from_json(
&output,
&HashSet::new(),
rustfix::Filter::Everything,
)
.context("reading output as rustfix suggestions")?;
Ok((diags, suggestions))
diags
}
BuildMode::Script(_) => todo!(),
};
let mut suggestions = Vec::new();
for cargo_msg in &diags {
// One diagnostic line might have multiple suggestions
suggestions.extend(rustfix::collect_suggestions(
cargo_msg,
&HashSet::new(),
rustfix::Filter::Everything,
));
}
Ok((diags, suggestions))
}
}
@ -114,8 +145,14 @@ impl Display for BuildResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (self.reproduces_issue, self.no_verify) {
(true, _) => f.write_str("yes"),
(false, true) => f.write_str("no (ignore)"),
(false, true) => f.write_str("yes (no-verify)"),
(false, false) => f.write_str("no"),
}
}
}
#[derive(Debug, Deserialize)]
pub struct CargoJsonCompileMessage {
pub reason: String,
pub message: Option<Diagnostic>,
}

View file

@ -1,14 +1,14 @@
use quote::ToTokens;
use syn::{parse_quote, visit_mut::VisitMut};
use crate::processor::{ProcessChecker, Processor};
use crate::processor::{ProcessChecker, ProcessState, Processor};
struct Visitor<'a> {
current_path: Vec<String>,
checker: &'a mut ProcessChecker,
loop_expr: syn::Block,
has_made_change: bool,
process_state: ProcessState,
}
impl<'a> Visitor<'a> {
@ -16,7 +16,7 @@ impl<'a> Visitor<'a> {
Self {
current_path: Vec::new(),
checker,
has_made_change: false,
process_state: ProcessState::NoChange,
loop_expr: parse_quote! { { loop {} } },
}
}
@ -30,7 +30,7 @@ impl VisitMut for Visitor<'_> {
}))] if loop_body.stmts.is_empty() => {}
_ => {
*block = self.loop_expr.clone();
self.has_made_change = true;
self.process_state = ProcessState::Changed;
}
}
}
@ -65,10 +65,14 @@ impl VisitMut for Visitor<'_> {
pub struct EverybodyLoops;
impl Processor for EverybodyLoops {
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker) -> bool {
fn process_file(
&mut self,
krate: &mut syn::File,
checker: &mut ProcessChecker,
) -> ProcessState {
let mut visitor = Visitor::new(checker);
visitor.visit_file_mut(krate);
visitor.has_made_change
visitor.process_state
}
fn name(&self) -> &'static str {

View file

@ -1,16 +1,16 @@
use syn::{parse_quote, visit_mut::VisitMut, Visibility};
use crate::processor::{ProcessChecker, Processor};
use crate::processor::{ProcessChecker, Processor, ProcessState};
struct Visitor {
pub_crate: Visibility,
has_made_change: bool,
process_state: ProcessState,
}
impl Visitor {
fn new() -> Self {
Self {
has_made_change: false,
process_state: ProcessState::NoChange,
pub_crate: parse_quote! { pub(crate) },
}
}
@ -19,7 +19,7 @@ impl Visitor {
impl VisitMut for Visitor {
fn visit_visibility_mut(&mut self, vis: &mut Visibility) {
if let Visibility::Public(_) = vis {
self.has_made_change = true;
self.process_state = ProcessState::Changed;
*vis = self.pub_crate.clone();
}
}
@ -29,10 +29,10 @@ impl VisitMut for Visitor {
pub struct Privatize {}
impl Processor for Privatize {
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> bool {
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> ProcessState {
let mut visitor = Visitor::new();
visitor.visit_file_mut(krate);
visitor.has_made_change
visitor.process_state
}
fn name(&self) -> &'static str {

View file

@ -4,7 +4,7 @@ use std::{
path::{Path, PathBuf},
};
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct SourceFile {
pub path: PathBuf,
}

View file

@ -1,7 +1,7 @@
mod files;
mod reaper;
use std::{ffi::OsStr, path::Path};
use std::{collections::HashSet, ffi::OsStr, path::Path};
use anyhow::{ensure, Context, Result};
@ -10,11 +10,19 @@ use crate::{build::Build, processor::files::Changes, Options};
use self::files::SourceFile;
pub trait Processor {
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker) -> bool;
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker)
-> ProcessState;
fn name(&self) -> &'static str;
}
#[derive(Debug, PartialEq, Eq)]
pub enum ProcessState {
NoChange,
Changed,
FileInvalidated,
}
#[derive(Debug)]
pub struct Minimizer {
files: Vec<SourceFile>,
@ -61,12 +69,18 @@ impl Minimizer {
);
}
let mut invalidated_files = HashSet::new();
for mut pass in passes {
'pass: loop {
println!("Starting a round of {}", pass.name());
let mut changes = Changes::default();
for file in &self.files {
if invalidated_files.contains(file) {
continue;
}
let file_display = file.path.display();
let mut change = file.try_change(&mut changes)?;
@ -76,22 +90,29 @@ impl Minimizer {
let has_made_change = pass.process_file(&mut krate, &mut ProcessChecker {});
if has_made_change {
let result = prettyplease::unparse(&krate);
match has_made_change {
ProcessState::Changed | ProcessState::FileInvalidated => {
let result = prettyplease::unparse(&krate);
change.write(&result)?;
change.write(&result)?;
let after = self.build.build()?;
let after = self.build.build()?;
println!("{file_display}: After {}: {after}", pass.name());
println!("{file_display}: After {}: {after}", pass.name());
if after.reproduces_issue() {
change.commit();
} else {
change.rollback()?;
if after.reproduces_issue() {
change.commit();
} else {
change.rollback()?;
}
if has_made_change == ProcessState::FileInvalidated {
invalidated_files.insert(file);
}
}
ProcessState::NoChange => {
println!("{file_display}: After {}: no change", pass.name());
}
} else {
println!("{file_display}: After {}: no change", pass.name());
}
}

View file

@ -1,6 +1,6 @@
//! Deletes dead code.
use super::{files::Changes, Minimizer, Processor};
use super::{files::Changes, Minimizer, ProcessState, Processor};
use anyhow::{ensure, Context, Result};
use proc_macro2::Span;
use rustfix::{diagnostics::Diagnostic, Suggestion};
@ -38,7 +38,10 @@ impl Minimizer {
// Always unconditionally apply unused imports.
self.apply_unused_imports(&suggestions_for_file)?;
self.run_passes([Box::new(DeleteUnusedFunctions(diags)) as Box<dyn Processor>])
self.run_passes([Box::new(DeleteUnusedFunctions {
diags,
invalid: false,
}) as Box<dyn Processor>])
.context("deleting unused functions")?;
Ok(())
@ -49,11 +52,12 @@ impl Minimizer {
suggestions: &HashMap<&str, Vec<&Suggestion>>,
) -> Result<()> {
for (file, suggestions) in suggestions {
let file = self
let Some(file) = self
.files
.iter()
.find(|source| source.path == Path::new(file))
.expect("unknown file");
.find(|source| source.path == Path::new(file)) else {
continue;
};
let mut changes = &mut Changes::default();
@ -85,14 +89,27 @@ impl Minimizer {
}
}
struct DeleteUnusedFunctions(Vec<Diagnostic>);
struct DeleteUnusedFunctions {
diags: Vec<Diagnostic>,
invalid: bool,
}
impl Processor for DeleteUnusedFunctions {
fn process_file(&mut self, krate: &mut syn::File, _: &mut super::ProcessChecker) -> bool {
let mut visitor = FindUnusedFunction::new(self.0.iter());
fn process_file(
&mut self,
krate: &mut syn::File,
_: &mut super::ProcessChecker,
) -> ProcessState {
assert!(!self.invalid, "processing with invalid state");
let mut visitor = FindUnusedFunction::new(self.diags.iter());
visitor.visit_file_mut(krate);
visitor.has_change
if visitor.process_state == ProcessState::FileInvalidated {
self.invalid = true;
}
visitor.process_state
}
fn name(&self) -> &'static str {
@ -122,7 +139,7 @@ impl Unused {
struct FindUnusedFunction {
unused_functions: Vec<Unused>,
has_change: bool,
process_state: ProcessState,
}
impl FindUnusedFunction {
@ -160,7 +177,7 @@ impl FindUnusedFunction {
Self {
unused_functions,
has_change: false,
process_state: ProcessState::NoChange,
}
}
@ -174,11 +191,11 @@ impl FindUnusedFunction {
assert!(
span_matches < 2,
"multiple dead_code spans matched identifier: {span_matches}"
"multiple dead_code spans matched identifier: {span_matches}."
);
if span_matches == 1 {
self.has_change = true;
self.process_state = ProcessState::FileInvalidated;
}
span_matches == 0