Skip to main content

rapx/helpers/
mir_utils.rs

1#[cfg(rapx_has_attr_ir)]
2use rustc_attr_ir::LangItem;
3#[cfg(all(not(rapx_has_attr_ir), not(rapx_ge_100)))]
4use rustc_hir::LangItem;
5#[cfg(all(not(rapx_has_attr_ir), rapx_ge_100))]
6use rustc_hir::attrs::lang_items::LangItem;
7use rustc_hir::{
8    ItemKind,
9    def_id::{DefId, LocalDefId},
10};
11#[cfg(rapx_const_ext)]
12use rustc_middle::ty::consts::ConstExt;
13use rustc_middle::{
14    mir::interpret::{AllocId, GlobalAlloc},
15    mir::{
16        BasicBlock, Body, ConstValue, Local, Operand, Place, Rvalue, StatementKind, SwitchTargets,
17        TerminatorKind,
18    },
19    ty::{
20        ConstKind, FieldDef, GenericArgKind, GenericArgsRef, PseudoCanonicalInput, Ty, TyCtxt,
21        TyKind, TypingEnv,
22    },
23};
24use rustc_span::{DUMMY_SP, Symbol};
25
26use std::collections::HashSet;
27
28#[cfg(not(rapx_has_skip_norm_wip))]
29use crate::compat::SkipNormWip;
30
31use crate::{compat::FxHashMap, helpers::mir_scan::Checkpoint};
32
33use super::def_use::PlaceKey;
34
35pub(crate) fn pointee_ty<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
36    match ty.kind() {
37        TyKind::RawPtr(ty, _) | TyKind::Ref(_, ty, _) => Some(*ty),
38        // `NonNull<T>` is a raw-pointer wrapper; unwrap it so pointer-arith
39        // effects (`NonNull::add`/`sub`/`offset`) keep their provenance.
40        TyKind::Adt(adt, args) if crate::def_id::nonnull_types().contains(&adt.did()) => {
41            args.types().next()
42        }
43        _ => None,
44    }
45}
46
47pub(crate) fn dep_callee_def_id(func: &Operand<'_>) -> Option<DefId> {
48    let Operand::Constant(c) = func else {
49        return None;
50    };
51    let TyKind::FnDef(def_id, _) = c.const_.ty().kind() else {
52        return None;
53    };
54    Some(*def_id)
55}
56
57/// Whether `func` is a call to `PartialEq::eq` (the equality comparison),
58/// determined from its `DefId` rather than by string-matching the callee path.
59pub(crate) fn is_eq_call(tcx: TyCtxt<'_>, func: &Operand<'_>) -> bool {
60    let Some(def_id) = dep_callee_def_id(func) else {
61        return false;
62    };
63    let Some(assoc) = tcx.opt_associated_item(def_id) else {
64        return false;
65    };
66    if assoc.name().as_str() != "eq" {
67        return false;
68    }
69    // Must be a trait method (`PartialEq::eq`), not an inherent `eq`.
70    let Some(trait_id) = assoc.trait_container(tcx) else {
71        return false;
72    };
73    tcx.def_path_str(trait_id).ends_with("PartialEq")
74}
75
76/// Whether `def_id` is `core::ptr::drop_in_place`.
77pub(crate) fn is_drop_in_place(def_id: DefId) -> bool {
78    crate::def_id::drop_in_place() == Some(def_id)
79}
80
81/// Whether `def_id` is a diverging call target: a `panic*` lang item or the
82/// `unreachable`/`abort` intrinsics.
83fn is_diverging_call(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
84    tcx.is_lang_item(def_id, LangItem::Panic)
85        || tcx.is_lang_item(def_id, LangItem::PanicNounwind)
86        || tcx.is_lang_item(def_id, LangItem::PanicFmt)
87        || tcx.is_lang_item(def_id, LangItem::PanicDisplay)
88        || tcx.is_lang_item(def_id, LangItem::ConstPanicFmt)
89        || tcx.is_lang_item(def_id, LangItem::PanicBoundsCheck)
90        || tcx.is_lang_item(def_id, LangItem::PanicMisalignedPointerDereference)
91        || tcx.intrinsic(def_id).is_some_and(|i| {
92            i.name == rustc_span::sym::unreachable || i.name == rustc_span::sym::abort
93        })
94}
95
96/// Whether a `SwitchInt`'s non-`otherwise` targets all lead straight to
97/// `panic`/`unreachable` (a `debug_assert!`/`assert!` dispatch).  Such a switch
98/// is dead on the normal path and can be inlined by following only the
99/// `otherwise` edge.
100pub(crate) fn switch_targets_unreachable<'tcx>(
101    tcx: TyCtxt<'tcx>,
102    body: &Body<'tcx>,
103    targets: &SwitchTargets,
104) -> bool {
105    targets.iter().all(|(_, target)| {
106        let mut cur = target;
107        let mut seen = HashSet::new();
108        loop {
109            if !seen.insert(cur) {
110                return false;
111            }
112            let bb = &body.basic_blocks[cur];
113            let term = bb.terminator();
114            match &term.kind {
115                TerminatorKind::Unreachable => return true,
116                TerminatorKind::Call { func, .. } => {
117                    let Some(callee) = dep_callee_def_id(func) else {
118                        return false;
119                    };
120                    return is_diverging_call(tcx, callee);
121                }
122                TerminatorKind::Goto { target: next } => {
123                    cur = *next;
124                }
125                // A bare `return` with no statements is a drop-flag skip (dead
126                // on the normal path); a `return` preceded by real statements
127                // computes a different value, so it is a semantic branch and
128                // must not be ignored.
129                TerminatorKind::Return => return bb.statements.is_empty(),
130                _ => return false,
131            }
132        }
133    })
134}
135
136/// Whether a block's `SwitchInt` is a `debug_assert!`-style dispatch (all
137/// non-`otherwise` targets are `panic`/`unreachable`), or has a constant /
138/// runtime-check discriminant that folds to a single live edge.  Such a switch
139/// is dead on the normal path and is safe to ignore when inlining.
140pub(crate) fn switch_is_debug_assert<'tcx>(
141    tcx: TyCtxt<'tcx>,
142    body: &Body<'tcx>,
143    bb: BasicBlock,
144) -> bool {
145    let TerminatorKind::SwitchInt { discr, targets } = &body.basic_blocks[bb].terminator().kind
146    else {
147        return false;
148    };
149    // A constant discriminant (e.g. `_3 = const true` for a no-drop flag) folds
150    // to a single live edge; the other edges are dead and can be ignored when
151    // inlining.  This includes a `move _3` whose `_3` is assigned a constant
152    // earlier in the body.
153    let discr_is_const = match discr {
154        Operand::Constant(_) => true,
155        // `ub_checks` lowers to `Operand::RuntimeChecks` on newer rustc: it is a
156        // compile-time runtime-check flag, not a semantic branch, so it can be
157        // folded to the no-check edge when inlining (mirroring
158        // `rvalue_runtime_checks_value` below).
159        #[cfg(rapx_ge_95)]
160        Operand::RuntimeChecks(_) => true,
161        Operand::Copy(p) | Operand::Move(p) => {
162            body.basic_blocks.iter().any(|bbd| {
163                bbd.statements.iter().any(|stmt| {
164                    let StatementKind::Assign(assign) = &stmt.kind else {
165                        return false;
166                    };
167                    let (dest, rvalue) = &**assign;
168                    if dest != p {
169                        return false;
170                    }
171                    match rvalue {
172                        #[cfg(rapx_rvalue_use_with_retag)]
173                        Rvalue::Use(Operand::Constant(_), _) => true,
174                        #[cfg(not(rapx_rvalue_use_with_retag))]
175                        Rvalue::Use(Operand::Constant(_)) => true,
176                        _ => rvalue_runtime_checks_value(rvalue).is_some(),
177                    }
178                })
179            })
180        }
181        #[allow(unreachable_patterns)]
182        _ => false,
183    };
184    if discr_is_const {
185        return true;
186    }
187    switch_targets_unreachable(tcx, body, targets)
188}
189
190/// Resolve a `cfg!`-style runtime-check flag (`UbChecks`, `ContractChecks`,
191/// `OverflowChecks`) to a constant `u64`. We fold to the *no-check* edge (`0`):
192/// the check only panics on a violated precondition, and its branchy body would
193/// otherwise corrupt field/Typed propagation during inlining. Older rustc lowers
194/// these to `Rvalue::NullaryOp(NullOp::RuntimeChecks)`; newer rustc lowers them
195/// to `Operand::RuntimeChecks`.
196pub(crate) fn rvalue_runtime_checks_value(rvalue: &Rvalue<'_>) -> Option<u64> {
197    #[cfg(rapx_rvalue_has_nullary_op)]
198    {
199        if let Rvalue::NullaryOp(rustc_middle::mir::NullOp::RuntimeChecks(_)) = rvalue {
200            return Some(0);
201        }
202    }
203    #[cfg(not(rapx_rvalue_has_nullary_op))]
204    {
205        #[cfg(rapx_rvalue_use_with_retag)]
206        if let Rvalue::Use(Operand::RuntimeChecks(_), _) = rvalue {
207            return Some(0);
208        }
209        #[cfg(not(rapx_rvalue_use_with_retag))]
210        if let Rvalue::Use(Operand::RuntimeChecks(_)) = rvalue {
211            return Some(0);
212        }
213    }
214    None
215}
216
217/// The concrete `core::ops::Range*` struct a `DefId` denotes.
218pub(crate) enum RangeKind {
219    RangeTo,
220    RangeFrom,
221    Range,
222    RangeInclusive,
223    Other,
224}
225
226/// Classify a `DefId` as one of the `core::ops::Range*` structs.
227pub(crate) fn range_kind(tcx: TyCtxt<'_>, def_id: DefId) -> RangeKind {
228    if tcx.is_lang_item(def_id, LangItem::RangeTo) {
229        RangeKind::RangeTo
230    } else if tcx.is_lang_item(def_id, LangItem::RangeFrom) {
231        RangeKind::RangeFrom
232    } else if tcx.is_lang_item(def_id, LangItem::RangeInclusiveStruct) {
233        RangeKind::RangeInclusive
234    } else if tcx.is_lang_item(def_id, LangItem::Range) {
235        RangeKind::Range
236    } else {
237        RangeKind::Other
238    }
239}
240
241/// Whether `def_id` is any `core::ops::Range*` struct.
242pub(crate) fn is_range_type(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
243    !matches!(range_kind(tcx, def_id), RangeKind::Other)
244        || tcx.is_lang_item(def_id, LangItem::RangeToInclusive)
245        || tcx.is_lang_item(def_id, LangItem::RangeFull)
246}
247
248/// Whether `def_id` is the `Index::index` / `IndexMut::index_mut` trait method.
249pub(crate) fn is_index_method(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
250    let Some(assoc) = tcx.opt_associated_item(def_id) else {
251        return false;
252    };
253    let name = assoc.name();
254    if name.as_str() != "index" && name.as_str() != "index_mut" {
255        return false;
256    }
257    let Some(trait_id) = assoc.trait_container(tcx) else {
258        return false;
259    };
260    (tcx.is_lang_item(trait_id, LangItem::Index) && name.as_str() == "index")
261        || (tcx.is_lang_item(trait_id, LangItem::IndexMut) && name.as_str() == "index_mut")
262}
263
264/// Whether `def_id` is `slice::Iter`/`IterMut`'s private `post_inc_start`
265/// helper (a pointer-advancing side effect that cannot be inlined because of
266/// its ZST `SwitchInt` branch).
267pub(crate) fn is_post_inc_start(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
268    let name = tcx.item_name(def_id);
269    name.as_str() == "post_inc_start"
270}
271
272/// Whether `def_id` is `pre_dec_end` (the end-decrementing sibling of
273/// `post_inc_start`).
274pub(crate) fn is_pre_dec_end(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
275    let name = tcx.item_name(def_id);
276    name.as_str() == "pre_dec_end"
277}
278
279/// Whether `def_id` is one of `post_inc_start` / `pre_dec_end`.
280pub(crate) fn is_iter_ptr_adj(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
281    let name = tcx.item_name(def_id);
282    let n = name.as_str();
283    n == "post_inc_start" || n == "pre_dec_end"
284}
285
286/// Resolve a (possibly trait-method) callee to the concrete impl method that
287/// will actually be dispatched, given the caller context and the callee's
288/// generic arguments. Returns `None` when the callee cannot be resolved to a
289/// distinct concrete item (e.g. still generic/virtual), or is not a trait
290/// method at all (callers should then keep the original DefId).
291pub(crate) fn resolve_callee_impl<'tcx>(
292    tcx: TyCtxt<'tcx>,
293    caller_def_id: DefId,
294    callee_def_id: DefId,
295    callee_args: GenericArgsRef<'tcx>,
296) -> Option<DefId> {
297    let assoc = tcx.opt_associated_item(callee_def_id)?;
298    if assoc.trait_container(tcx).is_none() {
299        return None;
300    }
301    let typing_env = TypingEnv::post_analysis(tcx, caller_def_id);
302    let instance =
303        rustc_middle::ty::Instance::try_resolve(tcx, typing_env, callee_def_id, callee_args)
304            .ok()
305            .flatten()?;
306    let resolved = match instance.def {
307        rustc_middle::ty::InstanceKind::Item(def_id) => def_id,
308        _ => return None,
309    };
310    if resolved == callee_def_id {
311        None
312    } else {
313        Some(resolved)
314    }
315}
316
317/// Like [`dep_callee_def_id`], but resolves trait-method callees to their
318/// concrete impl so cross-crate `Deref`/`DerefMut` bodies — whose trait-method
319/// DefId has no available MIR — can still be inlined.
320pub(crate) fn dep_callee_resolved_def_id<'tcx>(
321    tcx: TyCtxt<'tcx>,
322    caller: DefId,
323    func: &Operand<'tcx>,
324) -> Option<DefId> {
325    let Operand::Constant(c) = func else {
326        return None;
327    };
328    let TyKind::FnDef(def_id, callee_args) = c.const_.ty().kind() else {
329        return None;
330    };
331    let callee_def_id = *def_id;
332    #[cfg(rapx_ge_99)]
333    let callee_args = callee_args.skip_binder();
334    resolve_callee_impl(tcx, caller, callee_def_id, callee_args).or(Some(callee_def_id))
335}
336
337/// Collect all return basic block indices for a function body.
338pub fn collect_return_block_indices(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<BasicBlock> {
339    let mut blocks = Vec::new();
340    if !tcx.is_mir_available(def_id) {
341        return blocks;
342    }
343    let body = tcx.optimized_mir(def_id);
344    for (bb, data) in body.basic_blocks.iter_enumerated() {
345        if matches!(data.terminator().kind, TerminatorKind::Return) {
346            blocks.push(bb);
347        }
348    }
349    blocks
350}
351
352/// Whether `block` ends in a `Return` terminator (a normal exit point).
353pub fn is_return_block(tcx: TyCtxt<'_>, def_id: DefId, block: BasicBlock) -> bool {
354    if !tcx.is_mir_available(def_id) {
355        return false;
356    }
357    let body = tcx.optimized_mir(def_id);
358    body.basic_blocks
359        .get(block)
360        .is_some_and(|data| matches!(data.terminator().kind, TerminatorKind::Return))
361}
362
363/// Return the callee argument index represented by a MIR local.
364///
365/// Contract annotations written with parameter names are parsed in the callee's
366/// local namespace.  MIR local `_0` is the return place and argument locals are
367/// `_1..=_arg_count`, so callee local `_1` denotes checkpoint argument `0`.
368pub fn callee_param_index_for_local(tcx: TyCtxt<'_>, callee: DefId, local: usize) -> Option<usize> {
369    let arg_count = if tcx.is_mir_available(callee) {
370        tcx.optimized_mir(callee).arg_count
371    } else {
372        tcx.fn_sig(callee)
373            .skip_binder()
374            .inputs()
375            .skip_binder()
376            .len()
377    };
378    arg_of_local(Local::from_usize(local), arg_count)
379}
380
381pub fn is_std_crate_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
382    matches!(
383        tcx.crate_name(def_id.krate).as_str(),
384        "core" | "std" | "alloc"
385    )
386}
387
388pub fn is_trait_unsafe(tcx: TyCtxt<'_>, trait_def_id: DefId) -> bool {
389    let Some(local_id) = trait_def_id.as_local() else {
390        return false;
391    };
392    let item = tcx.hir_expect_item(local_id);
393
394    #[cfg(not(rapx_ge_99))]
395    if let ItemKind::Trait(_, _, unsafety, _, _, _, _) = &item.kind {
396        return matches!(unsafety, rustc_hir::Safety::Unsafe);
397    }
398    #[cfg(rapx_ge_99)]
399    if let ItemKind::Trait { safety, .. } = &item.kind {
400        return matches!(safety, rustc_hir::Safety::Unsafe);
401    }
402
403    false
404}
405
406pub fn resolve_impl_self_ty_def_id(item: &rustc_hir::Item<'_>) -> Option<DefId> {
407    let ItemKind::Impl(rustc_hir::Impl { self_ty, .. }) = &item.kind else {
408        return None;
409    };
410    match &self_ty.kind {
411        rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) => match path.res {
412            rustc_hir::def::Res::Def(
413                rustc_hir::def::DefKind::Struct
414                | rustc_hir::def::DefKind::Enum
415                | rustc_hir::def::DefKind::Union,
416                def_id,
417            ) => Some(def_id),
418            _ => None,
419        },
420        _ => None,
421    }
422}
423
424/// Whether a local item carries a `#[rapx::<name>(...)]` attribute.
425pub(crate) fn has_rapx_attr(tcx: TyCtxt<'_>, def_id: LocalDefId, name: Symbol) -> bool {
426    let hir_id = tcx.local_def_id_to_hir_id(def_id);
427
428    let rapx = Symbol::intern("rapx");
429    let attrs = tcx.hir_attrs(hir_id);
430
431    attrs.iter().any(|attr| {
432        if attr.is_doc_comment().is_some() {
433            return false;
434        }
435
436        let path = attr.path();
437
438        path.len() == 2 && path[0] == rapx && path[1] == name
439    })
440}
441
442pub fn has_rapx_verify_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
443    has_rapx_attr(tcx, def_id, Symbol::intern("verify"))
444}
445
446/// True when a type transitively contains a const-generic parameter or
447/// an associated type alias (which may be layout-ambiguous).
448fn ty_has_param_const(ty: Ty<'_>) -> bool {
449    for arg in ty.walk() {
450        match arg.kind() {
451            GenericArgKind::Const(c) if matches!(c.kind(), ConstKind::Param(_)) => return true,
452            GenericArgKind::Type(inner_ty) if matches!(inner_ty.kind(), TyKind::Alias(..)) => {
453                return true;
454            }
455            _ => {}
456        }
457    }
458    false
459}
460
461/// Run `f` inside `catch_unwind`, returning either the result or the
462/// downcasted panic message.
463pub(crate) fn catch_panic<T>(f: impl FnOnce() -> T) -> Result<T, String> {
464    std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_err(|e| {
465        e.downcast_ref::<String>()
466            .cloned()
467            .or_else(|| e.downcast_ref::<&str>().map(|s| s.to_string()))
468            .unwrap_or_else(|| "<rustc ICE>".to_string())
469    })
470}
471
472/// Return a stable, human-readable name for a MIR call operand.
473pub fn call_name(tcx: TyCtxt<'_>, func: &Operand<'_>) -> String {
474    dep_callee_def_id(func)
475        .map(|def_id| tcx.def_path_str(def_id))
476        .unwrap_or_else(|| format!("{func:?}"))
477}
478
479/// Return the zero-based argument index of `local`, if it is a MIR argument.
480///
481/// MIR local `_0` is the return place; argument locals start at `_1`.
482pub fn arg_of_local(local: Local, arg_count: usize) -> Option<usize> {
483    let i = local.as_usize();
484    if i >= 1 && i <= arg_count {
485        Some(i - 1)
486    } else {
487        None
488    }
489}
490
491pub fn has_crate(tcx: TyCtxt<'_>, name: &str) -> bool {
492    for num in tcx.crates(()) {
493        if tcx.crate_name(*num) == Symbol::intern(name) {
494            return true;
495        }
496    }
497    false
498}
499
500/// Extracts the source `Place` from an rvalue for simple forwarding operations
501/// (copy, move, cast, reference, raw-pointer, copy-for-deref).
502pub fn rvalue_source_place<'a, 'tcx>(
503    rvalue: &'a Rvalue<'tcx>,
504) -> Option<&'a rustc_middle::mir::Place<'tcx>> {
505    use rustc_middle::mir::{Operand, Rvalue};
506    match rvalue {
507        Rvalue::Use(Operand::Copy(place), ..)
508        | Rvalue::Use(Operand::Move(place), ..)
509        | Rvalue::Cast(_, Operand::Copy(place), _)
510        | Rvalue::Cast(_, Operand::Move(place), _)
511        | Rvalue::Ref(_, _, place)
512        | Rvalue::RawPtr(_, place)
513        | Rvalue::CopyForDeref(place) => Some(place),
514        _ => None,
515    }
516}
517
518// ── PlaceKey / operand utilities ─────────────────────────────────
519
520/// Extract a PlaceKey from a MIR operand.
521pub fn operand_place(operand: &Operand<'_>) -> Option<PlaceKey> {
522    operand_mir_place(operand).map(PlaceKey::from_mir_place)
523}
524
525/// Extract the MIR Place from an operand.
526pub fn operand_mir_place<'a, 'tcx>(operand: &'a Operand<'tcx>) -> Option<&'a Place<'tcx>> {
527    match operand {
528        Operand::Copy(place) | Operand::Move(place) => Some(place),
529        _ => None,
530    }
531}
532
533/// Return the destination local for a checkpoint's call or deref.
534pub fn call_destination<'tcx>(tcx: TyCtxt<'tcx>, checkpoint: &Checkpoint<'tcx>) -> Option<Local> {
535    if checkpoint.kind == crate::helpers::mir_scan::CheckpointKind::RawPtrDeref {
536        return checkpoint.destination;
537    }
538    let body = tcx.optimized_mir(checkpoint.caller);
539    let terminator = body.basic_blocks[checkpoint.block].terminator();
540    let TerminatorKind::Call { destination, .. } = &terminator.kind else {
541        return None;
542    };
543    Some(destination.local)
544}
545
546// ── Place resolution utilities ───────────────────────────────────
547
548// ── Block reachability ───────────────────────────────────────────
549
550/// Collect all basic blocks reachable after a call block's normal return, or —
551/// for a non-call block such as a raw-pointer deref statement — after the block
552/// itself (all of its successors).
553pub fn blocks_reachable_after_call(
554    tcx: TyCtxt<'_>,
555    caller: DefId,
556    call_block: BasicBlock,
557) -> HashSet<BasicBlock> {
558    let body = tcx.optimized_mir(caller);
559    let mut starts = Vec::new();
560    if let TerminatorKind::Call { target, .. } = &body.basic_blocks[call_block].terminator().kind
561        && let Some(target) = target
562    {
563        starts.push(*target);
564    } else {
565        starts.extend(body.basic_blocks[call_block].terminator().successors());
566    }
567
568    let mut seen = HashSet::new();
569    let mut stack = starts;
570    while let Some(block) = stack.pop() {
571        if !seen.insert(block) {
572            continue;
573        }
574        let terminator = body.basic_blocks[block].terminator();
575        for successor in terminator.successors() {
576            stack.push(successor);
577        }
578    }
579    seen
580}
581
582// ── MIR place alias mapping ──────────────────────────────────────
583
584// ── Rvalue place scanning ────────────────────────────────────────
585
586/// Check whether any MIR place used in an rvalue matches a predicate.
587pub fn rvalue_any_place_matching<'tcx>(
588    rvalue: &Rvalue<'tcx>,
589    pred: &mut impl FnMut(&Place<'tcx>) -> bool,
590) -> bool {
591    match rvalue {
592        Rvalue::Aggregate(_, operands) => operands.iter().any(|operand| match operand {
593            Operand::Copy(place) | Operand::Move(place) => pred(place),
594            Operand::Constant(_) => false,
595            #[cfg(rapx_ge_95)]
596            Operand::RuntimeChecks(_) => false,
597        }),
598        _ => rvalue_source_place(rvalue).is_some_and(pred),
599    }
600}
601
602// ── Pointer arithmetic origin tracing ────────────────────────────
603
604/// Resolve a `const` item (e.g. `const CAPACITY: usize = 2 * B - 1`) by name in
605/// the local crate to its evaluated unsigned-integer value, for use in the
606/// contract DSL (`ValidNum(len <= CAPACITY)`).
607pub(crate) fn resolve_const_item_value<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> Option<u128> {
608    for item_id in tcx.hir_crate_items(()).free_items() {
609        let def_id = item_id.owner_id.to_def_id();
610        let Some(item_name) = tcx.opt_item_name(def_id) else {
611            continue;
612        };
613        if item_name.as_str() != name {
614            continue;
615        }
616        if !matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::Const { .. }) {
617            continue;
618        }
619        let instance = rustc_middle::ty::Instance::mono(tcx, def_id);
620        let cid = rustc_middle::mir::interpret::GlobalId {
621            instance,
622            promoted: None,
623        };
624        let Ok(val) = tcx.const_eval_global_id(TypingEnv::fully_monomorphized(), cid, DUMMY_SP)
625        else {
626            continue;
627        };
628        let Some(scalar) = val.try_to_scalar_int() else {
629            continue;
630        };
631        return Some(scalar.to_bits(scalar.size()));
632    }
633    None
634}
635
636/// Extract raw bytes from a `ConstValue`, following reference indirection.
637fn const_value_bytes<'tcx>(tcx: TyCtxt<'tcx>, value: ConstValue, depth: usize) -> Option<Vec<u8>> {
638    if depth > 4 {
639        return None;
640    }
641    match value {
642        ConstValue::Slice { alloc_id, .. } => alloc_id_bytes(tcx, alloc_id, depth),
643        ConstValue::Scalar(scalar) => {
644            #[cfg(rapx_scalar_to_pointer_interp_result)]
645            let ptr = scalar.to_pointer(&tcx).discard_err()?;
646            #[cfg(not(rapx_scalar_to_pointer_interp_result))]
647            let ptr = scalar.to_pointer(&tcx);
648            let alloc_id = ptr.provenance?.alloc_id();
649            alloc_id_bytes(tcx, alloc_id, depth)
650        }
651        ConstValue::Indirect { alloc_id, .. } => alloc_id_bytes(tcx, alloc_id, depth),
652        _ => None,
653    }
654}
655
656/// Read bytes from a global allocation.
657fn alloc_id_bytes<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, depth: usize) -> Option<Vec<u8>> {
658    if depth > 4 {
659        return None;
660    }
661    let alloc = match tcx.global_alloc(alloc_id) {
662        GlobalAlloc::Memory(alloc) => alloc,
663        GlobalAlloc::Static(def_id) => tcx.eval_static_initializer(def_id).ok()?,
664        _ => return None,
665    };
666    let alloc = alloc.inner();
667    let provenance = alloc.provenance().ptrs();
668    if let Some((_, prov)) = provenance.iter().next() {
669        return alloc_id_bytes(tcx, prov.alloc_id(), depth + 1);
670    }
671    Some(
672        alloc
673            .inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len())
674            .to_vec(),
675    )
676}
677
678// ── Type layout helpers ───────────────────────────────────────────
679
680/// If `constant` is a promoted `offset_of!(Container, field)` constant (an
681/// unevaluated `Const` whose body is a call to the `offset_of` intrinsic),
682/// return the container type.
683///
684/// Used by the verifier to recognise `byte_add(offset_of!(Container, ..))` and
685/// prove the resulting pointer stays within the container allocation.
686pub(crate) fn offset_of_container<'tcx>(
687    tcx: TyCtxt<'tcx>,
688    constant: &rustc_middle::mir::Const<'tcx>,
689) -> Option<Ty<'tcx>> {
690    let rustc_middle::mir::Const::Unevaluated(uneval, _) = constant else {
691        return None;
692    };
693    // `mir_for_ctfe` only accepts const-like defs; unevaluated consts may also
694    // reference plain functions, so gate on the def kind first.
695    if !is_const_def_kind(tcx, uneval.def) {
696        return None;
697    }
698    // `mir_for_ctfe` panics for cross-crate constants (e.g. `char::MAX` from
699    // `core`), since it only serves local, CTFE-able definitions. `offset_of!`
700    // always expands to a local `AnonConst`, so rejecting external defs loses
701    // nothing but avoids the ICE.
702    if !uneval.def.is_local() {
703        return None;
704    }
705    let body = tcx.mir_for_ctfe(uneval.def);
706    for bb in body.basic_blocks.iter() {
707        if let Some(term) = &bb.terminator
708            && let TerminatorKind::Call { func, .. } = &term.kind
709            && let Some(ty) = offset_of_ty_from_func(tcx, func)
710        {
711            return Some(ty);
712        }
713    }
714    None
715}
716
717/// Whether a `DefId` is a const-like item that `mir_for_ctfe` accepts.
718fn is_const_def_kind(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
719    use rustc_hir::def::DefKind;
720    #[cfg(rapx_ge_99)]
721    let base = matches!(
722        tcx.def_kind(def_id),
723        DefKind::Const { .. }
724            | DefKind::Static { .. }
725            | DefKind::AssocConst { .. }
726            | DefKind::AnonConst
727    );
728    #[cfg(not(rapx_ge_99))]
729    let base = matches!(
730        tcx.def_kind(def_id),
731        DefKind::Const | DefKind::Static { .. } | DefKind::AssocConst | DefKind::AnonConst
732    );
733    #[cfg(rapx_ge_99)]
734    {
735        base
736    }
737    #[cfg(not(rapx_ge_99))]
738    {
739        base || matches!(tcx.def_kind(def_id), DefKind::InlineConst)
740    }
741}
742
743/// Extract the first `Type` generic argument of an `FnDef` call operand.
744///
745/// Many monomorphized std APIs have the interesting type (the receiver or
746/// container element) as their first generic argument.
747pub(crate) fn fn_def_first_type_arg<'tcx>(func: &Operand<'tcx>) -> Option<Ty<'tcx>> {
748    let Operand::Constant(c) = func else {
749        return None;
750    };
751    let TyKind::FnDef(_, args) = c.const_.ty().kind() else {
752        return None;
753    };
754    args.iter().find_map(|a| {
755        #[cfg(rapx_ge_99)]
756        let a = a.skip_binder();
757        match a.kind() {
758            GenericArgKind::Type(t) => Some(t),
759            _ => None,
760        }
761    })
762}
763
764fn offset_of_ty_from_func<'tcx>(tcx: TyCtxt<'tcx>, func: &Operand<'tcx>) -> Option<Ty<'tcx>> {
765    let Operand::Constant(c) = func else {
766        return None;
767    };
768    let TyKind::FnDef(def_id, _) = c.const_.ty().kind() else {
769        return None;
770    };
771    if !tcx.is_lang_item(*def_id, LangItem::OffsetOf) {
772        return None;
773    }
774    fn_def_first_type_arg(func)
775}
776
777pub fn type_layout<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> Option<(u64, u64)> {
778    if ty_has_param_const(ty) {
779        return None;
780    }
781    match layout_of_ty(tcx, caller, ty) {
782        Some(l) => Some((l.align.abi.bytes(), l.size.bytes())),
783        None if matches!(ty.kind(), TyKind::Param(_)) => Some((0, 0)),
784        None => None,
785    }
786}
787
788/// Compute the full type layout, catching rustc panics and layout errors.
789/// Shared by `type_layout` and the symbolic VM's size/align/field-offset
790/// queries so the `layout_of` call and its panic-guard live in one place.
791pub fn layout_of_ty<'tcx>(
792    tcx: TyCtxt<'tcx>,
793    caller: DefId,
794    ty: Ty<'tcx>,
795) -> Option<rustc_abi::TyAndLayout<'tcx, Ty<'tcx>>> {
796    let env = TypingEnv::post_analysis(tcx, caller);
797    catch_panic(|| {
798        tcx.layout_of(PseudoCanonicalInput {
799            typing_env: env,
800            value: ty,
801        })
802    })
803    .ok()
804    .and_then(|r| r.ok())
805}
806
807/// Byte offset of a struct field within its container type (0 on failure).
808pub fn field_offset_in_bytes<'tcx>(
809    tcx: TyCtxt<'tcx>,
810    caller: DefId,
811    ty: Ty<'tcx>,
812    field_idx: usize,
813) -> u64 {
814    let Some(layout) = layout_of_ty(tcx, caller, ty) else {
815        return 0;
816    };
817    if let rustc_abi::FieldsShape::Arbitrary { ref offsets, .. } = layout.fields {
818        let idx = rustc_abi::FieldIdx::from_usize(field_idx);
819        if idx.as_usize() < offsets.len() {
820            return offsets[idx].bytes();
821        }
822    }
823    0
824}
825
826pub fn destination_stride<'tcx>(
827    tcx: TyCtxt<'tcx>,
828    caller: DefId,
829    dest: Option<Local>,
830) -> Option<u64> {
831    let d = dest?;
832    let pointee = pointee_ty(tcx.optimized_mir(caller).local_decls[d].ty)?;
833    let (_, s) = type_layout(tcx, caller, pointee)?;
834    // A generic pointee (`T`) has no concrete size; signal "symbolic stride" by
835    // returning None so the VM supplies the shared `sizeof_T` constant instead
836    // of collapsing it to 0 (which would drop the element offset entirely).
837    if s == 0 && ty_has_type_param(pointee) {
838        return None;
839    }
840    Some(s)
841}
842
843/// Whether `ty` mentions a (type) generic parameter anywhere in its structure,
844/// e.g. `T`, `*mut T`, `Option<T>`.
845pub(crate) fn ty_has_type_param(ty: Ty<'_>) -> bool {
846    ty.walk().any(|t| {
847        matches!(
848            t.kind(),
849            GenericArgKind::Type(inner) if matches!(inner.kind(), TyKind::Param(_))
850        )
851    })
852}
853
854pub fn pointee_alignment<'tcx>(
855    tcx: TyCtxt<'tcx>,
856    caller: DefId,
857    dest: Option<Local>,
858) -> Option<(u64, String)> {
859    let d = dest?;
860    let ty = tcx.optimized_mir(caller).local_decls[d].ty;
861    let pointee = pointee_ty(ty).or(Some(ty))?;
862    if let Some((a, _)) = type_layout(tcx, caller, pointee) {
863        return Some((a, format!("{pointee:?}")));
864    }
865    if let TyKind::Array(e, _) = pointee.kind()
866        && let Some((a, _)) = type_layout(tcx, caller, *e)
867    {
868        return Some((a, format!("{pointee:?}")));
869    }
870    Some((0, format!("{pointee:?}")))
871}
872
873// ── Constant scalar / byte-string extraction ───────────────────
874
875/// Parse an integer from a MIR constant's `Debug` text. Handles decimal,
876/// `0x` hex, and `Value(...)` forms.
877pub fn const_int_from_debug(text: &str) -> Option<u64> {
878    if let Ok(v) = text.parse::<u64>() {
879        return Some(v);
880    }
881    if let Some(start) = text.find("0x") {
882        let hex_part = &text[start..];
883        let end = hex_part
884            .find(|c: char| !c.is_ascii_hexdigit() && c != 'x')
885            .unwrap_or(hex_part.len());
886        u64::from_str_radix(&hex_part[2..end], 16).ok()
887    } else if let Some(start) = text.find("Value(") {
888        let inner = &text[start + 6..];
889        if let Some(end) = inner.find(')') {
890            inner[..end].parse::<u64>().ok()
891        } else {
892            None
893        }
894    } else {
895        None
896    }
897}
898
899/// Resolve a MIR constant to a concrete integer, falling back from the cheap
900/// debug-text parse to full const evaluation.
901///
902/// Layout constants (`offset_of!(Container, field)`) and the `T::{BITS,MAX,MIN}`
903/// associated constants of *small* integer types (`u8`..`u32`, `i8`..`i32`) are
904/// evaluated here.  Arbitrary unevaluated consts — and the wide bounds
905/// `usize::MAX` / `u64::MAX` / `u128::MAX` — are deliberately left symbolic:
906/// forcing them to a concrete `u64` would overflow downstream size arithmetic.
907pub fn eval_const_scalar_int<'tcx>(
908    tcx: TyCtxt<'tcx>,
909    constant: &rustc_middle::mir::Const<'tcx>,
910    text: &str,
911) -> Option<i128> {
912    if let Some(v) = const_int_from_debug(text) {
913        return Some(v as i128);
914    }
915    // Resolve `T::{BITS,MAX,MIN}` associated constants of small integer types,
916    // used in numeric bounds (`u32::MAX`) and shift-width masks (`u32::BITS`).
917    let is_num_bound = text.contains("::BITS") || text.contains("::MAX") || text.contains("::MIN");
918    // An unevaluated `const` item (e.g. `const CAPACITY: usize = 2 * B - 1`)
919    // must be const-evaluated to its scalar value, so comparisons like
920    // `idx < CAPACITY` are modeled as `idx < 11` rather than an opaque symbol.
921    let is_unevaluated = matches!(*constant, rustc_middle::mir::Const::Unevaluated(..));
922    if !is_num_bound && !is_unevaluated && offset_of_container(tcx, constant).is_none() {
923        return None;
924    }
925    let typing_env = TypingEnv::fully_monomorphized();
926    let val = constant.eval(tcx, typing_env, rustc_span::DUMMY_SP).ok()?;
927    let scalar = val.try_to_scalar_int()?;
928    let bits = scalar.size().bits() as u32;
929    let raw = scalar.to_bits(scalar.size()) as i128;
930    // Keep wide bounds (`u64::MAX`, `usize::MAX`, `u128::MAX`) symbolic so
931    // they don't overflow downstream size arithmetic.
932    if raw > u32::MAX as i128 {
933        return None;
934    }
935    // Sign-extend signed integer constants (e.g. `i32::MIN` == -2147483648).
936    let ty = constant.ty();
937    if let TyKind::Int(_) = ty.kind() {
938        let sign = 1i128 << (bits - 1);
939        if raw >= sign {
940            Some(raw - (1i128 << bits))
941        } else {
942            Some(raw)
943        }
944    } else {
945        Some(raw)
946    }
947}
948
949/// Resolve an array length const (`ty::Const`) to a concrete `u64`. Unlike
950/// `try_to_target_usize`, this also evaluates unevaluated const expressions
951/// (e.g. `[MaybeUninit<K>; CAPACITY]` where `CAPACITY = 2 * B - 1`), so the
952/// VM can allocate the array with its true element count instead of a
953/// collapsed zero-size fallback.
954pub(crate) fn eval_array_len<'tcx>(
955    tcx: TyCtxt<'tcx>,
956    c: &rustc_middle::ty::Const<'tcx>,
957) -> Option<u64> {
958    if let Some(v) = c.try_to_target_usize(tcx) {
959        return Some(v);
960    }
961    // An unevaluated array length (`[MaybeUninit<K>; CAPACITY]` where
962    // `CAPACITY = 2 * B - 1`) is an anonymous const. Its `ConstKind`
963    // representation differs across toolchains: `Unevaluated` before the
964    // `Alias` rename (~2026-07), `Alias` afterwards.
965    #[cfg(rapx_constkind_alias)]
966    let def_id = {
967        let ConstKind::Alias(_, alias_const) = c.kind() else {
968            return None;
969        };
970        #[cfg(not(rapx_alias_const_inherent_self))]
971        let def_id = alias_const.kind.opt_def_id()?;
972        #[cfg(rapx_alias_const_inherent_self)]
973        let def_id = {
974            use rustc_middle::ty::AliasConstKind;
975            match alias_const.kind {
976                AliasConstKind::Projection { def_id } => def_id.into(),
977                AliasConstKind::InherentSelf { def_id } => def_id.into(),
978                AliasConstKind::InherentImpl { def_id } => def_id.into(),
979                AliasConstKind::Free { def_id } => def_id.into(),
980                AliasConstKind::Anon { def_id } => def_id.into(),
981            }
982        };
983        def_id
984    };
985    #[cfg(not(rapx_constkind_alias))]
986    let def_id = {
987        let ConstKind::Unevaluated(uneval) = c.kind() else {
988            return None;
989        };
990        uneval.def
991    };
992    let instance = rustc_middle::ty::Instance::mono(tcx, def_id);
993    let cid = rustc_middle::mir::interpret::GlobalId {
994        instance,
995        promoted: None,
996    };
997    if let Ok(val) = tcx.const_eval_global_id(TypingEnv::fully_monomorphized(), cid, DUMMY_SP) {
998        if let Some(scalar) = val.try_to_scalar_int() {
999            return Some(scalar.to_target_usize(tcx));
1000        }
1001    }
1002    None
1003}
1004
1005/// Try to extract raw bytes from a MIR constant operand that is a reference
1006/// to a byte array/slice (e.g. `b"hello\0"`). Returns the byte values.
1007/// Used by the VM to populate byte-level tracking for constant C strings.
1008pub fn const_operand_bytes<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>) -> Option<Vec<u8>> {
1009    let constant = match operand {
1010        Operand::Constant(c) => c,
1011        _ => return None,
1012    };
1013    let ty = constant.const_.ty();
1014    let inner_ty = match ty.kind() {
1015        TyKind::Ref(_, inner, _) => *inner,
1016        _ => return None,
1017    };
1018    // Peel through nested references (e.g. &&[u8])
1019    let inner_ty = if let TyKind::Ref(_, innermost, _) = inner_ty.kind() {
1020        *innermost
1021    } else {
1022        inner_ty
1023    };
1024    if !matches!(inner_ty.kind(), TyKind::Array(..) | TyKind::Slice(..)) {
1025        return None;
1026    }
1027
1028    // Evaluate the MIR constant to get a ConstValue
1029    let typing_env = TypingEnv::fully_monomorphized();
1030    let value = constant
1031        .const_
1032        .eval(tcx, typing_env, rustc_span::DUMMY_SP)
1033        .ok()?;
1034
1035    const_value_bytes(tcx, value, 0)
1036}
1037
1038/// Extract the bare local from a Copy/Move operand with no projection.
1039pub fn extract_local(operand: &Operand<'_>) -> Option<Local> {
1040    operand_mir_place(operand)
1041        .filter(|place| place.projection.is_empty())
1042        .map(|place| place.local)
1043}
1044
1045/// Extract a constant u64 value from an operand, if it's a known constant.
1046pub fn operand_const_u64(operand: &Operand<'_>) -> Option<u64> {
1047    operand_scalar_int(operand).map(|v| v as u64)
1048}
1049
1050/// Whether a type is a `u8` array (`[u8; N]`) or `u8` slice (`[u8]`).
1051pub fn is_u8_array_or_slice(ty: Ty<'_>) -> bool {
1052    match ty.kind() {
1053        TyKind::Array(elem_ty, _) => {
1054            matches!(elem_ty.kind(), TyKind::Uint(rustc_middle::ty::UintTy::U8))
1055        }
1056        TyKind::Slice(elem_ty) => {
1057            matches!(elem_ty.kind(), TyKind::Uint(rustc_middle::ty::UintTy::U8))
1058        }
1059        _ => false,
1060    }
1061}
1062
1063/// Whether a type transitively contains a reference.
1064///
1065/// This is a shallow check: it recurses only through `Adt` generic arguments,
1066/// not through tuple elements or `Adt` fields. See [`type_contains_raw_ptr`]
1067/// for a deeper check that also matches raw pointers.
1068pub fn type_contains_reference(ty: Ty<'_>) -> bool {
1069    match ty.kind() {
1070        TyKind::Ref(..) => true,
1071        TyKind::Adt(_, substs) => substs.types().any(type_contains_reference),
1072        _ => false,
1073    }
1074}
1075
1076/// Whether a type transitively contains a raw pointer, recursing through tuple
1077/// elements and `Adt` fields. Unlike [`type_contains_reference`], this does not
1078/// match references — a method returning `&*self.raw` re-borrows the pointee
1079/// (safe) rather than leaking the raw pointer value itself (unsafe).
1080pub fn type_contains_raw_ptr<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
1081    match ty.kind() {
1082        TyKind::RawPtr(_, _) => true,
1083        TyKind::Tuple(elems) => elems.iter().any(|t| type_contains_raw_ptr(tcx, t)),
1084        TyKind::Adt(def, args) => {
1085            if args.iter().any(|arg| {
1086                if let Some(t) = arg.as_type() {
1087                    type_contains_raw_ptr(tcx, t)
1088                } else {
1089                    false
1090                }
1091            }) {
1092                return true;
1093            }
1094            let adt = tcx.adt_def(def.did());
1095            adt.all_fields()
1096                .any(|field| type_contains_raw_ptr(tcx, field_ty(tcx, field, args)))
1097        }
1098        _ => false,
1099    }
1100}
1101
1102/// Resolve a struct field's type, normalizing where the rustc version requires it.
1103///
1104/// On newer rustc `field.ty(tcx, args)` returns an `Unnormalized<Ty>` that must
1105/// be `.skip_norm_wip()`-ed; on older toolchains the `SkipNormWip` shim makes
1106/// the same call a no-op, so this is version-independent.
1107pub fn field_ty<'tcx>(tcx: TyCtxt<'tcx>, field: &FieldDef, args: GenericArgsRef<'tcx>) -> Ty<'tcx> {
1108    field.ty(tcx, args).skip_norm_wip()
1109}
1110
1111/// Whether `def_id` is a single-field struct wrapping a raw pointer (i.e.
1112/// `NonNull`-shaped).  Used by alias/ownership reasoning to recognize pointer
1113/// wrappers — including local re-implementations — by their structure rather
1114/// than by a std `DefId`.
1115pub fn is_raw_ptr_wrapper<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
1116    let adt = tcx.adt_def(def_id);
1117    let variant = adt.non_enum_variant();
1118    if variant.fields.len() != 1 {
1119        return false;
1120    }
1121    let args = rustc_middle::ty::GenericArgs::identity_for_item(tcx, def_id);
1122    let field = variant.fields.iter().next().unwrap();
1123    let field_ty = field_ty(tcx, field, args);
1124    matches!(field_ty.kind(), TyKind::RawPtr(..))
1125}
1126
1127/// Collect the layouts of every concrete implementor of a generic type
1128/// parameter's trait bounds (empty for non-param types).
1129fn generic_param_impl_layouts<'tcx>(
1130    tcx: TyCtxt<'tcx>,
1131    caller: DefId,
1132    ty: Ty<'tcx>,
1133) -> Vec<rustc_abi::TyAndLayout<'tcx, Ty<'tcx>>> {
1134    if !matches!(ty.kind(), TyKind::Param(_)) {
1135        return Vec::new();
1136    }
1137    let param_env = tcx.param_env(caller);
1138    let typing_env = TypingEnv::post_analysis(tcx, caller);
1139    for clause in param_env.caller_bounds() {
1140        let Some(trait_clause) = clause.as_trait_clause() else {
1141            continue;
1142        };
1143        let self_ty = trait_clause.self_ty().skip_binder();
1144        if self_ty != ty {
1145            continue;
1146        }
1147        let mut layouts = Vec::new();
1148        for impl_def_id in tcx.all_impls(trait_clause.def_id()) {
1149            let impl_ty = tcx.type_of(impl_def_id).skip_binder();
1150            if ty_has_param_const(impl_ty) {
1151                continue;
1152            }
1153            let Ok(Ok(layout)) = catch_panic(|| {
1154                tcx.layout_of(PseudoCanonicalInput {
1155                    typing_env,
1156                    value: impl_ty,
1157                })
1158            }) else {
1159                continue;
1160            };
1161            layouts.push(layout);
1162        }
1163        return layouts;
1164    }
1165    Vec::new()
1166}
1167
1168/// Max `size_of` over all implementors of a generic type parameter's trait
1169/// bounds (0 for non-param types).
1170pub fn size_of_generic_param<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> u64 {
1171    generic_param_impl_layouts(tcx, caller, ty)
1172        .into_iter()
1173        .map(|l| l.size.bytes())
1174        .max()
1175        .unwrap_or(0)
1176}
1177
1178/// Min `align_of` over all implementors of a generic type parameter's trait
1179/// bounds (0 for non-param types).
1180pub fn min_align_of_generic_param<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> u64 {
1181    generic_param_impl_layouts(tcx, caller, ty)
1182        .into_iter()
1183        .map(|l| l.align.abi.bytes())
1184        .min()
1185        .unwrap_or(0)
1186}
1187
1188/// Max `align_of` over all implementors of a generic type parameter's trait
1189/// bounds (0 for non-param types or a parameter without a bounded set of
1190/// implementors).
1191pub fn max_align_of_generic_param<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> u64 {
1192    generic_param_impl_layouts(tcx, caller, ty)
1193        .into_iter()
1194        .map(|l| l.align.abi.bytes())
1195        .max()
1196        .unwrap_or(0)
1197}
1198
1199/// Follow a `parents` map (built by `verify::property_checker::cstr`'s
1200/// `body_parents`) from `start` to its root local, guarding against cycles.
1201pub fn follow_parents(parents: &FxHashMap<Local, Local>, start: Local) -> Local {
1202    let mut current = start;
1203    let mut seen = std::collections::HashSet::new();
1204    while seen.insert(current) {
1205        let Some(next) = parents.get(&current) else {
1206            break;
1207        };
1208        current = *next;
1209    }
1210    current
1211}
1212
1213/// Resolve a local through `Cast` assignments back to its non-cast source.
1214pub fn resolve_through_casts<'tcx>(body: &Body<'tcx>, local: Local) -> Local {
1215    let mut current = local;
1216    let mut seen = std::collections::HashSet::new();
1217    while seen.insert(current) {
1218        let found = body.basic_blocks.iter().any(|data| {
1219            data.statements.iter().any(|stmt| {
1220                let StatementKind::Assign(assign) = &stmt.kind else {
1221                    return false;
1222                };
1223                let (target, rvalue) = assign.as_ref();
1224                if target.local != current || !target.projection.is_empty() {
1225                    return false;
1226                }
1227                if let Rvalue::Cast(_, operand, _) = rvalue {
1228                    #[allow(unreachable_patterns)]
1229                    match operand {
1230                        Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => {
1231                            current = p.local;
1232                            return true;
1233                        }
1234                        _ => {}
1235                    }
1236                }
1237                false
1238            })
1239        });
1240        if !found {
1241            break;
1242        }
1243    }
1244    current
1245}
1246
1247// ── Constant byte recovery from MIR ─────────────────────────────
1248
1249fn operand_scalar_int(operand: &Operand<'_>) -> Option<u128> {
1250    let constant = match operand {
1251        Operand::Constant(c) => c,
1252        _ => return None,
1253    };
1254    constant
1255        .const_
1256        .try_to_scalar_int()
1257        .map(|s| s.to_uint(s.size()))
1258        .or_else(|| const_int_from_debug(&format!("{:?}", constant.const_)).map(|v| v as u128))
1259}
1260
1261fn is_as_ptr_or_as_method(name: &str) -> bool {
1262    name.contains("as_ptr") || name.contains("::as_")
1263}
1264
1265fn rvalue_const_bytes<'tcx>(tcx: TyCtxt<'tcx>, rvalue: &Rvalue<'tcx>) -> Option<Vec<u8>> {
1266    let constant = match rvalue {
1267        Rvalue::Use(Operand::Constant(constant), ..)
1268        | Rvalue::Cast(_, Operand::Constant(constant), _) => constant,
1269        _ => return None,
1270    };
1271    let value = constant
1272        .const_
1273        .eval(tcx, TypingEnv::fully_monomorphized(), DUMMY_SP)
1274        .ok()?;
1275    const_value_bytes(tcx, value, 0)
1276}
1277
1278pub fn collect_all_const_bytes_worklist<'tcx>(
1279    tcx: TyCtxt<'tcx>,
1280    body: &Body<'tcx>,
1281    root: Local,
1282) -> Vec<Vec<u8>> {
1283    let mut results: Vec<Vec<u8>> = Vec::new();
1284    let mut worklist: Vec<Local> = vec![root];
1285    let mut visited: std::collections::HashSet<Local> = std::collections::HashSet::new();
1286
1287    while let Some(local) = worklist.pop() {
1288        if !visited.insert(local) {
1289            continue;
1290        }
1291
1292        for data in body.basic_blocks.iter() {
1293            for statement in &data.statements {
1294                let StatementKind::Assign(assign) = &statement.kind else {
1295                    continue;
1296                };
1297                let (target, rvalue) = assign.as_ref();
1298                if target.local != local || !target.projection.is_empty() {
1299                    continue;
1300                }
1301
1302                if let Rvalue::Ref(_, _, place) = rvalue {
1303                    if let Some(bytes) = const_bytes_for_local(tcx, body, place.local) {
1304                        results.push(bytes);
1305                    }
1306                    continue;
1307                }
1308
1309                if let Rvalue::Use(operand, ..) = rvalue {
1310                    #[allow(unreachable_patterns)]
1311                    match operand {
1312                        Operand::Copy(p) | Operand::Move(p) => {
1313                            worklist.push(p.local);
1314                            if let Some(bytes) = const_bytes_for_local(tcx, body, p.local) {
1315                                results.push(bytes);
1316                            }
1317                            continue;
1318                        }
1319                        Operand::Constant(_) => {}
1320                        _ => continue,
1321                    }
1322                }
1323
1324                if let Some(bytes) = rvalue_const_bytes(tcx, rvalue) {
1325                    results.push(bytes);
1326                }
1327            }
1328        }
1329
1330        for data in body.basic_blocks.iter() {
1331            if let Some(terminator) = &data.terminator {
1332                if let TerminatorKind::Call {
1333                    destination,
1334                    func,
1335                    args,
1336                    ..
1337                } = &terminator.kind
1338                {
1339                    let dlocal = destination.local;
1340                    if dlocal != local {
1341                        continue;
1342                    }
1343                    if !destination.projection.is_empty() {
1344                        continue;
1345                    }
1346                    let name = call_name(tcx, func);
1347                    if is_as_ptr_or_as_method(&name) {
1348                        for arg in args {
1349                            if let Some(bytes) =
1350                                trace_const_bytes_from_operand(tcx, body, &arg.node)
1351                            {
1352                                results.push(bytes);
1353                            }
1354                        }
1355                    }
1356                    if name.contains("::add") {
1357                        if let Some(offset) = args.get(1).and_then(|a| operand_scalar_int(&a.node))
1358                        {
1359                            if let Some(base) = args.first() {
1360                                if let Some(bytes) =
1361                                    trace_const_bytes_from_operand(tcx, body, &base.node)
1362                                {
1363                                    let start = offset as usize;
1364                                    if start < bytes.len() {
1365                                        results.push(bytes[start..].to_vec());
1366                                    }
1367                                }
1368                            }
1369                        }
1370                    }
1371                    if name.contains("box_assume_init_into_vec_unsafe") {
1372                        if let Some(box_op) = args.first() {
1373                            if let Operand::Copy(p) | Operand::Move(p) = &box_op.node {
1374                                if p.projection.is_empty() {
1375                                    worklist.push(p.local);
1376                                }
1377                            }
1378                        }
1379                    }
1380                }
1381            }
1382        }
1383    }
1384
1385    collect_aggregate_const_bytes(tcx, body, &mut results);
1386    collect_as_ptr_const_bytes(tcx, body, &mut results);
1387
1388    results
1389}
1390
1391fn collect_aggregate_const_bytes<'tcx>(
1392    tcx: TyCtxt<'tcx>,
1393    body: &Body<'tcx>,
1394    results: &mut Vec<Vec<u8>>,
1395) {
1396    for data in body.basic_blocks.iter() {
1397        for statement in &data.statements {
1398            let StatementKind::Assign(assign) = &statement.kind else {
1399                continue;
1400            };
1401            let (_, rvalue) = assign.as_ref();
1402            let Rvalue::Aggregate(_, operands) = rvalue else {
1403                continue;
1404            };
1405            if operands.len() < 2 {
1406                continue;
1407            }
1408            let last_op = operands.iter().last().unwrap();
1409            if !is_constant_zero(last_op) {
1410                continue;
1411            }
1412            let mut all_nonzero = true;
1413            for op in operands.iter().take(operands.len() - 1) {
1414                if !aggregate_op_is_nonzero(tcx, body, op) {
1415                    all_nonzero = false;
1416                    break;
1417                }
1418            }
1419            if all_nonzero {
1420                let len = operands.len();
1421                let mut bytes = Vec::with_capacity(len);
1422                for _ in 0..len - 1 {
1423                    bytes.push(b'x');
1424                }
1425                bytes.push(0);
1426                results.push(bytes);
1427            }
1428        }
1429    }
1430}
1431
1432fn collect_as_ptr_const_bytes<'tcx>(
1433    tcx: TyCtxt<'tcx>,
1434    body: &Body<'tcx>,
1435    results: &mut Vec<Vec<u8>>,
1436) {
1437    for data in body.basic_blocks.iter() {
1438        if let Some(terminator) = &data.terminator {
1439            if let TerminatorKind::Call { func, args, .. } = &terminator.kind {
1440                let name = call_name(tcx, func);
1441                if is_as_ptr_or_as_method(&name) {
1442                    for arg in args {
1443                        if let Some(bytes) = trace_const_bytes_from_operand(tcx, body, &arg.node) {
1444                            results.push(bytes);
1445                        }
1446                    }
1447                }
1448            }
1449        }
1450    }
1451}
1452
1453fn trace_const_bytes_from_operand<'tcx>(
1454    tcx: TyCtxt<'tcx>,
1455    body: &Body<'tcx>,
1456    operand: &Operand<'tcx>,
1457) -> Option<Vec<u8>> {
1458    if let Some(bytes) = const_operand_bytes(tcx, operand) {
1459        return Some(bytes);
1460    }
1461    match operand {
1462        Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => {
1463            if let Some(bytes) = const_bytes_for_local(tcx, body, p.local) {
1464                return Some(bytes);
1465            }
1466            const_bytes_from_call_dest(tcx, body, p.local)
1467        }
1468        _ => None,
1469    }
1470}
1471
1472fn const_bytes_from_call_dest<'tcx>(
1473    tcx: TyCtxt<'tcx>,
1474    body: &Body<'tcx>,
1475    local: Local,
1476) -> Option<Vec<u8>> {
1477    for data in body.basic_blocks.iter() {
1478        if let Some(terminator) = &data.terminator {
1479            if let TerminatorKind::Call {
1480                destination,
1481                func,
1482                args,
1483                ..
1484            } = &terminator.kind
1485            {
1486                if destination.local != local || !destination.projection.is_empty() {
1487                    continue;
1488                }
1489                let name = call_name(tcx, func);
1490                if is_as_ptr_or_as_method(&name) {
1491                    for arg in args {
1492                        if let Some(bytes) = trace_const_bytes_from_operand(tcx, body, &arg.node) {
1493                            return Some(bytes);
1494                        }
1495                    }
1496                }
1497            }
1498        }
1499    }
1500    None
1501}
1502
1503pub fn const_bytes_for_local<'tcx>(
1504    tcx: TyCtxt<'tcx>,
1505    body: &Body<'tcx>,
1506    root: Local,
1507) -> Option<Vec<u8>> {
1508    for data in body.basic_blocks.iter() {
1509        for statement in &data.statements {
1510            let StatementKind::Assign(assign) = &statement.kind else {
1511                continue;
1512            };
1513            let (target, rvalue) = assign.as_ref();
1514            if target.local != root || !target.projection.is_empty() {
1515                continue;
1516            }
1517            if let Rvalue::Ref(_, _, place) = rvalue {
1518                let deref_local = place.local;
1519                if let Some(bytes) = const_bytes_for_local(tcx, body, deref_local) {
1520                    return Some(bytes);
1521                }
1522                continue;
1523            }
1524            if let Rvalue::Use(operand, ..) = rvalue {
1525                #[allow(unreachable_patterns)]
1526                match operand {
1527                    Operand::Copy(p) | Operand::Move(p) => {
1528                        if let Some(bytes) = const_bytes_for_local(tcx, body, p.local) {
1529                            return Some(bytes);
1530                        }
1531                        if let Some(bytes) = const_bytes_from_call_dest(tcx, body, p.local) {
1532                            return Some(bytes);
1533                        }
1534                        continue;
1535                    }
1536                    Operand::Constant(_) => {}
1537                    _ => continue,
1538                }
1539            }
1540            if let Rvalue::Cast(_, operand, _) = rvalue {
1541                if let Operand::Copy(p) | Operand::Move(p) = operand {
1542                    if p.projection.is_empty() {
1543                        if let Some(bytes) = const_bytes_for_local(tcx, body, p.local) {
1544                            return Some(bytes);
1545                        }
1546                    }
1547                }
1548                continue;
1549            }
1550            return rvalue_const_bytes(tcx, rvalue);
1551        }
1552    }
1553    None
1554}
1555
1556fn aggregate_op_is_nonzero<'tcx>(
1557    tcx: TyCtxt<'tcx>,
1558    body: &Body<'tcx>,
1559    operand: &Operand<'tcx>,
1560) -> bool {
1561    if is_constant_zero(operand) {
1562        return false;
1563    }
1564    if const_operand_bytes(tcx, operand).is_some() {
1565        return true;
1566    }
1567    match operand {
1568        Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => {
1569            for data in body.basic_blocks.iter() {
1570                if let Some(terminator) = &data.terminator {
1571                    if let TerminatorKind::Call {
1572                        destination, func, ..
1573                    } = &terminator.kind
1574                    {
1575                        if destination.local == p.local && destination.projection.is_empty() {
1576                            return fn_always_returns_nonzero(tcx, func);
1577                        }
1578                    }
1579                }
1580            }
1581            false
1582        }
1583        Operand::Constant(_) => operand_scalar_int(operand).is_some_and(|v| v != 0),
1584        _ => false,
1585    }
1586}
1587
1588fn is_constant_zero(operand: &Operand<'_>) -> bool {
1589    operand_scalar_int(operand) == Some(0)
1590}
1591
1592fn fn_always_returns_nonzero<'tcx>(tcx: TyCtxt<'tcx>, func: &Operand<'tcx>) -> bool {
1593    let Some(fn_def_id) = dep_callee_def_id(func) else {
1594        return false;
1595    };
1596    let callee_body = tcx.optimized_mir(fn_def_id);
1597
1598    let mut has_return = false;
1599    for bb_data in callee_body.basic_blocks.iter() {
1600        if let Some(terminator) = &bb_data.terminator {
1601            if matches!(terminator.kind, TerminatorKind::Return) {
1602                has_return = true;
1603            }
1604        }
1605        for stmt in &bb_data.statements {
1606            let StatementKind::Assign(assign) = &stmt.kind else {
1607                continue;
1608            };
1609            let (target, rvalue) = assign.as_ref();
1610            if target.local != Local::from_usize(0) || !target.projection.is_empty() {
1611                continue;
1612            }
1613            if !rvalue_is_nonzero(rvalue) {
1614                return false;
1615            }
1616        }
1617    }
1618
1619    has_return
1620}
1621
1622fn rvalue_is_nonzero(rvalue: &Rvalue<'_>) -> bool {
1623    match rvalue {
1624        #[allow(unreachable_patterns)]
1625        Rvalue::Use(operand, ..) => match operand {
1626            Operand::Constant(_) => operand_scalar_int(operand).is_some_and(|v| v != 0),
1627            Operand::Copy(_) | Operand::Move(_) => true,
1628            _ => false,
1629        },
1630        _ => false,
1631    }
1632}