1+ use clippy_config:: Conf ;
12use clippy_utils:: diagnostics:: span_lint_and_note;
2- use clippy_utils:: macros:: { is_panic , root_macro_call_first_node} ;
3+ use clippy_utils:: macros:: root_macro_call_first_node;
34use clippy_utils:: ty:: is_type_diagnostic_item;
45use clippy_utils:: visitors:: Visitable ;
56use clippy_utils:: { is_in_test_function, method_chain_args} ;
@@ -11,8 +12,8 @@ use rustc_hir::{AnonConst, Expr, ExprKind, Item, ItemKind};
1112use rustc_lint:: { LateContext , LateLintPass } ;
1213use rustc_middle:: hir:: nested_filter;
1314use rustc_middle:: ty;
14- use rustc_session:: declare_lint_pass ;
15- use rustc_span:: { Span , sym} ;
15+ use rustc_session:: impl_lint_pass ;
16+ use rustc_span:: sym;
1617
1718declare_clippy_lint ! {
1819 /// ### What it does
@@ -46,10 +47,26 @@ declare_clippy_lint! {
4647 #[ clippy:: version = "1.82.0" ]
4748 pub TEST_WITHOUT_FAIL_CASE ,
4849 restriction,
49- "test function cannot fail because it does not panic or assert"
50+ "test function cannot fail because it does not anyway to panic or assert"
5051}
5152
52- declare_lint_pass ! ( TestWithoutFailCase => [ TEST_WITHOUT_FAIL_CASE ] ) ;
53+ pub struct TestWithoutFailCase {
54+ config : SearchConfig ,
55+ }
56+
57+ impl TestWithoutFailCase {
58+ pub fn new ( conf : & Conf ) -> Self {
59+ Self {
60+ config : SearchConfig {
61+ indexing_into_slice_fallible : conf. test_without_fail_case . include_slice_indexing ,
62+ fallible_paths : conf. test_without_fail_case . fallible_paths . iter ( ) . cloned ( ) . collect ( ) ,
63+ non_fallible_paths : conf. test_without_fail_case . non_fallible_paths . iter ( ) . cloned ( ) . collect ( ) ,
64+ } ,
65+ }
66+ }
67+ }
68+
69+ impl_lint_pass ! ( TestWithoutFailCase => [ TEST_WITHOUT_FAIL_CASE ] ) ;
5370
5471impl < ' tcx > LateLintPass < ' tcx > for TestWithoutFailCase {
5572 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' tcx > ) {
@@ -59,8 +76,8 @@ impl<'tcx> LateLintPass<'tcx> for TestWithoutFailCase {
5976 {
6077 let body = cx. tcx . hir ( ) . body ( body_id) ;
6178 let typeck_results = cx. tcx . typeck ( item. owner_id ) ;
62- let panic_span = SearchPanicIntraFunction :: find_span ( cx, typeck_results, body) ;
63- if panic_span . is_none ( ) {
79+ let fail_found = SearchFailIntraFunction :: find_fail ( cx, typeck_results, & self . config , body) ;
80+ if !fail_found {
6481 // No way to panic for this test function
6582 span_lint_and_note (
6683 cx,
@@ -75,43 +92,61 @@ impl<'tcx> LateLintPass<'tcx> for TestWithoutFailCase {
7592 }
7693}
7794
95+ /// Set of options that user provivdes through configs, to modify the lint behaviour
96+ /// according to their repo.
97+ struct SearchConfig {
98+ /// If search should consider indexing into slice as fallible.
99+ indexing_into_slice_fallible : bool ,
100+ /// Set of paths that are marked as fallible.
101+ fallible_paths : FxHashSet < String > ,
102+ /// Set of paths that are marked as non fallible.
103+ non_fallible_paths : FxHashSet < String > ,
104+ }
105+
78106/// Visitor that searches for expressions that could cause a panic, such as `panic!`,
79107/// `assert!`, `unwrap()`, or calls to functions that can panic.
80- struct SearchPanicIntraFunction < ' a , ' tcx > {
108+ struct SearchFailIntraFunction < ' a , ' tcx > {
81109 /// The lint context
82110 cx : & ' a LateContext < ' tcx > ,
83- /// The span where a panic was found
84- panic_span : Option < Span > ,
111+ /// Whether a way to fail is found or not.
112+ fail_found : bool ,
85113 /// Type checking results for the current body
86114 typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ,
87115 /// Set of function `DefId`s that have been visited to avoid infinite recursion
88116 visited_functions : FxHashSet < DefId > ,
117+ /// Search configs containing the set of user provided configurations.
118+ search_config : & ' a SearchConfig ,
89119}
90120
91- impl < ' a , ' tcx > SearchPanicIntraFunction < ' a , ' tcx > {
92- /// Creates a new `FindPanicUnwrap` visitor
93- pub fn new ( cx : & ' a LateContext < ' tcx > , typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ) -> Self {
121+ impl < ' a , ' tcx > SearchFailIntraFunction < ' a , ' tcx > {
122+ pub fn new (
123+ cx : & ' a LateContext < ' tcx > ,
124+ typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ,
125+ search_config : & ' a SearchConfig ,
126+ ) -> Self {
94127 Self {
95128 cx,
96- panic_span : None ,
129+ fail_found : false ,
97130 typeck_results,
98131 visited_functions : FxHashSet :: default ( ) ,
132+ search_config,
99133 }
100134 }
101135
102136 /// Searches for a way to panic in the given body and returns the span if found
103- pub fn find_span (
137+ pub fn find_fail (
104138 cx : & ' a LateContext < ' tcx > ,
105139 typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ,
140+ search_config : & ' a SearchConfig ,
106141 body : impl Visitable < ' tcx > ,
107- ) -> Option < Span > {
108- let mut visitor = Self :: new ( cx, typeck_results) ;
142+ ) -> bool {
143+ let mut visitor = Self :: new ( cx, typeck_results, search_config ) ;
109144 body. visit ( & mut visitor) ;
110- visitor. panic_span
145+ visitor. fail_found
111146 }
112147
113148 /// Checks the called function to see if it contains a panic
114- fn check_called_function ( & mut self , def_id : DefId , span : Span ) {
149+ fn check_called_function ( & mut self , def_id : DefId ) {
115150 // Avoid infinite recursion by checking if we've already visited this function
116151 if !self . visited_functions . insert ( def_id) {
117152 return ;
@@ -122,30 +157,31 @@ impl<'a, 'tcx> SearchPanicIntraFunction<'a, 'tcx> {
122157 if let Some ( local_def_id) = def_id. as_local ( ) {
123158 if let Some ( body) = hir. maybe_body_owned_by ( local_def_id) {
124159 let typeck_results = self . cx . tcx . typeck ( local_def_id) ;
125- let mut new_visitor = SearchPanicIntraFunction {
160+ let mut new_visitor = SearchFailIntraFunction {
126161 cx : self . cx ,
127- panic_span : None ,
162+ fail_found : false ,
128163 typeck_results,
129164 visited_functions : self . visited_functions . clone ( ) ,
165+ search_config : & self . search_config ,
130166 } ;
131167 body. visit ( & mut new_visitor) ;
132- if let Some ( panic_span ) = new_visitor. panic_span {
133- self . panic_span = Some ( panic_span ) ;
168+ if new_visitor. fail_found {
169+ self . fail_found = true ;
134170 }
135171 }
136172 }
137173 } else {
138174 // For external functions, assume they can panic
139- self . panic_span = Some ( span ) ;
175+ self . fail_found = true ;
140176 }
141177 }
142178}
143179
144- impl < ' tcx > Visitor < ' tcx > for SearchPanicIntraFunction < ' _ , ' tcx > {
180+ impl < ' tcx > Visitor < ' tcx > for SearchFailIntraFunction < ' _ , ' tcx > {
145181 type NestedFilter = nested_filter:: OnlyBodies ;
146182
147183 fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
148- if self . panic_span . is_some ( ) {
184+ if self . fail_found {
149185 // If we've already found a panic, no need to continue
150186 return ;
151187 }
@@ -154,8 +190,8 @@ impl<'tcx> Visitor<'tcx> for SearchPanicIntraFunction<'_, 'tcx> {
154190 ExprKind :: Call ( callee, args) => {
155191 if let ExprKind :: Path ( ref qpath) = callee. kind {
156192 if let Res :: Def ( _, def_id) = self . cx . qpath_res ( qpath, callee. hir_id ) {
157- self . check_called_function ( def_id, expr . span ) ;
158- if self . panic_span . is_some ( ) {
193+ self . check_called_function ( def_id) ;
194+ if self . fail_found {
159195 return ;
160196 }
161197 }
@@ -167,8 +203,8 @@ impl<'tcx> Visitor<'tcx> for SearchPanicIntraFunction<'_, 'tcx> {
167203 } ,
168204 ExprKind :: MethodCall ( _, receiver, args, _) => {
169205 if let Some ( def_id) = self . typeck_results . type_dependent_def_id ( expr. hir_id ) {
170- self . check_called_function ( def_id, expr . span ) ;
171- if self . panic_span . is_some ( ) {
206+ self . check_called_function ( def_id) ;
207+ if self . fail_found {
172208 return ;
173209 }
174210 }
@@ -179,33 +215,34 @@ impl<'tcx> Visitor<'tcx> for SearchPanicIntraFunction<'_, 'tcx> {
179215 } ,
180216 _ => {
181217 if let Some ( macro_call) = root_macro_call_first_node ( self . cx , expr) {
182- let macro_name = self . cx . tcx . item_name ( macro_call. def_id ) ;
183- // Skip macros like `println!`, `print!`, `eprintln!`, `eprint!`.
184- // This is a special case, these macros can panic, but it is very unlikely
185- // that this is intended. In the name of reducing false positiveness we are
186- // giving out soundness.
218+ let macro_with_path = self . cx . tcx . def_path_str ( macro_call. def_id ) ;
219+ // Skip macros that are defined as `non_fallible` in the clippy.toml file.
220+ // Some examples that would fit here can be `println!`, `print!`, `eprintln!`,
221+ // `eprint!`. This is a special case, these macros can panic, but it is very
222+ // unlikely that this is intended as the tests assertion. In the name of
223+ // reducing false negatives we are giving out soundness.
187224 //
188- // This decision can be justified as it is highly unlikely that the tool is sound
225+ // This decision can be justified as it is highly unlikely that this lint is sound
189226 // without this additional check, and with this we are reducing the number of false
190- // positives .
191- if matches ! ( macro_name . as_str ( ) , "println" | "print" | "eprintln" | "eprint" | "dbg" ) {
227+ // negatives .
228+ if self . search_config . non_fallible_paths . contains ( & macro_with_path ) {
192229 return ;
193230 }
194- if is_panic ( self . cx , macro_call. def_id )
195- || matches ! ( macro_name. as_str( ) , "assert" | "assert_eq" | "assert_ne" )
196- {
197- self . panic_span = Some ( macro_call. span ) ;
231+
232+ if self . search_config . fallible_paths . contains ( & macro_with_path) {
233+ self . fail_found = true ;
198234 return ;
199235 }
200236 }
201237
238+ // TODO: also make these two configurable.
202239 // Check for `unwrap` and `expect` method calls
203240 if let Some ( arglists) = method_chain_args ( expr, & [ "unwrap" ] ) . or ( method_chain_args ( expr, & [ "expect" ] ) ) {
204241 let receiver_ty = self . typeck_results . expr_ty ( arglists[ 0 ] . 0 ) . peel_refs ( ) ;
205242 if is_type_diagnostic_item ( self . cx , receiver_ty, sym:: Option )
206243 || is_type_diagnostic_item ( self . cx , receiver_ty, sym:: Result )
207244 {
208- self . panic_span = Some ( expr . span ) ;
245+ self . fail_found = true ;
209246 return ;
210247 }
211248 }
0 commit comments