1use std::ops::ControlFlow;
2
3use rustc_data_structures::graph::iterate::{
4 NodeStatus, TriColorDepthFirstSearch, TriColorVisitor,
5};
6use rustc_hir::LangItem;
7use rustc_hir::def::DefKind;
8use rustc_middle::mir::{self, BasicBlock, BasicBlocks, Body, Terminator, TerminatorKind};
9use rustc_middle::ty::{self, GenericArg, GenericArgs, Instance, Ty, TyCtxt, Unnormalized};
10use rustc_session::lint::builtin::UNCONDITIONAL_RECURSION;
11use rustc_span::Span;
12
13use crate::diagnostics::UnconditionalRecursion;
14use crate::pass_manager::MirLint;
15
16pub(super) struct CheckCallRecursion;
17
18impl<'tcx> MirLint<'tcx> for CheckCallRecursion {
19 fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
20 let def_id = body.source.def_id().expect_local();
21
22 if let DefKind::Fn | DefKind::AssocFn = tcx.def_kind(def_id) {
23 let trait_args = match tcx.trait_of_assoc(def_id.to_def_id()) {
25 Some(trait_def_id) => {
26 let trait_args_count = tcx.generics_of(trait_def_id).count();
27 &GenericArgs::identity_for_item(tcx, def_id)[..trait_args_count]
28 }
29 _ => &[],
30 };
31
32 check_recursion(tcx, body, CallRecursion { trait_args })
33 }
34 }
35}
36
37pub(super) struct CheckDropRecursion;
39
40impl<'tcx> MirLint<'tcx> for CheckDropRecursion {
41 fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
42 let def_id = body.source.def_id().expect_local();
43
44 if let DefKind::AssocFn = tcx.def_kind(def_id)
46 && let Some(impl_id) = tcx.trait_impl_of_assoc(def_id.to_def_id())
47 && let trait_ref = tcx.impl_trait_ref(impl_id)
48 && tcx.is_lang_item(trait_ref.def_id(), LangItem::Drop)
49 && let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip()
51 && sig.inputs().skip_binder().len() == 1
52 {
53 if let ty::Ref(_, dropped_ty, _) =
56 tcx.liberate_late_bound_regions(def_id.to_def_id(), sig.input(0)).kind()
57 {
58 check_recursion(tcx, body, RecursiveDrop { drop_for: *dropped_ty });
59 }
60 }
61 }
62}
63
64fn check_recursion<'tcx>(
65 tcx: TyCtxt<'tcx>,
66 body: &Body<'tcx>,
67 classifier: impl TerminatorClassifier<'tcx>,
68) {
69 let def_id = body.source.def_id().expect_local();
70
71 if let DefKind::Fn | DefKind::AssocFn = tcx.def_kind(def_id) {
72 let mut vis = Search { tcx, body, classifier, reachable_recursive_calls: vec![] };
73 if let Some(NonRecursive) =
74 TriColorDepthFirstSearch::new(&body.basic_blocks).run_from_start(&mut vis)
75 {
76 return;
77 }
78 if vis.reachable_recursive_calls.is_empty() {
79 return;
80 }
81
82 vis.reachable_recursive_calls.sort();
83
84 let sp = tcx.def_span(def_id);
85 let hir_id = tcx.local_def_id_to_hir_id(def_id);
86 tcx.emit_node_span_lint(
87 UNCONDITIONAL_RECURSION,
88 hir_id,
89 sp,
90 UnconditionalRecursion { span: sp, call_sites: vis.reachable_recursive_calls },
91 );
92 }
93}
94
95trait TerminatorClassifier<'tcx> {
96 fn is_recursive_terminator(
97 &self,
98 tcx: TyCtxt<'tcx>,
99 body: &Body<'tcx>,
100 terminator: &Terminator<'tcx>,
101 ) -> bool;
102}
103
104struct NonRecursive;
105
106struct Search<'mir, 'tcx, C: TerminatorClassifier<'tcx>> {
107 tcx: TyCtxt<'tcx>,
108 body: &'mir Body<'tcx>,
109 classifier: C,
110
111 reachable_recursive_calls: Vec<Span>,
112}
113
114struct CallRecursion<'tcx> {
115 trait_args: &'tcx [GenericArg<'tcx>],
116}
117
118struct RecursiveDrop<'tcx> {
119 drop_for: Ty<'tcx>,
121}
122
123impl<'tcx> TerminatorClassifier<'tcx> for CallRecursion<'tcx> {
124 fn is_recursive_terminator(
126 &self,
127 tcx: TyCtxt<'tcx>,
128 body: &Body<'tcx>,
129 terminator: &Terminator<'tcx>,
130 ) -> bool {
131 let TerminatorKind::Call { func, args, .. } = &terminator.kind else {
132 return false;
133 };
134
135 if args.len() != body.arg_count {
139 return false;
140 }
141 let caller = body.source.def_id();
142 let typing_env = body.typing_env(tcx);
143
144 let func_ty = func.ty(body, tcx);
145 if let ty::FnDef(callee, args) = *func_ty.kind() {
146 let args = args.no_bound_vars().unwrap();
147 let Ok(normalized_args) =
148 tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(args))
149 else {
150 return false;
151 };
152 let (callee, call_args) = if let Ok(Some(instance)) =
153 Instance::try_resolve(tcx, typing_env, callee, normalized_args)
154 {
155 (instance.def_id(), instance.args)
156 } else {
157 (callee, normalized_args)
158 };
159
160 return callee == caller && &call_args[..self.trait_args.len()] == self.trait_args;
167 }
168
169 false
170 }
171}
172
173impl<'tcx> TerminatorClassifier<'tcx> for RecursiveDrop<'tcx> {
174 fn is_recursive_terminator(
175 &self,
176 tcx: TyCtxt<'tcx>,
177 body: &Body<'tcx>,
178 terminator: &Terminator<'tcx>,
179 ) -> bool {
180 let TerminatorKind::Drop { place, .. } = &terminator.kind else { return false };
181
182 let dropped_ty = place.ty(body, tcx).ty;
183 dropped_ty == self.drop_for
184 }
185}
186
187impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx>>
188 for Search<'mir, 'tcx, C>
189{
190 type BreakVal = NonRecursive;
191
192 fn node_examined(
193 &mut self,
194 bb: BasicBlock,
195 prior_status: Option<NodeStatus>,
196 ) -> ControlFlow<Self::BreakVal> {
197 if let Some(NodeStatus::Visited) = prior_status {
199 return ControlFlow::Break(NonRecursive);
200 }
201
202 match self.body[bb].terminator().kind {
203 TerminatorKind::UnwindTerminate(_)
205 | TerminatorKind::CoroutineDrop
206 | TerminatorKind::UnwindResume
207 | TerminatorKind::Return
208 | TerminatorKind::Unreachable
209 | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
210
211 TerminatorKind::InlineAsm { ref targets, .. } => {
214 if !targets.is_empty() {
215 ControlFlow::Continue(())
216 } else {
217 ControlFlow::Break(NonRecursive)
218 }
219 }
220
221 TerminatorKind::Assert { .. }
223 | TerminatorKind::Call { .. }
224 | TerminatorKind::Drop { .. }
225 | TerminatorKind::FalseEdge { .. }
226 | TerminatorKind::FalseUnwind { .. }
227 | TerminatorKind::Goto { .. }
228 | TerminatorKind::SwitchInt { .. } => ControlFlow::Continue(()),
229
230 TerminatorKind::TailCall { .. } => ControlFlow::Continue(()),
236 }
237 }
238
239 fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow<Self::BreakVal> {
240 let terminator = self.body[bb].terminator();
242
243 if self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) {
252 self.reachable_recursive_calls.push(terminator.source_info.span);
253 }
254
255 ControlFlow::Continue(())
256 }
257
258 fn ignore_edge(&mut self, bb: BasicBlock, target: BasicBlock) -> bool {
259 let terminator = self.body[bb].terminator();
260 let ignore_unwind = terminator.unwind() == Some(&mir::UnwindAction::Cleanup(target))
261 && terminator.successors().count() > 1;
262 if ignore_unwind || self.classifier.is_recursive_terminator(self.tcx, self.body, terminator)
263 {
264 return true;
265 }
266 match &terminator.kind {
267 TerminatorKind::FalseEdge { imaginary_target, .. } => imaginary_target == &target,
268 _ => false,
269 }
270 }
271}