Skip to main content

rapx/helpers/
fn_info.rs

1use rustc_hir::{Safety, def::DefKind, def_id::DefId};
2use rustc_middle::{
3    ty,
4    ty::{AssocKind, Mutability, TyCtxt, TyKind},
5};
6use rustc_span::{kw, sym};
7use std::{collections::HashSet, fmt::Debug, hash::Hash};
8use syn::Expr;
9
10pub use super::mir_scan::check_safety;
11pub use super::name::get_cleaned_def_path_name;
12
13#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
14pub enum FnKind {
15    Fn,
16    Method,
17    Constructor,
18    Intrinsic,
19}
20
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
22pub struct FnInfo {
23    pub def_id: DefId,
24    pub fn_safety: Safety,
25    pub fn_kind: FnKind,
26}
27
28impl FnInfo {
29    pub fn new(def_id: DefId, fn_safety: Safety, fn_kind: FnKind) -> Self {
30        FnInfo {
31            def_id,
32            fn_safety,
33            fn_kind,
34        }
35    }
36}
37
38#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
39pub struct AdtInfo {
40    pub def_id: DefId,
41    pub literal_cons_enabled: bool,
42}
43
44impl AdtInfo {
45    pub fn new(def_id: DefId, literal_cons_enabled: bool) -> Self {
46        AdtInfo {
47            def_id,
48            literal_cons_enabled,
49        }
50    }
51}
52
53pub fn check_visibility(tcx: TyCtxt, func_defid: DefId) -> bool {
54    tcx.visibility(func_defid).is_public()
55}
56
57/// Returns true when `ty` denotes `Self`: either the generic `Self` param
58/// (`ty.is_param(0)`) or a type equal to the impl's self type.
59fn is_self_ty<'tcx>(tcx: TyCtxt<'tcx>, assoc_item: &ty::AssocItem, ty: ty::Ty<'tcx>) -> bool {
60    if ty.is_param(0) {
61        return true;
62    }
63    assoc_item
64        .impl_container(tcx)
65        .is_some_and(|impl_id| ty == tcx.type_of(impl_id).skip_binder())
66}
67
68pub fn get_type(tcx: TyCtxt<'_>, def_id: DefId) -> FnKind {
69    let Some(assoc_item) = tcx.opt_associated_item(def_id) else {
70        return FnKind::Fn;
71    };
72    let AssocKind::Fn { has_self, .. } = assoc_item.kind else {
73        return FnKind::Fn;
74    };
75    if has_self {
76        return FnKind::Method;
77    }
78    let output = tcx.fn_sig(def_id).skip_binder().output().skip_binder();
79    if is_self_ty(tcx, &assoc_item, output) {
80        return FnKind::Constructor;
81    }
82    match output.kind() {
83        TyKind::Ref(_, ref_ty, _) => {
84            if is_self_ty(tcx, &assoc_item, *ref_ty) {
85                return FnKind::Constructor;
86            }
87        }
88        TyKind::Adt(adt_def, substs)
89            if adt_def.is_enum()
90                && (tcx.is_diagnostic_item(sym::Option, adt_def.did())
91                    || tcx.is_diagnostic_item(sym::Result, adt_def.did())
92                    || tcx.is_diagnostic_item(kw::Box, adt_def.did())) =>
93        {
94            if is_self_ty(tcx, &assoc_item, substs.type_at(0)) {
95                return FnKind::Constructor;
96            }
97        }
98        _ => {}
99    }
100    FnKind::Fn
101}
102
103/// Returns true when the function is a "wrapped" constructor that returns
104/// `Option<Self>` / `Result<Self, _>` rather than a bare `Self`.
105///
106/// `get_type` classifies these as [`FnKind::Constructor`], but for the wrapped
107/// forms the `None`/`Err` paths do not produce a `Self`, so a struct invariant
108/// can only be meaningfully discharged on the `Some`/`Ok` paths. This helper
109/// lets `verify_struct_invariants` skip the benign `Unknown` results on the
110/// non-`Self` paths. (`Box<Self>` is intentionally *not* included: every path
111/// still produces a `Self` behind the pointer.)
112pub fn returns_wrapped_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
113    let Some(assoc_item) = tcx.opt_associated_item(def_id) else {
114        return false;
115    };
116    if !matches!(
117        assoc_item.kind,
118        AssocKind::Fn {
119            has_self: false,
120            ..
121        }
122    ) {
123        return false;
124    }
125    let fn_sig = tcx.fn_sig(def_id).skip_binder();
126    let output = fn_sig.output().skip_binder();
127    let TyKind::Adt(adt_def, substs) = output.kind() else {
128        return false;
129    };
130    if !(adt_def.is_enum()
131        && (tcx.is_diagnostic_item(sym::Option, adt_def.did())
132            || tcx.is_diagnostic_item(sym::Result, adt_def.did())))
133    {
134        return false;
135    }
136    is_self_ty(tcx, &assoc_item, substs.type_at(0))
137}
138
139/// The `AdtDef` that `def_id`'s impl block is implemented for, if any.
140fn self_adt_def(tcx: TyCtxt<'_>, def_id: DefId) -> Option<ty::AdtDef<'_>> {
141    let assoc_item = tcx.opt_associated_item(def_id)?;
142    let impl_id = assoc_item.impl_container(tcx)?;
143    tcx.type_of(impl_id).skip_binder().ty_adt_def()
144}
145
146// result: adt_def_id, is_literal
147pub fn get_adt_via_method(tcx: TyCtxt<'_>, method_def_id: DefId) -> Option<AdtInfo> {
148    let adt_def = self_adt_def(tcx, method_def_id)?;
149    let adt_def_id = adt_def.did();
150
151    let total_count = adt_def.all_fields().count();
152
153    if total_count == 0 {
154        return Some(AdtInfo::new(adt_def_id, true));
155    }
156
157    let pub_count = public_field_indices(tcx, adt_def).len();
158
159    if pub_count == 0 {
160        return None;
161    }
162    Some(AdtInfo::new(adt_def_id, pub_count == total_count))
163}
164pub fn get_adt_def_id_by_adt_method(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
165    self_adt_def(tcx, def_id).map(|adt_def| adt_def.did())
166}
167
168/// Returns true when `def_id` is a method taking `&mut self` (a mutator).
169///
170/// Detection is based on the method signature rather than MIR, so it also
171/// works for foreign (e.g. std) functions whose MIR is unavailable.
172fn is_mut_self_method(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
173    let Some(assoc) = tcx.opt_associated_item(def_id) else {
174        return false;
175    };
176    if !matches!(assoc.kind, AssocKind::Fn { has_self: true, .. }) {
177        return false;
178    }
179    let fn_sig = tcx.fn_sig(def_id).instantiate_identity().skip_binder();
180    let Some(first) = fn_sig.inputs_and_output.first().copied() else {
181        return false;
182    };
183    matches!(first.kind(), TyKind::Ref(_, _, Mutability::Mut))
184}
185
186// Check each field's visibility, return the public fields vec
187fn public_field_indices(tcx: TyCtxt<'_>, adt_def: ty::AdtDef<'_>) -> HashSet<usize> {
188    adt_def
189        .all_fields()
190        .enumerate()
191        .filter_map(|(index, field_def)| tcx.visibility(field_def.did).is_public().then_some(index))
192        .collect()
193}
194
195/// parse expr into number.
196pub fn parse_expr_into_number(expr: &Expr) -> Option<usize> {
197    if let Expr::Lit(expr_lit) = expr {
198        if let syn::Lit::Int(lit_int) = &expr_lit.lit {
199            return lit_int.base10_parse::<usize>().ok();
200        }
201    }
202    None
203}
204
205pub fn get_all_std_fns_by_rustc_public(tcx: TyCtxt) -> Vec<DefId> {
206    let mut all_std_fn_def = Vec::new();
207    let mut results = Vec::new();
208    let mut core_fn_def: Vec<_> = rustc_public::find_crates("core")
209        .iter()
210        .flat_map(|krate| krate.fn_defs())
211        .collect();
212    let mut std_fn_def: Vec<_> = rustc_public::find_crates("std")
213        .iter()
214        .flat_map(|krate| krate.fn_defs())
215        .collect();
216    let mut alloc_fn_def: Vec<_> = rustc_public::find_crates("alloc")
217        .iter()
218        .flat_map(|krate| krate.fn_defs())
219        .collect();
220    all_std_fn_def.append(&mut core_fn_def);
221    all_std_fn_def.append(&mut std_fn_def);
222    all_std_fn_def.append(&mut alloc_fn_def);
223
224    for fn_def in &all_std_fn_def {
225        let def_id = crate::def_id::to_internal(fn_def, tcx);
226        results.push(def_id);
227    }
228    results
229}
230
231/// Find `&mut self` methods (mutators) on the same struct as `src_def_id`.
232///
233/// For std types the mutators are located among the std library's public
234/// functions; for user types they are the struct's inherent `&mut self` methods.
235pub fn get_all_mutable_methods(tcx: TyCtxt, src_def_id: DefId) -> HashSet<DefId> {
236    if get_type(tcx, src_def_id) == FnKind::Constructor {
237        return HashSet::new();
238    }
239    let target_adt_def = get_adt_def_id_by_adt_method(tcx, src_def_id);
240    let mut mutators = HashSet::new();
241    let mut is_std = false;
242    for def_id in get_all_std_fns_by_rustc_public(tcx) {
243        let adt_def = get_adt_def_id_by_adt_method(tcx, def_id);
244        if adt_def.is_some() && adt_def == target_adt_def && src_def_id != def_id {
245            if is_mut_self_method(tcx, def_id) {
246                mutators.insert(def_id);
247            }
248            is_std = true;
249        }
250    }
251    if is_std {
252        return mutators;
253    }
254    mutators.extend(get_muts(tcx, src_def_id));
255    mutators
256}
257
258/// Associated function `DefId`s (inherent impls only) on the struct that
259/// `def_id`'s impl block is implemented for.
260fn assoc_fns_of_self(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<DefId> {
261    let Some(adt_def) = self_adt_def(tcx, def_id) else {
262        return Vec::new();
263    };
264    let mut fns = Vec::new();
265    for impl_def_id in tcx.inherent_impls(adt_def.did()) {
266        for item in tcx.associated_item_def_ids(*impl_def_id) {
267            if matches!(tcx.def_kind(*item), DefKind::Fn | DefKind::AssocFn) {
268                fns.push(*item);
269            }
270        }
271    }
272    fns
273}
274
275pub fn get_cons(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<DefId> {
276    if tcx.def_kind(def_id) == DefKind::Fn || get_type(tcx, def_id) == FnKind::Constructor {
277        return Vec::new();
278    }
279    assoc_fns_of_self(tcx, def_id)
280        .into_iter()
281        .filter(|&item| get_type(tcx, item) == FnKind::Constructor)
282        .collect()
283}
284
285/// Find `&mut self` methods (mutators) on the same struct as `def_id`.
286///
287/// A mutator is a method whose first parameter is a mutable reference to Self.
288/// These methods can change struct fields and affect subsequent invariant checks.
289pub fn get_muts(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<DefId> {
290    assoc_fns_of_self(tcx, def_id)
291        .into_iter()
292        .filter(|&item| is_mut_self_method(tcx, item))
293        .collect()
294}
295
296pub fn append_fn_with_types(tcx: TyCtxt, def_id: DefId) -> FnInfo {
297    FnInfo::new(def_id, check_safety(tcx, def_id), get_type(tcx, def_id))
298}
299
300pub fn get_ptr_deref_dummy_def_id(tcx: TyCtxt<'_>) -> Option<DefId> {
301    tcx.hir_crate_items(()).free_items().find_map(|item_id| {
302        let def_id = item_id.owner_id.to_def_id();
303        let name = tcx.opt_item_name(def_id)?;
304
305        (name.as_str() == "__raw_ptr_deref_dummy").then_some(def_id)
306    })
307}
308
309/// Return field indices that a `&mut self` method writes to.
310///
311/// Scans the MIR body for assignments to `(*self).field_n` and returns the
312/// set of field indices that are modified.  Used by --skip-invariant mode to know which
313/// constructor-inherited invariants are invalidated by a mutator.
314pub fn get_mutated_fields(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<usize> {
315    use rustc_middle::mir::{ProjectionElem, StatementKind};
316
317    let body = tcx.optimized_mir(def_id);
318    let mut fields = Vec::new();
319
320    for (_, data) in body.basic_blocks.iter().enumerate() {
321        for statement in &data.statements {
322            if let StatementKind::Assign(assign) = &statement.kind {
323                let (place, _) = &**assign;
324                if place.local.as_usize() != 1 {
325                    continue;
326                }
327                let mut saw_deref = false;
328                for proj in place.projection.iter() {
329                    match proj {
330                        ProjectionElem::Deref => {
331                            saw_deref = true;
332                        }
333                        ProjectionElem::Field(index, _) if saw_deref => {
334                            let idx = index.as_usize();
335                            if !fields.contains(&idx) {
336                                fields.push(idx);
337                            }
338                        }
339                        _ => {}
340                    }
341                }
342            }
343        }
344    }
345
346    fields
347}
348
349pub fn is_externally_reachable(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
350    let Some(local) = def_id.as_local() else {
351        return true;
352    };
353    tcx.effective_visibilities(()).is_reachable(local)
354}