Skip to main content

rapx/verify/vm/
alias_hazard.rs

1//! MIR-level alias hazard scanning for the symbolic VM backend.
2//!
3//! This module holds the pure MIR hazard analysis (origin-based parameter
4//! safety, escape analysis, local hazard scanning, and ownership-transfer
5//! violation scanning) independent of VM state and Z3 terms. It was extracted
6//! from the old forward verifier's `smt_check/alias.rs`.
7//!
8//! `vm/alias.rs` provides the VM-specific provenance→origin resolution and
9//! orchestrates the overall alias check, calling into this module for the
10//! underlying MIR scanning.
11
12use std::collections::{HashMap, HashSet};
13
14use rustc_hir::{Safety, def::DefKind, def_id::DefId};
15use rustc_middle::{
16    mir::{
17        BasicBlock, Local, LocalDecls, Operand, Place, ProjectionElem, Rvalue, StatementKind,
18        TerminatorKind,
19    },
20    ty::{self, AssocKind, TyCtxt, TyKind},
21};
22
23use crate::analysis::alias::{FieldOrigin, resolve_any_field_origin, resolve_self_field_origin};
24use crate::helpers::fn_info::is_externally_reachable;
25use crate::{
26    helpers::mir_scan::check_safety,
27    verify::def_use::{PlaceBaseKey, PlaceKey},
28};
29
30// Re-export the mir_utils helpers still consumed by `vm/alias.rs`.
31pub(super) use crate::helpers::mir_utils::{call_destination, operand_mir_place, operand_place};
32// Remaining mir_utils helpers used only within this module.
33use crate::helpers::mir_utils::{blocks_reachable_after_call, rvalue_any_place_matching};
34
35// ── Shared types ─────────────────────────────────────────────────
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub(super) enum HazardKind {
39    SharedView,
40    UniqueView,
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub(super) enum AliasProducer {
45    View(HazardKind),
46    OwnershipTransfer,
47    ReadMemory,
48}
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51enum RawAccessKind {
52    Read,
53    Write,
54}
55
56#[derive(Clone, Debug)]
57struct LocalCallsite<'tcx> {
58    pub caller: DefId,
59    pub block: BasicBlock,
60    pub args: Vec<Operand<'tcx>>,
61    pub destination: Option<Local>,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub(super) enum HazardCheck {
66    Safe(String),
67    Violation(String),
68    Inconclusive,
69}
70
71// ── API classification ───────────────────────────────────────────
72
73pub(super) fn alias_producer(callee: DefId) -> Option<AliasProducer> {
74    if crate::verify::api_classify::is_from_raw_parts_mut(Some(callee)) {
75        return Some(AliasProducer::View(HazardKind::UniqueView));
76    }
77    if crate::verify::api_classify::is_vec_ownership_transfer(Some(callee)) {
78        return Some(AliasProducer::OwnershipTransfer);
79    }
80    if crate::verify::api_classify::is_from_raw_parts(Some(callee))
81        || crate::verify::api_classify::is_cstr_from_ptr(Some(callee))
82    {
83        return Some(AliasProducer::View(HazardKind::SharedView));
84    }
85    if crate::verify::api_classify::is_ownership_transfer(Some(callee)) {
86        return Some(AliasProducer::OwnershipTransfer);
87    }
88    if crate::verify::api_classify::is_ptr_read(Some(callee)) {
89        return Some(AliasProducer::ReadMemory);
90    }
91    None
92}
93
94// ── Origin-based parameter safety ────────────────────────────────
95
96pub(super) fn alias_proved_for_param_local(
97    tcx: TyCtxt<'_>,
98    caller: DefId,
99    local_index: usize,
100    kind: HazardKind,
101) -> HazardCheck {
102    let body = tcx.optimized_mir(caller);
103    let ty = body.local_decls[Local::from_usize(local_index)].ty;
104    match ty.kind() {
105        ty::Ref(_, _, ty::Mutability::Mut) => HazardCheck::Safe(
106            "returned view reinterprets a &mut param; no hidden raw-pointer conflict".into(),
107        ),
108        ty::Ref(_, _, ty::Mutability::Not) => {
109            if kind == HazardKind::UniqueView {
110                HazardCheck::Violation(
111                    "shared reference origin cannot safely produce a unique mut view".into(),
112                )
113            } else {
114                HazardCheck::Safe(
115                    "returned shared view tied to shared reference; no shared alias conflict"
116                        .into(),
117                )
118            }
119        }
120        _ if !matches!(ty.kind(), ty::RawPtr(..))
121            && local_index >= 1
122            && local_index <= body.arg_count =>
123        {
124            HazardCheck::Safe(
125                "returned view derives from an owned parameter; no external alias risk".into(),
126            )
127        }
128        _ => HazardCheck::Inconclusive,
129    }
130}
131
132pub(super) fn alias_proved_for_param_local_from_origin(
133    tcx: TyCtxt<'_>,
134    caller: DefId,
135    origin: &PlaceKey,
136    kind: HazardKind,
137) -> HazardCheck {
138    let body = tcx.optimized_mir(caller);
139    let local = match origin.base {
140        PlaceBaseKey::Local(l) => l,
141        _ => return HazardCheck::Inconclusive,
142    };
143    if !origin.fields.is_empty() {
144        return HazardCheck::Inconclusive;
145    }
146    let ty = body.local_decls[Local::from_usize(local)].ty;
147    match ty.kind() {
148        ty::Ref(_, _, ty::Mutability::Mut) if kind == HazardKind::SharedView => {
149            HazardCheck::Safe("shared raw-ptr-deref view through &mut param".into())
150        }
151        ty::Ref(_, _, ty::Mutability::Mut) => HazardCheck::Inconclusive,
152        ty::Ref(_, _, ty::Mutability::Not) if kind == HazardKind::SharedView => {
153            HazardCheck::Safe("shared raw-ptr-deref view through shared reference".into())
154        }
155        ty::Ref(_, _, ty::Mutability::Not) => HazardCheck::Violation(
156            "shared reference origin cannot safely produce a unique mut view".into(),
157        ),
158        _ => HazardCheck::Inconclusive,
159    }
160}
161
162pub(super) fn is_origin_a_reference(tcx: TyCtxt<'_>, caller: DefId, origin: &PlaceKey) -> bool {
163    let body = tcx.optimized_mir(caller);
164    let PlaceBaseKey::Local(mut local) = origin.base else {
165        return false;
166    };
167    if let ty::Ref(..) = body.local_decls[Local::from_usize(local)].ty.kind() {
168        return true;
169    }
170    let (resolved, _) = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller)
171        .resolve_local_to_root(Local::from_usize(local));
172    if resolved >= 1 && resolved <= body.arg_count {
173        local = resolved;
174    }
175    matches!(
176        body.local_decls[Local::from_usize(local)].ty.kind(),
177        ty::Ref(..)
178    )
179}
180
181/// Resolve `origin` to a parameter MIR local, tracing through local-copy origins
182/// when the base is not itself a parameter.
183///
184/// Returns a 1-based MIR local index (usable directly with `Local::from_usize`),
185/// or `None` when the origin does not trace back to a parameter.
186pub(super) fn resolve_param_origin(
187    tcx: TyCtxt<'_>,
188    caller: DefId,
189    origin: &PlaceKey,
190) -> Option<usize> {
191    let body = tcx.optimized_mir(caller);
192    if let PlaceBaseKey::Local(local) = origin.base {
193        if local >= 1 && local <= body.arg_count {
194            return Some(local);
195        }
196        let (resolved, _fields) = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller)
197            .resolve_local_to_root(Local::from_usize(local));
198        if resolved >= 1 && resolved <= body.arg_count {
199            return Some(resolved);
200        }
201    }
202    None
203}
204
205/// Return the 0-based argument index for `origin` when it is a direct, whole
206/// raw-pointer parameter (no field projections, no local-copy tracing).
207///
208/// Unlike [`resolve_param_origin`], this returns a 0-based index into the call
209/// argument list (used by `callsite_arg_origins`) and does not trace through the
210/// alias tree.
211fn param_index_of_origin(tcx: TyCtxt<'_>, caller: DefId, origin: &PlaceKey) -> Option<usize> {
212    let PlaceBaseKey::Local(local) = origin.base else {
213        return None;
214    };
215    if !origin.fields.is_empty() {
216        return None;
217    }
218    let body = tcx.optimized_mir(caller);
219    if local == 0 || local > body.arg_count {
220        return None;
221    }
222    let ty = body.local_decls[Local::from_usize(local)].ty;
223    matches!(ty.kind(), TyKind::RawPtr(..)).then_some(local - 1)
224}
225
226// ── Escape analysis ──────────────────────────────────────────────
227
228pub(super) fn destination_flows_to_return(
229    tcx: TyCtxt<'_>,
230    caller: DefId,
231    destination: Option<Local>,
232) -> bool {
233    let Some(destination) = destination else {
234        return false;
235    };
236    if destination.as_usize() == 0 {
237        return true;
238    }
239    let body = tcx.optimized_mir(caller);
240    if body.local_decls[Local::from_usize(0)].ty == body.local_decls[destination].ty {
241        return true;
242    }
243    let mut aliases: HashMap<Local, PlaceKey> = HashMap::new();
244    aliases.insert(
245        destination,
246        PlaceKey {
247            base: PlaceBaseKey::Local(destination.as_usize()),
248            fields: Vec::new(),
249        },
250    );
251    for block in body.basic_blocks.iter() {
252        for statement in &block.statements {
253            let StatementKind::Assign(assign) = &statement.kind else {
254                continue;
255            };
256            let (target, rvalue) = assign.as_ref();
257            if target.local.as_usize() == 0 {
258                if rvalue_mentions_local(rvalue, destination, &aliases) {
259                    return true;
260                }
261            }
262            if rvalue_mentions_local(rvalue, destination, &aliases) {
263                aliases.insert(target.local, aliases[&destination].clone());
264            }
265        }
266    }
267    false
268}
269
270pub(super) fn self_field_origin(
271    tcx: TyCtxt<'_>,
272    caller: DefId,
273    place: &PlaceKey,
274) -> Option<FieldOrigin> {
275    let PlaceBaseKey::Local(local) = place.base else {
276        return None;
277    };
278    resolve_self_field_origin(tcx, caller, local, &place.fields)
279}
280
281pub(super) fn any_struct_field_origin(
282    tcx: TyCtxt<'_>,
283    caller: DefId,
284    place: &PlaceKey,
285) -> Option<FieldOrigin> {
286    let PlaceBaseKey::Local(local) = place.base else {
287        return None;
288    };
289    if place.fields.is_empty() {
290        return None;
291    }
292    resolve_any_field_origin(tcx, caller, local, &place.fields)
293}
294
295/// The mutability (`Not` / `Mut`) of the borrow carried by `self_local`'s type
296/// (a `&T` / `&mut T`), or `None` if it is not a reference. `self_local` is
297/// `_1` for a method receiver, and any parameter for a free function.
298fn self_borrow_mutability(
299    tcx: TyCtxt<'_>,
300    def_id: DefId,
301    self_local: Local,
302) -> Option<ty::Mutability> {
303    let body = tcx.optimized_mir(def_id);
304    match body.local_decls[self_local].ty.kind() {
305        TyKind::Ref(_, _, m) => Some(*m),
306        _ => None,
307    }
308}
309
310pub(super) fn escaped_self_field_violation(
311    tcx: TyCtxt<'_>,
312    current: DefId,
313    origin: &FieldOrigin,
314) -> Option<String> {
315    if public_raw_field(tcx, origin) {
316        return Some(format!(
317            "returned view escapes while raw field `{}` is public",
318            origin.field_name
319        ));
320    }
321    let current_self = self_borrow_mutability(tcx, current, Local::from_usize(1));
322    for impl_def_id in impls_for_struct(tcx, origin.struct_def_id) {
323        for item in tcx.associated_item_def_ids(impl_def_id) {
324            if *item == current {
325                continue;
326            }
327            if !matches!(tcx.def_kind(*item), DefKind::Fn | DefKind::AssocFn) {
328                continue;
329            }
330            if check_safety(tcx, *item) == Safety::Unsafe {
331                continue;
332            }
333            let Some(assoc) = tcx.opt_associated_item(*item) else {
334                continue;
335            };
336            if !matches!(assoc.kind, AssocKind::Fn { has_self: true, .. }) {
337                continue;
338            }
339            if !tcx.is_mir_available(*item) {
340                continue;
341            }
342            if let Some(reason) =
343                check_fn_against_field(tcx, *item, origin, current_self, Local::from_usize(1))
344            {
345                return Some(reason);
346            }
347        }
348    }
349    // A raw field is also reachable from free functions in the same module
350    // (Rust privacy is module-scoped), so a free fn that writes or exposes the
351    // field breaks encapsulation exactly like a method would.
352    for (fn_def_id, param_locals) in free_fns_for_struct(tcx, origin.struct_def_id) {
353        if fn_def_id == current {
354            continue;
355        }
356        if check_safety(tcx, fn_def_id) == Safety::Unsafe {
357            continue;
358        }
359        if !tcx.is_mir_available(fn_def_id) {
360            continue;
361        }
362        for param_local in param_locals {
363            if let Some(reason) =
364                check_fn_against_field(tcx, fn_def_id, origin, current_self, param_local)
365            {
366                return Some(reason);
367            }
368        }
369    }
370    None
371}
372
373/// Check whether `item` (a struct method or a same-module free function) writes
374/// or exposes the raw field `origin` through the borrow carried by `self_local`.
375/// A shared current borrow (`&self`) is not invalidated by a mutable item borrow
376/// (`&mut self`), and a mutable/mutable pair is likewise fine; any other
377/// combination is a violation. Returns the violation description, or `None`.
378fn check_fn_against_field(
379    tcx: TyCtxt<'_>,
380    item: DefId,
381    origin: &FieldOrigin,
382    current_self: Option<ty::Mutability>,
383    self_local: Local,
384) -> Option<String> {
385    let item_self = self_borrow_mutability(tcx, item, self_local);
386    if method_writes_self_field(tcx, item, self_local, origin.field_index) {
387        if current_self.is_none() || item_self.is_none() {
388            return None;
389        }
390        if let (Some(ty::Mutability::Not), Some(ty::Mutability::Mut)) = (current_self, item_self) {
391            return None;
392        }
393        return Some(format!(
394            "safe fn `{}` writes through raw field `{}`",
395            tcx.def_path_str(item),
396            origin.field_name
397        ));
398    }
399    if method_exposes_self_field(tcx, item, self_local, origin.field_index) {
400        if current_self.is_none() || item_self.is_none() {
401            return None;
402        }
403        if let (Some(ty::Mutability::Not), Some(ty::Mutability::Mut)) = (current_self, item_self) {
404            return None;
405        }
406        if let (Some(ty::Mutability::Mut), Some(ty::Mutability::Mut)) = (current_self, item_self) {
407            return None;
408        }
409        return Some(format!(
410            "safe fn `{}` exposes raw field `{}`",
411            tcx.def_path_str(item),
412            origin.field_name
413        ));
414    }
415    None
416}
417
418fn public_raw_field(tcx: TyCtxt<'_>, origin: &FieldOrigin) -> bool {
419    let adt = tcx.adt_def(origin.struct_def_id);
420    let Some(field) = adt.all_fields().nth(origin.field_index) else {
421        return false;
422    };
423    field.vis.is_public()
424}
425
426fn impls_for_struct(tcx: TyCtxt<'_>, struct_def_id: DefId) -> Vec<DefId> {
427    let mut impls = tcx
428        .inherent_impls(struct_def_id)
429        .iter()
430        .copied()
431        .collect::<Vec<_>>();
432
433    for item_id in tcx.hir_crate_items(()).free_items() {
434        let item = tcx.hir_item(item_id);
435        let rustc_hir::ItemKind::Impl(impl_details) = &item.kind else {
436            continue;
437        };
438        let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) =
439            &impl_details.self_ty.kind
440        else {
441            continue;
442        };
443        let rustc_hir::def::Res::Def(_, def_id) = path.res else {
444            continue;
445        };
446        if def_id != struct_def_id {
447            continue;
448        }
449        let impl_def_id = item_id.owner_id.to_def_id();
450        if !impls.contains(&impl_def_id) {
451            impls.push(impl_def_id);
452        }
453    }
454
455    impls
456}
457
458/// Collect the free functions in the struct's own module that take a
459/// `&Struct` / `&mut Struct` parameter. Rust privacy is module-scoped, so only
460/// those can reach a private raw field. Each entry pairs the function with the
461/// parameter locals that carry the struct reference.
462fn free_fns_for_struct(tcx: TyCtxt<'_>, struct_def_id: DefId) -> Vec<(DefId, Vec<Local>)> {
463    let Some(struct_local) = struct_def_id.as_local() else {
464        return Vec::new();
465    };
466    let struct_module = tcx.parent_module_from_def_id(struct_local);
467    let mut fns = Vec::new();
468    for item_id in tcx.hir_crate_items(()).free_items() {
469        let item = tcx.hir_item(item_id);
470        let rustc_hir::ItemKind::Fn { .. } = &item.kind else {
471            continue;
472        };
473        let fn_def_id = item_id.owner_id.to_def_id();
474        let Some(fn_local) = fn_def_id.as_local() else {
475            continue;
476        };
477        if tcx.parent_module_from_def_id(fn_local) != struct_module {
478            continue;
479        }
480        let param_locals = struct_ref_param_locals(tcx, fn_def_id, struct_def_id);
481        if !param_locals.is_empty() {
482            fns.push((fn_def_id, param_locals));
483        }
484    }
485    fns
486}
487
488/// Return the parameter locals of `def_id` whose type is a `&Struct` /
489/// `&mut Struct` reference to `struct_def_id`.
490fn struct_ref_param_locals(tcx: TyCtxt<'_>, def_id: DefId, struct_def_id: DefId) -> Vec<Local> {
491    let body = tcx.optimized_mir(def_id);
492    (1..=body.arg_count)
493        .filter_map(|i| {
494            let local = Local::from_usize(i);
495            match body.local_decls[local].ty.kind() {
496                TyKind::Ref(_, pointee, _) => match pointee.kind() {
497                    TyKind::Adt(adt_def, _) => (adt_def.did() == struct_def_id).then_some(local),
498                    _ => None,
499                },
500                _ => None,
501            }
502        })
503        .collect()
504}
505
506/// Whether `method` writes through the raw field `field_index` of the struct
507/// borrowed via `self_local` (`_1` for a method, any parameter for a free fn).
508fn method_writes_self_field(
509    tcx: TyCtxt<'_>,
510    method: DefId,
511    self_local: Local,
512    field_index: usize,
513) -> bool {
514    let body = tcx.optimized_mir(method);
515    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, method);
516    let origin = self_field_key(self_local, field_index);
517
518    for block in body.basic_blocks.iter() {
519        for statement in &block.statements {
520            let StatementKind::Assign(assign) = &statement.kind else {
521                continue;
522            };
523            let (target, _) = assign.as_ref();
524            if place_is_raw_access_to_origin(target, &origin, &tree, &body.local_decls)
525                || place_raw_accesses_self_field(tcx, method, target, self_local, field_index)
526            {
527                return true;
528            }
529        }
530
531        let Some(terminator) = &block.terminator else {
532            continue;
533        };
534        if terminator_writes_origin(&terminator.kind, &origin, &tree) {
535            return true;
536        }
537    }
538
539    false
540}
541
542/// Whether `place` is a raw-pointer deref whose operand traces back to the
543/// field `field_index` of the struct borrowed via `self_local`.
544fn place_raw_accesses_self_field(
545    tcx: TyCtxt<'_>,
546    method: DefId,
547    place: &Place<'_>,
548    self_local: Local,
549    field_index: usize,
550) -> bool {
551    let body = tcx.optimized_mir(method);
552    let has_raw_deref = place.projection.iter().any(|projection| {
553        if let ProjectionElem::Deref = projection {
554            matches!(
555                body.local_decls[place.local].ty.kind(),
556                TyKind::RawPtr(_, _)
557            )
558        } else {
559            false
560        }
561    });
562    if !has_raw_deref {
563        return false;
564    }
565    local_traces_to_self_field(
566        tcx,
567        method,
568        place.local,
569        self_local,
570        field_index,
571        &mut HashSet::new(),
572    )
573}
574
575/// Backward-traces `local` through MIR assignments to check whether its value
576/// is derived from `(*self_local).field_index`.
577fn local_traces_to_self_field(
578    tcx: TyCtxt<'_>,
579    method: DefId,
580    local: Local,
581    self_local: Local,
582    field_index: usize,
583    seen: &mut HashSet<Local>,
584) -> bool {
585    if !seen.insert(local) {
586        return false;
587    }
588    let body = tcx.optimized_mir(method);
589    for block in body.basic_blocks.iter() {
590        for statement in &block.statements {
591            let StatementKind::Assign(assign) = &statement.kind else {
592                continue;
593            };
594            let (target, rvalue) = assign.as_ref();
595            if target.local != local {
596                continue;
597            }
598            let Some(source) = crate::helpers::mir_utils::rvalue_source_place(rvalue) else {
599                continue;
600            };
601            let source_key = PlaceKey::from_mir_place(source);
602            if source_key.base == PlaceBaseKey::Local(self_local.as_usize())
603                && source_key.fields.first() == Some(&field_index)
604            {
605                return true;
606            }
607            if source_key.fields.is_empty()
608                && local_traces_to_self_field(
609                    tcx,
610                    method,
611                    source.local,
612                    self_local,
613                    field_index,
614                    seen,
615                )
616            {
617                return true;
618            }
619        }
620    }
621    false
622}
623
624/// Whether `method` returns a raw-pointer-bearing value derived from the field
625/// `field_index` of the struct borrowed via `self_local`, i.e. it leaks the raw
626/// field to the caller.
627fn method_exposes_self_field(
628    tcx: TyCtxt<'_>,
629    method: DefId,
630    self_local: Local,
631    field_index: usize,
632) -> bool {
633    let body = tcx.optimized_mir(method);
634
635    let self_ty = body.local_decls[self_local].ty;
636    if !matches!(self_ty.kind(), TyKind::Ref(_, _, _)) {
637        return false;
638    }
639
640    let ret_ty = body.local_decls[Local::from_usize(0)].ty;
641    if !crate::helpers::mir_utils::type_contains_raw_ptr(tcx, ret_ty) {
642        return false;
643    }
644
645    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, method);
646    let origin = self_field_key(self_local, field_index);
647
648    for block in body.basic_blocks.iter() {
649        for statement in &block.statements {
650            let StatementKind::Assign(assign) = &statement.kind else {
651                continue;
652            };
653            let (target, rvalue) = assign.as_ref();
654            if target.local.as_usize() == 0 && rvalue_mentions_origin(rvalue, &origin, &tree) {
655                return true;
656            }
657        }
658    }
659
660    false
661}
662
663fn rvalue_mentions_origin(
664    rvalue: &Rvalue<'_>,
665    origin: &PlaceKey,
666    tree: &crate::verify::vm::alias_tree::AliasTree,
667) -> bool {
668    rvalue_any_place_matching(rvalue, &mut |place| {
669        let key = PlaceKey::from_mir_place(place);
670        let resolved = if key.fields.is_empty() {
671            let (root, fields) = tree.resolve_local_to_root(place.local);
672            PlaceKey::from_origin(root, fields)
673        } else {
674            key
675        };
676        resolved.overlaps(origin)
677    })
678}
679
680/// The `PlaceKey` for `(*self_local).field_index`.
681fn self_field_key(self_local: Local, field_index: usize) -> PlaceKey {
682    PlaceKey {
683        base: PlaceBaseKey::Local(self_local.as_usize()),
684        fields: vec![field_index],
685    }
686}
687
688fn rvalue_mentions_local(
689    rvalue: &Rvalue<'_>,
690    local: Local,
691    aliases: &HashMap<Local, PlaceKey>,
692) -> bool {
693    crate::helpers::mir_utils::rvalue_any_place_matching(rvalue, &mut |place| {
694        // A deref of `local` reads the pointee rather than flowing `local`'s
695        // value toward the return place, so it does not count as a copy.
696        let has_deref = place
697            .projection
698            .iter()
699            .any(|p| matches!(p, ProjectionElem::Deref));
700        !has_deref && (place.local == local || aliases.contains_key(&place.local))
701    })
702}
703
704// ── Local hazard scanning ────────────────────────────────────────
705
706fn raw_access_conflicts(kind: HazardKind, access: RawAccessKind) -> bool {
707    match kind {
708        HazardKind::SharedView => access == RawAccessKind::Write,
709        HazardKind::UniqueView => true,
710    }
711}
712
713pub(super) fn local_hazard_violation(
714    tcx: TyCtxt<'_>,
715    caller: DefId,
716    call_block: BasicBlock,
717    destination: Option<Local>,
718    origins: &[PlaceKey],
719    kind: HazardKind,
720    view_len_place: Option<PlaceKey>,
721) -> Option<String> {
722    local_hazard_violation_with(
723        tcx,
724        caller,
725        call_block,
726        destination,
727        origins,
728        kind,
729        false,
730        view_len_place,
731    )
732}
733
734fn local_hazard_violation_with(
735    tcx: TyCtxt<'_>,
736    caller: DefId,
737    call_block: BasicBlock,
738    destination: Option<Local>,
739    origins: &[PlaceKey],
740    kind: HazardKind,
741    strict_call_escape: bool,
742    view_len_place: Option<PlaceKey>,
743) -> Option<String> {
744    let body = tcx.optimized_mir(caller);
745    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
746    let mut origins = origins.to_vec();
747    expand_origin_aliases(&tree, &mut origins);
748    let mut hazard_locals: HashSet<Local> = destination.into_iter().collect();
749    expand_hazard_alias_locals(tcx, caller, &mut hazard_locals);
750    for data in body.basic_blocks.iter() {
751        if let Some(terminator) = &data.terminator {
752            if let TerminatorKind::Call {
753                func,
754                destination: call_dest,
755                ..
756            } = &terminator.kind
757            {
758                if crate::verify::api_classify::is_split_at(
759                    crate::helpers::mir_utils::dep_callee_def_id(func),
760                ) {
761                    hazard_locals.insert(call_dest.local);
762                }
763            }
764        }
765    }
766    origins.retain(|origin| !origin.local().is_some_and(|l| hazard_locals.contains(&l)));
767    let vec_owners = find_as_ptr_receivers(tcx, caller, &origins, &tree, true);
768    let reachable = blocks_reachable_after_call(tcx, caller, call_block);
769
770    for (block_index, block) in reverse_postorder_blocks(body) {
771        if !reachable.contains(&block_index) {
772            continue;
773        }
774        for (statement_index, statement) in block.statements.iter().enumerate() {
775            match &statement.kind {
776                StatementKind::StorageDead(local) => {
777                    hazard_locals.remove(local);
778                }
779                StatementKind::Assign(assign) => {
780                    let (target, rvalue) = assign.as_ref();
781                    if rvalue_mentions_any_local(rvalue, &hazard_locals) {
782                        let target_ty = body.local_decls[target.local].ty;
783                        if matches!(
784                            target_ty.kind(),
785                            TyKind::Ref(_, _, _) | TyKind::RawPtr(_, _)
786                        ) {
787                            hazard_locals.insert(target.local);
788                        }
789                    }
790                    if !hazard_locals.is_empty()
791                        && !hazard_locals.contains(&target.local)
792                        && raw_access_conflicts(kind, RawAccessKind::Write)
793                        && place_is_raw_access_to_any_origin(
794                            target,
795                            &origins,
796                            &tree,
797                            &body.local_decls,
798                        )
799                        && hazard_used_after_statement(
800                            tcx,
801                            caller,
802                            block_index,
803                            statement_index,
804                            &hazard_locals,
805                        )
806                    {
807                        return Some(format!(
808                            "raw write through original pointer after {:?} view creation",
809                            kind
810                        ));
811                    }
812                    if !hazard_locals.is_empty()
813                        && !hazard_locals.contains(&target.local)
814                        && raw_access_conflicts(kind, RawAccessKind::Read)
815                        && !crate::helpers::mir_utils::rvalue_source_place(rvalue)
816                            .is_some_and(|place| hazard_locals.contains(&place.local))
817                        && !rvalue_reads_like_view(rvalue, tcx, caller, &origins, &tree)
818                        && rvalue_reads_any_origin(rvalue, &origins, &tree, &body.local_decls)
819                        && hazard_used_after_statement(
820                            tcx,
821                            caller,
822                            block_index,
823                            statement_index,
824                            &hazard_locals,
825                        )
826                    {
827                        return Some(format!(
828                            "raw read through original pointer after {:?} view creation",
829                            kind
830                        ));
831                    }
832                }
833                _ => {}
834            }
835        }
836
837        if !hazard_locals.is_empty() {
838            let Some(terminator) = &block.terminator else {
839                continue;
840            };
841            if origins
842                .iter()
843                .any(|origin| terminator_writes_origin(&terminator.kind, origin, &tree))
844                && hazard_used_after_block(tcx, caller, block_index, &hazard_locals)
845            {
846                return Some(format!(
847                    "raw write call through original pointer after {:?} view creation",
848                    kind
849                ));
850            }
851            if kind == HazardKind::UniqueView
852                && !vec_owners.is_empty()
853                && terminator_invalidates_vec_owner(&terminator.kind, &vec_owners, &tree)
854                && hazard_used_after_block(tcx, caller, block_index, &hazard_locals)
855            {
856                return Some(
857                    "Vec may reallocate while a raw-derived mutable view is still live".to_string(),
858                );
859            }
860            if strict_call_escape
861                && block_index != call_block
862                && !terminator_is_benign_origin_use(&terminator.kind)
863                && origins
864                    .iter()
865                    .any(|origin| terminator_uses_origin(&terminator.kind, origin, &tree))
866                && hazard_used_after_block(tcx, caller, block_index, &hazard_locals)
867            {
868                return Some(format!(
869                    "raw pointer escapes to another call while the {:?} view is live",
870                    kind
871                ));
872            }
873            if view_len_place.is_some() {
874                if let TerminatorKind::Call {
875                    func,
876                    args,
877                    destination: call_dest,
878                    ..
879                } = &terminator.kind
880                {
881                    let callee = crate::helpers::mir_utils::dep_callee_def_id(func);
882                    if crate::verify::api_classify::is_from_raw_parts(callee) && args.len() >= 1 {
883                        if let Some(ptr_place) = operand_place(&args[0].node) {
884                            let offset_eq = is_ptr_add_offset_eq(
885                                tcx,
886                                caller,
887                                &ptr_place,
888                                view_len_place.as_ref().unwrap(),
889                            );
890                            let from_add = is_ptr_from_ptr_add(tcx, caller, &ptr_place);
891                            if offset_eq || from_add {
892                                hazard_locals.insert(call_dest.local);
893                                continue;
894                            }
895                        }
896                    }
897                }
898            }
899        }
900    }
901
902    None
903}
904
905fn reverse_postorder_blocks<'a, 'tcx>(
906    body: &'a rustc_middle::mir::Body<'tcx>,
907) -> impl Iterator<Item = (BasicBlock, &'a rustc_middle::mir::BasicBlockData<'tcx>)> {
908    rustc_middle::mir::traversal::reverse_postorder(body).map(|(block, data)| (block, data))
909}
910
911/// Compute the set of locals that are live (between `StorageLive` and
912/// `StorageDead`) at the deref point `(call_block, statement_index)`, scanning
913/// the function in execution order up to that point. Used by the callsite
914/// shared-XOR-mutable check to ignore temporaries that have already gone dead
915/// (e.g. a method call's `&self` receiver).
916pub(crate) fn live_locals_at(
917    tcx: TyCtxt<'_>,
918    caller: DefId,
919    call_block: BasicBlock,
920    statement_index: usize,
921    seed_params: bool,
922    track_moves: bool,
923) -> HashSet<Local> {
924    let body = tcx.optimized_mir(caller);
925    // Parameters (`_1..=arg_count`) are live on entry and have no explicit
926    // `StorageLive`; seed them so a `&self`/`String`/`Box` parameter is treated
927    // as live until its `StorageDead`.
928    let mut live: HashSet<Local> = if seed_params {
929        (1..=body.arg_count).map(Local::from_usize).collect()
930    } else {
931        HashSet::new()
932    };
933    for (block, data) in reverse_postorder_blocks(body) {
934        let reached = block == call_block;
935        for (i, statement) in data.statements.iter().enumerate() {
936            if reached && i >= statement_index {
937                break;
938            }
939            match &statement.kind {
940                StatementKind::StorageLive(local) => {
941                    live.insert(*local);
942                }
943                StatementKind::StorageDead(local) => {
944                    live.remove(local);
945                }
946                StatementKind::Assign(assign) if track_moves => {
947                    // A move out of a local (`std::mem::forget(data)` inlined as
948                    // `_x = move data`) consumes it even without a `StorageDead`.
949                    let (_, rvalue) = &**assign;
950                    if let rustc_middle::mir::Rvalue::Use(operand, ..) = rvalue {
951                        if let Operand::Move(place) = operand {
952                            live.remove(&place.local);
953                        }
954                    }
955                }
956                _ => {}
957            }
958        }
959        if reached {
960            break;
961        }
962        // A call that *moves* a local out (`Box::into_raw(value)`) consumes it,
963        // even though its `StorageDead` may only appear at the end of the body.
964        if track_moves {
965            if let TerminatorKind::Call { args, .. } = &data.terminator().kind {
966                for arg in args {
967                    if let Operand::Move(place) = &arg.node {
968                        live.remove(&place.local);
969                    }
970                }
971            }
972        }
973    }
974    live
975}
976
977fn expand_origin_aliases(
978    tree: &crate::verify::vm::alias_tree::AliasTree,
979    origins: &mut Vec<PlaceKey>,
980) {
981    let mut changed = true;
982    while changed {
983        changed = false;
984        for node in &tree.nodes {
985            let local_key = PlaceKey {
986                base: PlaceBaseKey::Local(node.local.as_usize()),
987                fields: Vec::new(),
988            };
989            let (root, fields) = tree.resolve_local_to_root(node.local);
990            let alias = PlaceKey::from_origin(root, fields);
991            let related = origins.iter().any(|origin| {
992                local_key.overlaps(origin)
993                    || origin.overlaps(&local_key)
994                    || alias.overlaps(origin)
995                    || origin.overlaps(&alias)
996            });
997            if !related {
998                continue;
999            }
1000            if !origins.contains(&local_key) {
1001                origins.push(local_key);
1002                changed = true;
1003            }
1004            if !origins.contains(&alias) {
1005                origins.push(alias);
1006                changed = true;
1007            }
1008        }
1009    }
1010}
1011
1012fn expand_hazard_alias_locals(tcx: TyCtxt<'_>, caller: DefId, hazard_locals: &mut HashSet<Local>) {
1013    let body = tcx.optimized_mir(caller);
1014    let mut changed = true;
1015    while changed {
1016        changed = false;
1017        for block in body.basic_blocks.iter() {
1018            for statement in &block.statements {
1019                let StatementKind::Assign(assign) = &statement.kind else {
1020                    continue;
1021                };
1022                let (target, rvalue) = assign.as_ref();
1023                if rvalue_mentions_any_local(rvalue, hazard_locals)
1024                    && hazard_locals.insert(target.local)
1025                {
1026                    changed = true;
1027                }
1028            }
1029        }
1030    }
1031}
1032
1033fn rvalue_mentions_any_local(rvalue: &Rvalue<'_>, locals: &HashSet<Local>) -> bool {
1034    rvalue_any_place_matching(rvalue, &mut |place| locals.contains(&place.local))
1035}
1036
1037fn hazard_used_after_statement(
1038    tcx: TyCtxt<'_>,
1039    caller: DefId,
1040    block: BasicBlock,
1041    statement_index: usize,
1042    hazard_locals: &HashSet<Local>,
1043) -> bool {
1044    let body = tcx.optimized_mir(caller);
1045    let data = &body.basic_blocks[block];
1046    for statement in data.statements.iter().skip(statement_index + 1) {
1047        if statement_uses_any_local(statement, hazard_locals) {
1048            return true;
1049        }
1050    }
1051    let terminator = data.terminator();
1052    if terminator_uses_any_local(&terminator.kind, hazard_locals) {
1053        return true;
1054    }
1055    hazard_used_after_block(tcx, caller, block, hazard_locals)
1056}
1057
1058fn hazard_used_after_block(
1059    tcx: TyCtxt<'_>,
1060    caller: DefId,
1061    start: BasicBlock,
1062    hazard_locals: &HashSet<Local>,
1063) -> bool {
1064    let body = tcx.optimized_mir(caller);
1065    let mut seen = HashSet::new();
1066    let mut stack: Vec<_> = body.basic_blocks[start].terminator().successors().collect();
1067
1068    while let Some(block) = stack.pop() {
1069        if !seen.insert(block) {
1070            continue;
1071        }
1072        let data = &body.basic_blocks[block];
1073        for statement in &data.statements {
1074            if statement_uses_any_local(statement, hazard_locals) {
1075                return true;
1076            }
1077        }
1078        let terminator = data.terminator();
1079        if terminator_uses_any_local(&terminator.kind, hazard_locals) {
1080            return true;
1081        }
1082        stack.extend(terminator.successors());
1083    }
1084
1085    false
1086}
1087
1088fn statement_uses_any_local(
1089    statement: &rustc_middle::mir::Statement<'_>,
1090    locals: &HashSet<Local>,
1091) -> bool {
1092    let StatementKind::Assign(assign) = &statement.kind else {
1093        return false;
1094    };
1095    let (target, rvalue) = assign.as_ref();
1096    locals.contains(&target.local) || rvalue_mentions_any_local(rvalue, locals)
1097}
1098
1099fn terminator_uses_any_local(terminator: &TerminatorKind<'_>, locals: &HashSet<Local>) -> bool {
1100    match terminator {
1101        TerminatorKind::Call { args, .. } => args.iter().any(|arg| match &arg.node {
1102            Operand::Copy(place) | Operand::Move(place) => locals.contains(&place.local),
1103            Operand::Constant(_) => false,
1104            #[cfg(rapx_ge_95)]
1105            Operand::RuntimeChecks(_) => false,
1106        }),
1107        TerminatorKind::SwitchInt { discr, .. } | TerminatorKind::Assert { cond: discr, .. } => {
1108            match discr {
1109                Operand::Copy(place) | Operand::Move(place) => locals.contains(&place.local),
1110                Operand::Constant(_) => false,
1111                #[cfg(rapx_ge_95)]
1112                Operand::RuntimeChecks(_) => false,
1113            }
1114        }
1115        TerminatorKind::Drop { place, .. } => locals.contains(&place.local),
1116        _ => false,
1117    }
1118}
1119
1120/// Resolve a MIR place through the alias tree: a field-projected place resolves
1121/// to itself; a whole-local place resolves to its ultimate origin.
1122fn resolve_mir_place_tree(
1123    tree: &crate::verify::vm::alias_tree::AliasTree,
1124    place: &Place<'_>,
1125) -> PlaceKey {
1126    let fields = PlaceKey::from_mir_place(place).fields;
1127    let (root, root_fields) = resolve_via_tree(tree, place.local, &fields);
1128    PlaceKey::from_origin(root, root_fields)
1129}
1130
1131/// Resolve a place's *local* through the alias tree (ignoring the place's own
1132/// field projections), falling back to the MIR place itself when unmapped.
1133fn resolve_place_key_tree(
1134    tree: &crate::verify::vm::alias_tree::AliasTree,
1135    place: &Place<'_>,
1136) -> PlaceKey {
1137    if tree.tag_of(place.local).is_some() {
1138        let (root, fields) = tree.resolve_local_to_root(place.local);
1139        PlaceKey::from_origin(root, fields)
1140    } else {
1141        PlaceKey::from_mir_place(place)
1142    }
1143}
1144
1145fn place_is_raw_access_to_any_origin(
1146    place: &Place<'_>,
1147    origins: &[PlaceKey],
1148    tree: &crate::verify::vm::alias_tree::AliasTree,
1149    local_decls: &LocalDecls<'_>,
1150) -> bool {
1151    origins
1152        .iter()
1153        .any(|origin| place_is_raw_access_to_origin(place, origin, tree, local_decls))
1154}
1155
1156fn place_is_raw_access_to_origin(
1157    place: &Place<'_>,
1158    origin: &PlaceKey,
1159    tree: &crate::verify::vm::alias_tree::AliasTree,
1160    local_decls: &LocalDecls<'_>,
1161) -> bool {
1162    let local = place.local;
1163    let has_raw_deref = place.projection.iter().any(|projection| {
1164        if let ProjectionElem::Deref = projection {
1165            matches!(local_decls[local].ty.kind(), TyKind::RawPtr(_, _))
1166        } else {
1167            false
1168        }
1169    });
1170    if !has_raw_deref {
1171        return false;
1172    }
1173    let pointer = resolve_place_key_tree(tree, place);
1174    pointer.overlaps(origin)
1175}
1176
1177fn rvalue_reads_like_view(
1178    rvalue: &Rvalue<'_>,
1179    tcx: TyCtxt<'_>,
1180    caller: DefId,
1181    origins: &[PlaceKey],
1182    tree: &crate::verify::vm::alias_tree::AliasTree,
1183) -> bool {
1184    let Some(place) = crate::helpers::mir_utils::rvalue_source_place(rvalue) else {
1185        return false;
1186    };
1187    if !place
1188        .projection
1189        .iter()
1190        .any(|p| matches!(p, ProjectionElem::Deref))
1191    {
1192        return false;
1193    }
1194    let pointer = resolve_place_key_tree(tree, place);
1195    if !origins.iter().any(|origin| pointer.overlaps(origin)) {
1196        return false;
1197    }
1198    is_origin_a_reference(tcx, caller, &pointer)
1199}
1200
1201fn rvalue_reads_any_origin(
1202    rvalue: &Rvalue<'_>,
1203    origins: &[PlaceKey],
1204    tree: &crate::verify::vm::alias_tree::AliasTree,
1205    local_decls: &LocalDecls<'_>,
1206) -> bool {
1207    rvalue_any_place_matching(rvalue, &mut |place| {
1208        place_is_raw_access_to_any_origin(place, origins, tree, local_decls)
1209    })
1210}
1211
1212fn terminator_writes_origin<'tcx>(
1213    terminator: &TerminatorKind<'tcx>,
1214    origin: &PlaceKey,
1215    tree: &crate::verify::vm::alias_tree::AliasTree,
1216) -> bool {
1217    let TerminatorKind::Call { func, args, .. } = terminator else {
1218        return false;
1219    };
1220    let callee = crate::helpers::mir_utils::dep_callee_def_id(func);
1221    if !crate::verify::api_classify::is_ptr_write(callee) {
1222        return false;
1223    }
1224    let Some(arg0) = args.first() else {
1225        return false;
1226    };
1227    let Some(place) = operand_mir_place(&arg0.node) else {
1228        return false;
1229    };
1230    resolve_mir_place_tree(tree, place).overlaps(origin)
1231}
1232
1233fn terminator_uses_origin<'tcx>(
1234    terminator: &TerminatorKind<'tcx>,
1235    origin: &PlaceKey,
1236    tree: &crate::verify::vm::alias_tree::AliasTree,
1237) -> bool {
1238    let TerminatorKind::Call { args, .. } = terminator else {
1239        return false;
1240    };
1241    args.iter().any(|arg| {
1242        let Some(place) = operand_mir_place(&arg.node) else {
1243            return false;
1244        };
1245        resolve_mir_place_tree(tree, place).overlaps(origin)
1246    })
1247}
1248
1249fn terminator_is_benign_origin_use<'tcx>(
1250    terminator: &TerminatorKind<'tcx>,
1251) -> bool {
1252    let TerminatorKind::Call { func, .. } = terminator else {
1253        return true;
1254    };
1255    crate::verify::api_classify::is_benign_origin_use(crate::helpers::mir_utils::dep_callee_def_id(
1256        func,
1257    ))
1258}
1259
1260fn terminator_invalidates_vec_owner<'tcx>(
1261    terminator: &TerminatorKind<'tcx>,
1262    owners: &[PlaceKey],
1263    tree: &crate::verify::vm::alias_tree::AliasTree,
1264) -> bool {
1265    let TerminatorKind::Call { func, args, .. } = terminator else {
1266        return false;
1267    };
1268    if !crate::verify::api_classify::is_vec_invalidating_method(
1269        crate::helpers::mir_utils::dep_callee_def_id(func),
1270    ) {
1271        return false;
1272    }
1273    args.iter().any(|arg| {
1274        let Some(place) = operand_mir_place(&arg.node) else {
1275            return false;
1276        };
1277        let arg = resolve_mir_place_tree(tree, place);
1278        owners
1279            .iter()
1280            .any(|owner| arg.overlaps(owner) || owner.overlaps(&arg))
1281    })
1282}
1283
1284fn find_as_ptr_receivers(
1285    tcx: TyCtxt<'_>,
1286    caller: DefId,
1287    origins: &[PlaceKey],
1288    tree: &crate::verify::vm::alias_tree::AliasTree,
1289    check_alias_dest: bool,
1290) -> Vec<PlaceKey> {
1291    let body = tcx.optimized_mir(caller);
1292    let mut result = Vec::new();
1293    for block in body.basic_blocks.iter() {
1294        let Some(terminator) = &block.terminator else {
1295            continue;
1296        };
1297        let TerminatorKind::Call {
1298            func,
1299            args,
1300            destination,
1301            ..
1302        } = &terminator.kind
1303        else {
1304            continue;
1305        };
1306        if !crate::verify::api_classify::is_as_ptr(crate::helpers::mir_utils::dep_callee_def_id(
1307            func,
1308        )) {
1309            continue;
1310        }
1311        let destination_key = PlaceKey {
1312            base: PlaceBaseKey::Local(destination.local.as_usize()),
1313            fields: Vec::new(),
1314        };
1315        let dest_overlaps = || {
1316            origins
1317                .iter()
1318                .any(|origin| destination_key.overlaps(origin))
1319                || (check_alias_dest
1320                    && tree.tag_of(destination.local).is_some_and(|_| {
1321                        let (root, fields) = tree.resolve_local_to_root(destination.local);
1322                        let alias = PlaceKey::from_origin(root, fields);
1323                        origins.iter().any(|o| alias.overlaps(o))
1324                    }))
1325        };
1326        if !dest_overlaps() {
1327            continue;
1328        }
1329        let Some(receiver) = args.first() else {
1330            continue;
1331        };
1332        let Some(place) = operand_mir_place(&receiver.node) else {
1333            continue;
1334        };
1335        let resolved = resolve_mir_place_tree(tree, place);
1336        if !result.contains(&resolved) {
1337            result.push(resolved);
1338        }
1339    }
1340    result
1341}
1342
1343fn is_ptr_add_offset_eq(
1344    tcx: TyCtxt<'_>,
1345    caller: DefId,
1346    ptr_place: &PlaceKey,
1347    view_len: &PlaceKey,
1348) -> bool {
1349    let body = tcx.optimized_mir(caller);
1350    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
1351    let view_len_root = view_len.local().map(|l| tree.resolve_local_to_root(l));
1352    for (_bb, data) in body.basic_blocks.iter_enumerated() {
1353        if let TerminatorKind::Call {
1354            func,
1355            args,
1356            destination,
1357            ..
1358        } = &data.terminator().kind
1359        {
1360            let ptr_key = PlaceKey::from_mir_place(destination);
1361            if ptr_key != *ptr_place {
1362                continue;
1363            }
1364            if crate::verify::api_classify::is_pointer_add(
1365                crate::helpers::mir_utils::dep_callee_def_id(func),
1366            ) && args.len() >= 2
1367            {
1368                if let Some(offset_place) = operand_place(&args[1].node) {
1369                    let offset_root = offset_place.local().map(|l| tree.resolve_local_to_root(l));
1370                    return offset_root == view_len_root;
1371                }
1372            }
1373        }
1374    }
1375    false
1376}
1377
1378fn is_ptr_from_ptr_add(tcx: TyCtxt<'_>, caller: DefId, ptr_place: &PlaceKey) -> bool {
1379    let body = tcx.optimized_mir(caller);
1380    for (_bb, data) in body.basic_blocks.iter_enumerated() {
1381        if let TerminatorKind::Call {
1382            func, destination, ..
1383        } = &data.terminator().kind
1384        {
1385            let ptr_key = PlaceKey::from_mir_place(destination);
1386            if ptr_key != *ptr_place {
1387                continue;
1388            }
1389            return crate::verify::api_classify::is_pointer_add(
1390                crate::helpers::mir_utils::dep_callee_def_id(func),
1391            );
1392        }
1393    }
1394    false
1395}
1396
1397// ── Ownership transfer violation scanning ────────────────────────
1398
1399pub(super) fn ownership_transfer_violation(
1400    tcx: TyCtxt<'_>,
1401    caller: DefId,
1402    call_block: BasicBlock,
1403    destination: Option<Local>,
1404    origin_place: &PlaceKey,
1405) -> Option<String> {
1406    let body = tcx.optimized_mir(caller);
1407    let mut owner_locals: HashSet<Local> = destination.into_iter().collect();
1408    expand_hazard_alias_locals(tcx, caller, &mut owner_locals);
1409    let reachable = blocks_reachable_after_call(tcx, caller, call_block);
1410
1411    for block_index in &reachable {
1412        if let Some(terminator) = &body.basic_blocks[*block_index].terminator
1413            && terminator_returns_ownership(&terminator.kind, &owner_locals)
1414        {
1415            return None;
1416        }
1417    }
1418
1419    let origins = places_holding_transferred_pointer(tcx, caller, call_block, origin_place);
1420
1421    if let Some(reason) = pre_existing_view_on_origin(tcx, caller, call_block, &reachable, &origins)
1422    {
1423        return Some(reason);
1424    }
1425
1426    let start = match &body.basic_blocks[call_block].terminator().kind {
1427        TerminatorKind::Call {
1428            target: Some(target),
1429            ..
1430        } => *target,
1431        _ => return None,
1432    };
1433
1434    let mut entry_states: HashMap<BasicBlock, Vec<PlaceKey>> = HashMap::new();
1435    let mut worklist: Vec<(BasicBlock, Vec<PlaceKey>)> = vec![(start, origins)];
1436
1437    while let Some((block_index, incoming)) = worklist.pop() {
1438        let mut live_origins = match entry_states.get_mut(&block_index) {
1439            Some(known) => {
1440                let mut changed = false;
1441                for origin in &incoming {
1442                    if !known.contains(origin) {
1443                        known.push(origin.clone());
1444                        changed = true;
1445                    }
1446                }
1447                if !changed {
1448                    continue;
1449                }
1450                known.clone()
1451            }
1452            None => {
1453                entry_states.insert(block_index, incoming.clone());
1454                incoming
1455            }
1456        };
1457
1458        let block = &body.basic_blocks[block_index];
1459        for statement in &block.statements {
1460            match &statement.kind {
1461                StatementKind::Assign(assign) => {
1462                    let (target, rvalue) = assign.as_ref();
1463                    let target_key = PlaceKey::from_mir_place(target);
1464                    let is_deref_to_pointee = target_key.fields.is_empty()
1465                        && target
1466                            .projection
1467                            .iter()
1468                            .any(|p| matches!(p, ProjectionElem::Deref));
1469                    if !is_deref_to_pointee {
1470                        live_origins.retain(|origin| !place_key_is_prefix_of(&target_key, origin));
1471                    }
1472                    if place_is_raw_access_to_live_origin(target, &live_origins)
1473                        || rvalue_any_place_matching(rvalue, &mut |place| {
1474                            place_is_raw_access_to_live_origin(place, &live_origins)
1475                        })
1476                    {
1477                        return Some(
1478                            "raw pointer reused after ownership was transferred to an owning value"
1479                                .into(),
1480                        );
1481                    }
1482                    let copies_origin = rvalue_copies_live_origin_value(rvalue, &live_origins);
1483                    kill_strongly_updated_origins(&body.local_decls, target, &mut live_origins);
1484                    if copies_origin
1485                        && !target
1486                            .projection
1487                            .iter()
1488                            .any(|projection| matches!(projection, ProjectionElem::Deref))
1489                    {
1490                        let target_key = PlaceKey::from_mir_place(target);
1491                        if !live_origins.contains(&target_key) {
1492                            live_origins.push(target_key);
1493                        }
1494                    }
1495                }
1496                StatementKind::StorageDead(local) => {
1497                    live_origins
1498                        .retain(|origin| origin.base != PlaceBaseKey::Local(local.as_usize()));
1499                }
1500                _ => {}
1501            }
1502        }
1503
1504        let Some(terminator) = &block.terminator else {
1505            continue;
1506        };
1507        if terminator_uses_live_origin(&terminator.kind, &live_origins) {
1508            return Some(
1509                "raw pointer passed to another call after ownership was transferred".into(),
1510            );
1511        }
1512        if let TerminatorKind::Call {
1513            destination: call_destination,
1514            ..
1515        } = &terminator.kind
1516        {
1517            kill_strongly_updated_origins(&body.local_decls, call_destination, &mut live_origins);
1518        }
1519        if live_origins.is_empty() {
1520            continue;
1521        }
1522        for successor in terminator.successors() {
1523            worklist.push((successor, live_origins.clone()));
1524        }
1525    }
1526
1527    None
1528}
1529
1530fn places_holding_transferred_pointer(
1531    tcx: TyCtxt<'_>,
1532    caller: DefId,
1533    call_block: BasicBlock,
1534    origin_place: &PlaceKey,
1535) -> Vec<PlaceKey> {
1536    let body = tcx.optimized_mir(caller);
1537    let mut holders = vec![origin_place.clone()];
1538    let mut killed: HashSet<Local> = HashSet::new();
1539    let mut block_index = call_block;
1540
1541    loop {
1542        let block = &body.basic_blocks[block_index];
1543        for statement in block.statements.iter().rev() {
1544            let StatementKind::Assign(assign) = &statement.kind else {
1545                continue;
1546            };
1547            let (target, rvalue) = assign.as_ref();
1548            if target
1549                .projection
1550                .iter()
1551                .any(|projection| matches!(projection, ProjectionElem::Deref))
1552            {
1553                continue;
1554            }
1555            let target_key = PlaceKey::from_mir_place(target);
1556            let target_defines_holder =
1557                !killed.contains(&target.local) && holders.iter().any(|h| target_key.overlaps(h));
1558
1559            let source_place = crate::helpers::mir_utils::rvalue_source_place(rvalue);
1560
1561            if target_defines_holder {
1562                if let Some(source) = source_place
1563                    && !killed.contains(&source.local)
1564                {
1565                    let source_key = PlaceKey::from_mir_place(source);
1566                    for holder in holders.clone() {
1567                        if let Some(spliced) =
1568                            splice_holder_fields(&target_key, &holder, &source_key)
1569                            && !holders.contains(&spliced)
1570                        {
1571                            holders.push(spliced);
1572                        }
1573                    }
1574                }
1575            } else if let Some(source) = source_place
1576                && !killed.contains(&target.local)
1577                && !source
1578                    .projection
1579                    .iter()
1580                    .any(|projection| matches!(projection, ProjectionElem::Deref))
1581            {
1582                let source_key = PlaceKey::from_mir_place(source);
1583                if holders.iter().any(|h| source_key.overlaps(h)) && !holders.contains(&target_key)
1584                {
1585                    holders.push(target_key.clone());
1586                }
1587            }
1588            killed.insert(target.local);
1589        }
1590
1591        let predecessors = &body.basic_blocks.predecessors()[block_index];
1592        if predecessors.len() != 1 {
1593            break;
1594        }
1595        block_index = predecessors[0];
1596        let terminator = body.basic_blocks[block_index].terminator();
1597        if let TerminatorKind::Call {
1598            func,
1599            args,
1600            destination: call_destination,
1601            ..
1602        } = &terminator.kind
1603        {
1604            let destination_key = PlaceKey::from_mir_place(call_destination);
1605            if !killed.contains(&call_destination.local)
1606                && holders.iter().any(|h| destination_key.overlaps(h))
1607            {
1608                if crate::verify::api_classify::is_as_ptr(
1609                    crate::helpers::mir_utils::dep_callee_def_id(func),
1610                ) && let Some(arg) = args.first()
1611                    && let Operand::Copy(place) | Operand::Move(place) = &arg.node
1612                    && !killed.contains(&place.local)
1613                {
1614                    let key = PlaceKey::from_mir_place(place);
1615                    if !holders.contains(&key) {
1616                        holders.push(key);
1617                    }
1618                }
1619            }
1620            killed.insert(call_destination.local);
1621        }
1622    }
1623
1624    holders
1625}
1626
1627fn splice_holder_fields(
1628    target: &PlaceKey,
1629    holder: &PlaceKey,
1630    source: &PlaceKey,
1631) -> Option<PlaceKey> {
1632    if !place_key_is_prefix_of(target, holder) {
1633        return None;
1634    }
1635    let mut fields = source.fields.clone();
1636    fields.extend_from_slice(&holder.fields[target.fields.len()..]);
1637    Some(PlaceKey {
1638        base: source.base.clone(),
1639        fields,
1640    })
1641}
1642
1643fn kill_strongly_updated_origins(
1644    local_decls: &LocalDecls<'_>,
1645    target: &Place<'_>,
1646    live_origins: &mut Vec<PlaceKey>,
1647) {
1648    let deref_count = target
1649        .projection
1650        .iter()
1651        .filter(|p| matches!(p, ProjectionElem::Deref))
1652        .count();
1653    if deref_count == 0 {
1654        let target_key = PlaceKey::from_mir_place(target);
1655        live_origins.retain(|origin| !place_key_is_prefix_of(&target_key, origin));
1656        return;
1657    }
1658    if deref_count == 1 && matches!(target.projection[0], ProjectionElem::Deref) {
1659        let ty = local_decls[target.local].ty;
1660        if matches!(ty.kind(), ty::Ref(_, _, ty::Mutability::Mut)) {
1661            let target_key = PlaceKey::from_mir_place(target);
1662            live_origins.retain(|origin| !place_key_is_prefix_of(&target_key, origin));
1663        }
1664    }
1665}
1666
1667fn place_key_is_prefix_of(prefix: &PlaceKey, place: &PlaceKey) -> bool {
1668    prefix.base == place.base
1669        && prefix.fields.len() <= place.fields.len()
1670        && place.fields[..prefix.fields.len()] == prefix.fields[..]
1671}
1672
1673fn place_is_raw_access_to_live_origin(place: &Place<'_>, live_origins: &[PlaceKey]) -> bool {
1674    if !place
1675        .projection
1676        .iter()
1677        .any(|projection| matches!(projection, ProjectionElem::Deref))
1678    {
1679        return false;
1680    }
1681    let key = PlaceKey::from_mir_place(place);
1682    live_origins.iter().any(|origin| key.overlaps(origin))
1683}
1684
1685fn rvalue_copies_live_origin_value(rvalue: &Rvalue<'_>, live_origins: &[PlaceKey]) -> bool {
1686    let Some(place) = crate::helpers::mir_utils::rvalue_source_place(rvalue) else {
1687        return false;
1688    };
1689    if place
1690        .projection
1691        .iter()
1692        .any(|projection| matches!(projection, ProjectionElem::Deref))
1693    {
1694        return false;
1695    }
1696    let key = PlaceKey::from_mir_place(place);
1697    live_origins.iter().any(|origin| key.overlaps(origin))
1698}
1699
1700fn terminator_uses_live_origin(kind: &TerminatorKind<'_>, live_origins: &[PlaceKey]) -> bool {
1701    let TerminatorKind::Call { args, .. } = kind else {
1702        return false;
1703    };
1704    args.iter().any(|arg| {
1705        let Some(place) = operand_mir_place(&arg.node) else {
1706            return false;
1707        };
1708        let key = PlaceKey::from_mir_place(place);
1709        live_origins.iter().any(|origin| key.overlaps(origin))
1710    })
1711}
1712
1713fn terminator_returns_ownership(
1714    terminator: &TerminatorKind<'_>,
1715    owner_locals: &HashSet<Local>,
1716) -> bool {
1717    let TerminatorKind::Call { func, args, .. } = terminator else {
1718        return false;
1719    };
1720    if !crate::verify::api_classify::is_ownership_return(
1721        crate::helpers::mir_utils::dep_callee_def_id(func),
1722    ) {
1723        return false;
1724    }
1725    args.iter().any(|arg| match &arg.node {
1726        Operand::Copy(place) | Operand::Move(place) => owner_locals.contains(&place.local),
1727        _ => false,
1728    })
1729}
1730
1731fn pre_existing_view_on_origin(
1732    tcx: TyCtxt<'_>,
1733    caller: DefId,
1734    call_block: BasicBlock,
1735    reachable_after: &HashSet<BasicBlock>,
1736    origin_holders: &[PlaceKey],
1737) -> Option<String> {
1738    let body = tcx.optimized_mir(caller);
1739    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
1740
1741    let holder_origins: Vec<(usize, Vec<usize>)> = origin_holders
1742        .iter()
1743        .flat_map(|h| {
1744            if let PlaceBaseKey::Local(l) = h.base {
1745                let resolved = resolve_via_tree(&tree, Local::from_usize(l), &h.fields);
1746                if resolved.0 == 1 && !resolved.1.is_empty() {
1747                    Some(resolved)
1748                } else {
1749                    None
1750                }
1751            } else {
1752                None
1753            }
1754        })
1755        .collect();
1756
1757    for (bb, data) in body.basic_blocks.iter_enumerated() {
1758        if reachable_after.contains(&bb) || bb == call_block {
1759            continue;
1760        }
1761        let terminator = data.terminator();
1762        if let TerminatorKind::Call { func, args, .. } = &terminator.kind {
1763            let callee_name = crate::helpers::mir_utils::call_name(tcx, func);
1764            if crate::verify::api_classify::is_nonnull_as_ref_as_mut(
1765                crate::helpers::mir_utils::dep_callee_def_id(func),
1766            ) {
1767                if let Some(arg) = args.first()
1768                    && let Some(place) = operand_mir_place(&arg.node)
1769                {
1770                    let arg_resolved = resolve_via_tree(
1771                        &tree,
1772                        place.local,
1773                        &PlaceKey::from_mir_place(place).fields,
1774                    );
1775                    if arg_resolved.0 == 1
1776                        && !arg_resolved.1.is_empty()
1777                        && holder_origins
1778                            .iter()
1779                            .any(|(h, hf)| *h == arg_resolved.0 && *hf == arg_resolved.1)
1780                    {
1781                        return Some(format!(
1782                            "pre-existing view from {} aliases the ownership-transferred pointer",
1783                            callee_name,
1784                        ));
1785                    }
1786                }
1787            }
1788        }
1789
1790        for statement in &data.statements {
1791            let StatementKind::Assign(assign) = &statement.kind else {
1792                continue;
1793            };
1794            let (_target, rvalue) = assign.as_ref();
1795            let src_place: Option<&Place<'_>> = match rvalue {
1796                Rvalue::Ref(_, _, place) => Some(place),
1797                Rvalue::Cast(rustc_middle::mir::CastKind::PtrToPtr, operand, _) => {
1798                    match operand {
1799                        Operand::Copy(place) | Operand::Move(place) => Some(place),
1800                        _ => None,
1801                    }
1802                }
1803                _ => None,
1804            };
1805            let Some(place) = src_place else {
1806                continue;
1807            };
1808            if !place
1809                .projection
1810                .iter()
1811                .any(|p| matches!(p, ProjectionElem::Deref))
1812            {
1813                continue;
1814            }
1815            let resolved =
1816                resolve_via_tree(&tree, place.local, &PlaceKey::from_mir_place(place).fields);
1817            if resolved.0 == 1
1818                && !resolved.1.is_empty()
1819                && holder_origins
1820                    .iter()
1821                    .any(|(h, hf)| *h == resolved.0 && *hf == resolved.1)
1822            {
1823                return Some(
1824                    "pre-existing &*raw_ptr view aliases the ownership-transferred pointer".into(),
1825                );
1826            }
1827        }
1828    }
1829    None
1830}
1831
1832/// Resolve `(local, fields)` through the alias tree: a place that already names
1833/// a field path resolves to itself; a whole-local place resolves to its ultimate
1834/// `(root, fields)` origin.
1835fn resolve_via_tree(
1836    tree: &crate::verify::vm::alias_tree::AliasTree,
1837    local: Local,
1838    fields: &[usize],
1839) -> (usize, Vec<usize>) {
1840    if !fields.is_empty() {
1841        return (local.as_usize(), fields.to_vec());
1842    }
1843    tree.resolve_local_to_root(local)
1844}
1845
1846// ── Cross-crate callsite analysis ────────────────────────────────
1847
1848pub(super) fn private_fn_callsite_delegation(
1849    tcx: TyCtxt<'_>,
1850    caller: DefId,
1851    origin: &PlaceKey,
1852    kind: HazardKind,
1853) -> Option<String> {
1854    let param_index = param_index_of_origin(tcx, caller, origin)?;
1855    if is_externally_reachable(tcx, caller) {
1856        return None;
1857    }
1858    for site in local_callsites(tcx, caller) {
1859        let mut origins = callsite_arg_origins(tcx, site.caller, &site.args, param_index);
1860        if origins.is_empty() {
1861            continue;
1862        }
1863        let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, site.caller);
1864        let extra = find_as_ptr_receivers(tcx, site.caller, &origins, &tree, false);
1865        for place in extra {
1866            if !origins.contains(&place) {
1867                origins.push(place);
1868            }
1869        }
1870        if let Some(reason) = local_hazard_violation_with(
1871            tcx,
1872            site.caller,
1873            site.block,
1874            site.destination,
1875            &origins,
1876            kind,
1877            true,
1878            None,
1879        ) {
1880            return Some(format!(
1881                "call site `{}` conflicts with the returned view: {reason}",
1882                tcx.def_path_str(site.caller)
1883            ));
1884        }
1885    }
1886    None
1887}
1888
1889fn local_callsites(tcx: TyCtxt<'_>, callee: DefId) -> Vec<LocalCallsite<'_>> {
1890    let mut sites = Vec::new();
1891    for def_id in tcx.mir_keys(()) {
1892        let def_id = def_id.to_def_id();
1893        if def_id == callee {
1894            continue;
1895        }
1896        if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
1897            continue;
1898        }
1899        if !tcx.is_mir_available(def_id) {
1900            continue;
1901        }
1902        let body = tcx.optimized_mir(def_id);
1903        for (block, data) in body.basic_blocks.iter_enumerated() {
1904            let Some(terminator) = &data.terminator else {
1905                continue;
1906            };
1907            let TerminatorKind::Call {
1908                func,
1909                args,
1910                destination,
1911                ..
1912            } = &terminator.kind
1913            else {
1914                continue;
1915            };
1916            let Some(target) = crate::helpers::mir_utils::dep_callee_def_id(func) else {
1917                continue;
1918            };
1919            if target != callee {
1920                continue;
1921            }
1922            sites.push(LocalCallsite {
1923                caller: def_id,
1924                block,
1925                args: args.iter().map(|arg| arg.node.clone()).collect(),
1926                destination: Some(destination.local),
1927            });
1928        }
1929    }
1930    sites
1931}
1932
1933fn callsite_arg_origins(
1934    tcx: TyCtxt<'_>,
1935    caller: DefId,
1936    args: &[Operand<'_>],
1937    param_index: usize,
1938) -> Vec<PlaceKey> {
1939    let Some(arg) = args.get(param_index) else {
1940        return Vec::new();
1941    };
1942    let Some(place) = (match arg {
1943        Operand::Copy(place) | Operand::Move(place) => Some(PlaceKey::from_mir_place(place)),
1944        _ => None,
1945    }) else {
1946        return Vec::new();
1947    };
1948    let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
1949    let mut origins = vec![place.clone()];
1950    if let Some(local) = place.local()
1951        && tree.tag_of(local).is_some()
1952    {
1953        let (root, fields) = tree.resolve_local_to_root(local);
1954        let alias = PlaceKey::from_origin(root, fields);
1955        if !origins.contains(&alias) {
1956            origins.push(alias);
1957        }
1958    }
1959    origins
1960}