Skip to main content

rapx/verify/vm/
alias.rs

1//! VM-specific alias origin tracing.
2//!
3//! Bridges `VmState` provenance tracking with the shared `alias_hazard`
4//! MIR scanning infrastructure. The VM already tracks which `AllocId`
5//! each local's value points to; this module traces that provenance
6//! back to the originating parameter/local.
7
8use rustc_hir::def_id::DefId;
9use rustc_middle::mir::{Local, Operand, ProjectionElem, Rvalue, StatementKind};
10use crate::verify::{
11    alias_hazard::{self, AliasProducer, HazardKind},
12    contract::Property,
13    def_use::PlaceKey,
14};
15use crate::helpers::mir_scan::Checkpoint;
16use crate::helpers::api_classify;
17use crate::analysis::alias::collect_local_origins;
18
19use super::state::{AllocId, VmState, VmValue};
20
21/// Information about a value's ultimate origin.
22#[derive(Clone, Debug)]
23pub struct VmOrigin {
24    /// The local (parameter or stack variable) that is the root source.
25    pub local: Local,
26    /// The allocation ID this pointer targets.
27    pub alloc_id: AllocId,
28    /// The type of the origin local (Ref/MutRef/RawPtr/Adt/...).
29    pub kind: VmOriginKind,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum VmOriginKind {
34    MutRef,
35    SharedRef,
36    RawMutPtr,
37    RawConstPtr,
38    Owned(DefId),
39    Unknown,
40}
41
42impl VmOrigin {
43    /// Whether this origin is a `&mut T` reference — safe to create a unique view from.
44    pub fn is_mut_ref(&self) -> bool {
45        matches!(self.kind, VmOriginKind::MutRef)
46    }
47
48    /// Whether this origin is a `&T` reference — safe to create a shared view from.
49    pub fn is_shared_ref(&self) -> bool {
50        matches!(self.kind, VmOriginKind::SharedRef)
51    }
52
53    /// Whether this origin is an owned type (Box, Vec) whose allocation was
54    /// transferred to this function.
55    pub fn is_owned(&self) -> bool {
56        matches!(self.kind, VmOriginKind::Owned(_))
57    }
58}
59
60impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
61    /// Trace the origin of a pointer value through VM provenance.
62    ///
63    /// Given a VmValue (extracted from a checkpoint argument), follows
64    /// its provenance back to determine where the allocation came from.
65    pub fn resolve_origin(&self, value: &VmValue<'ctx, 'tcx>) -> Option<VmOrigin> {
66        let Some(prov) = &value.provenance else {
67            return None;
68        };
69
70        let alloc_id = prov.alloc_id;
71
72        // Walk all locals to find which one(s) have the same provenance.
73        // Prefer parameters (arg_count) over temporaries.
74        let mut best: Option<VmOrigin> = None;
75
76        for (local, val) in &self.locals {
77            let Some(val_prov) = &val.provenance else {
78                continue;
79            };
80            if val_prov.alloc_id != alloc_id {
81                continue;
82            }
83
84            let kind = self.classify_local(local);
85            let candidate = VmOrigin {
86                local: *local,
87                alloc_id,
88                kind,
89            };
90
91            // Prefer parameter locals and owned origins (Box/Vec)
92            let is_param = local.as_usize() <= self.body.arg_count;
93            let is_owned = candidate.is_owned();
94
95            match &best {
96                None => best = Some(candidate),
97                Some(existing) => {
98                    let ex_is_param = existing.local.as_usize() <= self.body.arg_count;
99                    let ex_is_owned = existing.is_owned();
100                    // Parameters preferred over non-params
101                    if is_param && !ex_is_param {
102                        best = Some(candidate);
103                    } else if is_owned && !ex_is_owned {
104                        best = Some(candidate);
105                    } else if is_param == ex_is_param && is_owned == ex_is_owned {
106                        // If equal priority, prefer the one with lower local index
107                        if local.as_usize() < existing.local.as_usize() {
108                            best = Some(candidate);
109                        }
110                    }
111                }
112            }
113        }
114
115        best
116    }
117
118    /// Classify a local by its type.
119    fn classify_local(&self, local: &Local) -> VmOriginKind {
120        let ty = self.body.local_decls[*local].ty;
121        match ty.kind() {
122            rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Mut) => {
123                VmOriginKind::MutRef
124            }
125            rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not) => {
126                VmOriginKind::SharedRef
127            }
128            rustc_middle::ty::TyKind::RawPtr(inner_ty, rustc_middle::ty::Mutability::Mut) => {
129                let _ = inner_ty;
130                VmOriginKind::RawMutPtr
131            }
132            rustc_middle::ty::TyKind::RawPtr(..) => VmOriginKind::RawConstPtr,
133            rustc_middle::ty::TyKind::Adt(adt_def, _) => {
134                VmOriginKind::Owned(adt_def.did())
135            }
136            _ => VmOriginKind::Unknown,
137        }
138    }
139}
140
141// ── High-level VM alias check ────────────────────────────────────
142
143/// Result of the VM-based alias check.
144pub enum VmAliasResult {
145    Proved,
146    Failed(String),
147    Unknown,
148}
149
150/// Run the full alias hazard check for the VM backend.
151///
152/// This is the function the `PropertyChecker::check_alias` delegates to.
153pub fn check_alias_vm<'ctx, 'tcx>(
154    vm_state: &VmState<'ctx, 'tcx>,
155    checkpoint: &Checkpoint<'tcx>,
156    _property: &Property<'tcx>,
157) -> VmAliasResult {
158    let callee = match checkpoint.callee {
159        Some(c) => c,
160        // raw-ptr-deref / synthetic checkpoints: trace provenance to verify safety
161        None => {
162            let Some(origin_arg) = checkpoint.args.first() else {
163                return VmAliasResult::Unknown;
164            };
165            let origin_val = vm_state.value_of_operand(origin_arg);
166            if let Some(origin) = vm_state.resolve_origin(&origin_val) {
167                if origin.is_mut_ref() || origin.is_shared_ref() {
168                    return VmAliasResult::Proved;
169                }
170                if origin.is_owned() {
171                    return VmAliasResult::Proved;
172                }
173            }
174            // A raw-pointer deref in a method whose `self` is a *by-value*
175            // `NonNull<T>` is safe: consuming the `NonNull` transfers exclusive
176            // ownership of its pointer (e.g. `NonNull::as_uninit_mut(self)`).
177            if vm_state.body.arg_count >= 1 {
178                let self_ty = vm_state.body.local_decls[Local::from_usize(1)].ty;
179                if let rustc_middle::ty::TyKind::Adt(adt_def, _) = self_ty.kind() {
180                    if api_classify::is_std_nonnull(&vm_state.tcx.def_path_str(adt_def.did())) {
181                        return VmAliasResult::Proved;
182                    }
183                }
184            }
185            // Pointer has provenance: check if it's safe.
186            if let Some(prov) = &origin_val.provenance {
187                let is_external = vm_state.alloc(prov.alloc_id).is_external;
188                if !is_external {
189                    return VmAliasResult::Proved;
190                }
191                // External provenance: safe for shared ref, unsafe for mut ref.
192                let has_shared_ref = vm_state.body.local_decls.iter().any(|d| {
193                    matches!(d.ty.kind(), rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not))
194                });
195                if has_shared_ref {
196                    return VmAliasResult::Proved;
197                }
198            }
199            // Without provenance: fall back to any reference parameter.
200            if origin_val.provenance.is_none() {
201                for decl in &vm_state.body.local_decls {
202                    if matches!(decl.ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
203                        return VmAliasResult::Proved;
204                    }
205                }
206            }
207            // Field-type-aware check: if the raw-ptr-deref operand traces to a
208            // struct field and that field is a shared reference, the view is safe.
209            let tcx = vm_state.tcx;
210            let caller = checkpoint.caller;
211            let arg_place = alias_hazard::operand_mir_place(origin_arg)
212                .map(|p| PlaceKey::from_mir_place(p));
213            if let Some(mir_place) = arg_place {
214                let origin_map = collect_local_origins(tcx, caller);
215                let (root, fields) = alias_hazard::deep_resolve_place(
216                    mir_place.local().map(|l| l.as_usize()).unwrap_or(1),
217                    &origin_map,
218                );
219                if !fields.is_empty() {
220                    let resolved = PlaceKey::from_origin(root, fields);
221                    let sfo = alias_hazard::self_field_origin(tcx, caller, &resolved);
222                    if let Some(sfo) = sfo {
223                        if let Some(is_shared) = is_self_field_shared_ref(tcx, caller, &sfo) {
224                            if is_shared {
225                                return VmAliasResult::Proved;
226                            }
227                        }
228                    }
229                }
230            }
231            return VmAliasResult::Unknown;
232        }
233    };
234    let callee_name = vm_state.tcx.def_path_str(callee);
235
236    // NonNull::as_ref / as_mut fast-path (formerly part of Ptr2Ref checking):
237    // NonNull guarantees non-null + aligned + initialized by construction, so
238    // the only remaining question is whether the produced reference escapes.
239    // When the enclosing function returns a reference, the result may escape
240    // (hazard for struct-field Owning invariants) → Unknown; otherwise safe.
241    if callee_name.contains("::NonNull::")
242        && (callee_name.ends_with("::as_ref") || callee_name.ends_with("::as_mut"))
243    {
244        let ret_ty = vm_state.body.local_decls[rustc_middle::mir::RETURN_PLACE].ty;
245        if crate::helpers::mir_utils::type_contains_reference(ret_ty) {
246            return VmAliasResult::Unknown;
247        }
248        return VmAliasResult::Proved;
249    }
250
251    // Step 1: Determine the producer
252    let Some(producer) = alias_hazard::alias_producer(&callee_name) else {
253        return VmAliasResult::Unknown;
254    };
255
256    match producer {
257        AliasProducer::View(kind) => {
258            check_view_alias(vm_state, checkpoint, callee_name, kind)
259        }
260        AliasProducer::OwnershipTransfer => {
261            check_ownership_transfer_alias(vm_state, checkpoint)
262        }
263        AliasProducer::ReadMemory => {
264            check_read_memory_alias(vm_state, checkpoint)
265        }
266    }
267}
268
269fn check_view_alias<'ctx, 'tcx>(
270    vm_state: &VmState<'ctx, 'tcx>,
271    checkpoint: &Checkpoint<'tcx>,
272    _callee_name: String,
273    kind: HazardKind,
274) -> VmAliasResult {
275    let Some(origin_arg) = checkpoint.args.first() else {
276        return VmAliasResult::Unknown;
277    };
278    let origin_val = vm_state.value_of_operand(origin_arg);
279
280    let tcx = vm_state.tcx;
281    let caller = checkpoint.caller;
282    let call_block = checkpoint.block;
283    let destination = alias_hazard::call_destination(tcx, checkpoint);
284
285    // Resolve origin PlaceKey from the checkpoint argument
286    let origin_place = alias_hazard::operand_place(origin_arg)
287        .or_else(|| alias_hazard::operand_mir_place(origin_arg)
288            .map(|p| PlaceKey::from_mir_place(p)))
289        .unwrap_or_else(|| {
290            // Fallback: extract from the origin value's type
291            PlaceKey::from_origin(
292                crate::helpers::mir_utils::extract_local(origin_arg).map(|l| l.as_usize()).unwrap_or(1),
293                vec![],
294            )
295        });
296
297    // Trace through local origins to resolve intermediate copies/casts.
298    // e.g. `_tmp = self.ptr` → trace to `_1.0`
299    let resolved_origin = resolve_origin_place_mir(tcx, caller, &origin_place);
300    let mut origins = vec![origin_place.clone()];
301    if resolved_origin != origin_place {
302        origins.push(resolved_origin.clone());
303    }
304
305    // Also try to extract field projections from the checkpoint arg's MIR place.
306    // If the arg directly references a struct field (e.g., `(*_1).0`), capture it.
307    let mir_place_from_arg = checkpoint.args.first()
308        .and_then(|a| alias_hazard::operand_mir_place(a));
309    if let Some(place) = mir_place_from_arg {
310        if !place.projection.is_empty() && place.local == Local::from_usize(1) {
311            let field_key = PlaceKey::from_mir_place(place);
312            if !field_key.fields.is_empty() && !origins.contains(&field_key) {
313                origins.push(field_key);
314            }
315        }
316    }
317
318    // Try VM provenance tracing for fast-path checks
319    if let Some(origin) = vm_state.resolve_origin(&origin_val) {
320        match (kind, origin.kind) {
321            (HazardKind::UniqueView, VmOriginKind::MutRef) => return VmAliasResult::Proved,
322            (HazardKind::SharedView, VmOriginKind::SharedRef) => return VmAliasResult::Proved,
323            // Shared view from const raw pointer is safe: can't write through *const.
324            (HazardKind::SharedView, VmOriginKind::RawConstPtr) => return VmAliasResult::Proved,
325            (HazardKind::UniqueView, VmOriginKind::RawConstPtr) => {
326                return VmAliasResult::Failed(
327                    "const raw pointer cannot safely create a unique mutable view".into(),
328                );
329            }
330            (HazardKind::UniqueView, VmOriginKind::SharedRef) => {
331                // Shared-ref + unique view is only safe when backed by a private
332                // struct field — defer to the struct field / escape analysis below.
333            }
334            _ => {}
335        }
336        if origin.is_owned() {
337            let check = alias_hazard::alias_proved_for_param_local(
338                tcx, caller, origin.local.as_usize(), kind,
339            );
340            // Skip the early Safe return for Vec/CString (reallocatable) types,
341            // so MIR-level hazard scanning can detect reallocation hazards.
342            let is_reallocatable = match &origin.kind {
343                VmOriginKind::Owned(def_id) => {
344                    let def_path = tcx.def_path_str(*def_id);
345                    api_classify::is_std_vec(&def_path)
346                        || api_classify::is_std_cstring(&def_path)
347                }
348                _ => false,
349            };
350            if matches!(check, alias_hazard::HazardCheck::Safe(_)) && !is_reallocatable {
351                return VmAliasResult::Proved;
352            }
353        }
354    }
355
356    // Extract view length for from_raw_parts[_mut](ptr, len)
357    let view_len_place =
358        checkpoint.args.get(1).and_then(|a| alias_hazard::operand_place(a));
359
360    // Run MIR-level hazard scanning
361    if let Some(reason) = alias_hazard::local_hazard_violation(
362        tcx, caller, call_block, destination, &origins, kind, view_len_place,
363    ) {
364        return VmAliasResult::Failed(reason);
365    }
366
367    // Type-level safety checks (even when provenance is unavailable)
368    let origin_pk = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
369    if let Some(local_index) = origin_pk {
370        match alias_hazard::alias_proved_for_param_local(tcx, caller, local_index, kind) {
371            alias_hazard::HazardCheck::Safe(_) => return VmAliasResult::Proved,
372            alias_hazard::HazardCheck::Violation(_) => {
373                // Don't hard-fail here — the struct field analysis below may
374                // override this for &self methods with private raw ptr fields.
375            }
376            alias_hazard::HazardCheck::Inconclusive => {}
377        }
378    }
379    // Also try the origin local directly for reference-type checks
380    let origin_local_place = if origin_place.fields.is_empty() {
381        PlaceKey::from_origin(
382            origin_place.local().map(|l| l.as_usize()).unwrap_or(1),
383            vec![],
384        )
385    } else {
386        origin_place.clone()
387    };
388    match alias_hazard::alias_proved_for_param_local_from_origin(
389        tcx, caller, &origin_local_place, kind,
390    ) {
391        alias_hazard::HazardCheck::Violation(_) => {} // defer to struct field analysis
392        alias_hazard::HazardCheck::Safe(_) => {}
393        alias_hazard::HazardCheck::Inconclusive => {}
394    }
395
396    // Escape analysis
397    let dest_escapes = alias_hazard::destination_flows_to_return(tcx, caller, destination);
398    if dest_escapes {
399        // Try resolved origin first (traces through local copies to struct fields)
400        let field_origin = alias_hazard::self_field_origin(tcx, caller, &resolved_origin)
401            .or_else(|| alias_hazard::self_field_origin(tcx, caller, &origin_place))
402            // If tracing through origins failed, try to find the struct field by
403            // scanning all collector local origins for a _1 field mapping.
404            .or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint));
405        if let Some(sfo) = field_origin {
406            if let Some(reason) = alias_hazard::escaped_self_field_violation(tcx, caller, &sfo) {
407                return VmAliasResult::Failed(reason);
408            }
409            return VmAliasResult::Proved;
410        }
411        let any_field = alias_hazard::any_struct_field_origin(tcx, caller, &resolved_origin)
412            .or_else(|| alias_hazard::any_struct_field_origin(tcx, caller, &origin_place));
413        if let Some(sfo) = any_field {
414            if let Some(reason) = alias_hazard::escaped_self_field_violation(tcx, caller, &sfo) {
415                return VmAliasResult::Failed(reason);
416            }
417            return VmAliasResult::Proved;
418        }
419        if let Some(reason) = alias_hazard::private_fn_callsite_delegation(
420            tcx, caller, &origin_place, kind,
421        ) {
422            return VmAliasResult::Failed(reason);
423        }
424        if kind == HazardKind::SharedView {
425            let param_origin = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
426            if let Some(local) = param_origin
427                && alias_hazard::is_origin_a_reference(tcx, caller, &PlaceKey::from_origin(local, vec![]))
428            {
429                return VmAliasResult::Proved;
430            }
431        }
432    }
433
434    // If no hazard found and view doesn't escape: local view is safe
435    if !dest_escapes {
436        return VmAliasResult::Proved;
437    }
438
439    // A unique view that escapes with a raw-pointer origin not backed by a
440    // private struct field is a hazard.
441    if kind == HazardKind::UniqueView {
442        // Try to infer struct field from the caller's self type when origin
443        // tracing fails. For &self/&mut self methods, scan the struct's fields
444        // for a raw pointer field.
445        if let Some(sfo) = infer_self_field_from_type(tcx, caller, checkpoint)
446            .or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint))
447        {
448            if alias_hazard::escaped_self_field_violation(tcx, caller, &sfo).is_none() {
449                return VmAliasResult::Proved;
450            }
451        }
452        // For &self/&mut self methods, the borrow prevents concurrent access
453        // so a local-only view is safe even when we can't identify the field.
454        let body = tcx.optimized_mir(caller);
455        if body.arg_count >= 1 {
456            let self_ty = body.local_decls[Local::from_usize(1)].ty;
457            if matches!(self_ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
458                return VmAliasResult::Proved;
459            }
460            // A `NonNull<T>` consumed by value (e.g. `NonNull::as_uninit_mut(self)`)
461            // transfers exclusive ownership of its pointer, so producing a unique
462            // view is safe even though the receiver is not a `&mut self`.
463            if let rustc_middle::ty::TyKind::Adt(adt_def, _) = self_ty.kind() {
464                if api_classify::is_std_nonnull(&tcx.def_path_str(adt_def.did())) {
465                    return VmAliasResult::Proved;
466                }
467            }
468        }
469        return VmAliasResult::Failed(format!(
470            "returned unique view escapes while the original pointer is not owned by a private self field [origin={:?}]",
471            origin_place
472        ));
473    }
474
475    // Conservatively proved (origin traced to safe type or no conflicts found)
476    VmAliasResult::Proved
477}
478
479/// Attempt to extract the MIR local index from an operand for PlaceKey construction.
480/// Try to find a struct field origin by examining checkpoint arguments
481/// and the function's self type. Handles the case where origin tracing
482/// fails to resolve through intermediate locals.
483fn find_struct_field_origin_for_param<'tcx>(
484    tcx: rustc_middle::ty::TyCtxt<'tcx>,
485    caller: DefId,
486    checkpoint: &Checkpoint<'tcx>,
487) -> Option<alias_hazard::SelfFieldOrigin> {
488    let body = tcx.optimized_mir(caller);
489
490    let self_ty = body.local_decls[Local::from_usize(1)].ty;
491    let inner_adt = match self_ty.kind() {
492        rustc_middle::ty::TyKind::Ref(_, inner, _)
493            if matches!(inner.kind(), rustc_middle::ty::TyKind::Adt(..)) => *inner,
494        _ => return None,
495    };
496    let (adt_def, _) = crate::analysis::alias::adt_from_ty(inner_adt)?;
497
498    // Try to resolve the checkpoint's first arg to determine which field
499    let Some(arg0) = checkpoint.args.first() else { return None; };
500    let arg_place = match arg0 {
501        Operand::Copy(p) | Operand::Move(p) => p,
502        _ => return None,
503    };
504
505    // If the arg already has projections, use them directly
506    if !arg_place.projection.is_empty() && arg_place.local == Local::from_usize(1) {
507        let fields: Vec<usize> = arg_place.projection.iter()
508            .filter_map(|p| match p {
509                ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
510                _ => None,
511            })
512            .collect();
513        if !fields.is_empty() {
514            let field_index = fields[0];
515            let adt = tcx.adt_def(adt_def);
516            let field = adt.all_fields().nth(field_index)?;
517            return Some(alias_hazard::SelfFieldOrigin {
518                struct_def_id: adt_def,
519                field_index,
520                field_name: field.name.to_string(),
521            });
522        }
523    }
524
525    // Otherwise, scan MIR blocks for assignments from _1 to the arg's local
526    let arg_local = arg_place.local;
527    if arg_place.projection.is_empty() && arg_local != Local::from_usize(1) {
528        for block in body.basic_blocks.iter() {
529            for stmt in &block.statements {
530                let StatementKind::Assign(assign) = &stmt.kind else { continue };
531                let (target, rvalue) = assign.as_ref();
532                if target.local != arg_local { continue; }
533                let source = match rvalue {
534                    #[cfg(rapx_rvalue_use_with_retag)]
535                    Rvalue::Use(operand, _) => match operand {
536                        Operand::Copy(p) | Operand::Move(p) => p,
537                        _ => continue,
538                    },
539                    #[cfg(not(rapx_rvalue_use_with_retag))]
540                    Rvalue::Use(operand) => match operand {
541                        Operand::Copy(p) | Operand::Move(p) => p,
542                        _ => continue,
543                    },
544                    Rvalue::CopyForDeref(p) => p,
545                    _ => continue,
546                };
547                if source.local != Local::from_usize(1) { continue; }
548                let fields: Vec<usize> = source.projection.iter()
549                    .filter_map(|p| match p {
550                        ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
551                        _ => None,
552                    })
553                    .collect();
554                if fields.is_empty() { continue; }
555                let field_index = fields[0];
556                let adt = tcx.adt_def(adt_def);
557                let field = adt.all_fields().nth(field_index)?;
558                return Some(alias_hazard::SelfFieldOrigin {
559                    struct_def_id: adt_def,
560                    field_index,
561                    field_name: field.name.to_string(),
562                });
563            }
564        }
565    }
566
567    None
568}
569
570/// When origin tracing fails to resolve the exact struct field, try to infer
571/// it from the function's self type. Looks for a raw pointer field in the struct
572/// — for simple wrappers with a single raw pointer field, this works reliably.
573fn infer_self_field_from_type<'tcx>(
574    tcx: rustc_middle::ty::TyCtxt<'tcx>,
575    caller: DefId,
576    checkpoint: &Checkpoint<'tcx>,
577) -> Option<alias_hazard::SelfFieldOrigin> {
578    let body = tcx.optimized_mir(caller);
579    if body.arg_count == 0 {
580        return None;
581    }
582    let self_ty = body.local_decls[Local::from_usize(1)].ty;
583    let inner = match self_ty.kind() {
584        rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
585        _ => return None,
586    };
587    let Some((adt_def, _)) = crate::analysis::alias::adt_from_ty(inner) else {
588        return None;
589    };
590
591    let adt = tcx.adt_def(adt_def);
592    let mut raw_ptr_fields: Vec<(usize, String)> = Vec::new();
593    let variant = adt.non_enum_variant();
594    for (idx, field) in variant.fields.iter().enumerate() {
595        #[cfg(not(rapx_ge_99))]
596        let field_ty = field.ty(tcx, rustc_middle::ty::GenericArgs::identity_for_item(tcx, adt_def));
597        #[cfg(rapx_ge_99)]
598        let field_ty = field.ty(tcx, rustc_middle::ty::GenericArgs::identity_for_item(tcx, adt_def)).skip_norm_wip();
599        if matches!(field_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)) {
600            raw_ptr_fields.push((idx, field.name.to_string()));
601        }
602    }
603
604    if raw_ptr_fields.len() == 1 {
605        let (field_index, field_name) = raw_ptr_fields.into_iter().next().unwrap();
606        return Some(alias_hazard::SelfFieldOrigin {
607            struct_def_id: adt_def,
608            field_index,
609            field_name,
610        });
611    }
612
613    // Multiple raw ptr fields: try to match by the checkpoint arg's source
614    // This is less reliable but serves as a fallback.
615    if let Some(arg0) = checkpoint.args.first()
616        && let Some(place) = alias_hazard::operand_mir_place(arg0)
617    {
618        let fields: Vec<usize> = place.projection.iter()
619            .filter_map(|p| match p {
620                ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
621                _ => None,
622            })
623            .collect();
624        if let Some(&idx) = fields.first() {
625            if let Some(field) = adt.all_fields().nth(idx) {
626                return Some(alias_hazard::SelfFieldOrigin {
627                    struct_def_id: adt_def,
628                    field_index: idx,
629                    field_name: field.name.to_string(),
630                });
631            }
632        }
633    }
634
635    None
636}
637/// Check whether a self field's type is a shared reference (`&T` or `&[T]`).
638/// Used by raw-ptr-deref alias checks to prove shared views are safe when the
639/// underlying field is a shared reference.
640fn is_self_field_shared_ref(
641    tcx: rustc_middle::ty::TyCtxt<'_>,
642    caller: DefId,
643    origin: &alias_hazard::SelfFieldOrigin,
644) -> Option<bool> {
645    let body = tcx.optimized_mir(caller);
646    let self_ty = body.local_decls[Local::from_usize(1)].ty;
647    let ((adt_def, args), _) = match self_ty.kind() {
648        rustc_middle::ty::TyKind::Ref(_, inner, _)
649            if matches!(inner.kind(), rustc_middle::ty::TyKind::Adt(..)) =>
650        {
651            let (did, a) = crate::analysis::alias::adt_from_ty(*inner)?;
652            ((did, a), Some(inner))
653        }
654        _ => return None,
655    };
656    if adt_def != origin.struct_def_id {
657        return Some(false);
658    }
659    let adt = tcx.adt_def(adt_def);
660    let field = adt.all_fields().nth(origin.field_index)?;
661    #[cfg(not(rapx_ge_99))]
662    let field_ty = field.ty(tcx, args);
663    #[cfg(rapx_ge_99)]
664    let field_ty = field.ty(tcx, args).skip_norm_wip();
665    Some(matches!(
666        field_ty.kind(),
667        rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not)
668    ))
669}
670
671/// copies/casts (e.g. `_tmp = self.ptr` → `_1.0`).
672fn resolve_origin_place_mir(tcx: rustc_middle::ty::TyCtxt<'_>, caller: DefId, place: &PlaceKey) -> PlaceKey {
673    let Some(local) = place.local() else {
674        return place.clone();
675    };
676    let origins = crate::analysis::alias::collect_local_origins(tcx, caller);
677    let (root_local, mut root_fields) =
678        crate::verify::alias_hazard::deep_resolve_place(local.as_usize(), &origins);
679
680    // Preserve field projections from the original place if the root is same local
681    if root_local == local.as_usize() && root_fields.is_empty() && !place.fields.is_empty() {
682        root_fields = place.fields.clone();
683    }
684
685    // Combine: root's fields + any additional projections from the resolved chain
686    // (e.g. if place = _tmp, root = _1 with fields [0], keep fields [0])
687    if root_fields.is_empty() && !place.fields.is_empty() {
688        return place.clone();
689    }
690
691    PlaceKey::from_origin(root_local, root_fields)
692}
693
694fn check_ownership_transfer_alias<'ctx, 'tcx>(
695    vm_state: &VmState<'ctx, 'tcx>,
696    checkpoint: &Checkpoint<'tcx>,
697) -> VmAliasResult {
698    let Some(origin_arg) = checkpoint.args.first() else {
699        return VmAliasResult::Unknown;
700    };
701
702    let tcx = vm_state.tcx;
703    let caller = checkpoint.caller;
704    let call_block = checkpoint.block;
705    let destination = alias_hazard::call_destination(tcx, checkpoint);
706
707    let origin_place = alias_hazard::operand_place(origin_arg);
708    let Some(origin_place) = origin_place else {
709        return VmAliasResult::Unknown;
710    };
711
712    if let Some(reason) = alias_hazard::ownership_transfer_violation(
713        tcx, caller, call_block, destination, &origin_place,
714    ) {
715        return VmAliasResult::Failed(reason);
716    }
717
718    VmAliasResult::Proved
719}
720
721fn check_read_memory_alias<'ctx, 'tcx>(
722    vm_state: &VmState<'ctx, 'tcx>,
723    checkpoint: &Checkpoint<'tcx>,
724) -> VmAliasResult {
725    let Some(origin_arg) = checkpoint.args.first() else {
726        return VmAliasResult::Unknown;
727    };
728
729    let origin_val = vm_state.value_of_operand(origin_arg);
730
731    // If the enclosing function accepted the structural-alias hazard via its
732    // contract (e.g. `any(Trait(T, Copy), Alias(self, ret))`), the read is the
733    // accepted hazard rather than a violation.
734    if vm_state.contract_flags.alias_hazard_accepted {
735        return VmAliasResult::Proved;
736    }
737
738    // If the pointee type is Copy, read is safe
739    if let rustc_middle::ty::TyKind::RawPtr(pointee, _) = origin_val.ty.kind() {
740        let tcx = vm_state.tcx;
741        let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, checkpoint.caller);
742        if tcx.type_is_copy_modulo_regions(typing_env, *pointee) {
743            return VmAliasResult::Proved;
744        }
745    }
746
747    // If the returned value doesn't escape to the return, read is local and safe
748    let tcx = vm_state.tcx;
749    let destination = alias_hazard::call_destination(tcx, checkpoint);
750    if !alias_hazard::destination_flows_to_return(tcx, checkpoint.caller, destination) {
751        return VmAliasResult::Proved;
752    }
753
754    VmAliasResult::Failed(
755        "read API value escapes while the source pointer persists — structural alias hazard"
756            .into(),
757    )
758}