]>
Commit | Line | Data |
---|---|---|
c0240ec0 | 1 | use clippy_utils::diagnostics::span_lint_and_then; |
e8be2606 | 2 | use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id}; |
c0240ec0 FG |
3 | use rustc_ast::BinOpKind; |
4 | use rustc_data_structures::fx::FxHashMap; | |
5 | use rustc_hir as hir; | |
6 | use rustc_hir::def::{DefKind, Res}; | |
7 | use rustc_hir::def_id::{DefId, LocalDefId}; | |
8 | use rustc_hir::intravisit::{walk_body, walk_expr, FnKind, Visitor}; | |
9 | use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, Node, QPath, TyKind}; | |
e8be2606 | 10 | use rustc_hir_analysis::lower_ty; |
c0240ec0 FG |
11 | use rustc_lint::{LateContext, LateLintPass}; |
12 | use rustc_middle::hir::map::Map; | |
13 | use rustc_middle::hir::nested_filter; | |
14 | use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt}; | |
15 | use rustc_session::impl_lint_pass; | |
16 | use rustc_span::symbol::{kw, Ident}; | |
17 | use rustc_span::{sym, Span}; | |
18 | use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor; | |
19 | ||
20 | declare_clippy_lint! { | |
21 | /// ### What it does | |
e8be2606 FG |
22 | /// Checks that there isn't an infinite recursion in trait |
23 | /// implementations. | |
c0240ec0 FG |
24 | /// |
25 | /// ### Why is this bad? | |
e8be2606 | 26 | /// This is a hard to find infinite recursion that will crash any code |
c0240ec0 FG |
27 | /// using it. |
28 | /// | |
29 | /// ### Example | |
30 | /// ```no_run | |
31 | /// enum Foo { | |
32 | /// A, | |
33 | /// B, | |
34 | /// } | |
35 | /// | |
36 | /// impl PartialEq for Foo { | |
37 | /// fn eq(&self, other: &Self) -> bool { | |
38 | /// self == other // bad! | |
39 | /// } | |
40 | /// } | |
41 | /// ``` | |
42 | /// Use instead: | |
43 | /// | |
44 | /// In such cases, either use `#[derive(PartialEq)]` or don't implement it. | |
e8be2606 | 45 | #[clippy::version = "1.77.0"] |
c0240ec0 FG |
46 | pub UNCONDITIONAL_RECURSION, |
47 | suspicious, | |
48 | "detect unconditional recursion in some traits implementation" | |
49 | } | |
50 | ||
51 | #[derive(Default)] | |
52 | pub struct UnconditionalRecursion { | |
53 | /// The key is the `DefId` of the type implementing the `Default` trait and the value is the | |
54 | /// `DefId` of the return call. | |
55 | default_impl_for_type: FxHashMap<DefId, DefId>, | |
56 | } | |
57 | ||
58 | impl_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]); | |
59 | ||
60 | fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) { | |
61 | span_lint_and_then( | |
62 | cx, | |
63 | UNCONDITIONAL_RECURSION, | |
64 | method_span, | |
65 | "function cannot return without recursing", | |
66 | |diag| { | |
67 | diag.span_note(expr.span, "recursive call site"); | |
68 | }, | |
69 | ); | |
70 | } | |
71 | ||
72 | fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> { | |
73 | let TyKind::Path(qpath) = hir_ty.kind else { return None }; | |
74 | match qpath { | |
75 | QPath::Resolved(_, path) => path.res.opt_def_id(), | |
76 | QPath::TypeRelative(_, _) => { | |
e8be2606 | 77 | let ty = lower_ty(tcx, &hir_ty); |
c0240ec0 FG |
78 | |
79 | match ty.kind() { | |
80 | ty::Alias(ty::Projection, proj) => { | |
81 | Res::<HirId>::Def(DefKind::Trait, proj.trait_ref(tcx).def_id).opt_def_id() | |
82 | }, | |
83 | _ => None, | |
84 | } | |
85 | }, | |
86 | QPath::LangItem(..) => None, | |
87 | } | |
88 | } | |
89 | ||
90 | fn get_return_calls_in_body<'tcx>(body: &'tcx Body<'tcx>) -> Vec<&'tcx Expr<'tcx>> { | |
91 | let mut visitor = ReturnsVisitor::default(); | |
92 | ||
93 | visitor.visit_body(body); | |
94 | visitor.returns | |
95 | } | |
96 | ||
97 | fn has_conditional_return(body: &Body<'_>, expr: &Expr<'_>) -> bool { | |
98 | match get_return_calls_in_body(body).as_slice() { | |
99 | [] => false, | |
100 | [return_expr] => return_expr.hir_id != expr.hir_id, | |
101 | _ => true, | |
102 | } | |
103 | } | |
104 | ||
105 | fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Option<DefId> { | |
106 | let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id); | |
107 | if let Some(( | |
108 | _, | |
109 | Node::Item(Item { | |
110 | kind: ItemKind::Impl(impl_), | |
111 | owner_id, | |
112 | .. | |
113 | }), | |
114 | )) = cx.tcx.hir().parent_iter(hir_id).next() | |
115 | // We exclude `impl` blocks generated from rustc's proc macros. | |
116 | && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) | |
117 | // It is a implementation of a trait. | |
118 | && let Some(trait_) = impl_.of_trait | |
119 | { | |
120 | trait_.trait_def_id() | |
121 | } else { | |
122 | None | |
123 | } | |
124 | } | |
125 | ||
126 | /// When we have `x == y` where `x = &T` and `y = &T`, then that resolves to | |
127 | /// `<&T as PartialEq<&T>>::eq`, which is not the same as `<T as PartialEq<T>>::eq`, | |
128 | /// however we still would want to treat it the same, because we know that it's a blanket impl | |
129 | /// that simply delegates to the `PartialEq` impl with one reference removed. | |
130 | /// | |
131 | /// Still, we can't just do `lty.peel_refs() == rty.peel_refs()` because when we have `x = &T` and | |
132 | /// `y = &&T`, this is not necessarily the same as `<T as PartialEq<T>>::eq` | |
133 | /// | |
134 | /// So to avoid these FNs and FPs, we keep removing a layer of references from *both* sides | |
135 | /// until both sides match the expected LHS and RHS type (or they don't). | |
136 | fn matches_ty<'tcx>( | |
137 | mut left: Ty<'tcx>, | |
138 | mut right: Ty<'tcx>, | |
139 | expected_left: Ty<'tcx>, | |
140 | expected_right: Ty<'tcx>, | |
141 | ) -> bool { | |
142 | while let (&ty::Ref(_, lty, _), &ty::Ref(_, rty, _)) = (left.kind(), right.kind()) { | |
143 | if lty == expected_left && rty == expected_right { | |
144 | return true; | |
145 | } | |
146 | left = lty; | |
147 | right = rty; | |
148 | } | |
149 | false | |
150 | } | |
151 | ||
152 | fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) { | |
153 | let Some(sig) = cx | |
154 | .typeck_results() | |
155 | .liberated_fn_sigs() | |
156 | .get(cx.tcx.local_def_id_to_hir_id(method_def_id)) | |
157 | else { | |
158 | return; | |
159 | }; | |
160 | ||
161 | // That has two arguments. | |
162 | if let [self_arg, other_arg] = sig.inputs() | |
163 | && let &ty::Ref(_, self_arg, _) = self_arg.kind() | |
164 | && let &ty::Ref(_, other_arg, _) = other_arg.kind() | |
165 | // The two arguments are of the same type. | |
166 | && let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id) | |
167 | // The trait is `PartialEq`. | |
168 | && cx.tcx.is_diagnostic_item(sym::PartialEq, trait_def_id) | |
169 | { | |
170 | let to_check_op = if name.name == sym::eq { | |
171 | BinOpKind::Eq | |
172 | } else { | |
173 | BinOpKind::Ne | |
174 | }; | |
175 | let is_bad = match expr.kind { | |
176 | ExprKind::Binary(op, left, right) if op.node == to_check_op => { | |
177 | // Then we check if the LHS matches self_arg and RHS matches other_arg | |
178 | let left_ty = cx.typeck_results().expr_ty_adjusted(left); | |
179 | let right_ty = cx.typeck_results().expr_ty_adjusted(right); | |
180 | matches_ty(left_ty, right_ty, self_arg, other_arg) | |
181 | }, | |
182 | ExprKind::MethodCall(segment, receiver, [arg], _) if segment.ident.name == name.name => { | |
183 | let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver); | |
184 | let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); | |
185 | ||
186 | if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) | |
187 | && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) | |
188 | && trait_id == trait_def_id | |
189 | && matches_ty(receiver_ty, arg_ty, self_arg, other_arg) | |
190 | { | |
191 | true | |
192 | } else { | |
193 | false | |
194 | } | |
195 | }, | |
196 | _ => false, | |
197 | }; | |
198 | if is_bad { | |
199 | span_error(cx, method_span, expr); | |
200 | } | |
201 | } | |
202 | } | |
203 | ||
c0240ec0 FG |
204 | fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) { |
205 | let args = cx | |
206 | .tcx | |
207 | .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) | |
208 | .inputs(); | |
209 | // That has one argument. | |
210 | if let [_self_arg] = args | |
211 | && let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id) | |
212 | && let Some(( | |
213 | _, | |
214 | Node::Item(Item { | |
215 | kind: ItemKind::Impl(impl_), | |
216 | owner_id, | |
217 | .. | |
218 | }), | |
219 | )) = cx.tcx.hir().parent_iter(hir_id).next() | |
220 | // We exclude `impl` blocks generated from rustc's proc macros. | |
221 | && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) | |
222 | // It is a implementation of a trait. | |
223 | && let Some(trait_) = impl_.of_trait | |
224 | && let Some(trait_def_id) = trait_.trait_def_id() | |
225 | // The trait is `ToString`. | |
e8be2606 | 226 | && cx.tcx.is_diagnostic_item(sym::ToString, trait_def_id) |
c0240ec0 FG |
227 | { |
228 | let is_bad = match expr.kind { | |
229 | ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => { | |
230 | if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) | |
231 | && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) | |
232 | && trait_id == trait_def_id | |
233 | { | |
234 | true | |
235 | } else { | |
236 | false | |
237 | } | |
238 | }, | |
239 | _ => false, | |
240 | }; | |
241 | if is_bad { | |
242 | span_error(cx, method_span, expr); | |
243 | } | |
244 | } | |
245 | } | |
246 | ||
247 | fn is_default_method_on_current_ty<'tcx>(tcx: TyCtxt<'tcx>, qpath: QPath<'tcx>, implemented_ty_id: DefId) -> bool { | |
248 | match qpath { | |
249 | QPath::Resolved(_, path) => match path.segments { | |
250 | [first, .., last] => last.ident.name == kw::Default && first.res.opt_def_id() == Some(implemented_ty_id), | |
251 | _ => false, | |
252 | }, | |
253 | QPath::TypeRelative(ty, segment) => { | |
254 | if segment.ident.name != kw::Default { | |
255 | return false; | |
256 | } | |
257 | if matches!( | |
258 | ty.kind, | |
259 | TyKind::Path(QPath::Resolved( | |
260 | _, | |
261 | hir::Path { | |
262 | res: Res::SelfTyAlias { .. }, | |
263 | .. | |
264 | }, | |
265 | )) | |
266 | ) { | |
267 | return true; | |
268 | } | |
269 | get_hir_ty_def_id(tcx, *ty) == Some(implemented_ty_id) | |
270 | }, | |
271 | QPath::LangItem(..) => false, | |
272 | } | |
273 | } | |
274 | ||
275 | struct CheckCalls<'a, 'tcx> { | |
276 | cx: &'a LateContext<'tcx>, | |
277 | map: Map<'tcx>, | |
278 | implemented_ty_id: DefId, | |
279 | found_default_call: bool, | |
280 | method_span: Span, | |
281 | } | |
282 | ||
283 | impl<'a, 'tcx> Visitor<'tcx> for CheckCalls<'a, 'tcx> | |
284 | where | |
285 | 'tcx: 'a, | |
286 | { | |
287 | type NestedFilter = nested_filter::OnlyBodies; | |
288 | ||
289 | fn nested_visit_map(&mut self) -> Self::Map { | |
290 | self.map | |
291 | } | |
292 | ||
c0240ec0 FG |
293 | fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { |
294 | if self.found_default_call { | |
295 | return; | |
296 | } | |
297 | walk_expr(self, expr); | |
298 | ||
299 | if let ExprKind::Call(f, _) = expr.kind | |
300 | && let ExprKind::Path(qpath) = f.kind | |
301 | && is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id) | |
302 | && let Some(method_def_id) = path_def_id(self.cx, f) | |
303 | && let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id) | |
e8be2606 | 304 | && self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id) |
c0240ec0 FG |
305 | { |
306 | self.found_default_call = true; | |
307 | span_error(self.cx, self.method_span, expr); | |
308 | } | |
309 | } | |
310 | } | |
311 | ||
312 | impl UnconditionalRecursion { | |
c0240ec0 FG |
313 | fn init_default_impl_for_type_if_needed(&mut self, cx: &LateContext<'_>) { |
314 | if self.default_impl_for_type.is_empty() | |
e8be2606 | 315 | && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) |
c0240ec0 FG |
316 | { |
317 | let impls = cx.tcx.trait_impls_of(default_trait_id); | |
318 | for (ty, impl_def_ids) in impls.non_blanket_impls() { | |
319 | let Some(self_def_id) = ty.def() else { continue }; | |
320 | for impl_def_id in impl_def_ids { | |
321 | if !cx.tcx.has_attr(*impl_def_id, sym::automatically_derived) && | |
322 | let Some(assoc_item) = cx | |
323 | .tcx | |
324 | .associated_items(impl_def_id) | |
325 | .in_definition_order() | |
326 | // We're not interested in foreign implementations of the `Default` trait. | |
327 | .find(|item| { | |
328 | item.kind == AssocKind::Fn && item.def_id.is_local() && item.name == kw::Default | |
329 | }) | |
330 | && let Some(body_node) = cx.tcx.hir().get_if_local(assoc_item.def_id) | |
331 | && let Some(body_id) = body_node.body_id() | |
332 | && let body = cx.tcx.hir().body(body_id) | |
333 | // We don't want to keep it if it has conditional return. | |
334 | && let [return_expr] = get_return_calls_in_body(body).as_slice() | |
335 | && let ExprKind::Call(call_expr, _) = return_expr.kind | |
336 | // We need to use typeck here to infer the actual function being called. | |
337 | && let body_def_id = cx.tcx.hir().enclosing_body_owner(call_expr.hir_id) | |
338 | && let Some(body_owner) = cx.tcx.hir().maybe_body_owned_by(body_def_id) | |
339 | && let typeck = cx.tcx.typeck_body(body_owner) | |
340 | && let Some(call_def_id) = typeck.type_dependent_def_id(call_expr.hir_id) | |
341 | { | |
342 | self.default_impl_for_type.insert(self_def_id, call_def_id); | |
343 | } | |
344 | } | |
345 | } | |
346 | } | |
347 | } | |
348 | ||
349 | fn check_default_new<'tcx>( | |
350 | &mut self, | |
351 | cx: &LateContext<'tcx>, | |
352 | decl: &FnDecl<'tcx>, | |
353 | body: &'tcx Body<'tcx>, | |
354 | method_span: Span, | |
355 | method_def_id: LocalDefId, | |
356 | ) { | |
357 | // We're only interested into static methods. | |
358 | if decl.implicit_self.has_implicit_self() { | |
359 | return; | |
360 | } | |
361 | // We don't check trait implementations. | |
362 | if get_impl_trait_def_id(cx, method_def_id).is_some() { | |
363 | return; | |
364 | } | |
365 | ||
366 | let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id); | |
367 | if let Some(( | |
368 | _, | |
369 | Node::Item(Item { | |
370 | kind: ItemKind::Impl(impl_), | |
371 | .. | |
372 | }), | |
373 | )) = cx.tcx.hir().parent_iter(hir_id).next() | |
374 | && let Some(implemented_ty_id) = get_hir_ty_def_id(cx.tcx, *impl_.self_ty) | |
375 | && { | |
376 | self.init_default_impl_for_type_if_needed(cx); | |
377 | true | |
378 | } | |
379 | && let Some(return_def_id) = self.default_impl_for_type.get(&implemented_ty_id) | |
380 | && method_def_id.to_def_id() == *return_def_id | |
381 | { | |
382 | let mut c = CheckCalls { | |
383 | cx, | |
384 | map: cx.tcx.hir(), | |
385 | implemented_ty_id, | |
386 | found_default_call: false, | |
387 | method_span, | |
388 | }; | |
389 | walk_body(&mut c, body); | |
390 | } | |
391 | } | |
392 | } | |
393 | ||
e8be2606 FG |
394 | fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, expr: &Expr<'_>) { |
395 | let Some(sig) = cx | |
396 | .typeck_results() | |
397 | .liberated_fn_sigs() | |
398 | .get(cx.tcx.local_def_id_to_hir_id(method_def_id)) | |
399 | else { | |
400 | return; | |
401 | }; | |
402 | ||
403 | // Check if we are calling `Into::into` where the node args match with our `From::from` signature: | |
404 | // From::from signature: fn(S1) -> S2 | |
405 | // <S1 as Into<S2>>::into(s1), node_args=[S1, S2] | |
406 | // If they do match, then it must mean that it is the blanket impl, | |
407 | // which calls back into our `From::from` again (`Into` is not specializable). | |
408 | // rustc's unconditional_recursion already catches calling `From::from` directly | |
409 | if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr) | |
410 | && let [s1, s2] = **node_args | |
411 | && let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type()) | |
412 | && let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id) | |
413 | && cx.tcx.is_diagnostic_item(sym::Into, trait_def_id) | |
414 | && get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From) | |
415 | && s1 == sig.inputs()[0] | |
416 | && s2 == sig.output() | |
417 | { | |
418 | span_error(cx, method_span, expr); | |
419 | } | |
420 | } | |
421 | ||
c0240ec0 FG |
422 | impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { |
423 | fn check_fn( | |
424 | &mut self, | |
425 | cx: &LateContext<'tcx>, | |
426 | kind: FnKind<'tcx>, | |
427 | decl: &'tcx FnDecl<'tcx>, | |
428 | body: &'tcx Body<'tcx>, | |
429 | method_span: Span, | |
430 | method_def_id: LocalDefId, | |
431 | ) { | |
432 | // If the function is a method... | |
433 | if let FnKind::Method(name, _) = kind | |
434 | && let expr = expr_or_init(cx, body.value).peel_blocks() | |
435 | // Doesn't have a conditional return. | |
436 | && !has_conditional_return(body, expr) | |
437 | { | |
e8be2606 FG |
438 | match name.name { |
439 | sym::eq | sym::ne => check_partial_eq(cx, method_span, method_def_id, name, expr), | |
440 | sym::to_string => check_to_string(cx, method_span, method_def_id, name, expr), | |
441 | sym::from => check_from(cx, method_span, method_def_id, expr), | |
442 | _ => {}, | |
c0240ec0 FG |
443 | } |
444 | self.check_default_new(cx, decl, body, method_span, method_def_id); | |
445 | } | |
446 | } | |
447 | } |