@@ -32,10 +32,12 @@ use middle::subst::Substs;
32
32
use middle:: ty:: { self , Ty } ;
33
33
use middle:: { def, pat_util, stability} ;
34
34
use middle:: const_eval:: { eval_const_expr_partial, const_int, const_uint} ;
35
+ use middle:: cfg;
35
36
use util:: ppaux:: { ty_to_string} ;
36
37
use util:: nodemap:: { FnvHashMap , NodeSet } ;
37
- use lint:: { Context , LintPass , LintArray , Lint } ;
38
+ use lint:: { Level , Context , LintPass , LintArray , Lint } ;
38
39
40
+ use std:: collections:: BitvSet ;
39
41
use std:: collections:: hash_map:: Entry :: { Occupied , Vacant } ;
40
42
use std:: num:: SignedInt ;
41
43
use std:: { cmp, slice} ;
@@ -1788,6 +1790,194 @@ impl LintPass for Stability {
1788
1790
}
1789
1791
}
1790
1792
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
+
1791
1981
declare_lint ! {
1792
1982
pub UNUSED_IMPORTS ,
1793
1983
Warn ,
0 commit comments