Skip to main content

rapx/analysis/safety_flow/
root.rs

1use 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/// Kind of unsafe operation found in a function body.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum UnsafeOpKind {
12    CallsUnsafeFn,
13    DerefsRawPtr,
14    AccessesStaticMut,
15}
16
17/// A function that contains unsafe operations — an "unsafe root".
18///
19/// This is the unified entry point for both the safetyflow analysis and the
20/// verify module to determine whether a function needs safety verification.
21#[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
30/// Fast HIR-level pre-check: does this function contain `unsafe` blocks
31/// or is it declared `unsafe fn`?
32///
33/// This is a cheap check that can quickly filter out functions that are
34/// entirely safe and have no unsafe operations of any kind.
35pub 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
40/// Check if a struct has `#[rapx::invariant(...)]` annotations.
41///
42/// This is a cheap HIR attribute scan — it only checks for the presence of
43/// the attribute path, without parsing the annotation arguments.
44pub 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
60/// Quick check: does this function's owning struct have invariants?
61pub 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
75/// Quick check: does this function's containing impl implement an `unsafe trait`?
76///
77/// This is a fast HIR-level pre-filter similar to [`function_has_struct_invariant`].
78pub 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    // Check if the trait is declared `unsafe trait`
99    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
112/// Full MIR-level detection: scan the function body for all unsafe operations.
113///
114/// Returns `None` if the function has no unsafe callees, no raw pointer
115/// dereferences, and no static mutable accesses.
116pub 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}