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 super::alias_hazard::{self, AliasProducer, HazardKind};
9use crate::analysis::alias::FieldOrigin;
10use crate::helpers::mir_scan::Checkpoint;
11use crate::verify::api_classify;
12use crate::verify::contract::{Property, PropertyKind};
13use crate::verify::def_use::PlaceKey;
14use rustc_hir::def_id::DefId;
15use rustc_middle::mir::{Local, Operand, ProjectionElem, Rvalue, StatementKind};
16
17use super::state::{VmState, VmValue};
18
19/// Information about a value's ultimate origin.
20#[derive(Clone, Debug)]
21pub(crate) struct VmOrigin {
22    /// The local (parameter or stack variable) that is the root source.
23    pub local: Local,
24    /// The type of the origin local (Ref/MutRef/RawPtr/Adt/...).
25    pub kind: VmOriginKind,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub(crate) enum VmOriginKind {
30    MutRef,
31    SharedRef,
32    /// A raw pointer (`*const T` / `*mut T`). The const/mut distinction is
33    /// compile-time-only (the casts are safe in both directions), so both are
34    /// treated as one origin kind for alias analysis.
35    RawPtr,
36    Owned(DefId),
37    Unknown,
38}
39
40impl VmOrigin {
41    /// Whether this origin is a `&mut T` reference — safe to create a unique view from.
42    pub(crate) fn is_mut_ref(&self) -> bool {
43        matches!(self.kind, VmOriginKind::MutRef)
44    }
45
46    /// Whether this origin is a `&T` reference — safe to create a shared view from.
47    pub(crate) fn is_shared_ref(&self) -> bool {
48        matches!(self.kind, VmOriginKind::SharedRef)
49    }
50
51    /// Whether this origin is an owned type (Box, Vec) whose allocation was
52    /// transferred to this function.
53    pub(crate) fn is_owned(&self) -> bool {
54        matches!(self.kind, VmOriginKind::Owned(_))
55    }
56}
57
58impl<'z3, 'tcx> VmState<'z3, 'tcx> {
59    /// Trace the origin of a pointer value through VM provenance.
60    ///
61    /// Given a VmValue (extracted from a checkpoint argument), follows
62    /// its provenance back to determine where the allocation came from.
63    pub(crate) fn resolve_origin(&self, value: &VmValue<'z3, 'tcx>) -> Option<VmOrigin> {
64        let Some(prov) = &value.provenance else {
65            return None;
66        };
67
68        let alloc_id = prov.alloc_id;
69
70        // Walk all locals to find which one(s) have the same provenance.
71        // Prefer parameters (arg_count) over temporaries.
72        let mut best: Option<VmOrigin> = None;
73
74        for (local, val) in self.all_local_values() {
75            let Some(val_prov) = &val.provenance else {
76                continue;
77            };
78            if val_prov.alloc_id != alloc_id {
79                continue;
80            }
81
82            let kind = self.classify_local(&local);
83            let candidate = VmOrigin {
84                local,
85                kind,
86            };
87
88            // Prefer parameter locals and owned origins (Box/Vec), then the
89            // lower local index.
90            let is_param = local.as_usize() >= 1 && local.as_usize() <= self.body().arg_count;
91            let is_owned = candidate.is_owned();
92
93            match &best {
94                None => best = Some(candidate),
95                Some(existing) => {
96                    let ex_is_param = existing.local.as_usize() >= 1
97                        && existing.local.as_usize() <= self.body().arg_count;
98                    let ex_is_owned = existing.is_owned();
99                    let rank = |p: bool, o: bool| {
100                        if p {
101                            0
102                        } else if o {
103                            1
104                        } else {
105                            2
106                        }
107                    };
108                    let cand_rank = rank(is_param, is_owned);
109                    let ex_rank = rank(ex_is_param, ex_is_owned);
110                    if cand_rank < ex_rank
111                        || (cand_rank == ex_rank && local.as_usize() < existing.local.as_usize())
112                    {
113                        best = Some(candidate);
114                    }
115                }
116            }
117        }
118
119        best
120    }
121
122    /// Classify a local by its type.
123    fn classify_local(&self, local: &Local) -> VmOriginKind {
124        let ty = self.body().local_decls[*local].ty;
125        match ty.kind() {
126            rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Mut) => {
127                VmOriginKind::MutRef
128            }
129            rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not) => {
130                VmOriginKind::SharedRef
131            }
132            rustc_middle::ty::TyKind::RawPtr(..) => VmOriginKind::RawPtr,
133            rustc_middle::ty::TyKind::Adt(adt_def, _) => VmOriginKind::Owned(adt_def.did()),
134            _ => VmOriginKind::Unknown,
135        }
136    }
137}
138
139// ── High-level VM alias check ────────────────────────────────────
140
141/// Result of the VM-based alias check.
142pub(crate) enum VmAliasResult {
143    Proved,
144    Failed(String),
145    Unknown,
146}
147
148/// Whether a contract property tree contains an `Alias` atom (used to detect a
149/// caller-declared `Alias`/`Ptr2Ref` precondition).
150fn property_contains_alias(property: &Property<'_>) -> bool {
151    match property {
152        Property::Atom(a) => a.kind == PropertyKind::Alias,
153        Property::And(and) => and.conjuncts.iter().any(|p| property_contains_alias(p)),
154        Property::Or(or) => or.disjuncts.iter().any(|p| property_contains_alias(p)),
155    }
156}
157
158/// Whether the caller declares an `Alias` assumption in its `#[rapx::requires]`
159/// (directly or via a compound like `Ptr2Ref`). Such a function relies on its
160/// caller-guaranteed precondition rather than on field encapsulation, so the
161/// field-encapsulation escape check must not fire on it.
162fn fn_has_alias_requires(tcx: rustc_middle::ty::TyCtxt<'_>, def_id: DefId) -> bool {
163    crate::verify::target::get_contract_from_annotation(tcx, def_id)
164        .iter()
165        .any(property_contains_alias)
166}
167
168/// Flow-sensitive shared-XOR-mutable check for a view-producing checkpoint.
169/// Walks the VM's *current* locals (not a static derivation tree), grouping a
170/// live reference view as conflicting when it names the same allocation, or a
171/// sub-allocation of it (`root_alloc` — `from_raw_parts`/`split_at` keep a
172/// `parent` edge), with the opposite mutability.
173fn flow_xor_violation<'z3, 'tcx>(
174    vm_state: &VmState<'z3, 'tcx>,
175    checkpoint: &Checkpoint<'tcx>,
176    unique: bool,
177    statement_index: usize,
178) -> Option<String> {
179    let origin_arg = checkpoint.args.first()?;
180    let origin_place = alias_hazard::operand_mir_place(origin_arg)?;
181    let origin_local = origin_place.local;
182    let origin_alloc = vm_state
183        .value_of_operand(origin_arg)
184        .provenance_alloc_id()?;
185    let origin_root = vm_state.root_alloc(origin_alloc);
186    let live = alias_hazard::live_locals_at(
187        vm_state.tcx,
188        checkpoint.caller,
189        checkpoint.block,
190        statement_index,
191        false,
192        false,
193    );
194    let body = vm_state.body();
195    for (local, val) in &vm_state.all_local_values() {
196        if *local == origin_local {
197            continue;
198        }
199        if !live.contains(local) {
200            continue;
201        }
202        let Some(prov) = &val.provenance else {
203            continue;
204        };
205        let mutability = match body.local_decls[*local].ty.kind() {
206            rustc_middle::ty::TyKind::Ref(_, _, m) => *m,
207            _ => continue,
208        };
209        let same_alloc = prov.alloc_id == origin_alloc;
210        let same_root = vm_state.root_alloc(prov.alloc_id) == origin_root;
211        if !same_alloc && !same_root {
212            continue;
213        }
214        let view_mut = mutability == rustc_middle::ty::Mutability::Mut;
215        let conflicts = if unique { !view_mut } else { view_mut };
216        if conflicts {
217            let produced = if unique { "&mut" } else { "&" };
218            let live_kind = if view_mut { "&mut" } else { "&" };
219            return Some(format!(
220                "producing {produced} while a live {live_kind} aliases the same data"
221            ));
222        }
223    }
224    None
225}
226
227/// Run the full alias hazard check for the VM backend.
228///
229/// This is the function the `PropertyChecker::check_alias` delegates to.
230pub(crate) fn check_alias_vm<'z3, 'tcx>(
231    vm_state: &VmState<'z3, 'tcx>,
232    checkpoint: &Checkpoint<'tcx>,
233) -> VmAliasResult {
234    let callee = match checkpoint.callee {
235        Some(c) => c,
236        // raw-ptr-deref / synthetic checkpoints: trace provenance to verify safety
237        None => {
238            let Some(origin_arg) = checkpoint.args.first() else {
239                return VmAliasResult::Unknown;
240            };
241            let origin_val = vm_state.value_of_operand(origin_arg);
242            // Step 4 (forward check): while the produced view is live, a later
243            // raw access through the same origin violates shared-XOR-mutable.
244            if let Some(origin_place) = alias_hazard::operand_place(origin_arg) {
245                let kind = if checkpoint.is_mut_ref {
246                    HazardKind::UniqueView
247                } else {
248                    HazardKind::SharedView
249                };
250                if let Some(reason) = alias_hazard::local_hazard_violation(
251                    vm_state.tcx,
252                    checkpoint.caller,
253                    checkpoint.block,
254                    checkpoint.destination,
255                    &[origin_place],
256                    kind,
257                    None,
258                ) {
259                    return VmAliasResult::Failed(reason);
260                }
261            }
262            if let Some(origin) = vm_state.resolve_origin(&origin_val) {
263                // Step 3 (callsite check): unless an `Alias`/`Ptr2Ref` precondition
264                // discharges the hazard, the deref must not violate shared-XOR-mutable
265                // against a live alias of the opposite mutability (tree-based,
266                // grouped by root or allocation).
267                if !fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
268                    if let Some(reason) = flow_xor_violation(
269                        vm_state,
270                        checkpoint,
271                        checkpoint.is_mut_ref,
272                        checkpoint.statement_index,
273                    ) {
274                        return VmAliasResult::Failed(reason);
275                    }
276                }
277                if origin.is_mut_ref() {
278                    // A mut view produced through a raw field of a reference
279                    // parameter (`(*self).next`). When it escapes, trace to the
280                    // root parameter and reject a shared-reference origin.
281                    let dest_escapes = alias_hazard::destination_flows_to_return(
282                        vm_state.tcx,
283                        checkpoint.caller,
284                        checkpoint.destination,
285                    );
286                    if dest_escapes
287                        && let Some(mir_place) =
288                            crate::helpers::mir_utils::operand_mir_place(origin_arg)
289                    {
290                        let (root, fields) = crate::verify::vm::alias_tree::AliasTree::build(
291                            vm_state.tcx,
292                            checkpoint.caller,
293                        )
294                        .resolve_local_to_root(mir_place.local);
295                        if !fields.is_empty() && root >= 1 && root <= vm_state.body().arg_count {
296                            let root_ty = vm_state.body().local_decls
297                                [rustc_middle::mir::Local::from_usize(root)]
298                            .ty;
299                            if let rustc_middle::ty::TyKind::Ref(
300                                _,
301                                _,
302                                rustc_middle::ty::Mutability::Not,
303                            ) = root_ty.kind()
304                                && checkpoint.is_mut_ref
305                            {
306                                return VmAliasResult::Failed(
307                                    "&mut deref through a shared reference writes immutable data"
308                                        .into(),
309                                );
310                            }
311                        }
312                    }
313                    return VmAliasResult::Proved;
314                }
315                if origin.is_shared_ref() {
316                    if checkpoint.is_mut_ref {
317                        return VmAliasResult::Failed(
318                            "&mut deref through a shared reference writes immutable data".into(),
319                        );
320                    }
321                    // A shared view (`&*self.next`) produced through a raw field
322                    // of a reference parameter. When the view escapes (flows to
323                    // the return place), deep-resolve to that field and apply the
324                    // same encapsulation check used by the view-producer path: a
325                    // public or safe-code-exposed raw field cannot uphold the
326                    // returned `&T`.
327                    let dest_escapes = alias_hazard::destination_flows_to_return(
328                        vm_state.tcx,
329                        checkpoint.caller,
330                        checkpoint.destination,
331                    );
332                    if dest_escapes
333                        && let Some(mir_place) =
334                            crate::helpers::mir_utils::operand_mir_place(origin_arg)
335                    {
336                        let (root, fields) = crate::verify::vm::alias_tree::AliasTree::build(
337                            vm_state.tcx,
338                            checkpoint.caller,
339                        )
340                        .resolve_local_to_root(mir_place.local);
341                        if !fields.is_empty() && root >= 1 && root <= vm_state.body().arg_count {
342                            let resolved = PlaceKey::from_origin(root, fields);
343                            if let Some(sfo) = alias_hazard::self_field_origin(
344                                vm_state.tcx,
345                                checkpoint.caller,
346                                &resolved,
347                            ) {
348                                // A caller that already declares an `Alias`
349                                // precondition (e.g. `Ptr2Ref`) relies on its
350                                // caller rather than field encapsulation, so the
351                                // encapsulation check must not fire there.
352                                if !fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
353                                    return check_escaped_field(
354                                        vm_state.tcx,
355                                        checkpoint.caller,
356                                        &sfo,
357                                        HazardKind::SharedView,
358                                    );
359                                }
360                            }
361                        } else if root >= 1 && root <= vm_state.body().arg_count {
362                            // A direct shared-reference parameter whose view
363                            // escapes to the return must not claim a region that
364                            // outlives its own (e.g. returning a `&'b str` as
365                            // `&'a str` when `'a: 'b`).
366                            if let Some(reason) = escape_region_violation(
367                                vm_state.tcx,
368                                checkpoint.caller,
369                                root,
370                            ) {
371                                return VmAliasResult::Failed(reason);
372                            }
373                        }
374                    }
375                    return VmAliasResult::Proved;
376                }
377                if origin.is_owned() {
378                    return VmAliasResult::Proved;
379                }
380                // An *independent* raw-pointer *parameter* (`*const T` / `*mut T`)
381                // carries no borrow information. An `Alias`/`Ptr2Ref` precondition
382                // discharges the hazard (the caller guarantees no aliasing), and a
383                // local (non-escaping) view is safe; an escaping view without such
384                // a precondition is an undeclared aliasing hazard — not a hard
385                // violation, because the caller is an `unsafe fn` whose contract
386                // (`Alias`/`Ptr2Ref` requires) carries the obligation. A
387                // raw-pointer *field copy* (`_tmp = self.head`) is a temp local
388                // above `arg_count`, derived from a borrow field — it falls
389                // through to the field-type-aware check below.
390                if matches!(origin.kind, VmOriginKind::RawPtr)
391                    && origin.local.as_usize() <= vm_state.body().arg_count
392                {
393                    if fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
394                        return VmAliasResult::Proved;
395                    }
396                    let escapes = alias_hazard::destination_flows_to_return(
397                        vm_state.tcx,
398                        checkpoint.caller,
399                        checkpoint.destination,
400                    );
401                    if !escapes {
402                        return VmAliasResult::Proved;
403                    }
404                    return VmAliasResult::Unknown;
405                }
406            }
407            // A raw-pointer deref in a method whose `self` is a *by-value*
408            // `NonNull<T>` is safe: consuming the `NonNull` transfers exclusive
409            // ownership of its pointer (e.g. `NonNull::as_uninit_mut(self)`).
410            // A *by-reference* `&NonNull<T>` / `&mut NonNull<T>` self is equally
411            // safe (`NonNull::as_ref`/`as_mut`): the reference carries the borrow
412            // (shared or exclusive) over the `NonNull`, whose pointer is the only
413            // source of the deref.
414            if vm_state.body().arg_count >= 1 {
415                let self_ty = vm_state.body().local_decls[Local::from_usize(1)].ty;
416                let nonnull_adt = match self_ty.kind() {
417                    rustc_middle::ty::TyKind::Adt(adt_def, _) => Some(*adt_def),
418                    rustc_middle::ty::TyKind::Ref(_, pointee, _) => match pointee.kind() {
419                        rustc_middle::ty::TyKind::Adt(adt_def, _) => Some(*adt_def),
420                        _ => None,
421                    },
422                    _ => None,
423                };
424                if nonnull_adt.is_some_and(|adt| api_classify::is_std_nonnull(adt.did())) {
425                    return VmAliasResult::Proved;
426                }
427            }
428            // Pointer has provenance: check if it's safe.
429            if let Some(prov) = &origin_val.provenance {
430                let is_external = vm_state.alloc(prov.alloc_id).is_external();
431                if !is_external {
432                    return VmAliasResult::Proved;
433                }
434                // External provenance: safe for shared ref, unsafe for mut ref.
435                let has_shared_ref = vm_state.body().local_decls.iter().any(|d| {
436                    matches!(
437                        d.ty.kind(),
438                        rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not)
439                    )
440                });
441                if has_shared_ref {
442                    return VmAliasResult::Proved;
443                }
444            }
445            // Without provenance: fall back to any reference parameter.
446            if origin_val.provenance.is_none() {
447                for decl in &vm_state.body().local_decls {
448                    if matches!(decl.ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
449                        return VmAliasResult::Proved;
450                    }
451                }
452            }
453            // Field-type-aware check: if the raw-ptr-deref operand traces to a
454            // struct field and that field is a shared reference, the view is safe.
455            let tcx = vm_state.tcx;
456            let caller = checkpoint.caller;
457            let arg_place =
458                alias_hazard::operand_mir_place(origin_arg).map(|p| PlaceKey::from_mir_place(p));
459            if let Some(mir_place) = arg_place {
460                let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
461                let local = mir_place
462                    .local()
463                    .unwrap_or(rustc_middle::mir::Local::from_usize(1));
464                let (root, fields) = tree.resolve_local_to_root(local);
465                if !fields.is_empty() {
466                    // A raw-pointer field of a *by-value* `self` is exclusively
467                    // owned by this call only when the `self` was *moved* (not
468                    // copied), so re-borrowing it (`&mut *self.v`) cannot alias
469                    // any live reference. A `Copy` by-value `self` is copied —
470                    // the caller's copy still aliases the raw target — so it
471                    // must not be treated as exclusive. A `&`/`&mut self` is
472                    // handled by the shared/mut-ref origin paths above.
473                    let root_ty =
474                        vm_state.body().local_decls[rustc_middle::mir::Local::from_usize(root)].ty;
475                    if !matches!(root_ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
476                        let typing_env =
477                            rustc_middle::ty::TypingEnv::post_analysis(tcx, caller);
478                        if !tcx.type_is_copy_modulo_regions(typing_env, root_ty) {
479                            return VmAliasResult::Proved;
480                        }
481                    }
482                    let resolved = PlaceKey::from_origin(root, fields);
483                    let sfo = alias_hazard::self_field_origin(tcx, caller, &resolved);
484                    if let Some(sfo) = sfo {
485                        if let Some(is_shared) = is_self_field_shared_ref(tcx, caller, &sfo) {
486                            if is_shared {
487                                return VmAliasResult::Proved;
488                            }
489                        }
490                    }
491                }
492            }
493            return VmAliasResult::Unknown;
494        }
495    };
496
497    // NonNull::as_ref / as_mut fast-path (formerly part of Ptr2Ref checking):
498    // NonNull guarantees non-null + aligned + initialized by construction, so
499    // the only remaining question is whether the produced reference escapes.
500    // An escaping `&mut` (as_mut) is a confirmed shared-XOR-mut violation — the
501    // exclusive view is derived from a raw pointer with no borrow information,
502    // so it cannot be exclusive while the enclosing `&mut self` is still live.
503    // An escaping `&` (as_ref) is only a *possible* hazard → Unknown.
504    if api_classify::is_nonnull_as_ref_as_mut(Some(callee)) {
505        let ret_ty = vm_state.body().local_decls[rustc_middle::mir::RETURN_PLACE].ty;
506        if crate::helpers::mir_utils::type_contains_reference(ret_ty) {
507            if api_classify::is_nonnull_as_mut(Some(callee)) {
508                return VmAliasResult::Failed(
509                    "escaping `&mut` derived from a raw pointer without borrow information"
510                        .into(),
511                );
512            }
513            return VmAliasResult::Unknown;
514        }
515        return VmAliasResult::Proved;
516    }
517
518    // Step 1: Determine the producer
519    let Some(producer) = alias_hazard::alias_producer(callee) else {
520        return VmAliasResult::Unknown;
521    };
522
523    match producer {
524        AliasProducer::View(kind) => check_view_alias(vm_state, checkpoint, kind),
525        AliasProducer::OwnershipTransfer => check_ownership_transfer_alias(vm_state, checkpoint),
526        AliasProducer::ReadMemory => check_read_memory_alias(vm_state, checkpoint),
527    }
528}
529
530fn check_view_alias<'z3, 'tcx>(
531    vm_state: &VmState<'z3, 'tcx>,
532    checkpoint: &Checkpoint<'tcx>,
533    kind: HazardKind,
534) -> VmAliasResult {
535    let Some(origin_arg) = checkpoint.args.first() else {
536        return VmAliasResult::Unknown;
537    };
538    let origin_val = vm_state.value_of_operand(origin_arg);
539
540    let tcx = vm_state.tcx;
541    let caller = checkpoint.caller;
542    let call_block = checkpoint.block;
543    let destination = alias_hazard::call_destination(tcx, checkpoint);
544
545    // Tree-based shared-XOR-mutable: producing a view must not conflict with a
546    // *live* opposite-mutability view of the same allocation. This runs before
547    // the `Owned`/`MutRef`/`SharedRef` fast-paths below, which otherwise prove
548    // without checking — e.g. two raw pointers split from one owned `Vec`, then
549    // `&` and `&mut` views of each while the first is still live. An
550    // `Alias`/`Ptr2Ref` precondition discharges the obligation.
551    if !fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
552        if let Some(reason) = flow_xor_violation(
553            vm_state,
554            checkpoint,
555            kind == HazardKind::UniqueView,
556            usize::MAX,
557        ) {
558            return VmAliasResult::Failed(reason);
559        }
560    }
561
562    // Resolve origin PlaceKey from the checkpoint argument
563    let origin_place = alias_hazard::operand_place(origin_arg).unwrap_or_else(|| {
564        // Fallback: extract from the origin value's type
565        PlaceKey::from_origin(
566            crate::helpers::mir_utils::extract_local(origin_arg)
567                .map(|l| l.as_usize())
568                .unwrap_or(1),
569            vec![],
570        )
571    });
572
573    // Trace through local origins to resolve intermediate copies/casts.
574    // e.g. `_tmp = self.ptr` → trace to `_1.0`
575    let resolved_origin = resolve_origin_place_mir(tcx, caller, &origin_place);
576    let mut origins = vec![origin_place.clone()];
577    if resolved_origin != origin_place {
578        origins.push(resolved_origin.clone());
579    }
580
581    // Also try to extract field projections from the checkpoint arg's MIR place.
582    // If the arg directly references a struct field (e.g., `(*_1).0`), capture it.
583    let mir_place_from_arg = checkpoint
584        .args
585        .first()
586        .and_then(|a| alias_hazard::operand_mir_place(a));
587    if let Some(place) = mir_place_from_arg {
588        if !place.projection.is_empty() && place.local == Local::from_usize(1) {
589            let field_key = PlaceKey::from_mir_place(place);
590            if !field_key.fields.is_empty() && !origins.contains(&field_key) {
591                origins.push(field_key);
592            }
593        }
594    }
595
596    // Try VM provenance tracing for fast-path checks
597    if let Some(origin) = vm_state.resolve_origin(&origin_val) {
598        match (kind, origin.kind) {
599            (HazardKind::UniqueView, VmOriginKind::MutRef) => return VmAliasResult::Proved,
600            (HazardKind::SharedView, VmOriginKind::SharedRef) => {
601                // Aliasing-safe, but if the view escapes to the return, its
602                // claimed region must not outlive the source reference's region.
603                if let Some(reason) = shared_view_escape_region_violation(
604                    tcx,
605                    caller,
606                    destination,
607                    origin.local.as_usize(),
608                ) {
609                    return VmAliasResult::Failed(reason);
610                }
611                return VmAliasResult::Proved;
612            }
613            (HazardKind::UniqueView, VmOriginKind::SharedRef) => {
614                // `&T` → `&mut T` violates shared-XOR-mutable regardless of
615                // field encapsulation: the caller can re-enter the method and
616                // obtain a second `&mut` to the same data.
617                return VmAliasResult::Failed(
618                    "shared reference cannot produce a unique mutable view".into(),
619                );
620            }
621            // Raw-pointer origins (*const / *mut, compile-time-equivalent)
622            // defer: the local hazard scan / escape / field analysis decides.
623            _ => {}
624        }
625        if origin.is_owned() {
626            let check = alias_hazard::alias_proved_for_param_local(
627                tcx,
628                caller,
629                origin.local.as_usize(),
630                kind,
631            );
632            // Skip the early Safe return for Vec/CString (reallocatable) types,
633            // so MIR-level hazard scanning can detect reallocation hazards.
634            let is_reallocatable = match &origin.kind {
635                VmOriginKind::Owned(def_id) => {
636                    api_classify::is_std_vec(*def_id) || api_classify::is_std_cstring(*def_id)
637                }
638                _ => false,
639            };
640            if matches!(check, alias_hazard::HazardCheck::Safe(_)) && !is_reallocatable {
641                return VmAliasResult::Proved;
642            }
643        }
644    }
645
646    // Extract view length for from_raw_parts[_mut](ptr, len)
647    let view_len_place = checkpoint
648        .args
649        .get(1)
650        .and_then(|a| alias_hazard::operand_place(a));
651
652    // Run MIR-level hazard scanning
653    if let Some(reason) = alias_hazard::local_hazard_violation(
654        tcx,
655        caller,
656        call_block,
657        destination,
658        &origins,
659        kind,
660        view_len_place,
661    ) {
662        return VmAliasResult::Failed(reason);
663    }
664
665    // Type-level safety checks (even when provenance is unavailable)
666    let origin_pk = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
667    if let Some(local_index) = origin_pk {
668        match alias_hazard::alias_proved_for_param_local(tcx, caller, local_index, kind) {
669            alias_hazard::HazardCheck::Safe(_) => return VmAliasResult::Proved,
670            alias_hazard::HazardCheck::Violation(_) => {
671                // Don't hard-fail here — the struct field analysis below may
672                // override this for &self methods with private raw ptr fields.
673            }
674            alias_hazard::HazardCheck::Inconclusive => {}
675        }
676    }
677    // Also try the origin local directly for reference-type checks
678    let origin_local_place = if origin_place.fields.is_empty() {
679        PlaceKey::from_origin(
680            origin_place.local().map(|l| l.as_usize()).unwrap_or(1),
681            vec![],
682        )
683    } else {
684        origin_place.clone()
685    };
686    match alias_hazard::alias_proved_for_param_local_from_origin(
687        tcx,
688        caller,
689        &origin_local_place,
690        kind,
691    ) {
692        alias_hazard::HazardCheck::Violation(_) => {} // defer to struct field analysis
693        alias_hazard::HazardCheck::Safe(_) => {}
694        alias_hazard::HazardCheck::Inconclusive => {}
695    }
696
697    // Escape analysis
698    let dest_escapes = alias_hazard::destination_flows_to_return(tcx, caller, destination);
699    if dest_escapes {
700        // Try resolved origin first (traces through local copies to struct fields)
701        let field_origin =
702            resolve_escaped_field_origin(tcx, caller, &resolved_origin, &origin_place, checkpoint);
703        if let Some(sfo) = field_origin {
704            return check_escaped_field(tcx, caller, &sfo, kind);
705        }
706        let any_field = alias_hazard::any_struct_field_origin(tcx, caller, &resolved_origin)
707            .or_else(|| alias_hazard::any_struct_field_origin(tcx, caller, &origin_place));
708        if let Some(sfo) = any_field {
709            return check_escaped_field(tcx, caller, &sfo, kind);
710        }
711        if let Some(reason) =
712            alias_hazard::private_fn_callsite_delegation(tcx, caller, &origin_place, kind)
713        {
714            return VmAliasResult::Failed(reason);
715        }
716        if kind == HazardKind::SharedView {
717            let param_origin = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
718            if let Some(local) = param_origin
719                && alias_hazard::is_origin_a_reference(
720                    tcx,
721                    caller,
722                    &PlaceKey::from_origin(local, vec![]),
723                )
724            {
725                // The returned reference must not claim a region that outlives
726                // the source reference's region: a shared view re-borrowed from
727                // `&'b str` cannot be returned as `&'a str` when `'a: 'b` (the
728                // source is only valid for the shorter `'b`).
729                if let Some(reason) = escape_region_violation(tcx, caller, local) {
730                    return VmAliasResult::Failed(reason);
731                }
732                return VmAliasResult::Proved;
733            }
734        }
735    }
736
737    // If no hazard found and view doesn't escape: local view is safe
738    if !dest_escapes {
739        return VmAliasResult::Proved;
740    }
741
742    // A unique view that escapes with a raw-pointer origin not backed by a
743    // private struct field is a hazard.
744    if kind == HazardKind::UniqueView {
745        // Try to infer struct field from the caller's self type when origin
746        // tracing fails. For &self/&mut self methods, scan the struct's fields
747        // for a raw pointer field.
748        if let Some(sfo) = infer_self_field_from_type(tcx, caller, checkpoint)
749            .or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint))
750        {
751            if alias_hazard::escaped_self_field_violation(tcx, caller, &sfo).is_none() {
752                return VmAliasResult::Proved;
753            }
754        }
755        let body = tcx.optimized_mir(caller);
756        if body.arg_count >= 1 {
757            let self_ty = body.local_decls[Local::from_usize(1)].ty;
758            // A `NonNull<T>` consumed by value (e.g. `NonNull::as_uninit_mut(self)`)
759            // transfers exclusive ownership of its pointer, so producing a unique
760            // view is safe even though the receiver is not a `&mut self`.
761            if let rustc_middle::ty::TyKind::Adt(adt_def, _) = self_ty.kind() {
762                if api_classify::is_std_nonnull(adt_def.did()) {
763                    return VmAliasResult::Proved;
764                }
765            }
766        }
767        return VmAliasResult::Failed(format!(
768            "returned unique view escapes while the original pointer is not owned by a private self field [origin={:?}]",
769            origin_place
770        ));
771    }
772
773    // Conservatively proved (origin traced to safe type or no conflicts found)
774    VmAliasResult::Proved
775}
776
777/// A shared view re-borrowed from the reference parameter `local` and returned
778/// must not claim a region that outlives the parameter's own region. Returns a
779/// violation reason when `local`'s region does not outlive the return region.
780fn shared_view_escape_region_violation(
781    tcx: rustc_middle::ty::TyCtxt<'_>,
782    caller: DefId,
783    destination: Option<Local>,
784    local: usize,
785) -> Option<String> {
786    if !alias_hazard::destination_flows_to_return(tcx, caller, destination) {
787        return None;
788    }
789    escape_region_violation(tcx, caller, local)
790}
791
792/// Check whether the shared view's claimed return region outlives the source
793/// reference `local`'s region. Returns a violation reason when it does.
794fn escape_region_violation(
795    tcx: rustc_middle::ty::TyCtxt<'_>,
796    caller: DefId,
797    local: usize,
798) -> Option<String> {
799    // MIR `local_decls` erases lifetime regions (`ReErased`), so take the
800    // precise (late-bound-liberated) regions from the function signature.
801    let src_region =
802        super::region::fn_arg_ty(tcx, caller, local - 1).and_then(|ty| match ty.kind() {
803            rustc_middle::ty::TyKind::Ref(region, _, _) => Some(*region),
804            _ => None,
805        })?;
806    let ret_region = super::region::fn_return_region(tcx, caller)?;
807    if !super::region::region_outlives(tcx, caller, src_region, ret_region) {
808        return Some(format!(
809            "returned region `{ret_region:?}` outlives the source reference's region `{src_region:?}`"
810        ));
811    }
812    None
813}
814
815/// Attempt to extract the MIR local index from an operand for PlaceKey construction.
816/// Try to find a struct field origin by examining checkpoint arguments
817/// and the function's self type. Handles the case where origin tracing
818/// fails to resolve through intermediate locals.
819fn find_struct_field_origin_for_param<'tcx>(
820    tcx: rustc_middle::ty::TyCtxt<'tcx>,
821    caller: DefId,
822    checkpoint: &Checkpoint<'tcx>,
823) -> Option<FieldOrigin> {
824    let body = tcx.optimized_mir(caller);
825    let (adt_def, _) = self_adt(tcx, caller)?;
826
827    // Try to resolve the checkpoint's first arg to determine which field
828    let Some(arg0) = checkpoint.args.first() else {
829        return None;
830    };
831    let arg_place = match arg0 {
832        Operand::Copy(p) | Operand::Move(p) => p,
833        _ => return None,
834    };
835
836    // If the arg already has projections, use them directly
837    if !arg_place.projection.is_empty() && arg_place.local == Local::from_usize(1) {
838        let fields: Vec<usize> = arg_place
839            .projection
840            .iter()
841            .filter_map(|p| match p {
842                ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
843                _ => None,
844            })
845            .collect();
846        if !fields.is_empty() {
847            let field_index = fields[0];
848            let adt = tcx.adt_def(adt_def);
849            let field = adt.all_fields().nth(field_index)?;
850            return Some(FieldOrigin {
851                struct_def_id: adt_def,
852                field_index,
853                field_name: field.name.to_string(),
854            });
855        }
856    }
857
858    // Otherwise, scan MIR blocks for assignments from _1 to the arg's local
859    let arg_local = arg_place.local;
860    if arg_place.projection.is_empty() && arg_local != Local::from_usize(1) {
861        for block in body.basic_blocks.iter() {
862            for stmt in &block.statements {
863                let StatementKind::Assign(assign) = &stmt.kind else {
864                    continue;
865                };
866                let (target, rvalue) = assign.as_ref();
867                if target.local != arg_local {
868                    continue;
869                }
870                let source = match rvalue {
871                    #[cfg(rapx_rvalue_use_with_retag)]
872                    Rvalue::Use(operand, _) => match operand {
873                        Operand::Copy(p) | Operand::Move(p) => p,
874                        _ => continue,
875                    },
876                    #[cfg(not(rapx_rvalue_use_with_retag))]
877                    Rvalue::Use(operand) => match operand {
878                        Operand::Copy(p) | Operand::Move(p) => p,
879                        _ => continue,
880                    },
881                    Rvalue::CopyForDeref(p) => p,
882                    _ => continue,
883                };
884                if source.local != Local::from_usize(1) {
885                    continue;
886                }
887                let fields: Vec<usize> = source
888                    .projection
889                    .iter()
890                    .filter_map(|p| match p {
891                        ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
892                        _ => None,
893                    })
894                    .collect();
895                if fields.is_empty() {
896                    continue;
897                }
898                let field_index = fields[0];
899                let adt = tcx.adt_def(adt_def);
900                let field = adt.all_fields().nth(field_index)?;
901                return Some(FieldOrigin {
902                    struct_def_id: adt_def,
903                    field_index,
904                    field_name: field.name.to_string(),
905                });
906            }
907        }
908    }
909
910    None
911}
912
913/// When origin tracing fails to resolve the exact struct field, try to infer
914/// it from the function's self type. Looks for a raw pointer field in the struct
915/// — for simple wrappers with a single raw pointer field, this works reliably.
916fn infer_self_field_from_type<'tcx>(
917    tcx: rustc_middle::ty::TyCtxt<'tcx>,
918    caller: DefId,
919    checkpoint: &Checkpoint<'tcx>,
920) -> Option<FieldOrigin> {
921    let Some((adt_def, _)) = self_adt(tcx, caller) else {
922        return None;
923    };
924
925    let adt = tcx.adt_def(adt_def);
926    let mut raw_ptr_fields: Vec<(usize, String)> = Vec::new();
927    let variant = adt.non_enum_variant();
928    for (idx, field) in variant.fields.iter().enumerate() {
929        let field_ty = crate::helpers::mir_utils::field_ty(
930            tcx,
931            field,
932            rustc_middle::ty::GenericArgs::identity_for_item(tcx, adt_def),
933        );
934        if matches!(field_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)) {
935            raw_ptr_fields.push((idx, field.name.to_string()));
936        }
937    }
938
939    if raw_ptr_fields.len() == 1 {
940        let (field_index, field_name) = raw_ptr_fields.into_iter().next().unwrap();
941        return Some(FieldOrigin {
942            struct_def_id: adt_def,
943            field_index,
944            field_name,
945        });
946    }
947
948    // Multiple raw ptr fields: try to match by the checkpoint arg's source
949    // This is less reliable but serves as a fallback.
950    if let Some(arg0) = checkpoint.args.first()
951        && let Some(place) = alias_hazard::operand_mir_place(arg0)
952    {
953        let fields: Vec<usize> = place
954            .projection
955            .iter()
956            .filter_map(|p| match p {
957                ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
958                _ => None,
959            })
960            .collect();
961        if let Some(&idx) = fields.first() {
962            if let Some(field) = adt.all_fields().nth(idx) {
963                return Some(FieldOrigin {
964                    struct_def_id: adt_def,
965                    field_index: idx,
966                    field_name: field.name.to_string(),
967                });
968            }
969        }
970    }
971
972    None
973}
974/// Check whether a self field's type is a shared reference (`&T` or `&[T]`).
975/// Used by raw-ptr-deref alias checks to prove shared views are safe when the
976/// underlying field is a shared reference.
977fn is_self_field_shared_ref(
978    tcx: rustc_middle::ty::TyCtxt<'_>,
979    caller: DefId,
980    origin: &FieldOrigin,
981) -> Option<bool> {
982    let (adt_def, args) = self_adt(tcx, caller)?;
983    if adt_def != origin.struct_def_id {
984        return Some(false);
985    }
986    let adt = tcx.adt_def(adt_def);
987    let field = adt.all_fields().nth(origin.field_index)?;
988    let field_ty = crate::helpers::mir_utils::field_ty(tcx, field, args);
989    Some(matches!(
990        field_ty.kind(),
991        rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not)
992    ))
993}
994
995/// Shared escape + field-encapsulation check: a view that escapes and traces to
996/// a struct field is safe only if the field is private and not written/exposed
997/// by safe code. A unique (`&mut`) view escaping through a private raw field is
998/// still unsound — the caller can re-enter and obtain a second `&mut`.
999fn check_escaped_field(
1000    tcx: rustc_middle::ty::TyCtxt<'_>,
1001    caller: DefId,
1002    sfo: &FieldOrigin,
1003    kind: HazardKind,
1004) -> VmAliasResult {
1005    if let Some(reason) = alias_hazard::escaped_self_field_violation(tcx, caller, sfo) {
1006        return VmAliasResult::Failed(reason);
1007    }
1008    if kind == HazardKind::UniqueView {
1009        return VmAliasResult::Failed("unique view escapes through a private raw field".into());
1010    }
1011    VmAliasResult::Proved
1012}
1013
1014/// Resolve the struct field an escaping view came from: try the derivation tree
1015/// first (via the resolved and raw origins), then fall back to self-type
1016/// heuristics when tree resolution fails to reach a field.
1017fn resolve_escaped_field_origin<'tcx>(
1018    tcx: rustc_middle::ty::TyCtxt<'tcx>,
1019    caller: DefId,
1020    resolved_origin: &PlaceKey,
1021    origin_place: &PlaceKey,
1022    checkpoint: &Checkpoint<'tcx>,
1023) -> Option<FieldOrigin> {
1024    alias_hazard::self_field_origin(tcx, caller, resolved_origin)
1025        .or_else(|| alias_hazard::self_field_origin(tcx, caller, origin_place))
1026        .or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint))
1027}
1028
1029/// copies/casts (e.g. `_tmp = self.ptr` → `_1.0`).
1030fn resolve_origin_place_mir(
1031    tcx: rustc_middle::ty::TyCtxt<'_>,
1032    caller: DefId,
1033    place: &PlaceKey,
1034) -> PlaceKey {
1035    let Some(local) = place.local() else {
1036        return place.clone();
1037    };
1038    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
1039    let (root_local, mut root_fields) = tree.resolve_local_to_root(local);
1040
1041    // Preserve field projections from the original place if the root is same local
1042    if root_local == local.as_usize() && root_fields.is_empty() && !place.fields.is_empty() {
1043        root_fields = place.fields.clone();
1044    }
1045
1046    // Combine: root's fields + any additional projections from the resolved chain
1047    // (e.g. if place = _tmp, root = _1 with fields [0], keep fields [0])
1048    if root_fields.is_empty() && !place.fields.is_empty() {
1049        return place.clone();
1050    }
1051
1052    PlaceKey::from_origin(root_local, root_fields)
1053}
1054
1055/// Extract the `&self`/`&mut self` receiver's ADT (peeling one reference layer),
1056/// or `None` for non-ADT receivers.  Shared by the struct-field origin heuristics.
1057fn self_adt<'tcx>(
1058    tcx: rustc_middle::ty::TyCtxt<'tcx>,
1059    caller: DefId,
1060) -> Option<(DefId, rustc_middle::ty::GenericArgsRef<'tcx>)> {
1061    let body = tcx.optimized_mir(caller);
1062    if body.arg_count == 0 {
1063        return None;
1064    }
1065    let self_ty = body.local_decls[Local::from_usize(1)].ty;
1066    let inner = match self_ty.kind() {
1067        rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
1068        _ => return None,
1069    };
1070    crate::analysis::alias::adt_from_ty(inner)
1071}
1072
1073fn check_ownership_transfer_alias<'z3, 'tcx>(
1074    vm_state: &VmState<'z3, 'tcx>,
1075    checkpoint: &Checkpoint<'tcx>,
1076) -> VmAliasResult {
1077    let Some(origin_arg) = checkpoint.args.first() else {
1078        return VmAliasResult::Unknown;
1079    };
1080
1081    let tcx = vm_state.tcx;
1082    let caller = checkpoint.caller;
1083    let call_block = checkpoint.block;
1084    let destination = alias_hazard::call_destination(tcx, checkpoint);
1085
1086    let origin_place = alias_hazard::operand_place(origin_arg);
1087    let Some(origin_place) = origin_place else {
1088        return VmAliasResult::Unknown;
1089    };
1090
1091    if let Some(reason) = alias_hazard::ownership_transfer_violation(
1092        tcx,
1093        caller,
1094        call_block,
1095        destination,
1096        &origin_place,
1097    ) {
1098        return VmAliasResult::Failed(reason);
1099    }
1100
1101    VmAliasResult::Proved
1102}
1103
1104fn check_read_memory_alias<'z3, 'tcx>(
1105    vm_state: &VmState<'z3, 'tcx>,
1106    checkpoint: &Checkpoint<'tcx>,
1107) -> VmAliasResult {
1108    let Some(origin_arg) = checkpoint.args.first() else {
1109        return VmAliasResult::Unknown;
1110    };
1111
1112    let origin_val = vm_state.value_of_operand(origin_arg);
1113
1114    // If the enclosing function accepted the structural-alias hazard via its
1115    // contract (e.g. `any(Trait(T, Copy), Alias(self, ret))`), the read is the
1116    // accepted hazard rather than a violation.
1117    if vm_state.path_facts.alias_hazard_accepted {
1118        return VmAliasResult::Proved;
1119    }
1120
1121    // If the pointee type is Copy, read is safe
1122    if let rustc_middle::ty::TyKind::RawPtr(pointee, _) = origin_val.ty.kind() {
1123        let tcx = vm_state.tcx;
1124        let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, checkpoint.caller);
1125        if tcx.type_is_copy_modulo_regions(typing_env, *pointee) {
1126            return VmAliasResult::Proved;
1127        }
1128    }
1129
1130    // If the returned value doesn't escape to the return, read is local and safe
1131    let tcx = vm_state.tcx;
1132    let destination = alias_hazard::call_destination(tcx, checkpoint);
1133    if !alias_hazard::destination_flows_to_return(tcx, checkpoint.caller, destination) {
1134        return VmAliasResult::Proved;
1135    }
1136
1137    VmAliasResult::Failed(
1138        "read API value escapes while the source pointer persists — structural alias hazard".into(),
1139    )
1140}