1use super::default::CallGraph;
2use rustc_hir::def_id::DefId;
3use rustc_middle::mir;
4use rustc_middle::ty::{FnDef, Instance, InstanceKind, TyCtxt, TypingEnv};
5#[cfg(rapx_rustc_ge_198)]
6use rustc_middle::ty::ShimKind;
7use std::collections::HashSet;
8
9pub struct CallGraphVisitor<'b, 'tcx> {
10 tcx: TyCtxt<'tcx>,
11 def_id: DefId,
12 body: &'tcx mir::Body<'tcx>,
13 call_graph_info: &'b mut CallGraph<'tcx>,
14}
15
16impl<'b, 'tcx> CallGraphVisitor<'b, 'tcx> {
17 pub fn new(
18 tcx: TyCtxt<'tcx>,
19 def_id: DefId,
20 body: &'tcx mir::Body<'tcx>,
21 call_graph_info: &'b mut CallGraph<'tcx>,
22 ) -> Self {
23 Self {
24 tcx: tcx,
25 def_id: def_id,
26 body: body,
27 call_graph_info: call_graph_info,
28 }
29 }
30
31 fn add_fn_call(&mut self, callee_def_id: DefId, terminator: &'tcx mir::Terminator<'tcx>) {
32 self.call_graph_info.register_fn(callee_def_id);
33 self.call_graph_info.add_funciton_call(
34 self.def_id.clone(),
35 callee_def_id,
36 Some(terminator),
37 );
38 }
39
40 fn handle_fn_call(
41 &mut self,
42 callee_def_id: DefId,
43 is_virtual: bool,
44 terminator: &'tcx mir::Terminator<'tcx>,
45 ) {
46 if is_virtual {
47 self.handle_virtual_call(callee_def_id, terminator);
49 } else {
50 self.add_fn_call(callee_def_id, terminator);
51 }
52 }
53
54 fn handle_virtual_call(
55 &mut self,
56 stub_def_id: DefId, terminator: &'tcx mir::Terminator<'tcx>,
58 ) {
59 let visited = !self.call_graph_info.register_fn(stub_def_id);
62 self.add_fn_call(stub_def_id, terminator);
63
64 if visited {
66 return;
67 }
68
69 let mut candidates: HashSet<DefId> = HashSet::new();
71 if let Some(trait_def_id) = self.tcx.trait_of_assoc(stub_def_id) {
72 rap_debug!(
73 "[Callgraph] Virtual fn {:?} belongs to trait {:?}",
74 stub_def_id,
75 trait_def_id
76 );
77 for impl_id in self.tcx.all_impls(trait_def_id) {
78 let impl_map = self.tcx.impl_item_implementor_ids(impl_id);
79 if let Some(candidate_def_id) = impl_map.get(&stub_def_id) {
80 candidates.insert(*candidate_def_id);
81 }
82 }
83 }
84 rap_debug!(
85 "[Callgraph] Implementors of {:?}: {:?}",
86 stub_def_id,
87 candidates
88 );
89
90 for candidate_def_id in candidates {
92 self.add_fn_call(candidate_def_id, terminator);
93 }
94 }
95
96 pub fn visit(&mut self) {
97 self.call_graph_info.register_fn(self.def_id);
98 for (_, data) in self.body.basic_blocks.iter().enumerate() {
99 let terminator = data.terminator();
100 self.visit_terminator(&terminator);
101 }
102 }
103
104 fn visit_terminator(&mut self, terminator: &'tcx mir::Terminator<'tcx>) {
105 if let mir::TerminatorKind::Call { func, .. } = &terminator.kind {
106 if let mir::Operand::Constant(constant) = func {
107 if let FnDef(callee_def_id, callee_substs) = constant.const_.ty().kind() {
108 let ty_env = TypingEnv::post_analysis(self.tcx, self.def_id);
109 if let Ok(Some(instance)) =
110 Instance::try_resolve(self.tcx, ty_env, *callee_def_id, callee_substs)
111 {
112 let mut is_virtual = false;
113 #[cfg(rapx_rustc_ge_198)]
115 let instance_def_id = match instance.def {
116 InstanceKind::Item(def_id) => Some(def_id),
117 InstanceKind::Intrinsic(def_id) => Some(def_id),
118 InstanceKind::Virtual(def_id, _) => {
119 is_virtual = true;
120 Some(def_id)
121 }
122 InstanceKind::Shim(shim_kind) => match shim_kind {
123 ShimKind::VTable(def_id) => Some(def_id),
124 ShimKind::Reify(def_id, _) => Some(def_id),
125 ShimKind::FnPtr(def_id, _) => Some(def_id),
126 ShimKind::ClosureOnce { call_once, .. } => Some(call_once),
127 ShimKind::ConstructCoroutineInClosure {
128 coroutine_closure_def_id,
129 ..
130 } => Some(coroutine_closure_def_id),
131 ShimKind::ThreadLocal(def_id) => Some(def_id),
132 ShimKind::DropGlue(def_id, _) => Some(def_id),
133 ShimKind::FnPtrAddr(def_id, _) => Some(def_id),
134 ShimKind::AsyncDropGlueCtor(def_id, _) => Some(def_id),
135 ShimKind::Clone(def_id, _) => {
136 if !self.tcx.is_closure_like(def_id) {
137 Some(def_id)
138 } else {
139 None
140 }
141 }
142 _ => todo!(),
143 },
144 };
145
146 #[cfg(not(rapx_rustc_ge_198))]
147 let instance_def_id = match instance.def {
148 InstanceKind::Item(def_id) => Some(def_id),
149 InstanceKind::Intrinsic(def_id) => Some(def_id),
150 InstanceKind::VTableShim(def_id) => Some(def_id),
151 InstanceKind::ReifyShim(def_id, _) => Some(def_id),
152 InstanceKind::FnPtrShim(def_id, _) => Some(def_id),
153 InstanceKind::Virtual(def_id, _) => {
154 is_virtual = true;
155 Some(def_id)
156 }
157 InstanceKind::ClosureOnceShim { call_once, .. } => Some(call_once),
158 InstanceKind::ConstructCoroutineInClosureShim {
159 coroutine_closure_def_id,
160 ..
161 } => Some(coroutine_closure_def_id),
162 InstanceKind::ThreadLocalShim(def_id) => Some(def_id),
163 InstanceKind::DropGlue(def_id, _) => Some(def_id),
164 InstanceKind::FnPtrAddrShim(def_id, _) => Some(def_id),
165 InstanceKind::AsyncDropGlueCtorShim(def_id, _) => Some(def_id),
166 InstanceKind::CloneShim(def_id, _) => {
167 if !self.tcx.is_closure_like(def_id) {
168 Some(def_id)
169 } else {
170 None
171 }
172 }
173 _ => todo!(),
174 };
175
176 if let Some(instance_def_id) = instance_def_id {
177 self.handle_fn_call(instance_def_id, is_virtual, terminator);
178 }
179 } else {
180 self.handle_fn_call(*callee_def_id, false, terminator);
182 }
183 }
184 }
185 }
186 }
187}