-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add dead-code warning pass #10477
Add dead-code warning pass #10477
Conversation
@ktt3ja Great! I'll take a look at this in the morning. |
use syntax::visit::Visitor; | ||
use syntax::visit; | ||
|
||
fn should_explore(tcx: ty::ctxt, def_id: ast::DefId) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the criterion for exploring? (could we have a comment about what it is checking?)
This seems like it copies a fair bit of code from |
Some test-case ideas:
|
@alexcrichton: I wanted to see how the final code would take shape before thinking of refactoring. If it turns out that the propagation logic does not need to change at all, then I think it would be as simple as modifying reachable::ReachableContext to take in a visitor and then pass in my own visitor. (that would require making reachable::ReachableContext pub, though, which I'm not sure would be fine) |
Certainly! That sounds like a good strategy to me (thanks!) |
@alexcrichton: It looks like I have to change ReachableContext a bit more to make dead-code warning pass works. In particular, I need to replace the lines for method in methods.iter() {
self.worklist.push(method.id);
} in reachable with for method in methods.iter() {
match method.vis {
ast::public => {
self.worklist.push(method.id);
}
_ => (),
}
} However, I'm not sure if it's going to break something, so I would like you to give comment on that (I tried to run pub struct Foo;
impl Foo { fn baz(); } the method |
I'm fairly confident that it's possible to do that without breaking things, but I would rather not have anything look at privacy after the privacy pass. That should be possible by checking if the method is within the |
I was thinking test6 would be #[crate_type="lib"];
pub use foo::Bar;
mod foo {
pub struct Bar;
} and so it shouldn't warn. (Although, the one you have there should warn, I would think.) BTW, you should add these test cases to |
Ah, I'm sorry. I deleted the comment since I was going to fix something first before notifying you. Here's the comment that huonw responded to (that I modified a bit at the end): @huonw: With the latest commit, here's my current progress: the code from original post produces the same error message. Here are test2.rs and its warning: fn dead_fn() {
}
fn used_fn() {
}
#[main]
fn this_is_main() {
used_fn();
}
// this is not main
fn main() {
dead_fn();
} /home/ktt3ja/dead-code-test/test2.rs:1:0: 2:1 warning: code is never used: `dead_fn`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test2.rs:1 fn dead_fn() {
/home/ktt3ja/dead-code-test/test2.rs:2 }
/home/ktt3ja/dead-code-test/test2.rs:13:0: 1:0 warning: code is never used: `main`, #[warn(dead_code)] on by default test3.rs: fn dead_fn() {
}
#[main]
fn dead_fn2() {
}
fn used_fn() {
}
#[start]
fn start(_: int, _: **u8) -> int {
used_fn();
0
}
// this is not main
fn main() {
dead_fn();
dead_fn2();
} /home/ktt3ja/dead-code-test/test3.rs:1:0: 2:1 warning: code is never used: `dead_fn`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test3.rs:1 fn dead_fn() {
/home/ktt3ja/dead-code-test/test3.rs:2 }
/home/ktt3ja/dead-code-test/test3.rs:5:0: 6:1 warning: code is never used: `dead_fn2`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test3.rs:5 fn dead_fn2() {
/home/ktt3ja/dead-code-test/test3.rs:6 }
/home/ktt3ja/dead-code-test/test3.rs:18:0: 1:0 warning: code is never used: `main`, #[warn(dead_code)] on by default test4.rs struct Foo;
impl Foo { fn foo(&self) { bar() } }
fn bar() {
fn baz() {}
Foo.foo();
baz();
}
fn main() {}
struct Foo2;
impl Foo2 { fn foo2(&self) { bar2() } }
fn bar2() {
fn baz2() {}
Foo2.foo2();
baz2();
}
pub fn pub_fn() {
let foo2_struct = Foo2;
foo2_struct.foo2();
} /home/ktt3ja/dead-code-test/test4.rs:1:0: 1:11 warning: code is never used: `Foo`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test4.rs:1 struct Foo;
^~~~~~~~~~~
/home/ktt3ja/dead-code-test/test4.rs:2:11: 2:34 warning: code is never used: `foo`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test4.rs:2 impl Foo { fn foo(&self) { bar() } }
^~~~~~~~~~~~~~~~~~~~~~~
/home/ktt3ja/dead-code-test/test4.rs:3:0: 8:1 warning: code is never used: `bar`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test4.rs:3 fn bar() {
/home/ktt3ja/dead-code-test/test4.rs:4 fn baz() {}
/home/ktt3ja/dead-code-test/test4.rs:5
/home/ktt3ja/dead-code-test/test4.rs:6 Foo.foo();
/home/ktt3ja/dead-code-test/test4.rs:7 baz();
/home/ktt3ja/dead-code-test/test4.rs:8 }
/home/ktt3ja/dead-code-test/test4.rs:4:3: 4:14 warning: code is never used: `baz`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test4.rs:4 fn baz() {}
^~~~~~~~~~~ test5.rs struct Foo;
impl Foo {
pub fn foo1() {}
fn foo2() {}
}
fn main() {
} /home/ktt3ja/dead-code-test/test5.rs:1:0: 1:11 warning: code is never used: `Foo`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test5.rs:1 struct Foo;
^~~~~~~~~~~
/home/ktt3ja/dead-code-test/test5.rs:5:1: 5:13 warning: code is never used: `foo2`, #[warn(dead_code)] on by default
/home/ktt3ja/dead-code-test/test5.rs:5 fn foo2() {}
^~~~~~~~~~~~ and test6.rs #[crate_type="lib"];
mod foo {
pub struct Bar; // not externally visible without the `pub use`
} produces no dead-code warning. I think that covers almost everything. Perhaps the |
@@ -20,7 +20,7 @@ use std::uint; | |||
use std::vec; | |||
|
|||
#[deriving(Clone)] | |||
struct SmallBitv { | |||
pub struct SmallBitv { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure these are meant to be public, although all their methods are pub
. (In any case, they shouldn't be warning, since they are used in the BitvVariant
enum, which is used in the public Bitv
struct.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was actually no warning on the SmallBitv
and BigBitv
struct; it was just that the negate
method of BigBitv
was never used. I have reverted the change on the visibility of those two structs, and modified Bitv
's negate
method to call BigBitv
's negate
. I think that's what was intended.
r? @alexcrichton and @huonw A few things that need mentioning:
|
It seems ok to me, and the tests look good; but I don't have enough familiarity with these parts of rustc to r+. |
Forgot to mention: it's still missing a test with |
Something like extern { fn abort() -> !; } which imports a symbol from libc should be ok, or, if it's not, using a compiler-internal extern "rust-intrinsic" { fn transmute<A,B>(x: A) -> B; } |
Two more high-level comments:
|
I think this is mostly just because making the decisions about whether something is supposed to be removed or not is hard for someone who's just starting out with the Rust code base (i.e. @ktt3ja), and it gets fairly tiresome. (This is me voting for warn by default.) (That is, people who are familiar with the various areas can go through and progressively remove the dead code, and adjust the code that's not supposed to be dead.) |
huonw is right about why there are so many
The dead-code pass is already a standalone pass. The differences between them are:
|
@alexcrichton: hm, it looks like github removed all the comments you made on my old commits and my responses to them. Anyway, I just updated my commits to reduce the amount of code duplication. Unfortunately, it involves littering |
alexcrichton's comments on an older commit (that got deleted by github):
|
@alexcrichton: r? I just updated my commit to separate out |
Awesome work! Just ping me when my comments have been addressed, and I think this is good to go! |
I'm going to repost one of your comments and respond to it here in case it gets deleted the next time I force push:
It just didn't occur to me that you could implement trait inside function, that's why I only traversed module. I mark all trait methods as live because it seems to be the easiest course of action. I don't have sufficient understanding of static/dynamic dispatch (and how they are implemented), nor do I a solid enough model in my head to tackle this problem in a more detailed-oriented manner. Perhaps a stricter analysis would be something like this:
However, I think a justification to the current behavior is that traits define an interface, and defining an interface is ultimately a matter of design. I think usually someone would remove a required method from a trait definition if they are convinced that it wouldn't be useful, and conversely they would want to keep it if they feel that the method is fitting and will be used eventually. It would be awkward to warn dead code on a trait method implementation, then for someone to remove it they would have to remove the method from the trait definition and all the other trait implementations. |
I think that assuming all trait implementations are "live code" is certainly ok for now. I'd just recommend recursing into inner functions as well. |
@alexcrichton: r+? My previous changes (re-)introduced some problems that made bors fail to build. I have fixed them by:
Here's the diff between my last commit and this one: diff --git a/src/etc/extract-tests.py b/src/etc/extract-tests.py
index 5904e10..ab2556f 100644
--- a/src/etc/extract-tests.py
+++ b/src/etc/extract-tests.py
@@ -64,6 +64,7 @@ while cur < len(lines):
#[ allow(dead_assignment) ];\n
#[ allow(unused_mut) ];\n
#[ allow(attribute_usage) ];\n
+#[ allow(dead_code) ];\n
#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n
""" + block
if xfail:
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index f07887c..e08726a 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -312,7 +312,7 @@ pub fn phase_3_run_analysis_passes(sess: Session,
time(time_passes, "death checking", (), |_|
middle::dead::check_crate(ty_cx, method_map,
- reachable_map, crate));
+ &exported_items, reachable_map, crate));
time(time_passes, "lint checking", (), |_|
lint::check_crate(ty_cx, &exported_items, crate));
diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs
index 88b0a35..332b706 100644
--- a/src/librustc/middle/dead.rs
+++ b/src/librustc/middle/dead.rs
@@ -196,31 +196,46 @@ impl Visitor<()> for TraitMethodSeeder {
}
fn create_and_seed_worklist(tcx: ty::ctxt,
+ exported_items: &privacy::ExportedItems,
reachable_symbols: &HashSet<ast::NodeId>,
crate: &ast::Crate) -> ~[ast::NodeId] {
let mut worklist = ~[];
+ // Preferably, we would only need to seed the worklist with reachable
+ // symbols. However, since the set of reachable symbols differs
+ // depending on whether a crate is built as bin or lib, and we want
+ // the warning to be consistent, we also seed the worklist with
+ // exported symbols.
+ for &id in exported_items.iter() {
+ worklist.push(id);
+ }
for &id in reachable_symbols.iter() {
worklist.push(id);
}
+
+ // Seed entry point
match *tcx.sess.entry_fn {
Some((id, _)) => worklist.push(id),
None => ()
}
- let mut trait_method_visitor = TraitMethodSeeder {
+
+ // Seed implemeneted trait methods
+ let mut trait_method_seeder = TraitMethodSeeder {
worklist: worklist
};
- visit::walk_crate(&mut trait_method_visitor, crate, ());
+ visit::walk_crate(&mut trait_method_seeder, crate, ());
- return trait_method_visitor.worklist;
+ return trait_method_seeder.worklist;
}
fn find_live(tcx: ty::ctxt,
method_map: typeck::method_map,
+ exported_items: &privacy::ExportedItems,
reachable_symbols: &HashSet<ast::NodeId>,
crate: &ast::Crate)
-> ~HashSet<ast::NodeId> {
- let worklist = create_and_seed_worklist(tcx, reachable_symbols, crate);
+ let worklist = create_and_seed_worklist(tcx, exported_items,
+ reachable_symbols, crate);
let mut symbol_visitor = MarkSymbolVisitor::new(tcx, method_map, worklist);
symbol_visitor.mark_live_symbols();
symbol_visitor.live_symbols
@@ -315,13 +330,19 @@ impl Visitor<()> for DeadVisitor {
}
visit::walk_block(self, block, ());
}
+
+ fn visit_trait_method(&mut self, _ :&ast::trait_method, _: ()) {
+ // Don't warn on trait method
+ }
}
pub fn check_crate(tcx: ty::ctxt,
method_map: typeck::method_map,
exported_items: &privacy::ExportedItems,
+ reachable_symbols: &HashSet<ast::NodeId>,
crate: &ast::Crate) {
- let live_symbols = find_live(tcx, method_map, exported_items, crate);
+ let live_symbols = find_live(tcx, method_map, exported_items,
+ reachable_symbols, crate);
let mut visitor = DeadVisitor { tcx: tcx, live_symbols: live_symbols };
visit::walk_crate(&mut visitor, crate, ());
} |
@alexcrichton: sorry, I keep missing these small details. :( Now I have it walk the body of a trait method. Is it okay now? |
PR for issue #1749 mainly to get some feedback and suggestion. This adds a pass that warns if a function, struct, enum, or static item is never used. For the following code, ```rust pub static pub_static: int = 0; static priv_static: int = 0; static used_static: int = 0; pub fn pub_fn() { used_fn(); } fn priv_fn() { let unused_struct = PrivStruct; } fn used_fn() {} pub struct PubStruct(); struct PrivStruct(); struct UsedStruct1 { x: int } struct UsedStruct2(int); struct UsedStruct3(); pub enum pub_enum { foo1, bar1 } enum priv_enum { foo2, bar2 } enum used_enum { foo3, bar3 } fn foo() { bar(); let unused_enum = foo2; } fn bar() { foo(); } fn main() { let used_struct1 = UsedStruct1 { x: 1 }; let used_struct2 = UsedStruct2(1); let used_struct3 = UsedStruct3; let t = used_static; let e = foo3; } ``` it would add the following warnings: ```rust /home/ktt3ja/test.rs:2:0: 2:28 warning: code is never used: `priv_static`, #[warn(dead_code)] on by default /home/ktt3ja/test.rs:2 static priv_static: int = 0; ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/ktt3ja/test.rs:6:0: 6:48 warning: code is never used: `priv_fn`, #[warn(dead_code)] on by default /home/ktt3ja/test.rs:6 fn priv_fn() { let unused_struct = PrivStruct; } ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/ktt3ja/test.rs:10:0: 10:20 warning: code is never used: `PrivStruct`, #[warn(dead_code)] on by default /home/ktt3ja/test.rs:10 struct PrivStruct(); ^~~~~~~~~~~~~~~~~~~~ /home/ktt3ja/test.rs:16:0: 16:29 warning: code is never used: `priv_enum`, #[warn(dead_code)] on by default /home/ktt3ja/test.rs:16 enum priv_enum { foo2, bar2 } ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/ktt3ja/test.rs:19:0: 22:1 warning: code is never used: `foo`, #[warn(dead_code)] on by default /home/ktt3ja/test.rs:19 fn foo() { /home/ktt3ja/test.rs:20 bar(); /home/ktt3ja/test.rs:21 let unused_enum = foo2; /home/ktt3ja/test.rs:22 } /home/ktt3ja/test.rs:24:0: 26:1 warning: code is never used: `bar`, #[warn(dead_code)] on by default /home/ktt3ja/test.rs:24 fn bar() { /home/ktt3ja/test.rs:25 foo(); /home/ktt3ja/test.rs:26 } ``` Furthermore, I would like to solicit some test cases since I haven't tested extensively and I'm still unclear about some of the things in here. For example, I'm not sure how reexports would affect this and just assumed that LiveContext (which is a copy of reachable::ReachableContext) does enough work to handle it. Also, the test case above doesn't include any impl or methods, etc.
\o/ |
PR for issue #1749 mainly to get some feedback and suggestion. This adds a pass that warns if a function, struct, enum, or static item is never used. For the following code,
it would add the following warnings:
Furthermore, I would like to solicit some test cases since I haven't tested extensively and I'm still unclear about some of the things in here. For example, I'm not sure how reexports would affect this and just assumed that LiveContext (which is a copy of reachable::ReachableContext) does enough work to handle it. Also, the test case above doesn't include any impl or methods, etc.