Skip to content
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 a lint unconditional_recursion to detect unconditional recursion. #20373

Merged
merged 3 commits into from
Jan 25, 2015

Conversation

huonw
Copy link
Member

@huonw huonw commented Dec 31, 2014

E.g. fn foo() { foo() }, or, more subtlely

impl Foo for Box<Foo+'static> {
    fn bar(&self) {
        self.bar();
    }
}

The compiler will warn and point out the points where recursion occurs,
if it determines that the function cannot return without calling itself.

Closes #17899.

@rust-highfive
Copy link
Collaborator

r? @luqmana

(rust_highfive has picked a reviewer for you, use r? to override)

@frewsxcv
Copy link
Member

This needs a rebase

@huonw
Copy link
Member Author

huonw commented Jan 1, 2015

@luqmana, speaking prescriptively, "recurs" is actually more correct than "recurses", e.g. http://english.stackexchange.com/questions/163446/does-a-recursive-procedure-recur ; I'm happy to change it if people prefer the "recurse" version.

@huonw
Copy link
Member Author

huonw commented Jan 1, 2015

@frewsxcv (Thanks! I'm actually leaving it unrebased so it can't be r+'d and landed before I resolve the questions described above.)

This allows one to look at an `ExprMethodCall` `foo.bar()` where `bar`
is a method in some trait and (sometimes) extract the `impl` that `bar`
is defined in, e.g.

    trait Foo {
        fn bar(&self);
    }

    impl Foo for uint { // <A>
        fn bar(&self) {}
    }

    fn main() {
        1u.bar(); // impl_def_id == Some(<A>)
    }

This definitely doesn't handle all cases, but is correct when it is
known, meaning it should only be used for certain linting/heuristic
purposes; no safety analysis.
@huonw
Copy link
Member Author

huonw commented Jan 24, 2015

Updated after discussions with @nikomatsakis.

(Also, this found a bug in the compiler. 😄)

I measured the total time required to construct all the CFGs in each crate (sorted from slowest to fastest), I think it's safe to say that I do not need to worry about the performance of this at the moment since even constructing the graphs for all functions in librustc is only 50 milliseconds.

crate milliseconds
librustc 51.3
libsyntax 40.1
librustc_trans 29.9
libcore 15.9
librustc_typeck 14.1
libstd 12.6
librustdoc 11.7
librustc_resolve 6.8
libserialize 5.4
libcollections 4.9
librustc_borrowck 3.6
librustc_back 2.5
libregex 2.4
libtest 1.5
libgetopts 1.4
librustc_driver 1.3
librbml 1.3
libterm 1.3
librustc_privacy 1.2
librand 1.2
libunicode 0.7
rustbook 0.6
librustc_llvm 0.5
liballoc 0.4
libfmt_macros 0.2
libgraphviz 0.1
liblog 0.1
libarena 0.1
libflate 0.0
librustc_bitflags 0.0
liblibc 0.0

(Collected with this diff + some emacs macros etc.)

E.g. `fn foo() { foo() }`, or, more subtlely

    impl Foo for Box<Foo+'static> {
        fn bar(&self) {
            self.bar();
        }
    }

The compiler will warn and point out the points where recursion occurs,
if it determines that the function cannot return without calling itself.

Closes rust-lang#17899.
This was detected by the unconditional_recursion lint.
@luqmana
Copy link
Member

luqmana commented Jan 25, 2015

@bors: r+ 0684c8e

bors added a commit that referenced this pull request Jan 25, 2015
E.g. `fn foo() { foo() }`, or, more subtlely

    impl Foo for Box<Foo+'static> {
        fn bar(&self) {
            self.bar();
        }
    }

The compiler will warn and point out the points where recursion occurs,
if it determines that the function cannot return without calling itself.

Closes #17899.

---

This is highly non-perfect, in particular, my wording above is quite precise, and I have some unresolved questions: This currently will warn about

```rust
fn foo() {
    if bar { loop {} }

    foo()
}
```

even though `foo` may never be called (i.e. our apparent "unconditional" recursion is actually conditional). I don't know if we should handle this case, and ones like it with `panic!()` instead of `loop` (or anything else that "returns" `!`).

However, strictly speaking, it seems to me that changing the above to not warn will require changing

```rust
fn foo() {
    while bar {}
    foo()
}
```

to also not warn since it could be that the `while` is an infinite loop and doesn't ever hit the `foo`.

I'm inclined to think we let these cases warn since true edge cases like the first one seem rare, and if they do occur they seem like they would usually be typos in the function call. (I could imagine someone accidentally having code like `fn foo() { assert!(bar()); foo() /* meant to be boo() */ }` which won't warn if the `loop` case is "fixed".)

(Part of the reason this is unresolved is wanting feedback, part of the reason is I couldn't devise a strategy that worked in all cases.)

---

The name `unconditional_self_calls` is kinda clunky; and this reconstructs the CFG for each function that is linted which may or may not be very expensive, I don't know.
@bors
Copy link
Contributor

bors commented Jan 25, 2015

⌛ Testing commit 0684c8e with merge 9bac017...

});
}

// check the number of sell calls because a function that
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "sell" be "self"?

@bors bors merged commit 0684c8e into rust-lang:master Jan 25, 2015
@huonw huonw changed the title Add a lint unconditional_self_calls to detect unconditional recursion. Add a lint unconditional_recursion to detect unconditional recursion. Jan 25, 2015
@huonw huonw deleted the self-call-lint branch January 25, 2015 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add lint for direct recursive self-call of function
6 participants