rapx/analysis/safety_flow/
root.rs1use crate::helpers::mir_scan::{collect_global_local_pairs, get_rawptr_deref, get_unsafe_callees};
2use rustc_hir::{BodyId, ItemKind, def_id::DefId};
3use rustc_middle::{mir::Local, ty::TyCtxt};
4use rustc_span::Symbol;
5use std::collections::HashSet;
6
7use super::hir_visitor::ContainsUnsafe;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum UnsafeOpKind {
12 CallsUnsafeFn,
13 DerefsRawPtr,
14 AccessesStaticMut,
15}
16
17#[derive(Debug, Clone)]
22pub struct UnsafeRoot {
23 pub def_id: DefId,
24 pub kinds: Vec<UnsafeOpKind>,
25 pub unsafe_callees: HashSet<DefId>,
26 pub raw_ptr_locals: HashSet<Local>,
27 pub static_muts: HashSet<DefId>,
28}
29
30pub fn hir_contains_unsafe(tcx: TyCtxt<'_>, body_id: BodyId) -> bool {
36 let (fn_unsafe, block_unsafe) = ContainsUnsafe::contains_unsafe(tcx, body_id);
37 fn_unsafe || block_unsafe
38}
39
40pub fn has_struct_invariant(tcx: TyCtxt<'_>, struct_def_id: DefId) -> bool {
45 let Some(local_def_id) = struct_def_id.as_local() else {
46 return false;
47 };
48 let rapx = Symbol::intern("rapx");
49 let invariant = Symbol::intern("invariant");
50 let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(local_def_id));
51 attrs.iter().any(|attr| {
52 if attr.is_doc_comment().is_some() {
53 return false;
54 }
55 let path = attr.path();
56 path.len() == 2 && path[0] == rapx && path[1] == invariant
57 })
58}
59
60pub fn function_has_struct_invariant(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
62 let Some(assoc_item) = tcx.opt_associated_item(def_id) else {
63 return false;
64 };
65 let Some(impl_id) = assoc_item.impl_container(tcx) else {
66 return false;
67 };
68 let self_ty = tcx.type_of(impl_id).skip_binder();
69 match self_ty.kind() {
70 rustc_middle::ty::TyKind::Adt(adt_def, _) => has_struct_invariant(tcx, adt_def.did()),
71 _ => false,
72 }
73}
74
75pub fn function_has_trait_ensurance(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
79 let Some(assoc_item) = tcx.opt_associated_item(def_id) else {
80 return false;
81 };
82 let Some(impl_id) = assoc_item.impl_container(tcx) else {
83 return false;
84 };
85
86 let trait_def_id = {
87 tcx.impl_opt_trait_ref(impl_id)
88 };
89 let Some(trait_ref) = trait_def_id else {
90 return false;
91 };
92 let trait_def_id = trait_ref.skip_binder().def_id;
93
94 let Some(local_id) = trait_def_id.as_local() else {
95 return false;
96 };
97
98 let item = tcx.hir_expect_item(local_id);
100 #[cfg(not(rapx_ge_99))]
101 if let ItemKind::Trait(_, _, unsafety, _, _, _, _) = &item.kind {
102 return matches!(unsafety, rustc_hir::Safety::Unsafe);
103 }
104 #[cfg(rapx_ge_99)]
105 if let ItemKind::Trait { safety, .. } = &item.kind {
106 return matches!(safety, rustc_hir::Safety::Unsafe);
107 }
108
109 false
110}
111
112pub fn scan_mir(tcx: TyCtxt<'_>, def_id: DefId) -> Option<UnsafeRoot> {
117 if !tcx.is_mir_available(def_id) {
118 return None;
119 }
120
121 let unsafe_callees = get_unsafe_callees(tcx, def_id);
122 let raw_ptr_locals = get_rawptr_deref(tcx, def_id);
123 let global_locals = collect_global_local_pairs(tcx, def_id);
124 let static_muts: HashSet<DefId> = global_locals.keys().copied().collect();
125
126 let global_locals_set: HashSet<Local> = global_locals.values().flatten().copied().collect();
127 let raw_ptr_locals: HashSet<Local> = raw_ptr_locals
128 .difference(&global_locals_set)
129 .copied()
130 .collect();
131
132 if unsafe_callees.is_empty() && raw_ptr_locals.is_empty() && static_muts.is_empty() {
133 return None;
134 }
135
136 let mut kinds = Vec::new();
137 if !unsafe_callees.is_empty() {
138 kinds.push(UnsafeOpKind::CallsUnsafeFn);
139 }
140 if !raw_ptr_locals.is_empty() {
141 kinds.push(UnsafeOpKind::DerefsRawPtr);
142 }
143 if !static_muts.is_empty() {
144 kinds.push(UnsafeOpKind::AccessesStaticMut);
145 }
146
147 Some(UnsafeRoot {
148 def_id,
149 kinds,
150 unsafe_callees,
151 raw_ptr_locals,
152 static_muts,
153 })
154}