]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
bump version to 1.79.0+dfsg1-1~bpo12+pve2
[rustc.git] / src / tools / clippy / clippy_lints / src / unconditional_recursion.rs
CommitLineData
c0240ec0 1use clippy_utils::diagnostics::span_lint_and_then;
e8be2606 2use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id};
c0240ec0
FG
3use rustc_ast::BinOpKind;
4use rustc_data_structures::fx::FxHashMap;
5use rustc_hir as hir;
6use rustc_hir::def::{DefKind, Res};
7use rustc_hir::def_id::{DefId, LocalDefId};
8use rustc_hir::intravisit::{walk_body, walk_expr, FnKind, Visitor};
9use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, Node, QPath, TyKind};
e8be2606 10use rustc_hir_analysis::lower_ty;
c0240ec0
FG
11use rustc_lint::{LateContext, LateLintPass};
12use rustc_middle::hir::map::Map;
13use rustc_middle::hir::nested_filter;
14use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt};
15use rustc_session::impl_lint_pass;
16use rustc_span::symbol::{kw, Ident};
17use rustc_span::{sym, Span};
18use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
19
20declare_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)]
52pub 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
58impl_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]);
59
60fn 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
72fn 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
90fn 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
97fn 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
105fn 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).
136fn 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
152fn 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
204fn 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
247fn 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
275struct 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
283impl<'a, 'tcx> Visitor<'tcx> for CheckCalls<'a, 'tcx>
284where
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
312impl 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
394fn 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
422impl<'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}