This commit is contained in:
nora 2022-12-18 11:27:23 +01:00
parent a36bf95c3e
commit 1cb5114557
7 changed files with 226 additions and 69 deletions

View file

@ -19,7 +19,7 @@ struct BuildInner {
#[derive(Debug)] #[derive(Debug)]
enum BuildMode { enum BuildMode {
Cargo, Cargo { args: Option<Vec<String>> },
Script(PathBuf), Script(PathBuf),
Rustc, Rustc,
} }
@ -28,10 +28,15 @@ impl Build {
pub fn new(options: &Options) -> Self { pub fn new(options: &Options) -> Self {
let mode = if options.rustc { let mode = if options.rustc {
BuildMode::Rustc BuildMode::Rustc
} else if let Some(script) = &options.verify_error_path { } else if let Some(script) = &options.script_path {
BuildMode::Script(script.clone()) BuildMode::Script(script.clone())
} else { } else {
BuildMode::Cargo BuildMode::Cargo {
args: options
.cargo_args
.as_ref()
.map(|cmd| cmd.split_whitespace().map(ToString::to_string).collect()),
}
}; };
Self { Self {
inner: Rc::new(BuildInner { inner: Rc::new(BuildInner {
@ -51,10 +56,14 @@ impl Build {
} }
let reproduces_issue = match &self.inner.mode { let reproduces_issue = match &self.inner.mode {
BuildMode::Cargo => { BuildMode::Cargo { args } => {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.arg("build"); cmd.arg("build");
for arg in args.into_iter().flatten() {
cmd.arg(arg);
}
let output = let output =
String::from_utf8(cmd.output().context("spawning rustc process")?.stderr) String::from_utf8(cmd.output().context("spawning rustc process")?.stderr)
.unwrap(); .unwrap();
@ -86,11 +95,15 @@ impl Build {
} }
pub fn get_diags(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> { pub fn get_diags(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
let diags = match self.inner.mode { let diags = match &self.inner.mode {
BuildMode::Cargo => { BuildMode::Cargo { args } => {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.args(["build", "--message-format=json"]); cmd.args(["build", "--message-format=json"]);
for arg in args.into_iter().flatten() {
cmd.arg(arg);
}
let cmd_output = cmd.output()?; let cmd_output = cmd.output()?;
let output = String::from_utf8(cmd_output.stdout.clone())?; let output = String::from_utf8(cmd_output.stdout.clone())?;

View file

@ -27,11 +27,12 @@ impl VisitMut for Visitor<'_> {
match block.stmts.as_slice() { match block.stmts.as_slice() {
[syn::Stmt::Expr(syn::Expr::Loop(syn::ExprLoop { [syn::Stmt::Expr(syn::Expr::Loop(syn::ExprLoop {
body: loop_body, .. body: loop_body, ..
}))] if loop_body.stmts.is_empty() && self.checker.can_process(&self.current_path) => {} }))] if loop_body.stmts.is_empty() => {}
_ => { _ if self.checker.can_process(&self.current_path) => {
*block = self.loop_expr.clone(); *block = self.loop_expr.clone();
self.process_state = ProcessState::Changed; self.process_state = ProcessState::Changed;
} }
_ => {}
} }
} }

View file

@ -21,7 +21,11 @@ enum Cargo {
#[derive(clap::Args, Debug)] #[derive(clap::Args, Debug)]
pub struct Options { pub struct Options {
#[arg(short, long)] #[arg(short, long)]
verify_error_path: Option<PathBuf>, script_path: Option<PathBuf>,
#[arg(long)]
cargo_args: Option<String>,
#[arg(long)] #[arg(long)]
rustc: bool, rustc: bool,
#[arg(long)] #[arg(long)]
@ -38,8 +42,6 @@ pub fn minimize() -> Result<()> {
let mut minimizer = Minimizer::new_glob_dir(&options.path, build); let mut minimizer = Minimizer::new_glob_dir(&options.path, build);
minimizer.delete_dead_code().context("deleting dead code")?;
minimizer.run_passes([ minimizer.run_passes([
Box::new(Privatize::default()) as Box<dyn Processor>, Box::new(Privatize::default()) as Box<dyn Processor>,
Box::new(EverybodyLoops::default()) as Box<dyn Processor>, Box::new(EverybodyLoops::default()) as Box<dyn Processor>,

View file

@ -1,7 +1,7 @@
mod files; mod files;
mod reaper; mod reaper;
use std::{collections::HashSet, ffi::OsStr, path::Path}; use std::{borrow::Borrow, collections::HashSet, ffi::OsStr, mem, path::Path};
use anyhow::{ensure, Context, Result}; use anyhow::{ensure, Context, Result};
@ -66,7 +66,7 @@ impl Minimizer {
} }
pub fn run_passes<'a>( pub fn run_passes<'a>(
&mut self, &self,
passes: impl IntoIterator<Item = Box<dyn Processor + 'a>>, passes: impl IntoIterator<Item = Box<dyn Processor + 'a>>,
) -> Result<()> { ) -> Result<()> {
let inital_build = self.build.build()?; let inital_build = self.build.build()?;
@ -83,7 +83,7 @@ impl Minimizer {
Ok(()) Ok(())
} }
fn run_pass(&mut self, pass: &mut dyn Processor) -> Result<()> { fn run_pass(&self, pass: &mut dyn Processor) -> Result<()> {
let mut invalidated_files = HashSet::new(); let mut invalidated_files = HashSet::new();
let mut refresh_and_try_again = false; let mut refresh_and_try_again = false;
@ -97,39 +97,7 @@ impl Minimizer {
continue; continue;
} }
let file_display = file.path.display(); self.process_file(pass, file, &mut invalidated_files, &mut changes)?;
let mut change = file.try_change(&mut changes)?;
let mut krate = syn::parse_file(change.before_content())
.with_context(|| format!("parsing file {file_display}"))?;
let has_made_change = pass.process_file(&mut krate, file, &mut PassController {});
match has_made_change {
ProcessState::Changed | ProcessState::FileInvalidated => {
let result = prettyplease::unparse(&krate);
change.write(&result)?;
let after = self.build.build()?;
println!("{file_display}: After {}: {after}", pass.name());
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());
}
}
} }
if !changes.had_changes() { if !changes.had_changes() {
@ -149,14 +117,174 @@ impl Minimizer {
} }
} }
} }
fn process_file<'file>(
&self,
pass: &mut dyn Processor,
file: &'file SourceFile,
invalidated_files: &mut HashSet<&'file SourceFile>,
changes: &mut Changes,
) -> Result<()> {
let mut checker = PassController::new();
loop {
dbg!(&checker);
let file_display = file.path.display();
let mut change = file.try_change(changes)?;
let mut krate = syn::parse_file(change.before_content())
.with_context(|| format!("parsing file {file_display}"))?;
let has_made_change = pass.process_file(&mut krate, file, &mut checker);
match has_made_change {
ProcessState::Changed | ProcessState::FileInvalidated => {
let result = prettyplease::unparse(&krate);
change.write(&result)?;
let after = self.build.build()?;
println!("{file_display}: After {}: {after}", pass.name());
if after.reproduces_issue() {
change.commit();
checker.reproduces();
} else {
change.rollback()?;
checker.does_not_reproduce();
}
if has_made_change == ProcessState::FileInvalidated {
invalidated_files.insert(file);
}
}
ProcessState::NoChange => {
println!("{file_display}: After {}: no change", pass.name());
checker.no_change();
}
}
if checker.is_finished() {
break;
}
}
Ok(())
}
} }
pub struct PassController {} #[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct AstPath(Vec<String>);
impl Borrow<[String]> for AstPath {
fn borrow(&self) -> &[String] {
&self.0
}
}
#[derive(Debug)]
pub struct PassController {
state: PassControllerState,
}
#[derive(Debug)]
enum PassControllerState {
InitialCollection {
candidates: Vec<AstPath>,
},
Bisecting {
current: HashSet<AstPath>,
worklist: Vec<Vec<AstPath>>,
},
Success,
}
impl PassController { impl PassController {
pub fn can_process(&mut self, _: &[String]) -> bool { fn new() -> Self {
// FIXME: Actually do smart things here. Self {
true state: PassControllerState::InitialCollection {
candidates: Vec::new(),
},
}
}
fn reproduces(&mut self) {
match &mut self.state {
PassControllerState::InitialCollection { .. } => {
self.state = PassControllerState::Success
}
PassControllerState::Bisecting {
current, worklist, ..
} => match worklist.pop() {
Some(next) => *current = next.into_iter().collect(),
None => {
self.state = PassControllerState::Success;
}
},
PassControllerState::Success => unreachable!("Processed after success"),
}
}
fn does_not_reproduce(&mut self) {
match &mut self.state {
PassControllerState::InitialCollection { candidates } => {
let candidates = mem::take(candidates);
let half = candidates.len() / 2;
let (first_half, second_half) = candidates.split_at(half);
self.state = PassControllerState::Bisecting {
current: first_half.iter().cloned().collect(),
worklist: vec![second_half.to_owned()],
};
}
PassControllerState::Bisecting { current, worklist } => {
dbg!(&current, &worklist);
todo!();
}
PassControllerState::Success => unreachable!("Processed after success"),
}
}
fn no_change(&mut self) {
match &self.state {
PassControllerState::InitialCollection { candidates } => {
assert!(
candidates.is_empty(),
"No change but received candidates: {candidates:?}"
);
self.state = PassControllerState::Success;
}
PassControllerState::Bisecting { current, .. } => {
unreachable!("No change while bisecting, current was empty somehow: {current:?}");
}
PassControllerState::Success => {}
}
}
fn is_finished(&mut self) -> bool {
match &mut self.state {
PassControllerState::InitialCollection { .. } => false,
PassControllerState::Bisecting { .. } => false,
PassControllerState::Success => true,
}
}
pub fn can_process(&mut self, path: &[String]) -> bool {
match &mut self.state {
PassControllerState::InitialCollection { candidates } => {
candidates.push(AstPath(path.to_owned()));
true
}
PassControllerState::Bisecting { current, .. } => current.contains(path),
PassControllerState::Success => {
unreachable!("Processed further after success");
}
}
} }
} }

View file

@ -226,16 +226,16 @@ impl<'a> FindUnusedFunction<'a> {
.filter(|&matches| matches) .filter(|&matches| matches)
.count(); .count();
assert!( match span_matches {
span_matches < 2, 0 => true,
"multiple dead_code spans matched identifier: {span_matches}." 1 => {
); self.process_state = ProcessState::FileInvalidated;
!self.checker.can_process(&self.current_path)
if span_matches == 1 { }
self.process_state = ProcessState::FileInvalidated; _ => {
panic!("multiple dead_code spans matched identifier: {span_matches}.");
}
} }
span_matches == 0 && self.checker.can_process(&self.current_path)
} }
} }
@ -246,9 +246,14 @@ impl VisitMut for FindUnusedFunction<'_> {
item_impl.items.retain(|item| match item { item_impl.items.retain(|item| match item {
ImplItem::Method(method) => { ImplItem::Method(method) => {
self.current_path.push(method.sig.ident.to_string());
let span = method.sig.ident.span(); let span = method.sig.ident.span();
self.should_retain_item(span) let should_retain = self.should_retain_item(span);
self.current_path.pop();
should_retain
} }
_ => true, _ => true,
}); });
@ -261,9 +266,13 @@ impl VisitMut for FindUnusedFunction<'_> {
fn visit_file_mut(&mut self, krate: &mut syn::File) { fn visit_file_mut(&mut self, krate: &mut syn::File) {
krate.items.retain(|item| match item { krate.items.retain(|item| match item {
Item::Fn(func) => { Item::Fn(func) => {
let span = func.sig.ident.span(); self.current_path.push(func.sig.ident.to_string());
self.should_retain_item(span) let span = func.sig.ident.span();
let should_retain = self.should_retain_item(span);
self.current_path.pop();
should_retain
} }
_ => true, _ => true,
}); });
@ -277,9 +286,13 @@ impl VisitMut for FindUnusedFunction<'_> {
if let Some((_, content)) = &mut module.content { if let Some((_, content)) = &mut module.content {
content.retain(|item| match item { content.retain(|item| match item {
Item::Fn(func) => { Item::Fn(func) => {
let span = func.sig.ident.span(); self.current_path.push(func.sig.ident.to_string());
self.should_retain_item(span) let span = func.sig.ident.span();
let should_retain = self.should_retain_item(span);
self.current_path.pop();
should_retain
} }
_ => true, _ => true,
}) })

View file

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

View file

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