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

13
Cargo.lock generated
View file

@ -156,6 +156,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustfix",
"serde",
"serde_json",
"syn",
"walkdir",
@ -1107,18 +1108,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.145"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.145"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
dependencies = [
"proc-macro2",
"quote",
@ -1194,9 +1195,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.102"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b"
dependencies = [
"proc-macro2",
"quote",

View file

@ -16,6 +16,7 @@ prettyplease = "0.1.19"
proc-macro2 = { version = "1.0.48", features = ["span-locations"] }
quote = "1.0.21"
rustfix = "0.6.1"
serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.90"
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] }
walkdir = "2.3.2"

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,19 +107,27 @@ 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,
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,
)
.context("reading output as rustfix suggestions")?;
));
}
Ok((diags, suggestions))
}
}
}
}
pub struct BuildResult {
@ -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,7 +90,8 @@ impl Minimizer {
let has_made_change = pass.process_file(&mut krate, &mut ProcessChecker {});
if has_made_change {
match has_made_change {
ProcessState::Changed | ProcessState::FileInvalidated => {
let result = prettyplease::unparse(&krate);
change.write(&result)?;
@ -90,10 +105,16 @@ impl Minimizer {
} else {
change.rollback()?;
}
} else {
if has_made_change == ProcessState::FileInvalidated {
invalidated_files.insert(file);
}
}
ProcessState::NoChange => {
println!("{file_display}: After {}: no change", pass.name());
}
}
}
if !changes.had_changes() {
println!("Finished {}", 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

View file

@ -4,7 +4,3 @@
name = "hello-world"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -1,5 +1,8 @@
fn unused() {}
fn main() {
unused();
fn unused() {
loop {}
}
fn main() {
loop {}
}
mod other;

View file

@ -0,0 +1,3 @@
fn unused() {
loop {}
}