rapx/helpers/
fn_info.rs

1use rustc_hir::{Safety, def::DefKind, def_id::DefId};
2use rustc_middle::{
3    mir::Local,
4    ty,
5    ty::{AssocKind, Mutability, Ty, TyCtxt, TyKind},
6};
7use rustc_span::{kw, sym};
8use std::{
9    collections::{HashMap, HashSet},
10    fmt::Debug,
11    hash::Hash,
12};
13use syn::Expr;
14
15pub use super::mir_scan::{
16    check_safety, collect_global_local_pairs, get_rawptr_deref, get_unsafe_callees,
17    place_has_raw_deref,
18};
19pub use super::name::{
20    access_ident_recursive, find_generic_in_ty, find_generic_param, get_cleaned_def_path_name,
21    get_known_std_names, get_std_api_signature_json, get_struct_name, match_primitive_type,
22    match_ty_with_ident, parse_local_signature, parse_outside_signature, parse_signature,
23};
24
25#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
26pub enum FnKind {
27    Fn,
28    Method,
29    Constructor,
30    Intrinsic,
31}
32
33#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
34pub struct FnInfo {
35    pub def_id: DefId,
36    pub fn_safety: Safety,
37    pub fn_kind: FnKind,
38}
39
40impl FnInfo {
41    pub fn new(def_id: DefId, fn_safety: Safety, fn_kind: FnKind) -> Self {
42        FnInfo {
43            def_id,
44            fn_safety,
45            fn_kind,
46        }
47    }
48}
49
50#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
51pub struct AdtInfo {
52    pub def_id: DefId,
53    pub literal_cons_enabled: bool,
54}
55
56impl AdtInfo {
57    pub fn new(def_id: DefId, literal_cons_enabled: bool) -> Self {
58        AdtInfo {
59            def_id,
60            literal_cons_enabled,
61        }
62    }
63}
64
65pub fn check_visibility(tcx: TyCtxt, func_defid: DefId) -> bool {
66    if !tcx.visibility(func_defid).is_public() {
67        return false;
68    }
69    true
70}
71
72pub fn get_type(tcx: TyCtxt<'_>, def_id: DefId) -> FnKind {
73    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
74        match assoc_item.kind {
75            AssocKind::Fn { has_self, .. } => {
76                if has_self {
77                    return FnKind::Method;
78                } else {
79                    let fn_sig = tcx.fn_sig(def_id).skip_binder();
80                    let output = fn_sig.output().skip_binder();
81                    // return type is 'Self'
82                    if output.is_param(0) {
83                        return FnKind::Constructor;
84                    }
85                    // return type is struct's name
86                    if let Some(impl_id) = assoc_item.impl_container(tcx) {
87                        let ty = tcx.type_of(impl_id).skip_binder();
88                        if output == ty {
89                            return FnKind::Constructor;
90                        }
91                    }
92                    match output.kind() {
93                        TyKind::Ref(_, ref_ty, _) => {
94                            if ref_ty.is_param(0) {
95                                return FnKind::Constructor;
96                            }
97                            if let Some(impl_id) = assoc_item.impl_container(tcx) {
98                                let ty = tcx.type_of(impl_id).skip_binder();
99                                if *ref_ty == ty {
100                                    return FnKind::Constructor;
101                                }
102                            }
103                        }
104                        TyKind::Adt(adt_def, substs) => {
105                            if adt_def.is_enum()
106                                && (tcx.is_diagnostic_item(sym::Option, adt_def.did())
107                                    || tcx.is_diagnostic_item(sym::Result, adt_def.did())
108                                    || tcx.is_diagnostic_item(kw::Box, adt_def.did()))
109                            {
110                                let inner_ty = substs.type_at(0);
111                                if inner_ty.is_param(0) {
112                                    return FnKind::Constructor;
113                                }
114                                if let Some(impl_id) = assoc_item.impl_container(tcx) {
115                                    let ty_impl = tcx.type_of(impl_id).skip_binder();
116                                    if inner_ty == ty_impl {
117                                        return FnKind::Constructor;
118                                    }
119                                }
120                            }
121                        }
122                        _ => {}
123                    }
124                }
125            }
126            _ => todo!(),
127        }
128    }
129    return FnKind::Fn;
130}
131// result: adt_def_id, is_literal
132pub fn get_adt_via_method(tcx: TyCtxt<'_>, method_def_id: DefId) -> Option<AdtInfo> {
133    let assoc_item = tcx.opt_associated_item(method_def_id)?;
134    let impl_id = assoc_item.impl_container(tcx)?;
135    let ty = tcx.type_of(impl_id).skip_binder();
136    let adt_def = ty.ty_adt_def()?;
137    let adt_def_id = adt_def.did();
138
139    let all_fields: Vec<_> = adt_def.all_fields().collect();
140    let total_count = all_fields.len();
141
142    if total_count == 0 {
143        return Some(AdtInfo::new(adt_def_id, true));
144    }
145
146    let pub_count = all_fields
147        .iter()
148        .filter(|field| tcx.visibility(field.did).is_public())
149        .count();
150
151    if pub_count == 0 {
152        return None;
153    }
154    Some(AdtInfo::new(adt_def_id, pub_count == total_count))
155}
156// return all the impls def id of corresponding struct
157pub fn get_impls_for_struct(tcx: TyCtxt<'_>, struct_def_id: DefId) -> Vec<DefId> {
158    let mut impls = Vec::new();
159    for item_id in tcx.hir_crate_items(()).free_items() {
160        let item = tcx.hir_item(item_id);
161        if let rustc_hir::ItemKind::Impl(impl_details) = &item.kind {
162            if let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) =
163                &impl_details.self_ty.kind
164            {
165                if let rustc_hir::def::Res::Def(_, def_id) = path.res {
166                    if def_id == struct_def_id {
167                        impls.push(item_id.owner_id.to_def_id());
168                    }
169                }
170            }
171        }
172    }
173    impls
174}
175
176pub fn get_adt_def_id_by_adt_method(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
177    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
178        if let Some(impl_id) = assoc_item.impl_container(tcx) {
179            // get struct ty
180            let ty = tcx.type_of(impl_id).skip_binder();
181            if let Some(adt_def) = ty.ty_adt_def() {
182                return Some(adt_def.did());
183            }
184        }
185    }
186    None
187}
188
189// get the pointee or wrapped type
190pub fn get_pointee(matched_ty: Ty<'_>) -> Ty<'_> {
191    // progress_info!("get_pointee: > {:?} as type: {:?}", matched_ty, matched_ty.kind());
192    let pointee = if let ty::RawPtr(ty_mut, _) = matched_ty.kind() {
193        get_pointee(*ty_mut)
194    } else if let ty::Ref(_, referred_ty, _) = matched_ty.kind() {
195        get_pointee(*referred_ty)
196    } else {
197        matched_ty
198    };
199    pointee
200}
201
202pub fn is_ptr(matched_ty: Ty<'_>) -> bool {
203    if let ty::RawPtr(_, _) = matched_ty.kind() {
204        return true;
205    }
206    false
207}
208
209pub fn has_mut_self_param(tcx: TyCtxt, def_id: DefId) -> bool {
210    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
211        match assoc_item.kind {
212            AssocKind::Fn { has_self, .. } => {
213                if has_self && tcx.is_mir_available(def_id) {
214                    let body = tcx.optimized_mir(def_id);
215                    let fst_arg = body.local_decls[Local::from_usize(1)].clone();
216                    let ty = fst_arg.ty;
217                    let is_mut_ref = matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut));
218                    return fst_arg.mutability.is_mut() || is_mut_ref;
219                }
220            }
221            _ => (),
222        }
223    }
224    false
225}
226
227// Check each field's visibility, return the public fields vec
228pub fn get_public_fields(tcx: TyCtxt, def_id: DefId) -> HashSet<usize> {
229    let adt_def = tcx.adt_def(def_id);
230    adt_def
231        .all_fields()
232        .enumerate()
233        .filter_map(|(index, field_def)| tcx.visibility(field_def.did).is_public().then_some(index))
234        .collect()
235}
236
237/// parse expr into number.
238pub fn parse_expr_into_number(expr: &Expr) -> Option<usize> {
239    if let Expr::Lit(expr_lit) = expr {
240        if let syn::Lit::Int(lit_int) = &expr_lit.lit {
241            return lit_int.base10_parse::<usize>().ok();
242        }
243    }
244    None
245}
246
247pub fn get_all_std_fns_by_rustc_public(tcx: TyCtxt) -> Vec<DefId> {
248    let mut all_std_fn_def = Vec::new();
249    let mut results = Vec::new();
250    let mut core_fn_def: Vec<_> = rustc_public::find_crates("core")
251        .iter()
252        .flat_map(|krate| krate.fn_defs())
253        .collect();
254    let mut std_fn_def: Vec<_> = rustc_public::find_crates("std")
255        .iter()
256        .flat_map(|krate| krate.fn_defs())
257        .collect();
258    let mut alloc_fn_def: Vec<_> = rustc_public::find_crates("alloc")
259        .iter()
260        .flat_map(|krate| krate.fn_defs())
261        .collect();
262    all_std_fn_def.append(&mut core_fn_def);
263    all_std_fn_def.append(&mut std_fn_def);
264    all_std_fn_def.append(&mut alloc_fn_def);
265
266    for fn_def in &all_std_fn_def {
267        let def_id = crate::def_id::to_internal(fn_def, tcx);
268        results.push(def_id);
269    }
270    results
271}
272
273// Input the adt def id
274// Return set of (mutable method def_id, fields can be modified)
275pub fn get_all_mutable_methods(tcx: TyCtxt, src_def_id: DefId) -> HashMap<DefId, HashSet<usize>> {
276    let mut std_results = HashMap::new();
277    if get_type(tcx, src_def_id) == FnKind::Constructor {
278        return std_results;
279    }
280    let all_std_fn_def = get_all_std_fns_by_rustc_public(tcx);
281    let target_adt_def = get_adt_def_id_by_adt_method(tcx, src_def_id);
282    let mut is_std = false;
283    for &def_id in &all_std_fn_def {
284        let adt_def = get_adt_def_id_by_adt_method(tcx, def_id);
285        if adt_def.is_some() && adt_def == target_adt_def && src_def_id != def_id {
286            if has_mut_self_param(tcx, def_id) {
287                std_results.insert(def_id, HashSet::new());
288            }
289            is_std = true;
290        }
291    }
292    if is_std {
293        return std_results;
294    }
295    let mut results = HashMap::new();
296    let public_fields = target_adt_def.map_or_else(HashSet::new, |def| get_public_fields(tcx, def));
297    let impl_vec = target_adt_def.map_or_else(Vec::new, |def| get_impls_for_struct(tcx, def));
298    for impl_id in impl_vec {
299        if !matches!(tcx.def_kind(impl_id), rustc_hir::def::DefKind::Impl { .. }) {
300            continue;
301        }
302        let associated_items = tcx.associated_items(impl_id);
303        for item in associated_items.in_definition_order() {
304            if let ty::AssocKind::Fn {
305                name: _,
306                has_self: _,
307            } = item.kind
308            {
309                let item_def_id = item.def_id;
310                if has_mut_self_param(tcx, item_def_id) {
311                    let modified_fields = public_fields.clone();
312                    results.insert(item_def_id, modified_fields);
313                }
314            }
315        }
316    }
317    results
318}
319
320pub fn get_cons(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<DefId> {
321    let mut cons = Vec::new();
322    if tcx.def_kind(def_id) == DefKind::Fn || get_type(tcx, def_id) == FnKind::Constructor {
323        return cons;
324    }
325    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
326        if let Some(impl_id) = assoc_item.impl_container(tcx) {
327            let ty = tcx.type_of(impl_id).skip_binder();
328            if let Some(adt_def) = ty.ty_adt_def() {
329                let adt_def_id = adt_def.did();
330                let impls = tcx.inherent_impls(adt_def_id);
331                for impl_def_id in impls {
332                    for item in tcx.associated_item_def_ids(*impl_def_id) {
333                        if (tcx.def_kind(*item) == DefKind::Fn
334                            || tcx.def_kind(*item) == DefKind::AssocFn)
335                            && get_type(tcx, *item) == FnKind::Constructor
336                        {
337                            cons.push(*item);
338                        }
339                    }
340                }
341            }
342        }
343    }
344    cons
345}
346
347/// Find `&mut self` methods (mutators) on the same struct as `def_id`.
348///
349/// A mutator is a method whose first parameter is a mutable reference to Self.
350/// These methods can change struct fields and affect subsequent invariant checks.
351pub fn get_muts(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<DefId> {
352    let mut muts = Vec::new();
353    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
354        if let Some(impl_id) = assoc_item.impl_container(tcx) {
355            let ty = tcx.type_of(impl_id).skip_binder();
356            if let Some(adt_def) = ty.ty_adt_def() {
357                let adt_def_id = adt_def.did();
358                let impls = tcx.inherent_impls(adt_def_id);
359                for impl_def_id in impls {
360                    for item in tcx.associated_item_def_ids(*impl_def_id) {
361                        if !matches!(tcx.def_kind(*item), DefKind::Fn | DefKind::AssocFn) {
362                            continue;
363                        }
364                        if get_type(tcx, *item) != FnKind::Method {
365                            continue;
366                        }
367                        let Some(assoc) = tcx.opt_associated_item(*item) else {
368                            continue;
369                        };
370                        if !matches!(assoc.kind, AssocKind::Fn { has_self: true, .. }) {
371                            continue;
372                        }
373                        let fn_sig = tcx.fn_sig(*item).instantiate_identity().skip_binder();
374                        let all = fn_sig.inputs_and_output;
375                        let first_param = all.first().copied();
376                        if let Some(TyKind::Ref(_, _, Mutability::Mut)) =
377                            first_param.map(|ty| ty.kind())
378                        {
379                            muts.push(*item);
380                        }
381                    }
382                }
383            }
384        }
385    }
386    muts
387}
388
389pub fn append_fn_with_types(tcx: TyCtxt, def_id: DefId) -> FnInfo {
390    FnInfo::new(def_id, check_safety(tcx, def_id), get_type(tcx, def_id))
391}
392
393pub fn get_ptr_deref_dummy_def_id(tcx: TyCtxt<'_>) -> Option<DefId> {
394    tcx.hir_crate_items(()).free_items().find_map(|item_id| {
395        let def_id = item_id.owner_id.to_def_id();
396        let name = tcx.opt_item_name(def_id)?;
397
398        (name.as_str() == "__raw_ptr_deref_dummy").then_some(def_id)
399    })
400}
401
402/// Return field indices that a `&mut self` method writes to.
403///
404/// Scans the MIR body for assignments to `(*self).field_n` and returns the
405/// set of field indices that are modified.  Used by invless mode to know which
406/// constructor-inherited invariants are invalidated by a mutator.
407pub fn get_mutated_fields(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<usize> {
408    use rustc_middle::mir::{ProjectionElem, StatementKind};
409
410    let body = tcx.optimized_mir(def_id);
411    let mut fields = Vec::new();
412
413    for (_, data) in body.basic_blocks.iter().enumerate() {
414        for statement in &data.statements {
415            if let StatementKind::Assign(assign) = &statement.kind {
416                let (place, _) = &**assign;
417                if place.local.as_usize() != 1 {
418                    continue;
419                }
420                let mut saw_deref = false;
421                for proj in place.projection.iter() {
422                    match proj {
423                        ProjectionElem::Deref => {
424                            saw_deref = true;
425                        }
426                        ProjectionElem::Field(index, _) if saw_deref => {
427                            let idx = index.as_usize();
428                            if !fields.contains(&idx) {
429                                fields.push(idx);
430                            }
431                        }
432                        _ => {}
433                    }
434                }
435            }
436        }
437    }
438
439    fields
440}