1use rustc_hir::{Safety, def_id::DefId};
2use rustc_middle::{
3 mir::{
4 BasicBlock, Body, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind,
5 TerminatorKind,
6 },
7 ty::{self, Ty, TyCtxt, TyKind},
8};
9use rustc_span::Span;
10use std::collections::{HashMap, HashSet};
11
12use super::name::get_cleaned_def_path_name;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
16pub struct CheckpointLocation {
17 pub caller: DefId,
19 pub block: BasicBlock,
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
25pub enum CheckpointKind {
26 UnsafeCall,
28 RawPtrDeref,
30 StaticMutAccess,
32}
33
34#[derive(Clone, Debug)]
40pub struct Checkpoint<'tcx> {
41 pub caller: DefId,
42 pub callee: Option<DefId>,
43 pub block: BasicBlock,
44 pub span: Span,
45 pub args: Vec<Operand<'tcx>>,
46 pub kind: CheckpointKind,
47 pub is_ref: bool,
48}
49
50impl<'tcx> Checkpoint<'tcx> {
51 pub fn location(&self) -> CheckpointLocation {
53 CheckpointLocation {
54 caller: self.caller,
55 block: self.block,
56 }
57 }
58
59 pub fn callee_name(&self, tcx: TyCtxt<'tcx>) -> String {
61 match self.callee {
62 Some(def_id) => get_cleaned_def_path_name(tcx, def_id),
63 None => match self.kind {
64 CheckpointKind::RawPtrDeref => "raw-ptr-deref".to_string(),
65 CheckpointKind::StaticMutAccess => "static-mut-access".to_string(),
66 CheckpointKind::UnsafeCall => "unknown-callee".to_string(),
67 },
68 }
69 }
70}
71
72pub fn check_safety(tcx: TyCtxt<'_>, def_id: DefId) -> Safety {
74 let poly_fn_sig = tcx.fn_sig(def_id);
75 let fn_sig = poly_fn_sig.skip_binder();
76 fn_sig.safety()
77}
78
79pub fn place_has_raw_deref<'tcx>(
81 _tcx: TyCtxt<'tcx>,
82 body: &Body<'tcx>,
83 place: &Place<'tcx>,
84) -> bool {
85 let local = place.local;
86 for proj in place.projection.iter() {
87 if let ProjectionElem::Deref = proj.kind() {
88 let ty = body.local_decls[local].ty;
89 if let TyKind::RawPtr(_, _) = ty.kind() {
90 return true;
91 }
92 }
93 }
94 false
95}
96
97pub fn get_rawptr_deref(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<Local> {
100 let mut raw_ptrs = HashSet::new();
101 if tcx.is_mir_available(def_id) {
102 let body = tcx.optimized_mir(def_id);
103 for bb in body.basic_blocks.iter() {
104 for stmt in &bb.statements {
105 if let StatementKind::Assign(assign) = &stmt.kind {
106 let (lhs, rhs) = &**assign;
107 if place_has_raw_deref(tcx, &body, lhs) {
108 raw_ptrs.insert(lhs.local);
109 }
110 if let Rvalue::Use(op, ..) = rhs {
111 match op {
112 Operand::Copy(place) | Operand::Move(place) => {
113 if place_has_raw_deref(tcx, &body, place) {
114 raw_ptrs.insert(place.local);
115 }
116 }
117 _ => {}
118 }
119 }
120 if let Rvalue::Ref(_, _, place) = rhs {
121 if place_has_raw_deref(tcx, &body, place) {
122 raw_ptrs.insert(place.local);
123 }
124 }
125 }
126 }
127 if let Some(terminator) = &bb.terminator {
128 match &terminator.kind {
129 rustc_middle::mir::TerminatorKind::Call { args, .. } => {
130 for arg in args {
131 match arg.node {
132 Operand::Copy(place) | Operand::Move(place) => {
133 if place_has_raw_deref(tcx, &body, &place) {
134 raw_ptrs.insert(place.local);
135 }
136 }
137 _ => {}
138 }
139 }
140 }
141 _ => {}
142 }
143 }
144 }
145 }
146 raw_ptrs
147}
148
149pub fn collect_global_local_pairs(tcx: TyCtxt<'_>, def_id: DefId) -> HashMap<DefId, Vec<Local>> {
152 let mut globals: HashMap<DefId, Vec<Local>> = HashMap::new();
153
154 if !tcx.is_mir_available(def_id) {
155 return globals;
156 }
157
158 let body = tcx.optimized_mir(def_id);
159
160 for bb in body.basic_blocks.iter() {
161 for stmt in &bb.statements {
162 if let StatementKind::Assign(assign) = &stmt.kind {
163 let (lhs, rhs) = &**assign;
164 if let Rvalue::Use(Operand::Constant(c), ..) = rhs {
165 if let Some(static_def_id) = c.check_static_ptr(tcx) {
166 globals.entry(static_def_id).or_default().push(lhs.local);
167 }
168 }
169 }
170 }
171 }
172
173 globals
174}
175
176pub fn get_unsafe_callees(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<DefId> {
178 let mut unsafe_callees = HashSet::new();
179 if tcx.is_mir_available(def_id) {
180 let body = tcx.optimized_mir(def_id);
181 for bb in body.basic_blocks.iter() {
182 if let TerminatorKind::Call { func, .. } = &bb.terminator().kind {
183 if let Operand::Constant(func_constant) = func {
184 if let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() {
185 if check_safety(tcx, *callee_def_id) == Safety::Unsafe {
186 unsafe_callees.insert(*callee_def_id);
187 }
188 }
189 }
190 }
191 }
192 }
193 unsafe_callees
194}
195
196pub fn collect_unsafe_callsites<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Vec<Checkpoint<'tcx>> {
198 let mut checkpoints = Vec::new();
199 if !tcx.is_mir_available(def_id) {
200 return checkpoints;
201 }
202
203 let body = tcx.optimized_mir(def_id);
204 for (bb, data) in body.basic_blocks.iter_enumerated() {
205 let TerminatorKind::Call {
206 func,
207 args,
208 fn_span,
209 ..
210 } = &data.terminator().kind
211 else {
212 continue;
213 };
214
215 let Operand::Constant(func_constant) = func else {
216 continue;
217 };
218
219 let ty::FnDef(callee_def_id, callee_args) = func_constant.const_.ty().kind() else {
220 continue;
221 };
222
223 if check_safety(tcx, *callee_def_id) != Safety::Unsafe {
224 continue;
225 }
226
227 let resolved_callee =
231 resolve_callee_impl(tcx, def_id, *callee_def_id, callee_args).unwrap_or(*callee_def_id);
232
233 checkpoints.push(Checkpoint {
234 caller: def_id,
235 callee: Some(resolved_callee),
236 block: bb,
237 span: *fn_span,
238 args: args.iter().map(|arg| arg.node.clone()).collect(),
239 kind: CheckpointKind::UnsafeCall,
240 is_ref: false,
241 });
242 }
243
244 checkpoints
245}
246
247fn resolve_callee_impl<'tcx>(
258 tcx: TyCtxt<'tcx>,
259 caller_def_id: DefId,
260 callee_def_id: DefId,
261 callee_args: ty::GenericArgsRef<'tcx>,
262) -> Option<DefId> {
263 let assoc = tcx.opt_associated_item(callee_def_id)?;
266 if assoc.trait_container(tcx).is_none() {
267 return None;
268 }
269
270 let typing_env = ty::TypingEnv::post_analysis(tcx, caller_def_id);
271 let instance = ty::Instance::try_resolve(tcx, typing_env, callee_def_id, callee_args)
272 .ok()
273 .flatten()?;
274
275 let resolved = match instance.def {
276 ty::InstanceKind::Item(def_id) => def_id,
277 _ => return None,
278 };
279
280 if resolved == callee_def_id {
281 None
282 } else {
283 Some(resolved)
284 }
285}
286
287#[derive(Clone, Debug)]
289pub struct RawPtrDerefInfo<'tcx> {
290 pub block: BasicBlock,
291 pub ptr_operand: Operand<'tcx>,
292 pub pointee_ty: Ty<'tcx>,
293 pub is_read: bool,
294 pub is_ref: bool,
295}
296
297pub fn collect_raw_ptr_deref_info<'tcx>(
300 tcx: TyCtxt<'tcx>,
301 def_id: DefId,
302) -> Vec<RawPtrDerefInfo<'tcx>> {
303 let mut infos = Vec::new();
304 if !tcx.is_mir_available(def_id) {
305 return infos;
306 }
307
308 let body = tcx.optimized_mir(def_id);
309 for (bb, data) in body.basic_blocks.iter_enumerated() {
310 for stmt in &data.statements {
311 let StatementKind::Assign(assign) = &stmt.kind else {
312 continue;
313 };
314 let (lhs, rhs) = &**assign;
315
316 let is_write = place_has_raw_deref(tcx, &body, lhs);
317 let (is_read, is_ref) = match rhs {
318 Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) => {
319 (place_has_raw_deref(tcx, &body, place), false)
320 }
321 Rvalue::Ref(_, _, place) => {
322 (place_has_raw_deref(tcx, &body, place), true)
323 }
324 _ => (false, false),
325 };
326
327 if !is_write && !is_read {
328 continue;
329 }
330
331 let deref_place = if is_write {
332 lhs
333 } else {
334 match rhs {
335 Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..)
336 | Rvalue::Ref(_, _, place) => place,
337 _ => continue,
338 }
339 };
340
341 let Some(ptr_operand) = ptr_operand_for_deref_place(deref_place) else {
342 continue;
343 };
344
345 let Some(pointee_ty) = deref_place_pointee_ty(&body, deref_place) else {
346 continue;
347 };
348
349 infos.push(RawPtrDerefInfo {
350 block: bb,
351 ptr_operand,
352 pointee_ty,
353 is_read,
354 is_ref,
355 });
356 }
357 }
358
359 infos
360}
361
362fn deref_place_pointee_ty<'tcx>(body: &Body<'tcx>, place: &Place<'tcx>) -> Option<Ty<'tcx>> {
364 let ty = body.local_decls[place.local].ty;
365 match ty.kind() {
366 TyKind::RawPtr(inner, _) => Some(*inner),
367 _ => None,
368 }
369}
370
371fn ptr_operand_for_deref_place<'tcx>(place: &Place<'tcx>) -> Option<Operand<'tcx>> {
373 use rustc_middle::ty::List;
374
375 let first_deref_idx = place
376 .projection
377 .iter()
378 .position(|p| matches!(p.kind(), ProjectionElem::Deref));
379
380 if let Some(idx) = first_deref_idx && idx > 0 {
381 return None;
382 }
383
384 Some(Operand::Copy(Place {
385 local: place.local,
386 projection: List::empty(),
387 }))
388}
389
390#[derive(Clone, Debug)]
392pub struct StaticMutAccessInfo<'tcx> {
393 pub block: BasicBlock,
395 pub static_def_id: DefId,
397 pub ty: Ty<'tcx>,
399 pub ptr_operand: Operand<'tcx>,
401}
402
403pub fn collect_static_mut_access_info<'tcx>(
409 tcx: TyCtxt<'tcx>,
410 def_id: DefId,
411) -> Vec<StaticMutAccessInfo<'tcx>> {
412 let mut infos = Vec::new();
413 if !tcx.is_mir_available(def_id) {
414 return infos;
415 }
416
417 let body = tcx.optimized_mir(def_id);
418 for (bb, data) in body.basic_blocks.iter_enumerated() {
419 for stmt in &data.statements {
420 if let StatementKind::Assign(assign) = &stmt.kind {
421 let (_lhs, rhs) = &**assign;
422 if let Rvalue::Use(op @ Operand::Constant(c), ..) = rhs {
423 if let Some(static_id) = c.check_static_ptr(tcx) {
424 if matches!(tcx.static_mutability(static_id), Some(m) if m.is_mut()) {
425 let ty = tcx.type_of(static_id).skip_binder();
426 infos.push(StaticMutAccessInfo {
427 block: bb,
428 static_def_id: static_id,
429 ty,
430 ptr_operand: op.clone(),
431 });
432 }
433 }
434 }
435 }
436 }
437
438 if let Some(terminator) = &data.terminator {
439 if let TerminatorKind::Call { args, .. } = &terminator.kind {
440 for arg in args {
441 match &arg.node {
442 op @ Operand::Constant(c) => {
443 if let Some(static_id) = c.check_static_ptr(tcx) {
444 if matches!(tcx.static_mutability(static_id), Some(m) if m.is_mut())
445 {
446 let ty = tcx.type_of(static_id).skip_binder();
447 infos.push(StaticMutAccessInfo {
448 block: bb,
449 static_def_id: static_id,
450 ty,
451 ptr_operand: op.clone(),
452 });
453 }
454 }
455 }
456 _ => {}
457 }
458 }
459 }
460 }
461 }
462
463 infos
464}