Skip to main content

rapx/verify/
alias_hazard.rs

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