Skip to content

Conversation

huonw
Copy link
Contributor

@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
Contributor

r? @luqmana

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

@frewsxcv
Copy link
Contributor

This needs a rebase

@huonw
Copy link
Contributor 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
Contributor 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
Contributor 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.)

huonw added 2 commits January 25, 2015 00:21
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
Contributor

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
Collaborator

bors commented Jan 25, 2015

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

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
Copy link
Collaborator

bors commented Jan 25, 2015

@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
lnicola pushed a commit to lnicola/rust that referenced this pull request Sep 1, 2025
…ast_instead_of_str

In handlers/extract_module.rs, generate ast::Module instead of String
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