rapx/verify/smt_check/
alias.rs

1//! Alias hazard checks for unsafe view-producing APIs.
2//!
3//! `Alias` is more stateful than numeric SPs such as `Align`: calls like
4//! `from_raw_parts_mut` create a view whose lifetime constrains later uses of
5//! the original raw pointer.  This module handles the first, deliberately small
6//! slice-view model:
7//!
8//! - a local view only checks later uses in the same function;
9//! - an escaped view from `self.field` checks whether the same struct still
10//!   exposes or writes through that raw field via safe methods or public fields.
11
12use std::collections::{HashMap, HashSet};
13
14use rustc_hir::{Safety, def::DefKind, def_id::DefId};
15use rustc_middle::{
16    mir::{
17        BasicBlock, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind, TerminatorKind,
18    },
19    ty::{self, AssocKind, GenericArgsRef, Ty, TyCtxt, TyKind},
20};
21
22use crate::{
23    helpers::mir_scan::check_safety,
24    verify::{
25        def_use::{PlaceBaseKey, PlaceKey},
26        helpers::Checkpoint,
27        primitive::PrimitiveCall,
28        report::CheckResult,
29        verifier::{AbstractValue, ForwardVisitResult, StateFact},
30    },
31};
32
33use super::common::{SmtCheckResult, SmtChecker};
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36enum HazardKind {
37    SharedView,
38    UniqueView,
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42enum AliasProducer {
43    View(HazardKind),
44    OwnershipTransfer,
45}
46
47#[derive(Clone, Debug)]
48struct SelfFieldOrigin {
49    struct_def_id: DefId,
50    field_index: usize,
51    field_name: String,
52}
53
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55enum RawAccessKind {
56    Read,
57    Write,
58}
59
60/// Check the path-sensitive / escaped hazard part of `Alias`.
61pub fn check<'tcx>(
62    checker: &SmtChecker<'tcx>,
63    checkpoint: &Checkpoint<'tcx>,
64    _forward_property: &crate::verify::contract::Property<'tcx>,
65    forward: &ForwardVisitResult<'tcx>,
66) -> SmtCheckResult {
67    let Some(callee) = checkpoint.callee else {
68        return SmtCheckResult::unknown("Alias target callee could not be resolved");
69    };
70    let callee_name = checker.tcx.def_path_str(callee);
71    let Some(producer) = alias_producer(callee_name.as_str()) else {
72        return SmtCheckResult::unknown(
73            "Alias lowering currently supports view producers and ownership-transfer APIs",
74        );
75    };
76
77    let Some(origin_arg) = checkpoint.args.first() else {
78        return SmtCheckResult::unknown("Alias producer has no pointer argument");
79    };
80    let Some(origin_place) = operand_place(origin_arg) else {
81        return SmtCheckResult::unknown("Alias pointer argument is not a MIR place");
82    };
83    let origin = resolve_forward_place(origin_place.clone(), forward);
84    let mut local_origins = vec![origin_place];
85    if !local_origins.contains(&origin) {
86        local_origins.push(origin.clone());
87    }
88    let destination = call_destination(checker.tcx, checkpoint);
89
90    let AliasProducer::View(kind) = producer else {
91        if let Some(reason) = ownership_transfer_violation(
92            checker.tcx,
93            checkpoint.caller,
94            checkpoint.block,
95            destination,
96            &local_origins,
97        ) {
98            return failed(reason);
99        }
100        return SmtCheckResult::proved(
101            "ownership-transfer API consumes the raw pointer and no later raw reuse was found",
102        );
103    };
104
105    if origin.base == PlaceBaseKey::Local(1) && origin.fields.is_empty() {
106        return SmtCheckResult::proved(
107            "returned view reinterprets the self parameter; no hidden raw-pointer conflict",
108        );
109    }
110
111    if let Some(reason) = local_hazard_violation(
112        checker.tcx,
113        checkpoint.caller,
114        checkpoint.block,
115        destination,
116        &local_origins,
117        kind,
118    ) {
119        return failed(reason);
120    }
121
122    if !destination_flows_to_return(checker.tcx, checkpoint.caller, destination) {
123        return SmtCheckResult::proved(
124            "Alias hazard is local and no conflicting raw access was found after the view producer",
125        );
126    }
127
128    if let Some(origin) = self_field_origin(checker.tcx, checkpoint.caller, &origin) {
129        if let Some(reason) = escaped_self_field_violation(checker.tcx, checkpoint.caller, &origin)
130        {
131            return failed(reason);
132        }
133        return SmtCheckResult::proved(format!(
134            "returned view is backed by private field `{}` and no safe raw-field breaker was found",
135            origin.field_name
136        ));
137    }
138
139    if origin.base == PlaceBaseKey::Local(1) && origin.fields.is_empty() {
140        return SmtCheckResult::proved(
141            "returned view reinterprets the self parameter; no hidden raw-pointer conflict",
142        );
143    }
144
145    let err_msg = format!(
146        "returned view escapes while the original pointer is not owned by a private self field [origin={:?}]",
147        origin
148    );
149    failed(err_msg)
150}
151
152fn failed(note: impl Into<String>) -> SmtCheckResult {
153    SmtCheckResult {
154        result: CheckResult::Failed,
155        query: None,
156        notes: vec![note.into()],
157    }
158}
159
160fn alias_producer(name: &str) -> Option<AliasProducer> {
161    if name.contains("from_raw_parts_mut") {
162        return Some(AliasProducer::View(HazardKind::UniqueView));
163    }
164    if name.contains("from_raw_parts") || name.contains("from_parts") || name.contains("from_ptr") {
165        if is_vec_ownership_transfer_api(name) {
166            return Some(AliasProducer::OwnershipTransfer);
167        }
168        return Some(AliasProducer::View(HazardKind::SharedView));
169    }
170    if is_ownership_transfer_api(name) {
171        return Some(AliasProducer::OwnershipTransfer);
172    }
173    None
174}
175
176fn is_ownership_transfer_api(name: &str) -> bool {
177    if is_vec_ownership_transfer_api(name) {
178        return true;
179    }
180    let is_from_raw = name.contains("from_raw");
181    is_from_raw
182        && (name.contains("boxed")
183            || name.contains("Box")
184            || name.contains("ffi::c_str")
185            || name.contains("CString")
186            || is_vec_ownership_transfer_api(name))
187}
188
189fn is_vec_ownership_transfer_api(name: &str) -> bool {
190    (name.contains("from_raw_parts") || name.contains("from_parts"))
191        && (name.contains("Vec") || name.contains("vec::"))
192}
193
194fn operand_place(operand: &Operand<'_>) -> Option<PlaceKey> {
195    match operand {
196        Operand::Copy(place) | Operand::Move(place) => Some(PlaceKey::from_mir_place(place)),
197        Operand::Constant(_) => None,
198        #[cfg(rapx_rustc_ge_196)]
199        Operand::RuntimeChecks(_) => None,
200    }
201}
202
203fn call_destination<'tcx>(tcx: TyCtxt<'tcx>, checkpoint: &Checkpoint<'tcx>) -> Option<Local> {
204    let body = tcx.optimized_mir(checkpoint.caller);
205    let terminator = body.basic_blocks[checkpoint.block].terminator();
206    let TerminatorKind::Call { destination, .. } = &terminator.kind else {
207        return None;
208    };
209    Some(destination.local)
210}
211
212fn resolve_forward_place<'tcx>(
213    mut place: PlaceKey,
214    forward: &ForwardVisitResult<'tcx>,
215) -> PlaceKey {
216    let mut seen = HashSet::new();
217    loop {
218        if !seen.insert(place.clone()) {
219            return place;
220        }
221        let Some(local) = place.local() else {
222            return place;
223        };
224        let Some(value) = forward.values.get(&local) else {
225            return place;
226        };
227        match value {
228            AbstractValue::Place(next) | AbstractValue::Ref(next) | AbstractValue::RawPtr(next) => {
229                place = next.clone();
230            }
231            AbstractValue::Cast(inner, _) => match inner.as_ref() {
232                AbstractValue::Place(next)
233                | AbstractValue::Ref(next)
234                | AbstractValue::RawPtr(next) => place = next.clone(),
235                _ => return place,
236            },
237            AbstractValue::CallResult(call)
238                if PrimitiveCall::classify(&call.func)
239                    .is_some_and(PrimitiveCall::is_as_ptr_like) =>
240            {
241                let Some(source) = forward.facts.iter().find_map(|fact| match fact {
242                    StateFact::PointsTo { pointer, source } if pointer.overlaps(&place) => {
243                        Some(source.clone())
244                    }
245                    _ => None,
246                }) else {
247                    return place;
248                };
249                place = resolve_forward_place(source, forward);
250            }
251            _ => return place,
252        }
253    }
254}
255
256fn destination_flows_to_return<'tcx>(
257    tcx: TyCtxt<'tcx>,
258    caller: DefId,
259    destination: Option<Local>,
260) -> bool {
261    let Some(destination) = destination else {
262        return false;
263    };
264    if destination.as_usize() == 0 {
265        return true;
266    }
267
268    let body = tcx.optimized_mir(caller);
269    if body.local_decls[Local::from_usize(0)].ty == body.local_decls[destination].ty {
270        return true;
271    }
272
273    let mut aliases: HashMap<Local, PlaceKey> = HashMap::new();
274    aliases.insert(
275        destination,
276        PlaceKey {
277            base: PlaceBaseKey::Local(destination.as_usize()),
278            fields: Vec::new(),
279        },
280    );
281
282    for block in body.basic_blocks.iter() {
283        for statement in &block.statements {
284            let StatementKind::Assign(assign) = &statement.kind else {
285                continue;
286            };
287            let (target, rvalue) = assign.as_ref();
288            if target.local.as_usize() != 0 {
289                continue;
290            }
291            if rvalue_mentions_local(rvalue, destination, &aliases) {
292                return true;
293            }
294        }
295    }
296    false
297}
298
299fn rvalue_mentions_local<'tcx>(
300    rvalue: &Rvalue<'tcx>,
301    local: Local,
302    aliases: &HashMap<Local, PlaceKey>,
303) -> bool {
304    match rvalue {
305        Rvalue::Use(Operand::Copy(place), ..)
306        | Rvalue::Use(Operand::Move(place), ..)
307        | Rvalue::Cast(_, Operand::Copy(place), _)
308        | Rvalue::Cast(_, Operand::Move(place), _)
309        | Rvalue::CopyForDeref(place) => place.local == local || aliases.contains_key(&place.local),
310        Rvalue::Aggregate(_, operands) => operands.iter().any(|operand| match operand {
311            Operand::Copy(place) | Operand::Move(place) => {
312                place.local == local || aliases.contains_key(&place.local)
313            }
314            Operand::Constant(_) => false,
315            #[cfg(rapx_rustc_ge_196)]
316            Operand::RuntimeChecks(_) => false,
317        }),
318        _ => false,
319    }
320}
321
322fn local_hazard_violation<'tcx>(
323    tcx: TyCtxt<'tcx>,
324    caller: DefId,
325    call_block: BasicBlock,
326    destination: Option<Local>,
327    origins: &[PlaceKey],
328    kind: HazardKind,
329) -> Option<String> {
330    let body = tcx.optimized_mir(caller);
331    let mut aliases = collect_place_aliases(tcx, caller);
332    let mut origins = origins.to_vec();
333    expand_origin_aliases(&aliases, &mut origins);
334    let mut hazard_locals: HashSet<Local> = destination.into_iter().collect();
335    expand_hazard_alias_locals(tcx, caller, &mut hazard_locals);
336    let vec_owners = vec_owners_for_origins(tcx, caller, &origins, &aliases);
337    let reachable = blocks_reachable_after_call(tcx, caller, call_block);
338
339    for (block_index, block) in body.basic_blocks.iter_enumerated() {
340        if !reachable.contains(&block_index) {
341            continue;
342        }
343
344        for (statement_index, statement) in block.statements.iter().enumerate() {
345            match &statement.kind {
346                StatementKind::StorageDead(local) => {
347                    hazard_locals.remove(local);
348                }
349                StatementKind::Assign(assign) => {
350                    let (target, rvalue) = assign.as_ref();
351                    if rvalue_mentions_any_local(rvalue, &hazard_locals) {
352                        hazard_locals.insert(target.local);
353                    }
354                    if let Some(alias) = alias_from_rvalue(tcx, caller, rvalue, &aliases) {
355                        aliases.insert(target.local, alias);
356                    }
357                    if !hazard_locals.is_empty()
358                        && raw_access_conflicts(kind, RawAccessKind::Write)
359                        && place_is_raw_access_to_any_origin(target, &origins, &aliases)
360                        && hazard_used_after_statement(
361                            tcx,
362                            caller,
363                            block_index,
364                            statement_index,
365                            &hazard_locals,
366                        )
367                    {
368                        return Some(format!(
369                            "raw write through original pointer after {:?} view creation",
370                            kind
371                        ));
372                    }
373                    if !hazard_locals.is_empty()
374                        && raw_access_conflicts(kind, RawAccessKind::Read)
375                        && rvalue_reads_any_origin(rvalue, &origins, &aliases)
376                        && hazard_used_after_statement(
377                            tcx,
378                            caller,
379                            block_index,
380                            statement_index,
381                            &hazard_locals,
382                        )
383                    {
384                        return Some(format!(
385                            "raw read through original pointer after {:?} view creation",
386                            kind
387                        ));
388                    }
389                }
390                _ => {}
391            }
392        }
393
394        if !hazard_locals.is_empty() {
395            let Some(terminator) = &block.terminator else {
396                continue;
397            };
398            if origins.iter().any(|origin| {
399                terminator_writes_origin(tcx, caller, &terminator.kind, origin, &aliases)
400            }) && hazard_used_after_block(tcx, caller, block_index, &hazard_locals)
401            {
402                return Some(format!(
403                    "raw write call through original pointer after {:?} view creation",
404                    kind
405                ));
406            }
407            if kind == HazardKind::UniqueView
408                && !vec_owners.is_empty()
409                && terminator_invalidates_vec_owner(
410                    tcx,
411                    caller,
412                    &terminator.kind,
413                    &vec_owners,
414                    &aliases,
415                )
416                && hazard_used_after_block(tcx, caller, block_index, &hazard_locals)
417            {
418                return Some(
419                    "Vec may reallocate while a raw-derived mutable view is still live".to_string(),
420                );
421            }
422        }
423    }
424
425    None
426}
427
428fn ownership_transfer_violation<'tcx>(
429    tcx: TyCtxt<'tcx>,
430    caller: DefId,
431    call_block: BasicBlock,
432    destination: Option<Local>,
433    origins: &[PlaceKey],
434) -> Option<String> {
435    let body = tcx.optimized_mir(caller);
436    let aliases = collect_place_aliases(tcx, caller);
437    let mut origins = origins.to_vec();
438    expand_origin_aliases(&aliases, &mut origins);
439    let mut owner_locals: HashSet<Local> = destination.into_iter().collect();
440    expand_hazard_alias_locals(tcx, caller, &mut owner_locals);
441    let reachable = blocks_reachable_after_call(tcx, caller, call_block);
442
443    for (block_index, block) in body.basic_blocks.iter_enumerated() {
444        if !reachable.contains(&block_index) {
445            continue;
446        }
447
448        for statement in &block.statements {
449            let StatementKind::Assign(assign) = &statement.kind else {
450                continue;
451            };
452            let (target, rvalue) = assign.as_ref();
453            if place_is_raw_access_to_any_origin(target, &origins, &aliases)
454                || rvalue_reads_any_origin(rvalue, &origins, &aliases)
455            {
456                return Some(
457                    "raw pointer reused after ownership was transferred to an owning value"
458                        .to_string(),
459                );
460            }
461        }
462
463        let Some(terminator) = &block.terminator else {
464            continue;
465        };
466        if terminator_returns_ownership(tcx, &terminator.kind, &owner_locals) {
467            return None;
468        }
469        if origins
470            .iter()
471            .any(|origin| terminator_uses_origin(tcx, caller, &terminator.kind, origin, &aliases))
472        {
473            return Some(
474                "raw pointer passed to another call after ownership was transferred".to_string(),
475            );
476        }
477    }
478
479    None
480}
481
482fn expand_origin_aliases(aliases: &HashMap<Local, PlaceKey>, origins: &mut Vec<PlaceKey>) {
483    let mut changed = true;
484    while changed {
485        changed = false;
486        for (local, alias) in aliases {
487            let local_key = PlaceKey {
488                base: PlaceBaseKey::Local(local.as_usize()),
489                fields: Vec::new(),
490            };
491
492            let related = origins.iter().any(|origin| {
493                local_key.overlaps(origin)
494                    || origin.overlaps(&local_key)
495                    || alias.overlaps(origin)
496                    || origin.overlaps(alias)
497            });
498            if !related {
499                continue;
500            }
501
502            if !origins.contains(&local_key) {
503                origins.push(local_key);
504                changed = true;
505            }
506            if !origins.contains(alias) {
507                origins.push(alias.clone());
508                changed = true;
509            }
510        }
511    }
512}
513
514fn expand_hazard_alias_locals<'tcx>(
515    tcx: TyCtxt<'tcx>,
516    caller: DefId,
517    hazard_locals: &mut HashSet<Local>,
518) {
519    let body = tcx.optimized_mir(caller);
520    let mut changed = true;
521    while changed {
522        changed = false;
523        for block in body.basic_blocks.iter() {
524            for statement in &block.statements {
525                let StatementKind::Assign(assign) = &statement.kind else {
526                    continue;
527                };
528                let (target, rvalue) = assign.as_ref();
529                if rvalue_mentions_any_local(rvalue, hazard_locals)
530                    && hazard_locals.insert(target.local)
531                {
532                    changed = true;
533                }
534            }
535        }
536    }
537}
538
539fn hazard_used_after_statement<'tcx>(
540    tcx: TyCtxt<'tcx>,
541    caller: DefId,
542    block: BasicBlock,
543    statement_index: usize,
544    hazard_locals: &HashSet<Local>,
545) -> bool {
546    let body = tcx.optimized_mir(caller);
547    let data = &body.basic_blocks[block];
548    for statement in data.statements.iter().skip(statement_index + 1) {
549        if statement_uses_any_local(statement, hazard_locals) {
550            return true;
551        }
552    }
553    let terminator = data.terminator();
554    if terminator_uses_any_local(&terminator.kind, hazard_locals) {
555        return true;
556    }
557    hazard_used_after_block(tcx, caller, block, hazard_locals)
558}
559
560fn hazard_used_after_block<'tcx>(
561    tcx: TyCtxt<'tcx>,
562    caller: DefId,
563    start: BasicBlock,
564    hazard_locals: &HashSet<Local>,
565) -> bool {
566    let body = tcx.optimized_mir(caller);
567    let mut seen = HashSet::new();
568    let mut stack: Vec<_> = body.basic_blocks[start].terminator().successors().collect();
569
570    while let Some(block) = stack.pop() {
571        if !seen.insert(block) {
572            continue;
573        }
574        let data = &body.basic_blocks[block];
575        for statement in &data.statements {
576            if statement_uses_any_local(statement, hazard_locals) {
577                return true;
578            }
579        }
580        let terminator = data.terminator();
581        if terminator_uses_any_local(&terminator.kind, hazard_locals) {
582            return true;
583        }
584        stack.extend(terminator.successors());
585    }
586
587    false
588}
589
590fn statement_uses_any_local<'tcx>(
591    statement: &rustc_middle::mir::Statement<'tcx>,
592    locals: &HashSet<Local>,
593) -> bool {
594    let StatementKind::Assign(assign) = &statement.kind else {
595        return false;
596    };
597    let (target, rvalue) = assign.as_ref();
598    locals.contains(&target.local) || rvalue_mentions_any_local(rvalue, locals)
599}
600
601fn terminator_uses_any_local<'tcx>(
602    terminator: &TerminatorKind<'tcx>,
603    locals: &HashSet<Local>,
604) -> bool {
605    match terminator {
606        TerminatorKind::Call { args, .. } => args.iter().any(|arg| match &arg.node {
607            Operand::Copy(place) | Operand::Move(place) => locals.contains(&place.local),
608            Operand::Constant(_) => false,
609            #[cfg(rapx_rustc_ge_196)]
610            Operand::RuntimeChecks(_) => false,
611        }),
612        TerminatorKind::SwitchInt { discr, .. } | TerminatorKind::Assert { cond: discr, .. } => {
613            match discr {
614                Operand::Copy(place) | Operand::Move(place) => locals.contains(&place.local),
615                Operand::Constant(_) => false,
616                #[cfg(rapx_rustc_ge_196)]
617                Operand::RuntimeChecks(_) => false,
618            }
619        }
620        TerminatorKind::Drop { place, .. } => locals.contains(&place.local),
621        _ => false,
622    }
623}
624
625fn rvalue_mentions_any_local<'tcx>(rvalue: &Rvalue<'tcx>, locals: &HashSet<Local>) -> bool {
626    match rvalue {
627        Rvalue::Use(Operand::Copy(place), ..)
628        | Rvalue::Use(Operand::Move(place), ..)
629        | Rvalue::Cast(_, Operand::Copy(place), _)
630        | Rvalue::Cast(_, Operand::Move(place), _)
631        | Rvalue::Ref(_, _, place)
632        | Rvalue::RawPtr(_, place)
633        | Rvalue::CopyForDeref(place) => locals.contains(&place.local),
634        Rvalue::Aggregate(_, operands) => operands.iter().any(|operand| match operand {
635            Operand::Copy(place) | Operand::Move(place) => locals.contains(&place.local),
636            Operand::Constant(_) => false,
637            #[cfg(rapx_rustc_ge_196)]
638            Operand::RuntimeChecks(_) => false,
639        }),
640        _ => false,
641    }
642}
643
644fn blocks_reachable_after_call<'tcx>(
645    tcx: TyCtxt<'tcx>,
646    caller: DefId,
647    call_block: BasicBlock,
648) -> HashSet<BasicBlock> {
649    let body = tcx.optimized_mir(caller);
650    let mut starts = Vec::new();
651    if let TerminatorKind::Call { target, .. } = &body.basic_blocks[call_block].terminator().kind
652        && let Some(target) = target
653    {
654        starts.push(*target);
655    }
656
657    let mut seen = HashSet::new();
658    let mut stack = starts;
659    while let Some(block) = stack.pop() {
660        if !seen.insert(block) {
661            continue;
662        }
663        let terminator = body.basic_blocks[block].terminator();
664        for successor in terminator.successors() {
665            stack.push(successor);
666        }
667    }
668    seen
669}
670
671fn raw_access_conflicts(kind: HazardKind, access: RawAccessKind) -> bool {
672    match kind {
673        HazardKind::SharedView => access == RawAccessKind::Write,
674        HazardKind::UniqueView => true,
675    }
676}
677
678fn self_field_origin<'tcx>(
679    tcx: TyCtxt<'tcx>,
680    caller: DefId,
681    place: &PlaceKey,
682) -> Option<SelfFieldOrigin> {
683    let PlaceBaseKey::Local(local) = place.base else {
684        return None;
685    };
686    if local != 1 || place.fields.is_empty() {
687        return None;
688    }
689    let body = tcx.optimized_mir(caller);
690    let self_ty = body.local_decls[Local::from_usize(1)].ty;
691    let (struct_def_id, _) = adt_from_receiver_ty(self_ty)?;
692    let field_index = place.fields[0];
693    let adt = tcx.adt_def(struct_def_id);
694    let field = adt.all_fields().nth(field_index)?;
695    Some(SelfFieldOrigin {
696        struct_def_id,
697        field_index,
698        field_name: field.name.to_string(),
699    })
700}
701
702fn escaped_self_field_violation<'tcx>(
703    tcx: TyCtxt<'tcx>,
704    current: DefId,
705    origin: &SelfFieldOrigin,
706) -> Option<String> {
707    if public_raw_field(tcx, origin) {
708        return Some(format!(
709            "returned view escapes while raw field `{}` is public",
710            origin.field_name
711        ));
712    }
713
714    for impl_def_id in impls_for_struct(tcx, origin.struct_def_id) {
715        for item in tcx.associated_item_def_ids(impl_def_id) {
716            if *item == current {
717                continue;
718            }
719            if !matches!(tcx.def_kind(*item), DefKind::Fn | DefKind::AssocFn) {
720                continue;
721            }
722            if check_safety(tcx, *item) == Safety::Unsafe {
723                continue;
724            }
725            let Some(assoc) = tcx.opt_associated_item(*item) else {
726                continue;
727            };
728            if !matches!(assoc.kind, AssocKind::Fn { has_self: true, .. }) {
729                continue;
730            }
731            if !tcx.is_mir_available(*item) {
732                continue;
733            }
734
735            if method_writes_self_field(tcx, *item, origin.field_index) {
736                return Some(format!(
737                    "safe method `{}` writes through raw field `{}`",
738                    tcx.def_path_str(*item),
739                    origin.field_name
740                ));
741            }
742            if method_exposes_self_field(tcx, *item, origin.field_index) {
743                return Some(format!(
744                    "safe method `{}` exposes raw field `{}`",
745                    tcx.def_path_str(*item),
746                    origin.field_name
747                ));
748            }
749        }
750    }
751
752    None
753}
754
755fn impls_for_struct(tcx: TyCtxt<'_>, struct_def_id: DefId) -> Vec<DefId> {
756    let mut impls = tcx
757        .inherent_impls(struct_def_id)
758        .iter()
759        .copied()
760        .collect::<Vec<_>>();
761
762    for item_id in tcx.hir_crate_items(()).free_items() {
763        let item = tcx.hir_item(item_id);
764        let rustc_hir::ItemKind::Impl(impl_details) = &item.kind else {
765            continue;
766        };
767        let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) =
768            &impl_details.self_ty.kind
769        else {
770            continue;
771        };
772        let rustc_hir::def::Res::Def(_, def_id) = path.res else {
773            continue;
774        };
775        if def_id != struct_def_id {
776            continue;
777        }
778        let impl_def_id = item_id.owner_id.to_def_id();
779        if !impls.contains(&impl_def_id) {
780            impls.push(impl_def_id);
781        }
782    }
783
784    impls
785}
786
787fn public_raw_field<'tcx>(tcx: TyCtxt<'tcx>, origin: &SelfFieldOrigin) -> bool {
788    let adt = tcx.adt_def(origin.struct_def_id);
789    let Some(field) = adt.all_fields().nth(origin.field_index) else {
790        return false;
791    };
792    if !field.vis.is_public() {
793        return false;
794    }
795    let args = ty::GenericArgs::identity_for_item(tcx, origin.struct_def_id);
796    #[cfg(not(rapx_rustc_ge_198))]
797    let field_ty = field.ty(tcx, args);
798    #[cfg(rapx_rustc_ge_198)]
799    let field_ty = field.ty(tcx, args).skip_norm_wip();
800    is_raw_pointer_ty(field_ty)
801}
802
803fn method_writes_self_field<'tcx>(tcx: TyCtxt<'tcx>, method: DefId, field_index: usize) -> bool {
804    let body = tcx.optimized_mir(method);
805    let aliases = collect_place_aliases(tcx, method);
806    let origin = self_field_key(field_index);
807
808    for block in body.basic_blocks.iter() {
809        for statement in &block.statements {
810            let StatementKind::Assign(assign) = &statement.kind else {
811                continue;
812            };
813            let (target, _) = assign.as_ref();
814            if place_is_raw_access_to_origin(target, &origin, &aliases)
815                || place_raw_accesses_self_field(tcx, method, target, field_index)
816            {
817                return true;
818            }
819        }
820
821        let Some(terminator) = &block.terminator else {
822            continue;
823        };
824        if terminator_writes_origin(tcx, method, &terminator.kind, &origin, &aliases) {
825            return true;
826        }
827    }
828
829    false
830}
831
832fn place_raw_accesses_self_field<'tcx>(
833    tcx: TyCtxt<'tcx>,
834    method: DefId,
835    place: &Place<'tcx>,
836    field_index: usize,
837) -> bool {
838    if !place
839        .projection
840        .iter()
841        .any(|projection| matches!(projection, ProjectionElem::Deref))
842    {
843        return false;
844    }
845    local_traces_to_self_field(tcx, method, place.local, field_index, &mut HashSet::new())
846}
847
848fn local_traces_to_self_field<'tcx>(
849    tcx: TyCtxt<'tcx>,
850    method: DefId,
851    local: Local,
852    field_index: usize,
853    seen: &mut HashSet<Local>,
854) -> bool {
855    if !seen.insert(local) {
856        return false;
857    }
858    let body = tcx.optimized_mir(method);
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 target.local != local {
866                continue;
867            }
868            let Some(source) = rvalue_source_place(rvalue) else {
869                continue;
870            };
871            let source_key = PlaceKey::from_mir_place(source);
872            if source_key.base == PlaceBaseKey::Local(1)
873                && source_key.fields.first() == Some(&field_index)
874            {
875                return true;
876            }
877            if source_key.fields.is_empty()
878                && local_traces_to_self_field(tcx, method, source.local, field_index, seen)
879            {
880                return true;
881            }
882        }
883    }
884    false
885}
886
887fn rvalue_source_place<'a, 'tcx>(rvalue: &'a Rvalue<'tcx>) -> Option<&'a Place<'tcx>> {
888    match rvalue {
889        Rvalue::Use(Operand::Copy(place), ..)
890        | Rvalue::Use(Operand::Move(place), ..)
891        | Rvalue::Cast(_, Operand::Copy(place), _)
892        | Rvalue::Cast(_, Operand::Move(place), _)
893        | Rvalue::Ref(_, _, place)
894        | Rvalue::RawPtr(_, place)
895        | Rvalue::CopyForDeref(place) => Some(place),
896        _ => None,
897    }
898}
899
900fn method_exposes_self_field<'tcx>(tcx: TyCtxt<'tcx>, method: DefId, field_index: usize) -> bool {
901    let body = tcx.optimized_mir(method);
902    let aliases = collect_place_aliases(tcx, method);
903    let origin = self_field_key(field_index);
904
905    for block in body.basic_blocks.iter() {
906        for statement in &block.statements {
907            let StatementKind::Assign(assign) = &statement.kind else {
908                continue;
909            };
910            let (target, rvalue) = assign.as_ref();
911            if target.local.as_usize() == 0 && rvalue_mentions_origin(rvalue, &origin, &aliases) {
912                return true;
913            }
914        }
915    }
916
917    false
918}
919
920fn self_field_key(field_index: usize) -> PlaceKey {
921    PlaceKey {
922        base: PlaceBaseKey::Local(1),
923        fields: vec![field_index],
924    }
925}
926
927fn collect_place_aliases<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> HashMap<Local, PlaceKey> {
928    let body = tcx.optimized_mir(def_id);
929    let mut aliases = HashMap::new();
930
931    for block in body.basic_blocks.iter() {
932        for statement in &block.statements {
933            let StatementKind::Assign(assign) = &statement.kind else {
934                continue;
935            };
936            let (target, rvalue) = assign.as_ref();
937            if let Some(alias) = alias_from_rvalue(tcx, def_id, rvalue, &aliases) {
938                aliases.insert(target.local, alias);
939            }
940        }
941    }
942
943    aliases
944}
945
946fn alias_from_rvalue<'tcx>(
947    tcx: TyCtxt<'tcx>,
948    def_id: DefId,
949    rvalue: &Rvalue<'tcx>,
950    aliases: &HashMap<Local, PlaceKey>,
951) -> Option<PlaceKey> {
952    let place = match rvalue {
953        Rvalue::Use(Operand::Copy(place), ..)
954        | Rvalue::Use(Operand::Move(place), ..)
955        | Rvalue::Cast(_, Operand::Copy(place), _)
956        | Rvalue::Cast(_, Operand::Move(place), _)
957        | Rvalue::Ref(_, _, place)
958        | Rvalue::RawPtr(_, place)
959        | Rvalue::CopyForDeref(place) => Some(place),
960        _ => None,
961    }?;
962    Some(resolve_mir_place(tcx, def_id, place, aliases))
963}
964
965fn resolve_mir_place<'tcx>(
966    _tcx: TyCtxt<'tcx>,
967    _def_id: DefId,
968    place: &Place<'tcx>,
969    aliases: &HashMap<Local, PlaceKey>,
970) -> PlaceKey {
971    let key = PlaceKey::from_mir_place(place);
972    if !key.fields.is_empty() {
973        return key;
974    }
975    aliases.get(&place.local).cloned().unwrap_or(key)
976}
977
978fn place_is_raw_access_to_origin<'tcx>(
979    place: &Place<'tcx>,
980    origin: &PlaceKey,
981    aliases: &HashMap<Local, PlaceKey>,
982) -> bool {
983    if !place
984        .projection
985        .iter()
986        .any(|projection| matches!(projection, ProjectionElem::Deref))
987    {
988        return false;
989    }
990    let pointer = aliases
991        .get(&place.local)
992        .cloned()
993        .unwrap_or_else(|| PlaceKey::from_mir_place(place));
994    pointer.overlaps(origin)
995}
996
997fn place_is_raw_access_to_any_origin<'tcx>(
998    place: &Place<'tcx>,
999    origins: &[PlaceKey],
1000    aliases: &HashMap<Local, PlaceKey>,
1001) -> bool {
1002    origins
1003        .iter()
1004        .any(|origin| place_is_raw_access_to_origin(place, origin, aliases))
1005}
1006
1007fn rvalue_reads_any_origin<'tcx>(
1008    rvalue: &Rvalue<'tcx>,
1009    origins: &[PlaceKey],
1010    aliases: &HashMap<Local, PlaceKey>,
1011) -> bool {
1012    match rvalue {
1013        Rvalue::Use(Operand::Copy(place), ..)
1014        | Rvalue::Use(Operand::Move(place), ..)
1015        | Rvalue::Cast(_, Operand::Copy(place), _)
1016        | Rvalue::Cast(_, Operand::Move(place), _)
1017        | Rvalue::CopyForDeref(place) => place_is_raw_access_to_any_origin(place, origins, aliases),
1018        Rvalue::Aggregate(_, operands) => operands.iter().any(|operand| match operand {
1019            Operand::Copy(place) | Operand::Move(place) => {
1020                place_is_raw_access_to_any_origin(place, origins, aliases)
1021            }
1022            Operand::Constant(_) => false,
1023            #[cfg(rapx_rustc_ge_196)]
1024            Operand::RuntimeChecks(_) => false,
1025        }),
1026        _ => false,
1027    }
1028}
1029
1030fn rvalue_mentions_origin<'tcx>(
1031    rvalue: &Rvalue<'tcx>,
1032    origin: &PlaceKey,
1033    aliases: &HashMap<Local, PlaceKey>,
1034) -> bool {
1035    match rvalue {
1036        Rvalue::Use(Operand::Copy(place), ..)
1037        | Rvalue::Use(Operand::Move(place), ..)
1038        | Rvalue::Cast(_, Operand::Copy(place), _)
1039        | Rvalue::Cast(_, Operand::Move(place), _)
1040        | Rvalue::Ref(_, _, place)
1041        | Rvalue::RawPtr(_, place)
1042        | Rvalue::CopyForDeref(place) => resolve_mir_place_dummy(place, aliases).overlaps(origin),
1043        Rvalue::Aggregate(_, operands) => operands.iter().any(|operand| match operand {
1044            Operand::Copy(place) | Operand::Move(place) => {
1045                resolve_mir_place_dummy(place, aliases).overlaps(origin)
1046            }
1047            Operand::Constant(_) => false,
1048            #[cfg(rapx_rustc_ge_196)]
1049            Operand::RuntimeChecks(_) => false,
1050        }),
1051        _ => false,
1052    }
1053}
1054
1055fn resolve_mir_place_dummy<'tcx>(
1056    place: &Place<'tcx>,
1057    aliases: &HashMap<Local, PlaceKey>,
1058) -> PlaceKey {
1059    let key = PlaceKey::from_mir_place(place);
1060    if !key.fields.is_empty() {
1061        key
1062    } else {
1063        aliases.get(&place.local).cloned().unwrap_or(key)
1064    }
1065}
1066
1067fn terminator_writes_origin<'tcx>(
1068    tcx: TyCtxt<'tcx>,
1069    caller: DefId,
1070    terminator: &TerminatorKind<'tcx>,
1071    origin: &PlaceKey,
1072    aliases: &HashMap<Local, PlaceKey>,
1073) -> bool {
1074    let TerminatorKind::Call { func, args, .. } = terminator else {
1075        return false;
1076    };
1077    let name = crate::verify::call_summary::call_name(tcx, func);
1078    if PrimitiveCall::classify(&name) != Some(PrimitiveCall::PtrWrite) {
1079        return false;
1080    }
1081    let Some(arg0) = args.first() else {
1082        return false;
1083    };
1084    let Some(place) = (match &arg0.node {
1085        Operand::Copy(place) | Operand::Move(place) => Some(place),
1086        Operand::Constant(_) => None,
1087        #[cfg(rapx_rustc_ge_196)]
1088        Operand::RuntimeChecks(_) => None,
1089    }) else {
1090        return false;
1091    };
1092    resolve_mir_place(tcx, caller, place, aliases).overlaps(origin)
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            Operand::Constant(_) => None,
1109            #[cfg(rapx_rustc_ge_196)]
1110            Operand::RuntimeChecks(_) => None,
1111        }) else {
1112            return false;
1113        };
1114        resolve_mir_place(tcx, caller, place, aliases).overlaps(origin)
1115    })
1116}
1117
1118fn terminator_returns_ownership<'tcx>(
1119    tcx: TyCtxt<'tcx>,
1120    terminator: &TerminatorKind<'tcx>,
1121    owner_locals: &HashSet<Local>,
1122) -> bool {
1123    let TerminatorKind::Call { func, args, .. } = terminator else {
1124        return false;
1125    };
1126    let name = crate::verify::call_summary::call_name(tcx, func);
1127    if !is_ownership_return_api(&name) {
1128        return false;
1129    }
1130    args.iter().any(|arg| match &arg.node {
1131        Operand::Copy(place) | Operand::Move(place) => owner_locals.contains(&place.local),
1132        Operand::Constant(_) => false,
1133        #[cfg(rapx_rustc_ge_196)]
1134        Operand::RuntimeChecks(_) => false,
1135    })
1136}
1137
1138fn is_ownership_return_api(name: &str) -> bool {
1139    name.contains("into_raw")
1140        && (name.contains("boxed")
1141            || name.contains("Box")
1142            || name.contains("ffi::c_str")
1143            || name.contains("CString"))
1144}
1145
1146fn vec_owners_for_origins<'tcx>(
1147    tcx: TyCtxt<'tcx>,
1148    caller: DefId,
1149    origins: &[PlaceKey],
1150    aliases: &HashMap<Local, PlaceKey>,
1151) -> Vec<PlaceKey> {
1152    let body = tcx.optimized_mir(caller);
1153    let mut owners = Vec::new();
1154
1155    for block in body.basic_blocks.iter() {
1156        let Some(terminator) = &block.terminator else {
1157            continue;
1158        };
1159        let TerminatorKind::Call {
1160            func,
1161            args,
1162            destination,
1163            ..
1164        } = &terminator.kind
1165        else {
1166            continue;
1167        };
1168        let name = crate::verify::call_summary::call_name(tcx, func);
1169        if !PrimitiveCall::classify(&name).is_some_and(|primitive| primitive.is_as_ptr_like()) {
1170            continue;
1171        }
1172        let destination_key = PlaceKey {
1173            base: PlaceBaseKey::Local(destination.local.as_usize()),
1174            fields: Vec::new(),
1175        };
1176        if !origins.iter().any(|origin| {
1177            destination_key.overlaps(origin)
1178                || aliases
1179                    .get(&destination.local)
1180                    .is_some_and(|alias| alias.overlaps(origin))
1181        }) {
1182            continue;
1183        }
1184        let Some(owner_arg) = args.first() else {
1185            continue;
1186        };
1187        let Some(owner_place) = (match &owner_arg.node {
1188            Operand::Copy(place) | Operand::Move(place) => Some(place),
1189            Operand::Constant(_) => None,
1190            #[cfg(rapx_rustc_ge_196)]
1191            Operand::RuntimeChecks(_) => None,
1192        }) else {
1193            continue;
1194        };
1195        let owner = resolve_mir_place(tcx, caller, owner_place, aliases);
1196        if !owners.contains(&owner) {
1197            owners.push(owner);
1198        }
1199    }
1200
1201    owners
1202}
1203
1204fn terminator_invalidates_vec_owner<'tcx>(
1205    tcx: TyCtxt<'tcx>,
1206    caller: DefId,
1207    terminator: &TerminatorKind<'tcx>,
1208    owners: &[PlaceKey],
1209    aliases: &HashMap<Local, PlaceKey>,
1210) -> bool {
1211    let TerminatorKind::Call { func, args, .. } = terminator else {
1212        return false;
1213    };
1214    let name = crate::verify::call_summary::call_name(tcx, func);
1215    if !is_vec_invalidating_method(&name) {
1216        return false;
1217    }
1218    args.iter().any(|arg| {
1219        let Some(place) = (match &arg.node {
1220            Operand::Copy(place) | Operand::Move(place) => Some(place),
1221            Operand::Constant(_) => None,
1222            #[cfg(rapx_rustc_ge_196)]
1223            Operand::RuntimeChecks(_) => None,
1224        }) else {
1225            return false;
1226        };
1227        let arg = resolve_mir_place(tcx, caller, place, aliases);
1228        owners
1229            .iter()
1230            .any(|owner| arg.overlaps(owner) || owner.overlaps(&arg))
1231    })
1232}
1233
1234fn is_vec_invalidating_method(name: &str) -> bool {
1235    (name.contains("Vec") || name.contains("vec::"))
1236        && (name.contains("::push")
1237            || name.contains("::reserve")
1238            || name.contains("::reserve_exact")
1239            || name.contains("::shrink_to_fit")
1240            || name.contains("::shrink_to")
1241            || name.contains("::insert")
1242            || name.contains("::remove")
1243            || name.contains("::clear")
1244            || name.contains("::truncate")
1245            || name.contains("::set_len"))
1246}
1247
1248fn adt_from_receiver_ty<'tcx>(ty: Ty<'tcx>) -> Option<(DefId, GenericArgsRef<'tcx>)> {
1249    match ty.kind() {
1250        TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => adt_from_receiver_ty(*inner),
1251        TyKind::Adt(adt, args) => Some((adt.did(), *args)),
1252        _ => None,
1253    }
1254}
1255
1256fn is_raw_pointer_ty(ty: Ty<'_>) -> bool {
1257    matches!(ty.kind(), TyKind::RawPtr(..))
1258}