1use super::alias_hazard::{self, AliasProducer, HazardKind};
9use crate::analysis::alias::FieldOrigin;
10use crate::helpers::mir_scan::Checkpoint;
11use crate::verify::api_classify;
12use crate::verify::contract::{Property, PropertyKind};
13use crate::verify::def_use::PlaceKey;
14use rustc_hir::def_id::DefId;
15use rustc_middle::mir::{Local, Operand, ProjectionElem, Rvalue, StatementKind};
16
17use super::state::{VmState, VmValue};
18
19#[derive(Clone, Debug)]
21pub(crate) struct VmOrigin {
22 pub local: Local,
24 pub kind: VmOriginKind,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub(crate) enum VmOriginKind {
30 MutRef,
31 SharedRef,
32 RawPtr,
36 Owned(DefId),
37 Unknown,
38}
39
40impl VmOrigin {
41 pub(crate) fn is_mut_ref(&self) -> bool {
43 matches!(self.kind, VmOriginKind::MutRef)
44 }
45
46 pub(crate) fn is_shared_ref(&self) -> bool {
48 matches!(self.kind, VmOriginKind::SharedRef)
49 }
50
51 pub(crate) fn is_owned(&self) -> bool {
54 matches!(self.kind, VmOriginKind::Owned(_))
55 }
56}
57
58impl<'z3, 'tcx> VmState<'z3, 'tcx> {
59 pub(crate) fn resolve_origin(&self, value: &VmValue<'z3, 'tcx>) -> Option<VmOrigin> {
64 let Some(prov) = &value.provenance else {
65 return None;
66 };
67
68 let alloc_id = prov.alloc_id;
69
70 let mut best: Option<VmOrigin> = None;
73
74 for (local, val) in self.all_local_values() {
75 let Some(val_prov) = &val.provenance else {
76 continue;
77 };
78 if val_prov.alloc_id != alloc_id {
79 continue;
80 }
81
82 let kind = self.classify_local(&local);
83 let candidate = VmOrigin {
84 local,
85 kind,
86 };
87
88 let is_param = local.as_usize() >= 1 && local.as_usize() <= self.body().arg_count;
91 let is_owned = candidate.is_owned();
92
93 match &best {
94 None => best = Some(candidate),
95 Some(existing) => {
96 let ex_is_param = existing.local.as_usize() >= 1
97 && existing.local.as_usize() <= self.body().arg_count;
98 let ex_is_owned = existing.is_owned();
99 let rank = |p: bool, o: bool| {
100 if p {
101 0
102 } else if o {
103 1
104 } else {
105 2
106 }
107 };
108 let cand_rank = rank(is_param, is_owned);
109 let ex_rank = rank(ex_is_param, ex_is_owned);
110 if cand_rank < ex_rank
111 || (cand_rank == ex_rank && local.as_usize() < existing.local.as_usize())
112 {
113 best = Some(candidate);
114 }
115 }
116 }
117 }
118
119 best
120 }
121
122 fn classify_local(&self, local: &Local) -> VmOriginKind {
124 let ty = self.body().local_decls[*local].ty;
125 match ty.kind() {
126 rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Mut) => {
127 VmOriginKind::MutRef
128 }
129 rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not) => {
130 VmOriginKind::SharedRef
131 }
132 rustc_middle::ty::TyKind::RawPtr(..) => VmOriginKind::RawPtr,
133 rustc_middle::ty::TyKind::Adt(adt_def, _) => VmOriginKind::Owned(adt_def.did()),
134 _ => VmOriginKind::Unknown,
135 }
136 }
137}
138
139pub(crate) enum VmAliasResult {
143 Proved,
144 Failed(String),
145 Unknown,
146}
147
148fn property_contains_alias(property: &Property<'_>) -> bool {
151 match property {
152 Property::Atom(a) => a.kind == PropertyKind::Alias,
153 Property::And(and) => and.conjuncts.iter().any(|p| property_contains_alias(p)),
154 Property::Or(or) => or.disjuncts.iter().any(|p| property_contains_alias(p)),
155 }
156}
157
158fn fn_has_alias_requires(tcx: rustc_middle::ty::TyCtxt<'_>, def_id: DefId) -> bool {
163 crate::verify::target::get_contract_from_annotation(tcx, def_id)
164 .iter()
165 .any(property_contains_alias)
166}
167
168fn flow_xor_violation<'z3, 'tcx>(
174 vm_state: &VmState<'z3, 'tcx>,
175 checkpoint: &Checkpoint<'tcx>,
176 unique: bool,
177 statement_index: usize,
178) -> Option<String> {
179 let origin_arg = checkpoint.args.first()?;
180 let origin_place = alias_hazard::operand_mir_place(origin_arg)?;
181 let origin_local = origin_place.local;
182 let origin_alloc = vm_state
183 .value_of_operand(origin_arg)
184 .provenance_alloc_id()?;
185 let origin_root = vm_state.root_alloc(origin_alloc);
186 let live = alias_hazard::live_locals_at(
187 vm_state.tcx,
188 checkpoint.caller,
189 checkpoint.block,
190 statement_index,
191 false,
192 false,
193 );
194 let body = vm_state.body();
195 for (local, val) in &vm_state.all_local_values() {
196 if *local == origin_local {
197 continue;
198 }
199 if !live.contains(local) {
200 continue;
201 }
202 let Some(prov) = &val.provenance else {
203 continue;
204 };
205 let mutability = match body.local_decls[*local].ty.kind() {
206 rustc_middle::ty::TyKind::Ref(_, _, m) => *m,
207 _ => continue,
208 };
209 let same_alloc = prov.alloc_id == origin_alloc;
210 let same_root = vm_state.root_alloc(prov.alloc_id) == origin_root;
211 if !same_alloc && !same_root {
212 continue;
213 }
214 let view_mut = mutability == rustc_middle::ty::Mutability::Mut;
215 let conflicts = if unique { !view_mut } else { view_mut };
216 if conflicts {
217 let produced = if unique { "&mut" } else { "&" };
218 let live_kind = if view_mut { "&mut" } else { "&" };
219 return Some(format!(
220 "producing {produced} while a live {live_kind} aliases the same data"
221 ));
222 }
223 }
224 None
225}
226
227pub(crate) fn check_alias_vm<'z3, 'tcx>(
231 vm_state: &VmState<'z3, 'tcx>,
232 checkpoint: &Checkpoint<'tcx>,
233) -> VmAliasResult {
234 let callee = match checkpoint.callee {
235 Some(c) => c,
236 None => {
238 let Some(origin_arg) = checkpoint.args.first() else {
239 return VmAliasResult::Unknown;
240 };
241 let origin_val = vm_state.value_of_operand(origin_arg);
242 if let Some(origin_place) = alias_hazard::operand_place(origin_arg) {
245 let kind = if checkpoint.is_mut_ref {
246 HazardKind::UniqueView
247 } else {
248 HazardKind::SharedView
249 };
250 if let Some(reason) = alias_hazard::local_hazard_violation(
251 vm_state.tcx,
252 checkpoint.caller,
253 checkpoint.block,
254 checkpoint.destination,
255 &[origin_place],
256 kind,
257 None,
258 ) {
259 return VmAliasResult::Failed(reason);
260 }
261 }
262 if let Some(origin) = vm_state.resolve_origin(&origin_val) {
263 if !fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
268 if let Some(reason) = flow_xor_violation(
269 vm_state,
270 checkpoint,
271 checkpoint.is_mut_ref,
272 checkpoint.statement_index,
273 ) {
274 return VmAliasResult::Failed(reason);
275 }
276 }
277 if origin.is_mut_ref() {
278 let dest_escapes = alias_hazard::destination_flows_to_return(
282 vm_state.tcx,
283 checkpoint.caller,
284 checkpoint.destination,
285 );
286 if dest_escapes
287 && let Some(mir_place) =
288 crate::helpers::mir_utils::operand_mir_place(origin_arg)
289 {
290 let (root, fields) = crate::verify::vm::alias_tree::AliasTree::build(
291 vm_state.tcx,
292 checkpoint.caller,
293 )
294 .resolve_local_to_root(mir_place.local);
295 if !fields.is_empty() && root >= 1 && root <= vm_state.body().arg_count {
296 let root_ty = vm_state.body().local_decls
297 [rustc_middle::mir::Local::from_usize(root)]
298 .ty;
299 if let rustc_middle::ty::TyKind::Ref(
300 _,
301 _,
302 rustc_middle::ty::Mutability::Not,
303 ) = root_ty.kind()
304 && checkpoint.is_mut_ref
305 {
306 return VmAliasResult::Failed(
307 "&mut deref through a shared reference writes immutable data"
308 .into(),
309 );
310 }
311 }
312 }
313 return VmAliasResult::Proved;
314 }
315 if origin.is_shared_ref() {
316 if checkpoint.is_mut_ref {
317 return VmAliasResult::Failed(
318 "&mut deref through a shared reference writes immutable data".into(),
319 );
320 }
321 let dest_escapes = alias_hazard::destination_flows_to_return(
328 vm_state.tcx,
329 checkpoint.caller,
330 checkpoint.destination,
331 );
332 if dest_escapes
333 && let Some(mir_place) =
334 crate::helpers::mir_utils::operand_mir_place(origin_arg)
335 {
336 let (root, fields) = crate::verify::vm::alias_tree::AliasTree::build(
337 vm_state.tcx,
338 checkpoint.caller,
339 )
340 .resolve_local_to_root(mir_place.local);
341 if !fields.is_empty() && root >= 1 && root <= vm_state.body().arg_count {
342 let resolved = PlaceKey::from_origin(root, fields);
343 if let Some(sfo) = alias_hazard::self_field_origin(
344 vm_state.tcx,
345 checkpoint.caller,
346 &resolved,
347 ) {
348 if !fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
353 return check_escaped_field(
354 vm_state.tcx,
355 checkpoint.caller,
356 &sfo,
357 HazardKind::SharedView,
358 );
359 }
360 }
361 } else if root >= 1 && root <= vm_state.body().arg_count {
362 if let Some(reason) = escape_region_violation(
367 vm_state.tcx,
368 checkpoint.caller,
369 root,
370 ) {
371 return VmAliasResult::Failed(reason);
372 }
373 }
374 }
375 return VmAliasResult::Proved;
376 }
377 if origin.is_owned() {
378 return VmAliasResult::Proved;
379 }
380 if matches!(origin.kind, VmOriginKind::RawPtr)
391 && origin.local.as_usize() <= vm_state.body().arg_count
392 {
393 if fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
394 return VmAliasResult::Proved;
395 }
396 let escapes = alias_hazard::destination_flows_to_return(
397 vm_state.tcx,
398 checkpoint.caller,
399 checkpoint.destination,
400 );
401 if !escapes {
402 return VmAliasResult::Proved;
403 }
404 return VmAliasResult::Unknown;
405 }
406 }
407 if vm_state.body().arg_count >= 1 {
415 let self_ty = vm_state.body().local_decls[Local::from_usize(1)].ty;
416 let nonnull_adt = match self_ty.kind() {
417 rustc_middle::ty::TyKind::Adt(adt_def, _) => Some(*adt_def),
418 rustc_middle::ty::TyKind::Ref(_, pointee, _) => match pointee.kind() {
419 rustc_middle::ty::TyKind::Adt(adt_def, _) => Some(*adt_def),
420 _ => None,
421 },
422 _ => None,
423 };
424 if nonnull_adt.is_some_and(|adt| api_classify::is_std_nonnull(adt.did())) {
425 return VmAliasResult::Proved;
426 }
427 }
428 if let Some(prov) = &origin_val.provenance {
430 let is_external = vm_state.alloc(prov.alloc_id).is_external();
431 if !is_external {
432 return VmAliasResult::Proved;
433 }
434 let has_shared_ref = vm_state.body().local_decls.iter().any(|d| {
436 matches!(
437 d.ty.kind(),
438 rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not)
439 )
440 });
441 if has_shared_ref {
442 return VmAliasResult::Proved;
443 }
444 }
445 if origin_val.provenance.is_none() {
447 for decl in &vm_state.body().local_decls {
448 if matches!(decl.ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
449 return VmAliasResult::Proved;
450 }
451 }
452 }
453 let tcx = vm_state.tcx;
456 let caller = checkpoint.caller;
457 let arg_place =
458 alias_hazard::operand_mir_place(origin_arg).map(|p| PlaceKey::from_mir_place(p));
459 if let Some(mir_place) = arg_place {
460 let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
461 let local = mir_place
462 .local()
463 .unwrap_or(rustc_middle::mir::Local::from_usize(1));
464 let (root, fields) = tree.resolve_local_to_root(local);
465 if !fields.is_empty() {
466 let root_ty =
474 vm_state.body().local_decls[rustc_middle::mir::Local::from_usize(root)].ty;
475 if !matches!(root_ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
476 let typing_env =
477 rustc_middle::ty::TypingEnv::post_analysis(tcx, caller);
478 if !tcx.type_is_copy_modulo_regions(typing_env, root_ty) {
479 return VmAliasResult::Proved;
480 }
481 }
482 let resolved = PlaceKey::from_origin(root, fields);
483 let sfo = alias_hazard::self_field_origin(tcx, caller, &resolved);
484 if let Some(sfo) = sfo {
485 if let Some(is_shared) = is_self_field_shared_ref(tcx, caller, &sfo) {
486 if is_shared {
487 return VmAliasResult::Proved;
488 }
489 }
490 }
491 }
492 }
493 return VmAliasResult::Unknown;
494 }
495 };
496
497 if api_classify::is_nonnull_as_ref_as_mut(Some(callee)) {
505 let ret_ty = vm_state.body().local_decls[rustc_middle::mir::RETURN_PLACE].ty;
506 if crate::helpers::mir_utils::type_contains_reference(ret_ty) {
507 if api_classify::is_nonnull_as_mut(Some(callee)) {
508 return VmAliasResult::Failed(
509 "escaping `&mut` derived from a raw pointer without borrow information"
510 .into(),
511 );
512 }
513 return VmAliasResult::Unknown;
514 }
515 return VmAliasResult::Proved;
516 }
517
518 let Some(producer) = alias_hazard::alias_producer(callee) else {
520 return VmAliasResult::Unknown;
521 };
522
523 match producer {
524 AliasProducer::View(kind) => check_view_alias(vm_state, checkpoint, kind),
525 AliasProducer::OwnershipTransfer => check_ownership_transfer_alias(vm_state, checkpoint),
526 AliasProducer::ReadMemory => check_read_memory_alias(vm_state, checkpoint),
527 }
528}
529
530fn check_view_alias<'z3, 'tcx>(
531 vm_state: &VmState<'z3, 'tcx>,
532 checkpoint: &Checkpoint<'tcx>,
533 kind: HazardKind,
534) -> VmAliasResult {
535 let Some(origin_arg) = checkpoint.args.first() else {
536 return VmAliasResult::Unknown;
537 };
538 let origin_val = vm_state.value_of_operand(origin_arg);
539
540 let tcx = vm_state.tcx;
541 let caller = checkpoint.caller;
542 let call_block = checkpoint.block;
543 let destination = alias_hazard::call_destination(tcx, checkpoint);
544
545 if !fn_has_alias_requires(vm_state.tcx, checkpoint.caller) {
552 if let Some(reason) = flow_xor_violation(
553 vm_state,
554 checkpoint,
555 kind == HazardKind::UniqueView,
556 usize::MAX,
557 ) {
558 return VmAliasResult::Failed(reason);
559 }
560 }
561
562 let origin_place = alias_hazard::operand_place(origin_arg).unwrap_or_else(|| {
564 PlaceKey::from_origin(
566 crate::helpers::mir_utils::extract_local(origin_arg)
567 .map(|l| l.as_usize())
568 .unwrap_or(1),
569 vec![],
570 )
571 });
572
573 let resolved_origin = resolve_origin_place_mir(tcx, caller, &origin_place);
576 let mut origins = vec![origin_place.clone()];
577 if resolved_origin != origin_place {
578 origins.push(resolved_origin.clone());
579 }
580
581 let mir_place_from_arg = checkpoint
584 .args
585 .first()
586 .and_then(|a| alias_hazard::operand_mir_place(a));
587 if let Some(place) = mir_place_from_arg {
588 if !place.projection.is_empty() && place.local == Local::from_usize(1) {
589 let field_key = PlaceKey::from_mir_place(place);
590 if !field_key.fields.is_empty() && !origins.contains(&field_key) {
591 origins.push(field_key);
592 }
593 }
594 }
595
596 if let Some(origin) = vm_state.resolve_origin(&origin_val) {
598 match (kind, origin.kind) {
599 (HazardKind::UniqueView, VmOriginKind::MutRef) => return VmAliasResult::Proved,
600 (HazardKind::SharedView, VmOriginKind::SharedRef) => {
601 if let Some(reason) = shared_view_escape_region_violation(
604 tcx,
605 caller,
606 destination,
607 origin.local.as_usize(),
608 ) {
609 return VmAliasResult::Failed(reason);
610 }
611 return VmAliasResult::Proved;
612 }
613 (HazardKind::UniqueView, VmOriginKind::SharedRef) => {
614 return VmAliasResult::Failed(
618 "shared reference cannot produce a unique mutable view".into(),
619 );
620 }
621 _ => {}
624 }
625 if origin.is_owned() {
626 let check = alias_hazard::alias_proved_for_param_local(
627 tcx,
628 caller,
629 origin.local.as_usize(),
630 kind,
631 );
632 let is_reallocatable = match &origin.kind {
635 VmOriginKind::Owned(def_id) => {
636 api_classify::is_std_vec(*def_id) || api_classify::is_std_cstring(*def_id)
637 }
638 _ => false,
639 };
640 if matches!(check, alias_hazard::HazardCheck::Safe(_)) && !is_reallocatable {
641 return VmAliasResult::Proved;
642 }
643 }
644 }
645
646 let view_len_place = checkpoint
648 .args
649 .get(1)
650 .and_then(|a| alias_hazard::operand_place(a));
651
652 if let Some(reason) = alias_hazard::local_hazard_violation(
654 tcx,
655 caller,
656 call_block,
657 destination,
658 &origins,
659 kind,
660 view_len_place,
661 ) {
662 return VmAliasResult::Failed(reason);
663 }
664
665 let origin_pk = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
667 if let Some(local_index) = origin_pk {
668 match alias_hazard::alias_proved_for_param_local(tcx, caller, local_index, kind) {
669 alias_hazard::HazardCheck::Safe(_) => return VmAliasResult::Proved,
670 alias_hazard::HazardCheck::Violation(_) => {
671 }
674 alias_hazard::HazardCheck::Inconclusive => {}
675 }
676 }
677 let origin_local_place = if origin_place.fields.is_empty() {
679 PlaceKey::from_origin(
680 origin_place.local().map(|l| l.as_usize()).unwrap_or(1),
681 vec![],
682 )
683 } else {
684 origin_place.clone()
685 };
686 match alias_hazard::alias_proved_for_param_local_from_origin(
687 tcx,
688 caller,
689 &origin_local_place,
690 kind,
691 ) {
692 alias_hazard::HazardCheck::Violation(_) => {} alias_hazard::HazardCheck::Safe(_) => {}
694 alias_hazard::HazardCheck::Inconclusive => {}
695 }
696
697 let dest_escapes = alias_hazard::destination_flows_to_return(tcx, caller, destination);
699 if dest_escapes {
700 let field_origin =
702 resolve_escaped_field_origin(tcx, caller, &resolved_origin, &origin_place, checkpoint);
703 if let Some(sfo) = field_origin {
704 return check_escaped_field(tcx, caller, &sfo, kind);
705 }
706 let any_field = alias_hazard::any_struct_field_origin(tcx, caller, &resolved_origin)
707 .or_else(|| alias_hazard::any_struct_field_origin(tcx, caller, &origin_place));
708 if let Some(sfo) = any_field {
709 return check_escaped_field(tcx, caller, &sfo, kind);
710 }
711 if let Some(reason) =
712 alias_hazard::private_fn_callsite_delegation(tcx, caller, &origin_place, kind)
713 {
714 return VmAliasResult::Failed(reason);
715 }
716 if kind == HazardKind::SharedView {
717 let param_origin = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
718 if let Some(local) = param_origin
719 && alias_hazard::is_origin_a_reference(
720 tcx,
721 caller,
722 &PlaceKey::from_origin(local, vec![]),
723 )
724 {
725 if let Some(reason) = escape_region_violation(tcx, caller, local) {
730 return VmAliasResult::Failed(reason);
731 }
732 return VmAliasResult::Proved;
733 }
734 }
735 }
736
737 if !dest_escapes {
739 return VmAliasResult::Proved;
740 }
741
742 if kind == HazardKind::UniqueView {
745 if let Some(sfo) = infer_self_field_from_type(tcx, caller, checkpoint)
749 .or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint))
750 {
751 if alias_hazard::escaped_self_field_violation(tcx, caller, &sfo).is_none() {
752 return VmAliasResult::Proved;
753 }
754 }
755 let body = tcx.optimized_mir(caller);
756 if body.arg_count >= 1 {
757 let self_ty = body.local_decls[Local::from_usize(1)].ty;
758 if let rustc_middle::ty::TyKind::Adt(adt_def, _) = self_ty.kind() {
762 if api_classify::is_std_nonnull(adt_def.did()) {
763 return VmAliasResult::Proved;
764 }
765 }
766 }
767 return VmAliasResult::Failed(format!(
768 "returned unique view escapes while the original pointer is not owned by a private self field [origin={:?}]",
769 origin_place
770 ));
771 }
772
773 VmAliasResult::Proved
775}
776
777fn shared_view_escape_region_violation(
781 tcx: rustc_middle::ty::TyCtxt<'_>,
782 caller: DefId,
783 destination: Option<Local>,
784 local: usize,
785) -> Option<String> {
786 if !alias_hazard::destination_flows_to_return(tcx, caller, destination) {
787 return None;
788 }
789 escape_region_violation(tcx, caller, local)
790}
791
792fn escape_region_violation(
795 tcx: rustc_middle::ty::TyCtxt<'_>,
796 caller: DefId,
797 local: usize,
798) -> Option<String> {
799 let src_region =
802 super::region::fn_arg_ty(tcx, caller, local - 1).and_then(|ty| match ty.kind() {
803 rustc_middle::ty::TyKind::Ref(region, _, _) => Some(*region),
804 _ => None,
805 })?;
806 let ret_region = super::region::fn_return_region(tcx, caller)?;
807 if !super::region::region_outlives(tcx, caller, src_region, ret_region) {
808 return Some(format!(
809 "returned region `{ret_region:?}` outlives the source reference's region `{src_region:?}`"
810 ));
811 }
812 None
813}
814
815fn find_struct_field_origin_for_param<'tcx>(
820 tcx: rustc_middle::ty::TyCtxt<'tcx>,
821 caller: DefId,
822 checkpoint: &Checkpoint<'tcx>,
823) -> Option<FieldOrigin> {
824 let body = tcx.optimized_mir(caller);
825 let (adt_def, _) = self_adt(tcx, caller)?;
826
827 let Some(arg0) = checkpoint.args.first() else {
829 return None;
830 };
831 let arg_place = match arg0 {
832 Operand::Copy(p) | Operand::Move(p) => p,
833 _ => return None,
834 };
835
836 if !arg_place.projection.is_empty() && arg_place.local == Local::from_usize(1) {
838 let fields: Vec<usize> = arg_place
839 .projection
840 .iter()
841 .filter_map(|p| match p {
842 ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
843 _ => None,
844 })
845 .collect();
846 if !fields.is_empty() {
847 let field_index = fields[0];
848 let adt = tcx.adt_def(adt_def);
849 let field = adt.all_fields().nth(field_index)?;
850 return Some(FieldOrigin {
851 struct_def_id: adt_def,
852 field_index,
853 field_name: field.name.to_string(),
854 });
855 }
856 }
857
858 let arg_local = arg_place.local;
860 if arg_place.projection.is_empty() && arg_local != Local::from_usize(1) {
861 for block in body.basic_blocks.iter() {
862 for stmt in &block.statements {
863 let StatementKind::Assign(assign) = &stmt.kind else {
864 continue;
865 };
866 let (target, rvalue) = assign.as_ref();
867 if target.local != arg_local {
868 continue;
869 }
870 let source = match rvalue {
871 #[cfg(rapx_rvalue_use_with_retag)]
872 Rvalue::Use(operand, _) => match operand {
873 Operand::Copy(p) | Operand::Move(p) => p,
874 _ => continue,
875 },
876 #[cfg(not(rapx_rvalue_use_with_retag))]
877 Rvalue::Use(operand) => match operand {
878 Operand::Copy(p) | Operand::Move(p) => p,
879 _ => continue,
880 },
881 Rvalue::CopyForDeref(p) => p,
882 _ => continue,
883 };
884 if source.local != Local::from_usize(1) {
885 continue;
886 }
887 let fields: Vec<usize> = source
888 .projection
889 .iter()
890 .filter_map(|p| match p {
891 ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
892 _ => None,
893 })
894 .collect();
895 if fields.is_empty() {
896 continue;
897 }
898 let field_index = fields[0];
899 let adt = tcx.adt_def(adt_def);
900 let field = adt.all_fields().nth(field_index)?;
901 return Some(FieldOrigin {
902 struct_def_id: adt_def,
903 field_index,
904 field_name: field.name.to_string(),
905 });
906 }
907 }
908 }
909
910 None
911}
912
913fn infer_self_field_from_type<'tcx>(
917 tcx: rustc_middle::ty::TyCtxt<'tcx>,
918 caller: DefId,
919 checkpoint: &Checkpoint<'tcx>,
920) -> Option<FieldOrigin> {
921 let Some((adt_def, _)) = self_adt(tcx, caller) else {
922 return None;
923 };
924
925 let adt = tcx.adt_def(adt_def);
926 let mut raw_ptr_fields: Vec<(usize, String)> = Vec::new();
927 let variant = adt.non_enum_variant();
928 for (idx, field) in variant.fields.iter().enumerate() {
929 let field_ty = crate::helpers::mir_utils::field_ty(
930 tcx,
931 field,
932 rustc_middle::ty::GenericArgs::identity_for_item(tcx, adt_def),
933 );
934 if matches!(field_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)) {
935 raw_ptr_fields.push((idx, field.name.to_string()));
936 }
937 }
938
939 if raw_ptr_fields.len() == 1 {
940 let (field_index, field_name) = raw_ptr_fields.into_iter().next().unwrap();
941 return Some(FieldOrigin {
942 struct_def_id: adt_def,
943 field_index,
944 field_name,
945 });
946 }
947
948 if let Some(arg0) = checkpoint.args.first()
951 && let Some(place) = alias_hazard::operand_mir_place(arg0)
952 {
953 let fields: Vec<usize> = place
954 .projection
955 .iter()
956 .filter_map(|p| match p {
957 ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
958 _ => None,
959 })
960 .collect();
961 if let Some(&idx) = fields.first() {
962 if let Some(field) = adt.all_fields().nth(idx) {
963 return Some(FieldOrigin {
964 struct_def_id: adt_def,
965 field_index: idx,
966 field_name: field.name.to_string(),
967 });
968 }
969 }
970 }
971
972 None
973}
974fn is_self_field_shared_ref(
978 tcx: rustc_middle::ty::TyCtxt<'_>,
979 caller: DefId,
980 origin: &FieldOrigin,
981) -> Option<bool> {
982 let (adt_def, args) = self_adt(tcx, caller)?;
983 if adt_def != origin.struct_def_id {
984 return Some(false);
985 }
986 let adt = tcx.adt_def(adt_def);
987 let field = adt.all_fields().nth(origin.field_index)?;
988 let field_ty = crate::helpers::mir_utils::field_ty(tcx, field, args);
989 Some(matches!(
990 field_ty.kind(),
991 rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not)
992 ))
993}
994
995fn check_escaped_field(
1000 tcx: rustc_middle::ty::TyCtxt<'_>,
1001 caller: DefId,
1002 sfo: &FieldOrigin,
1003 kind: HazardKind,
1004) -> VmAliasResult {
1005 if let Some(reason) = alias_hazard::escaped_self_field_violation(tcx, caller, sfo) {
1006 return VmAliasResult::Failed(reason);
1007 }
1008 if kind == HazardKind::UniqueView {
1009 return VmAliasResult::Failed("unique view escapes through a private raw field".into());
1010 }
1011 VmAliasResult::Proved
1012}
1013
1014fn resolve_escaped_field_origin<'tcx>(
1018 tcx: rustc_middle::ty::TyCtxt<'tcx>,
1019 caller: DefId,
1020 resolved_origin: &PlaceKey,
1021 origin_place: &PlaceKey,
1022 checkpoint: &Checkpoint<'tcx>,
1023) -> Option<FieldOrigin> {
1024 alias_hazard::self_field_origin(tcx, caller, resolved_origin)
1025 .or_else(|| alias_hazard::self_field_origin(tcx, caller, origin_place))
1026 .or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint))
1027}
1028
1029fn resolve_origin_place_mir(
1031 tcx: rustc_middle::ty::TyCtxt<'_>,
1032 caller: DefId,
1033 place: &PlaceKey,
1034) -> PlaceKey {
1035 let Some(local) = place.local() else {
1036 return place.clone();
1037 };
1038 let tree = crate::verify::vm::alias_tree::AliasTree::build(tcx, caller);
1039 let (root_local, mut root_fields) = tree.resolve_local_to_root(local);
1040
1041 if root_local == local.as_usize() && root_fields.is_empty() && !place.fields.is_empty() {
1043 root_fields = place.fields.clone();
1044 }
1045
1046 if root_fields.is_empty() && !place.fields.is_empty() {
1049 return place.clone();
1050 }
1051
1052 PlaceKey::from_origin(root_local, root_fields)
1053}
1054
1055fn self_adt<'tcx>(
1058 tcx: rustc_middle::ty::TyCtxt<'tcx>,
1059 caller: DefId,
1060) -> Option<(DefId, rustc_middle::ty::GenericArgsRef<'tcx>)> {
1061 let body = tcx.optimized_mir(caller);
1062 if body.arg_count == 0 {
1063 return None;
1064 }
1065 let self_ty = body.local_decls[Local::from_usize(1)].ty;
1066 let inner = match self_ty.kind() {
1067 rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
1068 _ => return None,
1069 };
1070 crate::analysis::alias::adt_from_ty(inner)
1071}
1072
1073fn check_ownership_transfer_alias<'z3, 'tcx>(
1074 vm_state: &VmState<'z3, 'tcx>,
1075 checkpoint: &Checkpoint<'tcx>,
1076) -> VmAliasResult {
1077 let Some(origin_arg) = checkpoint.args.first() else {
1078 return VmAliasResult::Unknown;
1079 };
1080
1081 let tcx = vm_state.tcx;
1082 let caller = checkpoint.caller;
1083 let call_block = checkpoint.block;
1084 let destination = alias_hazard::call_destination(tcx, checkpoint);
1085
1086 let origin_place = alias_hazard::operand_place(origin_arg);
1087 let Some(origin_place) = origin_place else {
1088 return VmAliasResult::Unknown;
1089 };
1090
1091 if let Some(reason) = alias_hazard::ownership_transfer_violation(
1092 tcx,
1093 caller,
1094 call_block,
1095 destination,
1096 &origin_place,
1097 ) {
1098 return VmAliasResult::Failed(reason);
1099 }
1100
1101 VmAliasResult::Proved
1102}
1103
1104fn check_read_memory_alias<'z3, 'tcx>(
1105 vm_state: &VmState<'z3, 'tcx>,
1106 checkpoint: &Checkpoint<'tcx>,
1107) -> VmAliasResult {
1108 let Some(origin_arg) = checkpoint.args.first() else {
1109 return VmAliasResult::Unknown;
1110 };
1111
1112 let origin_val = vm_state.value_of_operand(origin_arg);
1113
1114 if vm_state.path_facts.alias_hazard_accepted {
1118 return VmAliasResult::Proved;
1119 }
1120
1121 if let rustc_middle::ty::TyKind::RawPtr(pointee, _) = origin_val.ty.kind() {
1123 let tcx = vm_state.tcx;
1124 let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, checkpoint.caller);
1125 if tcx.type_is_copy_modulo_regions(typing_env, *pointee) {
1126 return VmAliasResult::Proved;
1127 }
1128 }
1129
1130 let tcx = vm_state.tcx;
1132 let destination = alias_hazard::call_destination(tcx, checkpoint);
1133 if !alias_hazard::destination_flows_to_return(tcx, checkpoint.caller, destination) {
1134 return VmAliasResult::Proved;
1135 }
1136
1137 VmAliasResult::Failed(
1138 "read API value escapes while the source pointer persists — structural alias hazard".into(),
1139 )
1140}