1use 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
60pub 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}