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