Skip to main content

rapx/helpers/
mir_utils.rs

1use rustc_hir::{
2    ItemKind,
3    def_id::{DefId, LocalDefId},
4};
5#[cfg(not(rapx_ge_100))]
6use rustc_hir::LangItem;
7#[cfg(rapx_ge_100)]
8use rustc_hir::attrs::lang_items::LangItem;
9use rustc_middle::{
10    mir::{BasicBlock, ConstValue, Local, Operand, Place, Rvalue, StatementKind, TerminatorKind},
11    mir::interpret::{AllocId, GlobalAlloc},
12    ty::{ConstKind, GenericArgKind, PseudoCanonicalInput, Ty, TyCtxt, TyKind, TypingEnv},
13};
14use rustc_span::Symbol;
15
16use std::collections::{HashMap, HashSet};
17
18use crate::{
19    analysis::alias::{collect_local_origins, LocalOriginMap},
20    helpers::mir_scan::Checkpoint,
21    verify::def_use::PlaceKey,
22};
23
24pub(crate) fn pointee_ty<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
25    match ty.kind() {
26        TyKind::RawPtr(ty, _) | TyKind::Ref(_, ty, _) => Some(*ty),
27        _ => None,
28    }
29}
30
31pub(crate) fn dep_callee_def_id(func: &Operand<'_>) -> Option<DefId> {
32    let Operand::Constant(func_constant) = func else { return None };
33    let TyKind::FnDef(def_id, _) = func_constant.const_.ty().kind() else { return None };
34    Some(*def_id)
35}
36
37/// Collect all return basic block indices for a function body.
38pub fn collect_return_block_indices(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<BasicBlock> {
39    let mut blocks = Vec::new();
40    if !tcx.is_mir_available(def_id) {
41        return blocks;
42    }
43    let body = tcx.optimized_mir(def_id);
44    for (bb, data) in body.basic_blocks.iter_enumerated() {
45        if matches!(data.terminator().kind, TerminatorKind::Return) {
46            blocks.push(bb);
47        }
48    }
49    blocks
50}
51
52/// Return true when `def_id`'s MIR body is "linear" enough for lightweight
53/// inlining: no `SwitchInt` terminators, at most one return, and at most
54/// `max_blocks` basic blocks.
55pub fn callee_is_linear(tcx: TyCtxt<'_>, def_id: DefId, max_blocks: usize) -> bool {
56    if !tcx.is_mir_available(def_id) {
57        return false;
58    }
59    let body = tcx.optimized_mir(def_id);
60    body.basic_blocks.len() <= max_blocks
61        && !body
62            .basic_blocks
63            .iter()
64            .any(|bb| matches!(bb.terminator().kind, TerminatorKind::SwitchInt { .. }))
65        && body
66            .basic_blocks
67            .iter()
68            .filter(|bb| matches!(bb.terminator().kind, TerminatorKind::Return))
69            .count()
70            <= 1
71}
72
73/// Return the callee argument index represented by a MIR local.
74///
75/// Contract annotations written with parameter names are parsed in the callee's
76/// local namespace.  MIR local `_0` is the return place and argument locals are
77/// `_1..=_arg_count`, so callee local `_1` denotes checkpoint argument `0`.
78pub fn callee_param_index_for_local(tcx: TyCtxt<'_>, callee: DefId, local: usize) -> Option<usize> {
79    let arg_count = if tcx.is_mir_available(callee) {
80        tcx.optimized_mir(callee).arg_count
81    } else {
82        tcx.fn_sig(callee)
83            .skip_binder()
84            .inputs()
85            .skip_binder()
86            .len()
87    };
88    arg_of_local(Local::from_usize(local), arg_count)
89}
90
91pub fn is_std_crate_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
92    matches!(
93        tcx.crate_name(def_id.krate).as_str(),
94        "core" | "std" | "alloc"
95    )
96}
97
98pub fn is_trait_unsafe(tcx: TyCtxt<'_>, trait_def_id: DefId) -> bool {
99    let Some(local_id) = trait_def_id.as_local() else {
100        return false;
101    };
102    let item = tcx.hir_expect_item(local_id);
103
104    #[cfg(not(rapx_ge_99))]
105    if let ItemKind::Trait(_, _, unsafety, _, _, _, _) = &item.kind {
106        return matches!(unsafety, rustc_hir::Safety::Unsafe);
107    }
108    #[cfg(rapx_ge_99)]
109    if let ItemKind::Trait { safety, .. } = &item.kind {
110        return matches!(safety, rustc_hir::Safety::Unsafe);
111    }
112
113    false
114}
115
116pub fn resolve_impl_self_ty_def_id(item: &rustc_hir::Item<'_>) -> Option<DefId> {
117    let ItemKind::Impl(rustc_hir::Impl { self_ty, .. }) = &item.kind else {
118        return None;
119    };
120    match &self_ty.kind {
121        rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) => match path.res {
122            rustc_hir::def::Res::Def(
123                rustc_hir::def::DefKind::Struct
124                | rustc_hir::def::DefKind::Enum
125                | rustc_hir::def::DefKind::Union,
126                def_id,
127            ) => Some(def_id),
128            _ => None,
129        },
130        _ => None,
131    }
132}
133
134pub fn has_rapx_verify_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
135    let hir_id = tcx.local_def_id_to_hir_id(def_id);
136
137    let rapx = Symbol::intern("rapx");
138    let verify = Symbol::intern("verify");
139
140    let attrs = tcx.hir_attrs(hir_id);
141
142    attrs.iter().any(|attr| {
143        if attr.is_doc_comment().is_some() {
144            return false;
145        }
146
147        let path = attr.path();
148
149        path.len() == 2 && path[0] == rapx && path[1] == verify
150    })
151}
152
153pub fn get_owner_struct_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
154    let assoc_item = tcx.opt_associated_item(def_id)?;
155    let impl_id = assoc_item.impl_container(tcx)?;
156    let self_ty = tcx.type_of(impl_id).skip_binder();
157
158    match self_ty.kind() {
159        TyKind::Adt(adt_def, _) => Some(adt_def.did()),
160        _ => None,
161    }
162}
163
164/// True when a type transitively contains a const-generic parameter or
165/// an associated type alias (which may be layout-ambiguous).
166pub(crate) fn ty_has_param_const(ty: Ty<'_>) -> bool {
167    for arg in ty.walk() {
168        match arg.kind() {
169            GenericArgKind::Const(c) if matches!(c.kind(), ConstKind::Param(_)) => return true,
170            GenericArgKind::Type(inner_ty) if matches!(inner_ty.kind(), TyKind::Alias(..)) => {
171                return true;
172            }
173            _ => {}
174        }
175    }
176    false
177}
178
179/// Run `f` inside `catch_unwind`, returning either the result or the
180/// downcasted panic message.
181pub(crate) fn catch_panic<T>(f: impl FnOnce() -> T) -> Result<T, String> {
182    std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_err(|e| {
183        e.downcast_ref::<String>()
184            .cloned()
185            .or_else(|| e.downcast_ref::<&str>().map(|s| s.to_string()))
186            .unwrap_or_else(|| "<rustc ICE>".to_string())
187    })
188}
189
190/// Return a stable, human-readable name for a MIR call operand.
191pub fn call_name(tcx: TyCtxt<'_>, func: &Operand<'_>) -> String {
192    dep_callee_def_id(func)
193        .map(|def_id| tcx.def_path_str(def_id))
194        .unwrap_or_else(|| format!("{func:?}"))
195}
196
197/// Return the zero-based argument index of `local`, if it is a MIR argument.
198///
199/// MIR local `_0` is the return place; argument locals start at `_1`.
200pub fn arg_of_local(local: Local, arg_count: usize) -> Option<usize> {
201    let i = local.as_usize();
202    if i >= 1 && i <= arg_count {
203        Some(i - 1)
204    } else {
205        None
206    }
207}
208
209pub fn has_crate(tcx: TyCtxt<'_>, name: &str) -> bool {
210    for num in tcx.crates(()) {
211        if tcx.crate_name(*num) == Symbol::intern(name) {
212            return true;
213        }
214    }
215    false
216}
217
218/// Extracts the source `Place` from an rvalue for simple forwarding operations
219/// (copy, move, cast, reference, raw-pointer, copy-for-deref).
220pub fn rvalue_source_place<'a, 'tcx>(rvalue: &'a Rvalue<'tcx>) -> Option<&'a rustc_middle::mir::Place<'tcx>> {
221    use rustc_middle::mir::{Operand, Rvalue};
222    match rvalue {
223        Rvalue::Use(Operand::Copy(place), ..)
224        | Rvalue::Use(Operand::Move(place), ..)
225        | Rvalue::Cast(_, Operand::Copy(place), _)
226        | Rvalue::Cast(_, Operand::Move(place), _)
227        |         Rvalue::Ref(_, _, place)
228        | Rvalue::RawPtr(_, place)
229        | Rvalue::CopyForDeref(place) => Some(place),
230        _ => None,
231    }
232}
233
234// ── PlaceKey / operand utilities ─────────────────────────────────
235
236/// Extract a PlaceKey from a MIR operand.
237pub fn operand_place(operand: &Operand<'_>) -> Option<PlaceKey> {
238    match operand {
239        Operand::Copy(place) | Operand::Move(place) => Some(PlaceKey::from_mir_place(place)),
240        Operand::Constant(_) => None,
241        #[cfg(rapx_ge_99)]
242        Operand::RuntimeChecks(_) => None,
243    }
244}
245
246/// Extract the MIR Place from an operand.
247pub fn operand_mir_place<'a, 'tcx>(operand: &'a Operand<'tcx>) -> Option<&'a Place<'tcx>> {
248    match operand {
249        Operand::Copy(place) | Operand::Move(place) => Some(place),
250        _ => None,
251    }
252}
253
254/// Return the destination local for a checkpoint's call or deref.
255pub fn call_destination<'tcx>(
256    tcx: TyCtxt<'tcx>,
257    checkpoint: &Checkpoint<'tcx>,
258) -> Option<Local> {
259    if checkpoint.kind == crate::helpers::mir_scan::CheckpointKind::RawPtrDeref {
260        return checkpoint.destination;
261    }
262    let body = tcx.optimized_mir(checkpoint.caller);
263    let terminator = body.basic_blocks[checkpoint.block].terminator();
264    let TerminatorKind::Call { destination, .. } = &terminator.kind else {
265        return None;
266    };
267    Some(destination.local)
268}
269
270// ── Place resolution utilities ───────────────────────────────────
271
272/// Follow local-origin associations transitively to resolve to the
273/// ultimate source (parameter or root local) and accumulated field path.
274pub fn deep_resolve_place(
275    mut local: usize,
276    origins: &LocalOriginMap,
277) -> (usize, Vec<usize>) {
278    let mut seen = HashSet::new();
279    let mut all_fields: Vec<usize> = Vec::new();
280    loop {
281        if !seen.insert(local) {
282            return (local, all_fields);
283        }
284        match origins.get(&local) {
285            Some((l, fields)) => {
286                let mut combined = fields.clone();
287                combined.extend(all_fields.iter());
288                all_fields = combined;
289                if *l == 1 {
290                    return (1, all_fields);
291                }
292                local = *l;
293            }
294            None => {
295                return (local, all_fields);
296            }
297        }
298    }
299}
300
301/// Trace a raw pointer local back through call terminators to find the
302/// originating place (e.g. slice from `get_unchecked`).
303pub fn trace_raw_ptr_through_call(
304    tcx: TyCtxt<'_>,
305    caller: DefId,
306    checkpoint_block: BasicBlock,
307    raw_ptr: Local,
308) -> Option<PlaceKey> {
309    let body = tcx.optimized_mir(caller);
310    let mut block = checkpoint_block;
311    let mut visited = HashSet::new();
312    loop {
313        if !visited.insert(block) {
314            break;
315        }
316        for statement in body.basic_blocks[block].statements.iter().rev() {
317            let StatementKind::Assign(assign) = &statement.kind else {
318                continue;
319            };
320            let (target, _rvalue) = assign.as_ref();
321            if target.local != raw_ptr {
322                continue;
323            }
324            break;
325        }
326        let predecessors = &body.basic_blocks.predecessors()[block];
327        if predecessors.len() != 1 {
328            break;
329        }
330        let prev = predecessors[0];
331        let terminator = body.basic_blocks[prev].terminator();
332        if let TerminatorKind::Call {
333            func,
334            args,
335            destination,
336            ..
337        } = &terminator.kind
338        {
339            if destination.local == raw_ptr {
340                let callee_name = call_name(tcx, func);
341                if callee_name.contains("::get_unchecked") {
342                    if let Some(slice) = args.get(1) {
343                        return operand_place(&slice.node);
344                    }
345                }
346                break;
347            }
348        }
349        block = prev;
350    }
351    None
352}
353
354// ── Block reachability ───────────────────────────────────────────
355
356/// Collect all basic blocks reachable after (and including) a call block.
357pub fn blocks_reachable_after_call(
358    tcx: TyCtxt<'_>,
359    caller: DefId,
360    call_block: BasicBlock,
361) -> HashSet<BasicBlock> {
362    let body = tcx.optimized_mir(caller);
363    let mut starts = Vec::new();
364    if let TerminatorKind::Call { target, .. } = &body.basic_blocks[call_block].terminator().kind
365        && let Some(target) = target
366    {
367        starts.push(*target);
368    }
369
370    let mut seen = HashSet::new();
371    let mut stack = starts;
372    while let Some(block) = stack.pop() {
373        if !seen.insert(block) {
374            continue;
375        }
376        let terminator = body.basic_blocks[block].terminator();
377        for successor in terminator.successors() {
378            stack.push(successor);
379        }
380    }
381    seen
382}
383
384// ── MIR place alias mapping ──────────────────────────────────────
385
386/// Build a mapping from MIR locals to their resolved PlaceKey origins.
387pub fn collect_place_aliases(
388    tcx: TyCtxt<'_>,
389    def_id: DefId,
390) -> HashMap<Local, PlaceKey> {
391    collect_local_origins(tcx, def_id)
392        .into_iter()
393        .map(|(local, (origin_local, fields))| {
394            (
395                Local::from_usize(local),
396                PlaceKey::from_origin(origin_local, fields),
397            )
398        })
399        .collect()
400}
401
402/// Resolve a MIR place through alias mapping to get a canonical PlaceKey.
403pub fn resolve_mir_place<'tcx>(
404    _tcx: TyCtxt<'tcx>,
405    place: &Place<'tcx>,
406    aliases: &HashMap<Local, PlaceKey>,
407) -> PlaceKey {
408    let key = PlaceKey::from_mir_place(place);
409    if !key.fields.is_empty() {
410        return key;
411    }
412    aliases.get(&place.local).cloned().unwrap_or(key)
413}
414
415// ── Rvalue place scanning ────────────────────────────────────────
416
417/// Check whether any MIR place used in an rvalue matches a predicate.
418pub fn rvalue_any_place_matching<'tcx>(
419    rvalue: &Rvalue<'tcx>,
420    pred: &mut impl FnMut(&Place<'tcx>) -> bool,
421) -> bool {
422    match rvalue {
423        Rvalue::Aggregate(_, operands) => operands.iter().any(|operand| match operand {
424            Operand::Copy(place) | Operand::Move(place) => pred(place),
425            Operand::Constant(_) => false,
426            #[cfg(rapx_ge_99)]
427            Operand::RuntimeChecks(_) => false,
428        }),
429        _ => rvalue_source_place(rvalue)
430            .map_or(false, |place| pred(place)),
431    }
432}
433
434// ── Pointer arithmetic origin tracing ────────────────────────────
435
436/// Trace a place back to its root local via local origin map.
437pub fn trace_place_root(
438    origins: &LocalOriginMap,
439    place: &PlaceKey,
440) -> Option<(usize, Vec<usize>)> {
441    let Some(local) = place.local() else {
442        return None;
443    };
444    let (root_local, root_fields) = deep_resolve_place(local.as_usize(), origins);
445    Some((root_local, root_fields))
446}
447
448/// Extract raw bytes from a `ConstValue`, following reference indirection.
449pub fn const_value_bytes<'tcx>(
450    tcx: TyCtxt<'tcx>,
451    value: ConstValue,
452    depth: usize,
453) -> Option<Vec<u8>> {
454    if depth > 4 {
455        return None;
456    }
457    match value {
458        ConstValue::Slice { alloc_id, .. } => alloc_id_bytes(tcx, alloc_id, depth),
459        ConstValue::Scalar(scalar) => {
460            #[cfg(rapx_scalar_to_pointer_interp_result)]
461            let ptr = scalar.to_pointer(&tcx).discard_err()?;
462            #[cfg(not(rapx_scalar_to_pointer_interp_result))]
463            let ptr = scalar.to_pointer(&tcx);
464            let alloc_id = ptr.provenance?.alloc_id();
465            alloc_id_bytes(tcx, alloc_id, depth)
466        }
467        ConstValue::Indirect { alloc_id, .. } => alloc_id_bytes(tcx, alloc_id, depth),
468        _ => None,
469    }
470}
471
472/// Read bytes from a global allocation.
473pub fn alloc_id_bytes<'tcx>(
474    tcx: TyCtxt<'tcx>,
475    alloc_id: AllocId,
476    depth: usize,
477) -> Option<Vec<u8>> {
478    if depth > 4 {
479        return None;
480    }
481    let alloc = match tcx.global_alloc(alloc_id) {
482        GlobalAlloc::Memory(alloc) => alloc,
483        GlobalAlloc::Static(def_id) => tcx.eval_static_initializer(def_id).ok()?,
484        _ => return None,
485    };
486    let alloc = alloc.inner();
487    let provenance = alloc.provenance().ptrs();
488    if let Some((_, prov)) = provenance.iter().next() {
489        return alloc_id_bytes(tcx, prov.alloc_id(), depth + 1);
490    }
491    Some(
492        alloc
493            .inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len())
494            .to_vec(),
495    )
496}
497
498// ── Type layout helpers ───────────────────────────────────────────
499
500/// If `constant` is a promoted `offset_of!(Container, field)` constant (an
501/// unevaluated `Const` whose body is a call to the `offset_of` intrinsic),
502/// return the container type.
503///
504/// Used by the verifier to recognise `byte_add(offset_of!(Container, ..))` and
505/// prove the resulting pointer stays within the container allocation.
506pub(crate) fn offset_of_container<'tcx>(
507    tcx: TyCtxt<'tcx>,
508    constant: &rustc_middle::mir::Const<'tcx>,
509) -> Option<Ty<'tcx>> {
510    let rustc_middle::mir::Const::Unevaluated(uneval, _) = constant else {
511        return None;
512    };
513    // `mir_for_ctfe` only accepts const-like defs; unevaluated consts may also
514    // reference plain functions, so gate on the def kind first.
515    if !is_const_def_kind(tcx, uneval.def) {
516        return None;
517    }
518    // `mir_for_ctfe` panics for cross-crate constants (e.g. `char::MAX` from
519    // `core`), since it only serves local, CTFE-able definitions. `offset_of!`
520    // always expands to a local `AnonConst`, so rejecting external defs loses
521    // nothing but avoids the ICE.
522    if !uneval.def.is_local() {
523        return None;
524    }
525    let body = tcx.mir_for_ctfe(uneval.def);
526    for bb in body.basic_blocks.iter() {
527        if let Some(term) = &bb.terminator
528            && let TerminatorKind::Call { func, .. } = &term.kind
529            && let Some(ty) = offset_of_ty_from_func(tcx, func)
530        {
531            return Some(ty);
532        }
533    }
534    None
535}
536
537/// Whether a `DefId` is a const-like item that `mir_for_ctfe` accepts.
538fn is_const_def_kind(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
539    use rustc_hir::def::DefKind;
540    #[cfg(rapx_ge_99)]
541    let base = matches!(
542        tcx.def_kind(def_id),
543        DefKind::Const { .. }
544            | DefKind::Static { .. }
545            | DefKind::AssocConst { .. }
546            | DefKind::AnonConst
547    );
548    #[cfg(not(rapx_ge_99))]
549    let base = matches!(
550        tcx.def_kind(def_id),
551        DefKind::Const | DefKind::Static { .. } | DefKind::AssocConst | DefKind::AnonConst
552    );
553    #[cfg(rapx_ge_99)]
554    {
555        base
556    }
557    #[cfg(not(rapx_ge_99))]
558    {
559        base || matches!(tcx.def_kind(def_id), DefKind::InlineConst)
560    }
561}
562
563fn offset_of_ty_from_func<'tcx>(
564    tcx: TyCtxt<'tcx>,
565    func: &Operand<'tcx>,
566) -> Option<Ty<'tcx>> {
567    let Operand::Constant(c) = func else { return None };
568    let TyKind::FnDef(def_id, args) = c.const_.ty().kind() else { return None };
569    if !tcx.is_lang_item(*def_id, LangItem::OffsetOf) {
570        return None;
571    }
572    args.iter().find_map(|a| {
573        #[cfg(rapx_ge_99)]
574        let a = a.skip_binder();
575        match a.kind() {
576            GenericArgKind::Type(t) => Some(t),
577            _ => None,
578        }
579    })
580}
581
582pub fn type_layout<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> Option<(u64, u64)> {
583    if ty_has_param_const(ty) { return None }
584    match layout_of_ty(tcx, caller, ty) {
585        Some(l) => Some((l.align.abi.bytes(), l.size.bytes())),
586        None if matches!(ty.kind(), TyKind::Param(_)) => Some((0, 0)),
587        None => None,
588    }
589}
590
591/// Compute the full type layout, catching rustc panics and layout errors.
592/// Shared by `type_layout` and the symbolic VM's size/align/field-offset
593/// queries so the `layout_of` call and its panic-guard live in one place.
594pub fn layout_of_ty<'tcx>(
595    tcx: TyCtxt<'tcx>,
596    caller: DefId,
597    ty: Ty<'tcx>,
598) -> Option<rustc_abi::TyAndLayout<'tcx, Ty<'tcx>>> {
599    let env = TypingEnv::post_analysis(tcx, caller);
600    catch_panic(|| tcx.layout_of(PseudoCanonicalInput { typing_env: env, value: ty }))
601        .ok()
602        .and_then(|r| r.ok())
603}
604
605/// Byte offset of a struct field within its container type (0 on failure).
606pub fn field_offset_in_bytes<'tcx>(
607    tcx: TyCtxt<'tcx>,
608    caller: DefId,
609    ty: Ty<'tcx>,
610    field_idx: usize,
611) -> u64 {
612    let Some(layout) = layout_of_ty(tcx, caller, ty) else { return 0 };
613    match layout.fields {
614        rustc_abi::FieldsShape::Arbitrary { ref offsets, .. } => {
615            let idx = rustc_abi::FieldIdx::from_usize(field_idx);
616            if idx.as_usize() < offsets.len() { return offsets[idx].bytes(); }
617        }
618        _ => {}
619    }
620    0
621}
622
623pub fn destination_stride<'tcx>(
624    tcx: TyCtxt<'tcx>, caller: DefId, dest: Option<Local>,
625) -> Option<u64> {
626    let d = dest?;
627    let pointee = pointee_ty(tcx.optimized_mir(caller).local_decls[d].ty)?;
628    type_layout(tcx, caller, pointee).map(|(_, s)| s)
629}
630
631pub fn pointee_alignment<'tcx>(
632    tcx: TyCtxt<'tcx>, caller: DefId, dest: Option<Local>,
633) -> Option<(u64, String)> {
634    let d = dest?;
635    let ty = tcx.optimized_mir(caller).local_decls[d].ty;
636    let pointee = pointee_ty(ty).or(Some(ty))?;
637    if let Some((a, _)) = type_layout(tcx, caller, pointee) {
638        return Some((a, format!("{pointee:?}")));
639    }
640    if let TyKind::Array(e, _) = pointee.kind()
641        && let Some((a, _)) = type_layout(tcx, caller, *e)
642    {
643        return Some((a, format!("{pointee:?}")));
644    }
645    Some((0, format!("{pointee:?}")))
646}
647
648pub fn nonnull_inner_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
649    let TyKind::Adt(def, args) = ty.kind() else { return None };
650    if !tcx.def_path_str(def.did()).contains("ptr::non_null::NonNull") { return None }
651    args.iter().find_map(|a| match a.kind() { GenericArgKind::Type(t) => Some(t), _ => None })
652}
653
654pub fn slice_element_size(
655    tcx: TyCtxt<'_>, caller: DefId, dest: Option<Local>,
656) -> u64 {
657    let d = match dest {
658        Some(d) => d,
659        None => return 1,
660    };
661    let ty = tcx.optimized_mir(caller).local_decls[d].ty;
662    let elem = match ty.kind() {
663        TyKind::Ref(_, inner, _) => match inner.kind() {
664            TyKind::Slice(e) => *e,
665            _ => return 1,
666        },
667        TyKind::RawPtr(inner, _) => match inner.kind() {
668            TyKind::Slice(e) => *e,
669            _ => return 1,
670        },
671        _ => return 1,
672    };
673    type_layout(tcx, caller, elem)
674        .map(|(_, s)| s)
675        .unwrap_or(1)
676}
677
678pub fn vec_element_size(tcx: TyCtxt<'_>, caller: DefId, dest: Option<Local>) -> u64 {
679    let d = match dest {
680        Some(d) => d,
681        None => return 1,
682    };
683    let ty = tcx.optimized_mir(caller).local_decls[d].ty;
684    vec_elem_ty(tcx, ty)
685        .and_then(|elem_ty| type_layout(tcx, caller, elem_ty).map(|(_, s)| s))
686        .unwrap_or(1)
687}
688
689// ── Constant scalar / byte-string extraction ───────────────────
690
691/// Parse an integer from a MIR constant's `Debug` text. Handles decimal,
692/// `0x` hex, and `Value(...)` forms.
693pub fn const_int_from_debug(text: &str) -> Option<u64> {
694    if let Ok(v) = text.parse::<u64>() {
695        return Some(v);
696    }
697    if let Some(start) = text.find("0x") {
698        let hex_part = &text[start..];
699        let end = hex_part
700            .find(|c: char| !c.is_ascii_hexdigit() && c != 'x')
701            .unwrap_or(hex_part.len());
702        u64::from_str_radix(&hex_part[2..end], 16).ok()
703    } else if let Some(start) = text.find("Value(") {
704        let inner = &text[start + 6..];
705        if let Some(end) = inner.find(')') {
706            inner[..end].parse::<u64>().ok()
707        } else {
708            None
709        }
710    } else {
711        None
712    }
713}
714
715/// Resolve a MIR constant to a concrete integer, falling back from the cheap
716/// debug-text parse to full const evaluation.
717///
718/// Layout constants (`offset_of!(Container, field)`) and the `T::{BITS,MAX,MIN}`
719/// associated constants of *small* integer types (`u8`..`u32`, `i8`..`i32`) are
720/// evaluated here.  Arbitrary unevaluated consts — and the wide bounds
721/// `usize::MAX` / `u64::MAX` / `u128::MAX` — are deliberately left symbolic:
722/// forcing them to a concrete `u64` would overflow downstream size arithmetic.
723pub fn const_scalar_int<'tcx>(
724    tcx: TyCtxt<'tcx>,
725    constant: &rustc_middle::mir::Const<'tcx>,
726    text: &str,
727) -> Option<i128> {
728    if let Some(v) = const_int_from_debug(text) {
729        return Some(v as i128);
730    }
731    // Resolve `T::{BITS,MAX,MIN}` associated constants of small integer types,
732    // used in numeric bounds (`u32::MAX`) and shift-width masks (`u32::BITS`).
733    let is_num_bound =
734        text.contains("::BITS") || text.contains("::MAX") || text.contains("::MIN");
735    if !is_num_bound && offset_of_container(tcx, constant).is_none() {
736        return None;
737    }
738    let typing_env = TypingEnv::fully_monomorphized();
739    let val = constant.eval(tcx, typing_env, rustc_span::DUMMY_SP).ok()?;
740    let scalar = val.try_to_scalar_int()?;
741    let bits = scalar.size().bits() as u32;
742    let raw = scalar.to_bits(scalar.size()) as i128;
743    // Keep wide bounds (`u64::MAX`, `usize::MAX`, `u128::MAX`) symbolic so
744    // they don't overflow downstream size arithmetic.
745    if raw > u32::MAX as i128 {
746        return None;
747    }
748    // Sign-extend signed integer constants (e.g. `i32::MIN` == -2147483648).
749    let ty = constant.ty();
750    if let TyKind::Int(_) = ty.kind() {
751        let sign = 1i128 << (bits - 1);
752        if raw >= sign {
753            Some(raw - (1i128 << bits))
754        } else {
755            Some(raw)
756        }
757    } else {
758        Some(raw)
759    }
760}
761
762/// Try to extract raw bytes from a MIR constant operand that is a reference
763/// to a byte array/slice (e.g. `b"hello\0"`). Returns the byte values.
764/// Used by the VM to populate byte-level tracking for constant C strings.
765pub fn extract_const_bytes_from_operand<'tcx>(
766    tcx: TyCtxt<'tcx>,
767    operand: &Operand<'tcx>,
768) -> Option<Vec<u8>> {
769    let constant = match operand {
770        Operand::Constant(c) => c,
771        _ => return None,
772    };
773    let ty = constant.const_.ty();
774    let (inner_ty, _is_ref) = match ty.kind() {
775        TyKind::Ref(_, inner, _) => (*inner, true),
776        _ => return None,
777    };
778    // Peel through nested references (e.g. &&[u8])
779    let inner_ty = if let TyKind::Ref(_, innermost, _) = inner_ty.kind() {
780        *innermost
781    } else {
782        inner_ty
783    };
784    let _elem_ty = match inner_ty.kind() {
785        TyKind::Array(elem, _) | TyKind::Slice(elem) => *elem,
786        _ => return None,
787    };
788
789    // Evaluate the MIR constant to get a ConstValue
790    let typing_env = TypingEnv::fully_monomorphized();
791    let value = constant
792        .const_
793        .eval(tcx, typing_env, rustc_span::DUMMY_SP)
794        .ok()?;
795
796    const_value_bytes(tcx, value, 0)
797}
798
799/// Extract the bare local from a Copy/Move operand with no projection.
800pub fn extract_local(operand: &Operand<'_>) -> Option<Local> {
801    match operand {
802        Operand::Copy(place) | Operand::Move(place)
803            if place.projection.is_empty() => Some(place.local),
804        _ => None,
805    }
806}
807
808/// Extract a constant u64 value from an operand, if it's a known constant.
809pub fn extract_operand_const(operand: &Operand<'_>) -> Option<u64> {
810    match operand {
811        Operand::Constant(constant) => {
812            let text = format!("{:?}", constant.const_);
813            const_int_from_debug(&text)
814        }
815        _ => None,
816    }
817}
818
819/// Whether a type is a `u8` array (`[u8; N]`) or `u8` slice (`[u8]`).
820pub fn is_u8_array_or_slice(ty: Ty<'_>) -> bool {
821    match ty.kind() {
822        TyKind::Array(elem_ty, _) => {
823            matches!(elem_ty.kind(), TyKind::Uint(rustc_middle::ty::UintTy::U8))
824        }
825        TyKind::Slice(elem_ty) => {
826            matches!(elem_ty.kind(), TyKind::Uint(rustc_middle::ty::UintTy::U8))
827        }
828        _ => false,
829    }
830}
831
832/// Whether a type transitively contains a reference.
833pub fn type_contains_reference(ty: Ty<'_>) -> bool {
834    match ty.kind() {
835        TyKind::Ref(..) => true,
836        TyKind::Adt(_, substs) => substs.types().any(type_contains_reference),
837        _ => false,
838    }
839}
840
841/// Element type of a `Vec<T>`, if `ty` is a `Vec`.
842pub fn vec_elem_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
843    if let TyKind::Adt(adt_def, substs) = ty.kind() {
844        let name = tcx.def_path_str(adt_def.did());
845        if crate::helpers::api_classify::is_std_vec(&name) {
846            return substs.first().and_then(|s| s.as_type());
847        }
848    }
849    None
850}
851
852/// Max `size_of` over all implementors of a generic type parameter's trait
853/// bounds (0 for non-param types).
854pub fn size_of_generic_param<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> u64 {
855    match ty.kind() {
856        TyKind::Param(_) => {}
857        _ => return 0,
858    };
859    let param_env = tcx.param_env(caller);
860    let typing_env = TypingEnv::post_analysis(tcx, caller);
861    for clause in param_env.caller_bounds() {
862        let Some(trait_clause) = clause.as_trait_clause() else { continue };
863        let self_ty = trait_clause.self_ty().skip_binder();
864        if self_ty != ty {
865            continue;
866        }
867        let trait_def_id = trait_clause.def_id();
868        let mut max_size: u64 = 0;
869        for impl_def_id in tcx.all_impls(trait_def_id) {
870            let impl_ty = tcx.type_of(impl_def_id).skip_binder();
871            if ty_has_param_const(impl_ty) {
872                continue;
873            }
874            let layout = match catch_panic(|| {
875                tcx.layout_of(PseudoCanonicalInput { typing_env, value: impl_ty })
876            }) {
877                Ok(Ok(l)) => l,
878                _ => continue,
879            };
880            max_size = max_size.max(layout.size.bytes());
881        }
882        return max_size;
883    }
884    0
885}
886
887/// Min `align_of` over all implementors of a generic type parameter's trait
888/// bounds (0 for non-param types).
889pub fn min_align_of_generic_param<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> u64 {
890    match ty.kind() {
891        TyKind::Param(_) => {}
892        _ => return 0,
893    };
894    let param_env = tcx.param_env(caller);
895    let typing_env = TypingEnv::post_analysis(tcx, caller);
896    for clause in param_env.caller_bounds() {
897        let Some(trait_clause) = clause.as_trait_clause() else { continue };
898        let self_ty = trait_clause.self_ty().skip_binder();
899        if self_ty != ty {
900            continue;
901        }
902        let trait_def_id = trait_clause.def_id();
903        let mut min_align: u64 = u64::MAX;
904        for impl_def_id in tcx.all_impls(trait_def_id) {
905            let impl_ty = tcx.type_of(impl_def_id).skip_binder();
906            if ty_has_param_const(impl_ty) {
907                continue;
908            }
909            let layout = match catch_panic(|| {
910                tcx.layout_of(PseudoCanonicalInput { typing_env, value: impl_ty })
911            }) {
912                Ok(Ok(l)) => l,
913                _ => continue,
914            };
915            min_align = min_align.min(layout.align.abi.bytes());
916        }
917        return if min_align == u64::MAX { 0 } else { min_align };
918    }
919    0
920}