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::errors::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.instantiate_identity().skip_norm_wip().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 Ok(normalized_args) =
147 tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(args))
148 else {
149 return false;
150 };
151 let (callee, call_args) = if let Ok(Some(instance)) =
152 Instance::try_resolve(tcx, typing_env, callee, normalized_args)
153 {
154 (instance.def_id(), instance.args)
155 } else {
156 (callee, normalized_args)
157 };
158
159 return callee == caller && &call_args[..self.trait_args.len()] == self.trait_args;
166 }
167
168 false
169 }
170}
171
172impl<'tcx> TerminatorClassifier<'tcx> for RecursiveDrop<'tcx> {
173 fn is_recursive_terminator(
174 &self,
175 tcx: TyCtxt<'tcx>,
176 body: &Body<'tcx>,
177 terminator: &Terminator<'tcx>,
178 ) -> bool {
179 let TerminatorKind::Drop { place, .. } = &terminator.kind else { return false };
180
181 let dropped_ty = place.ty(body, tcx).ty;
182 dropped_ty == self.drop_for
183 }
184}
185
186impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx>>
187 for Search<'mir, 'tcx, C>
188{
189 type BreakVal = NonRecursive;
190
191 fn node_examined(
192 &mut self,
193 bb: BasicBlock,
194 prior_status: Option<NodeStatus>,
195 ) -> ControlFlow<Self::BreakVal> {
196 if let Some(NodeStatus::Visited) = prior_status {
198 return ControlFlow::Break(NonRecursive);
199 }
200
201 match self.body[bb].terminator().kind {
202 TerminatorKind::UnwindTerminate(_)
204 | TerminatorKind::CoroutineDrop
205 | TerminatorKind::UnwindResume
206 | TerminatorKind::Return
207 | TerminatorKind::Unreachable
208 | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
209
210 TerminatorKind::InlineAsm { ref targets, .. } => {
213 if !targets.is_empty() {
214 ControlFlow::Continue(())
215 } else {
216 ControlFlow::Break(NonRecursive)
217 }
218 }
219
220 TerminatorKind::Assert { .. }
222 | TerminatorKind::Call { .. }
223 | TerminatorKind::Drop { .. }
224 | TerminatorKind::FalseEdge { .. }
225 | TerminatorKind::FalseUnwind { .. }
226 | TerminatorKind::Goto { .. }
227 | TerminatorKind::SwitchInt { .. } => ControlFlow::Continue(()),
228
229 TerminatorKind::TailCall { .. } => ControlFlow::Continue(()),
235 }
236 }
237
238 fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow<Self::BreakVal> {
239 let terminator = self.body[bb].terminator();
241
242 if self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) {
251 self.reachable_recursive_calls.push(terminator.source_info.span);
252 }
253
254 ControlFlow::Continue(())
255 }
256
257 fn ignore_edge(&mut self, bb: BasicBlock, target: BasicBlock) -> bool {
258 let terminator = self.body[bb].terminator();
259 let ignore_unwind = terminator.unwind() == Some(&mir::UnwindAction::Cleanup(target))
260 && terminator.successors().count() > 1;
261 if ignore_unwind || self.classifier.is_recursive_terminator(self.tcx, self.body, terminator)
262 {
263 return true;
264 }
265 match &terminator.kind {
266 TerminatorKind::FalseEdge { imaginary_target, .. } => imaginary_target == &target,
267 _ => false,
268 }
269 }
270}