Skip to content

Commit fbef241

Browse files
committedJan 24, 2015
Add a lint to detect unconditional recursion.
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.
1 parent 000dc07 commit fbef241

File tree

3 files changed

+258
-1
lines changed

3 files changed

+258
-1
lines changed
 

‎src/librustc/lint/builtin.rs

+191-1
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ use middle::subst::Substs;
3232
use middle::ty::{self, Ty};
3333
use middle::{def, pat_util, stability};
3434
use middle::const_eval::{eval_const_expr_partial, const_int, const_uint};
35+
use middle::cfg;
3536
use util::ppaux::{ty_to_string};
3637
use util::nodemap::{FnvHashMap, NodeSet};
37-
use lint::{Context, LintPass, LintArray, Lint};
38+
use lint::{Level, Context, LintPass, LintArray, Lint};
3839

40+
use std::collections::BitvSet;
3941
use std::collections::hash_map::Entry::{Occupied, Vacant};
4042
use std::num::SignedInt;
4143
use std::{cmp, slice};
@@ -1788,6 +1790,194 @@ impl LintPass for Stability {
17881790
}
17891791
}
17901792

1793+
declare_lint! {
1794+
pub UNCONDITIONAL_RECURSION,
1795+
Warn,
1796+
"functions that cannot return without calling themselves"
1797+
}
1798+
1799+
#[derive(Copy)]
1800+
pub struct UnconditionalRecursion;
1801+
1802+
1803+
impl LintPass for UnconditionalRecursion {
1804+
fn get_lints(&self) -> LintArray {
1805+
lint_array![UNCONDITIONAL_RECURSION]
1806+
}
1807+
1808+
fn check_fn(&mut self, cx: &Context, fn_kind: visit::FnKind, _: &ast::FnDecl,
1809+
blk: &ast::Block, sp: Span, id: ast::NodeId) {
1810+
type F = for<'tcx> fn(&ty::ctxt<'tcx>,
1811+
ast::NodeId, ast::NodeId, ast::Ident, ast::NodeId) -> bool;
1812+
1813+
let (name, checker) = match fn_kind {
1814+
visit::FkItemFn(name, _, _, _) => (name, id_refers_to_this_fn as F),
1815+
visit::FkMethod(name, _, _) => (name, id_refers_to_this_method as F),
1816+
// closures can't recur, so they don't matter.
1817+
visit::FkFnBlock => return
1818+
};
1819+
1820+
let impl_def_id = ty::impl_of_method(cx.tcx, ast_util::local_def(id))
1821+
.unwrap_or(ast_util::local_def(ast::DUMMY_NODE_ID));
1822+
assert!(ast_util::is_local(impl_def_id));
1823+
let impl_node_id = impl_def_id.node;
1824+
1825+
// Walk through this function (say `f`) looking to see if
1826+
// every possible path references itself, i.e. the function is
1827+
// called recursively unconditionally. This is done by trying
1828+
// to find a path from the entry node to the exit node that
1829+
// *doesn't* call `f` by traversing from the entry while
1830+
// pretending that calls of `f` are sinks (i.e. ignoring any
1831+
// exit edges from them).
1832+
//
1833+
// NB. this has an edge case with non-returning statements,
1834+
// like `loop {}` or `panic!()`: control flow never reaches
1835+
// the exit node through these, so one can have a function
1836+
// that never actually calls itselfs but is still picked up by
1837+
// this lint:
1838+
//
1839+
// fn f(cond: bool) {
1840+
// if !cond { panic!() } // could come from `assert!(cond)`
1841+
// f(false)
1842+
// }
1843+
//
1844+
// In general, functions of that form may be able to call
1845+
// itself a finite number of times and then diverge. The lint
1846+
// considers this to be an error for two reasons, (a) it is
1847+
// easier to implement, and (b) it seems rare to actually want
1848+
// to have behaviour like the above, rather than
1849+
// e.g. accidentally recurring after an assert.
1850+
1851+
let cfg = cfg::CFG::new(cx.tcx, blk);
1852+
1853+
let mut work_queue = vec![cfg.entry];
1854+
let mut reached_exit_without_self_call = false;
1855+
let mut self_call_spans = vec![];
1856+
let mut visited = BitvSet::new();
1857+
1858+
while let Some(idx) = work_queue.pop() {
1859+
let cfg_id = idx.node_id();
1860+
if idx == cfg.exit {
1861+
// found a path!
1862+
reached_exit_without_self_call = true;
1863+
break
1864+
} else if visited.contains(&cfg_id) {
1865+
// already done
1866+
continue
1867+
}
1868+
visited.insert(cfg_id);
1869+
let node_id = cfg.graph.node_data(idx).id;
1870+
1871+
// is this a recursive call?
1872+
if node_id != ast::DUMMY_NODE_ID && checker(cx.tcx, impl_node_id, id, name, node_id) {
1873+
1874+
self_call_spans.push(cx.tcx.map.span(node_id));
1875+
// this is a self call, so we shouldn't explore past
1876+
// this node in the CFG.
1877+
continue
1878+
}
1879+
// add the successors of this node to explore the graph further.
1880+
cfg.graph.each_outgoing_edge(idx, |_, edge| {
1881+
let target_idx = edge.target();
1882+
let target_cfg_id = target_idx.node_id();
1883+
if !visited.contains(&target_cfg_id) {
1884+
work_queue.push(target_idx)
1885+
}
1886+
true
1887+
});
1888+
}
1889+
1890+
// check the number of sell calls because a function that
1891+
// doesn't return (e.g. calls a `-> !` function or `loop { /*
1892+
// no break */ }`) shouldn't be linted unless it actually
1893+
// recurs.
1894+
if !reached_exit_without_self_call && self_call_spans.len() > 0 {
1895+
cx.span_lint(UNCONDITIONAL_RECURSION, sp,
1896+
"function cannot return without recurring");
1897+
1898+
// FIXME #19668: these could be span_lint_note's instead of this manual guard.
1899+
if cx.current_level(UNCONDITIONAL_RECURSION) != Level::Allow {
1900+
let sess = cx.sess();
1901+
// offer some help to the programmer.
1902+
for call in self_call_spans.iter() {
1903+
sess.span_note(*call, "recursive call site")
1904+
}
1905+
sess.span_help(sp, "a `loop` may express intention better if this is on purpose")
1906+
}
1907+
}
1908+
1909+
// all done
1910+
return;
1911+
1912+
// Functions for identifying if the given NodeId `id`
1913+
// represents a call to the function `fn_id`/method
1914+
// `method_id`.
1915+
1916+
fn id_refers_to_this_fn<'tcx>(tcx: &ty::ctxt<'tcx>,
1917+
_: ast::NodeId,
1918+
fn_id: ast::NodeId,
1919+
_: ast::Ident,
1920+
id: ast::NodeId) -> bool {
1921+
tcx.def_map.borrow().get(&id)
1922+
.map_or(false, |def| {
1923+
let did = def.def_id();
1924+
ast_util::is_local(did) && did.node == fn_id
1925+
})
1926+
}
1927+
1928+
// check if the method call `id` refers to method `method_id`
1929+
// (with name `method_name` contained in impl `impl_id`).
1930+
fn id_refers_to_this_method<'tcx>(tcx: &ty::ctxt<'tcx>,
1931+
impl_id: ast::NodeId,
1932+
method_id: ast::NodeId,
1933+
method_name: ast::Ident,
1934+
id: ast::NodeId) -> bool {
1935+
let did = match tcx.method_map.borrow().get(&ty::MethodCall::expr(id)) {
1936+
None => return false,
1937+
Some(m) => match m.origin {
1938+
// There's no way to know if a method call via a
1939+
// vtable is recursion, so we assume it's not.
1940+
ty::MethodTraitObject(_) => return false,
1941+
1942+
// This `did` refers directly to the method definition.
1943+
ty::MethodStatic(did) | ty::MethodStaticUnboxedClosure(did) => did,
1944+
1945+
// MethodTypeParam are methods from traits:
1946+
1947+
// The `impl ... for ...` of this method call
1948+
// isn't known, e.g. it might be a default method
1949+
// in a trait, so we get the def-id of the trait
1950+
// method instead.
1951+
ty::MethodTypeParam(
1952+
ty::MethodParam { ref trait_ref, method_num, impl_def_id: None, }) => {
1953+
ty::trait_item(tcx, trait_ref.def_id, method_num).def_id()
1954+
}
1955+
1956+
// The `impl` is known, so we check that with a
1957+
// special case:
1958+
ty::MethodTypeParam(
1959+
ty::MethodParam { impl_def_id: Some(impl_def_id), .. }) => {
1960+
1961+
let name = match tcx.map.expect_expr(id).node {
1962+
ast::ExprMethodCall(ref sp_ident, _, _) => sp_ident.node,
1963+
_ => tcx.sess.span_bug(
1964+
tcx.map.span(id),
1965+
"non-method call expr behaving like a method call?")
1966+
};
1967+
// it matches if it comes from the same impl,
1968+
// and has the same method name.
1969+
return ast_util::is_local(impl_def_id)
1970+
&& impl_def_id.node == impl_id
1971+
&& method_name.name == name.name
1972+
}
1973+
}
1974+
};
1975+
1976+
ast_util::is_local(did) && did.node == method_id
1977+
}
1978+
}
1979+
}
1980+
17911981
declare_lint! {
17921982
pub UNUSED_IMPORTS,
17931983
Warn,

‎src/librustc/lint/context.rs

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ impl LintStore {
211211
UnusedAllocation,
212212
MissingCopyImplementations,
213213
UnstableFeatures,
214+
UnconditionalRecursion,
214215
);
215216

216217
add_builtin_with_new!(sess,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![deny(unconditional_recursion)]
12+
#![allow(dead_code)]
13+
fn foo() { //~ ERROR function cannot return without recurring
14+
foo(); //~ NOTE recursive call site
15+
}
16+
17+
fn bar() {
18+
if true {
19+
bar()
20+
}
21+
}
22+
23+
fn baz() { //~ ERROR function cannot return without recurring
24+
if true {
25+
baz() //~ NOTE recursive call site
26+
} else {
27+
baz() //~ NOTE recursive call site
28+
}
29+
}
30+
31+
fn qux() {
32+
loop {}
33+
}
34+
35+
fn quz() -> bool { //~ ERROR function cannot return without recurring
36+
if true {
37+
while quz() {} //~ NOTE recursive call site
38+
true
39+
} else {
40+
loop { quz(); } //~ NOTE recursive call site
41+
}
42+
}
43+
44+
trait Foo {
45+
fn bar(&self) { //~ ERROR function cannot return without recurring
46+
self.bar() //~ NOTE recursive call site
47+
}
48+
}
49+
50+
impl Foo for Box<Foo+'static> {
51+
fn bar(&self) { //~ ERROR function cannot return without recurring
52+
loop {
53+
self.bar() //~ NOTE recursive call site
54+
}
55+
}
56+
57+
}
58+
59+
struct Baz;
60+
impl Baz {
61+
fn qux(&self) { //~ ERROR function cannot return without recurring
62+
self.qux(); //~ NOTE recursive call site
63+
}
64+
}
65+
66+
fn main() {}

0 commit comments

Comments
 (0)
Please sign in to comment.