1
+ use clippy_config:: Conf ;
1
2
use 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;
3
4
use clippy_utils:: ty:: is_type_diagnostic_item;
4
5
use clippy_utils:: visitors:: Visitable ;
5
6
use clippy_utils:: { is_in_test_function, method_chain_args} ;
@@ -11,8 +12,8 @@ use rustc_hir::{AnonConst, Expr, ExprKind, Item, ItemKind};
11
12
use rustc_lint:: { LateContext , LateLintPass } ;
12
13
use rustc_middle:: hir:: nested_filter;
13
14
use 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;
16
17
17
18
declare_clippy_lint ! {
18
19
/// ### What it does
@@ -46,10 +47,26 @@ declare_clippy_lint! {
46
47
#[ clippy:: version = "1.82.0" ]
47
48
pub TEST_WITHOUT_FAIL_CASE ,
48
49
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"
50
51
}
51
52
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 ] ) ;
53
70
54
71
impl < ' tcx > LateLintPass < ' tcx > for TestWithoutFailCase {
55
72
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' tcx > ) {
@@ -59,8 +76,8 @@ impl<'tcx> LateLintPass<'tcx> for TestWithoutFailCase {
59
76
{
60
77
let body = cx. tcx . hir ( ) . body ( body_id) ;
61
78
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 {
64
81
// No way to panic for this test function
65
82
span_lint_and_note (
66
83
cx,
@@ -75,43 +92,61 @@ impl<'tcx> LateLintPass<'tcx> for TestWithoutFailCase {
75
92
}
76
93
}
77
94
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
+
78
106
/// Visitor that searches for expressions that could cause a panic, such as `panic!`,
79
107
/// `assert!`, `unwrap()`, or calls to functions that can panic.
80
- struct SearchPanicIntraFunction < ' a , ' tcx > {
108
+ struct SearchFailIntraFunction < ' a , ' tcx > {
81
109
/// The lint context
82
110
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 ,
85
113
/// Type checking results for the current body
86
114
typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ,
87
115
/// Set of function `DefId`s that have been visited to avoid infinite recursion
88
116
visited_functions : FxHashSet < DefId > ,
117
+ /// Search configs containing the set of user provided configurations.
118
+ search_config : & ' a SearchConfig ,
89
119
}
90
120
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 {
94
127
Self {
95
128
cx,
96
- panic_span : None ,
129
+ fail_found : false ,
97
130
typeck_results,
98
131
visited_functions : FxHashSet :: default ( ) ,
132
+ search_config,
99
133
}
100
134
}
101
135
102
136
/// 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 (
104
138
cx : & ' a LateContext < ' tcx > ,
105
139
typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ,
140
+ search_config : & ' a SearchConfig ,
106
141
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 ) ;
109
144
body. visit ( & mut visitor) ;
110
- visitor. panic_span
145
+ visitor. fail_found
111
146
}
112
147
113
148
/// 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 ) {
115
150
// Avoid infinite recursion by checking if we've already visited this function
116
151
if !self . visited_functions . insert ( def_id) {
117
152
return ;
@@ -122,30 +157,31 @@ impl<'a, 'tcx> SearchPanicIntraFunction<'a, 'tcx> {
122
157
if let Some ( local_def_id) = def_id. as_local ( ) {
123
158
if let Some ( body) = hir. maybe_body_owned_by ( local_def_id) {
124
159
let typeck_results = self . cx . tcx . typeck ( local_def_id) ;
125
- let mut new_visitor = SearchPanicIntraFunction {
160
+ let mut new_visitor = SearchFailIntraFunction {
126
161
cx : self . cx ,
127
- panic_span : None ,
162
+ fail_found : false ,
128
163
typeck_results,
129
164
visited_functions : self . visited_functions . clone ( ) ,
165
+ search_config : & self . search_config ,
130
166
} ;
131
167
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 ;
134
170
}
135
171
}
136
172
}
137
173
} else {
138
174
// For external functions, assume they can panic
139
- self . panic_span = Some ( span ) ;
175
+ self . fail_found = true ;
140
176
}
141
177
}
142
178
}
143
179
144
- impl < ' tcx > Visitor < ' tcx > for SearchPanicIntraFunction < ' _ , ' tcx > {
180
+ impl < ' tcx > Visitor < ' tcx > for SearchFailIntraFunction < ' _ , ' tcx > {
145
181
type NestedFilter = nested_filter:: OnlyBodies ;
146
182
147
183
fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
148
- if self . panic_span . is_some ( ) {
184
+ if self . fail_found {
149
185
// If we've already found a panic, no need to continue
150
186
return ;
151
187
}
@@ -154,8 +190,8 @@ impl<'tcx> Visitor<'tcx> for SearchPanicIntraFunction<'_, 'tcx> {
154
190
ExprKind :: Call ( callee, args) => {
155
191
if let ExprKind :: Path ( ref qpath) = callee. kind {
156
192
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 {
159
195
return ;
160
196
}
161
197
}
@@ -167,8 +203,8 @@ impl<'tcx> Visitor<'tcx> for SearchPanicIntraFunction<'_, 'tcx> {
167
203
} ,
168
204
ExprKind :: MethodCall ( _, receiver, args, _) => {
169
205
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 {
172
208
return ;
173
209
}
174
210
}
@@ -179,33 +215,34 @@ impl<'tcx> Visitor<'tcx> for SearchPanicIntraFunction<'_, 'tcx> {
179
215
} ,
180
216
_ => {
181
217
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.
187
224
//
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
189
226
// 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 ) {
192
229
return ;
193
230
}
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 ;
198
234
return ;
199
235
}
200
236
}
201
237
238
+ // TODO: also make these two configurable.
202
239
// Check for `unwrap` and `expect` method calls
203
240
if let Some ( arglists) = method_chain_args ( expr, & [ "unwrap" ] ) . or ( method_chain_args ( expr, & [ "expect" ] ) ) {
204
241
let receiver_ty = self . typeck_results . expr_ty ( arglists[ 0 ] . 0 ) . peel_refs ( ) ;
205
242
if is_type_diagnostic_item ( self . cx , receiver_ty, sym:: Option )
206
243
|| is_type_diagnostic_item ( self . cx , receiver_ty, sym:: Result )
207
244
{
208
- self . panic_span = Some ( expr . span ) ;
245
+ self . fail_found = true ;
209
246
return ;
210
247
}
211
248
}
0 commit comments