1use std::collections::HashSet;
13use std::panic::{AssertUnwindSafe, catch_unwind};
14
15use rustc_hir::def_id::DefId;
16use rustc_middle::{
17 mir::{BasicBlock, Local, Operand, Rvalue, StatementKind, TerminatorKind},
18 ty::{ConstKind, GenericArgKind, PseudoCanonicalInput, Ty, TyCtxt, TyKind},
19};
20
21use crate::analysis::dataflow::{DataflowAnalysis, default::DataflowAnalyzer};
22use crate::analysis::path_analysis::graph::{PathEnumerator, PathGraph};
23
24use super::{primitive::PrimitiveCall, slicer::ForgetReason};
25
26#[derive(Clone, Debug)]
28pub struct CallDependencySummary {
29 pub callee: Option<DefId>,
31 pub name: String,
33 pub return_depends_on_args: Vec<usize>,
35 pub may_write_args: Vec<usize>,
37 pub unsupported: bool,
39}
40
41impl CallDependencySummary {
42 fn unknown(callee: Option<DefId>, name: String, arg_count: usize) -> Self {
44 Self {
45 callee,
46 name,
47 return_depends_on_args: (0..arg_count).collect(),
48 may_write_args: Vec::new(),
49 unsupported: true,
50 }
51 }
52}
53
54#[derive(Clone, Debug)]
56pub struct CallEffectSummary {
57 pub callee: Option<DefId>,
59 pub name: String,
61 pub destination: Option<Local>,
63 pub effects: Vec<CallEffect>,
65 pub unsupported: bool,
67}
68
69impl CallEffectSummary {
70 fn unknown(callee: Option<DefId>, name: String, destination: Option<Local>) -> Self {
72 Self {
73 callee,
74 name,
75 destination,
76 effects: Vec::new(),
77 unsupported: true,
78 }
79 }
80}
81
82#[derive(Clone, Debug)]
84pub enum CallEffect {
85 ReturnAliasArg { arg: usize },
87 ReturnPointerFromArg { arg: usize },
89 ReturnPointerAdd {
91 base_arg: usize,
92 offset_arg: usize,
93 stride: Option<u64>,
94 },
95 ReturnPointerSub {
97 base_arg: usize,
98 offset_arg: usize,
99 stride: Option<u64>,
100 },
101 ReturnNonZero,
103 ReturnAligned { align: u64, ty_name: String },
105 ReturnConst { value: u64, label: String },
107 ReadMemory { arg: usize },
109 WriteMemory { pointer_arg: usize },
111 ReturnLengthOfArg { arg: usize },
113 ReturnTupleFieldLength { field: usize, from_arg: usize },
116 ForgetArgFacts { arg: usize, reason: ForgetReason },
118}
119
120pub fn dependency_summary<'tcx>(
122 tcx: TyCtxt<'tcx>,
123 func: &Operand<'tcx>,
124 arg_count: usize,
125) -> CallDependencySummary {
126 let callee = callee_def_id(func);
127 let name = call_name(tcx, func);
128
129 let primitive = PrimitiveCall::classify(&name);
130
131 if primitive.is_some_and(PrimitiveCall::is_as_ptr_like) {
132 return CallDependencySummary {
133 callee,
134 name,
135 return_depends_on_args: vec![0],
136 may_write_args: Vec::new(),
137 unsupported: false,
138 };
139 }
140
141 if primitive == Some(PrimitiveCall::AsPtrRange)
142 || primitive == Some(PrimitiveCall::AsMutPtrRange)
143 {
144 return CallDependencySummary {
145 callee,
146 name,
147 return_depends_on_args: vec![0],
148 may_write_args: Vec::new(),
149 unsupported: false,
150 };
151 }
152
153 if primitive.is_some_and(PrimitiveCall::is_pointer_arithmetic) {
154 return CallDependencySummary {
155 callee,
156 name,
157 return_depends_on_args: vec![0, 1],
158 may_write_args: Vec::new(),
159 unsupported: false,
160 };
161 }
162
163 if primitive == Some(PrimitiveCall::PtrRead) {
164 return CallDependencySummary {
165 callee,
166 name,
167 return_depends_on_args: vec![0],
168 may_write_args: Vec::new(),
169 unsupported: false,
170 };
171 }
172
173 if primitive == Some(PrimitiveCall::PtrWrite) {
174 return CallDependencySummary {
175 callee,
176 name,
177 return_depends_on_args: Vec::new(),
178 may_write_args: vec![0],
179 unsupported: false,
180 };
181 }
182
183 if primitive == Some(PrimitiveCall::Len) {
184 return CallDependencySummary {
185 callee,
186 name,
187 return_depends_on_args: vec![0],
188 may_write_args: Vec::new(),
189 unsupported: false,
190 };
191 }
192
193 if primitive == Some(PrimitiveCall::MaybeUninitUninit) {
194 return CallDependencySummary {
195 callee,
196 name,
197 return_depends_on_args: Vec::new(),
198 may_write_args: Vec::new(),
199 unsupported: false,
200 };
201 }
202
203 if primitive.is_some_and(PrimitiveCall::is_layout_constant) {
204 return CallDependencySummary {
205 callee,
206 name,
207 return_depends_on_args: Vec::new(),
208 may_write_args: Vec::new(),
209 unsupported: false,
210 };
211 }
212
213 if is_from_trait_call(&name) {
214 return CallDependencySummary {
215 callee,
216 name,
217 return_depends_on_args: vec![0],
218 may_write_args: Vec::new(),
219 unsupported: false,
220 };
221 }
222
223 if let Some(callee) = callee {
224 if name.contains("::intrinsics::")
228 || name.starts_with("intrinsics::")
229 || name.ends_with("::drop_in_place")
230 {
231 return CallDependencySummary::unknown(Some(callee), name, arg_count);
232 }
233 if let Some(must_write_args) = local_must_write_args(tcx, callee) {
234 if !must_write_args.is_empty() {
235 return CallDependencySummary {
236 callee: Some(callee),
237 name,
238 return_depends_on_args: Vec::new(),
239 may_write_args: must_write_args
240 .into_iter()
241 .filter(|index| *index < arg_count)
242 .collect(),
243 unsupported: false,
244 };
245 }
246 }
247 if let Some(return_deps) = local_return_dependencies(tcx, callee) {
248 return CallDependencySummary {
249 callee: Some(callee),
250 name,
251 return_depends_on_args: return_deps
252 .into_iter()
253 .filter(|index| *index < arg_count)
254 .collect(),
255 may_write_args: Vec::new(),
256 unsupported: false,
257 };
258 }
259 }
260
261 CallDependencySummary::unknown(callee, name, arg_count)
262}
263
264pub fn effect_summary<'tcx>(
266 tcx: TyCtxt<'tcx>,
267 caller: DefId,
268 func: &Operand<'tcx>,
269 destination: Local,
270) -> CallEffectSummary {
271 let callee = callee_def_id(func);
272 let name = call_name(tcx, func);
273 let destination = Some(destination);
274
275 let primitive = PrimitiveCall::classify(&name);
276
277 if primitive.is_some_and(PrimitiveCall::is_as_ptr_like) {
278 let mut effects = vec![
279 CallEffect::ReturnPointerFromArg { arg: 0 },
280 CallEffect::ReturnNonZero,
281 ];
282 if let Some((align, ty_name)) = destination_pointee_alignment(tcx, caller, destination) {
283 effects.push(CallEffect::ReturnAligned { align, ty_name });
284 }
285 return CallEffectSummary {
286 callee,
287 name,
288 destination,
289 effects,
290 unsupported: false,
291 };
292 }
293
294 if primitive == Some(PrimitiveCall::AsPtrRange)
295 || primitive == Some(PrimitiveCall::AsMutPtrRange)
296 {
297 let effects = vec![CallEffect::ReturnAliasArg { arg: 0 }];
298 return CallEffectSummary {
299 callee,
300 name,
301 destination,
302 effects,
303 unsupported: false,
304 };
305 }
306
307 if primitive.is_some_and(PrimitiveCall::is_pointer_add_like) {
308 let stride = if primitive.is_some_and(PrimitiveCall::is_byte_pointer_arithmetic) {
309 Some(1)
310 } else {
311 destination_stride(tcx, caller, destination)
312 };
313 return CallEffectSummary {
314 callee,
315 name,
316 destination,
317 effects: vec![CallEffect::ReturnPointerAdd {
318 base_arg: 0,
319 offset_arg: 1,
320 stride,
321 }],
322 unsupported: false,
323 };
324 }
325
326 if primitive.is_some_and(PrimitiveCall::is_pointer_sub_like) {
327 let stride = if primitive.is_some_and(PrimitiveCall::is_byte_pointer_arithmetic) {
328 Some(1)
329 } else {
330 destination_stride(tcx, caller, destination)
331 };
332 return CallEffectSummary {
333 callee,
334 name,
335 destination,
336 effects: vec![CallEffect::ReturnPointerSub {
337 base_arg: 0,
338 offset_arg: 1,
339 stride,
340 }],
341 unsupported: false,
342 };
343 }
344
345 if primitive == Some(PrimitiveCall::PtrRead) {
346 return CallEffectSummary {
347 callee,
348 name,
349 destination,
350 effects: vec![CallEffect::ReadMemory { arg: 0 }],
351 unsupported: false,
352 };
353 }
354
355 if primitive == Some(PrimitiveCall::PtrWrite) {
356 return CallEffectSummary {
357 callee,
358 name,
359 destination,
360 effects: vec![CallEffect::WriteMemory { pointer_arg: 0 }],
361 unsupported: false,
362 };
363 }
364
365 if primitive == Some(PrimitiveCall::Len) {
366 return CallEffectSummary {
367 callee,
368 name,
369 destination,
370 effects: vec![CallEffect::ReturnLengthOfArg { arg: 0 }],
371 unsupported: false,
372 };
373 }
374
375 if primitive == Some(PrimitiveCall::MaybeUninitUninit) {
376 return CallEffectSummary {
377 callee,
378 name,
379 destination,
380 effects: Vec::new(),
381 unsupported: false,
382 };
383 }
384
385 if primitive.is_some_and(PrimitiveCall::is_layout_constant) {
386 let effects = layout_constant_effect(tcx, caller, func, &name)
387 .into_iter()
388 .collect();
389 return CallEffectSummary {
390 callee,
391 name,
392 destination,
393 effects,
394 unsupported: false,
395 };
396 }
397
398 if let Some(prim) = primitive
399 && matches!(prim, PrimitiveCall::SplitAt | PrimitiveCall::SplitAtMut)
400 {
401 return CallEffectSummary {
402 callee,
403 name,
404 destination,
405 effects: vec![CallEffect::ReturnTupleFieldLength {
406 field: 0,
407 from_arg: 1,
408 }],
409 unsupported: false,
410 };
411 }
412
413 if let Some(prim) = primitive
414 && matches!(prim, PrimitiveCall::FromRawParts | PrimitiveCall::FromRawPartsMut)
415 {
416 let mut effects = vec![
417 CallEffect::ReturnAliasArg { arg: 0 },
418 CallEffect::ReturnNonZero,
419 ];
420 if let Some((align, ty_name)) = destination_pointee_alignment(tcx, caller, destination) {
421 effects.push(CallEffect::ReturnAligned { align, ty_name });
422 }
423 return CallEffectSummary {
424 callee,
425 name,
426 destination,
427 effects,
428 unsupported: false,
429 };
430 }
431
432 if is_from_trait_call(&name) && is_nonnull_destination(tcx, caller, destination) {
433 let mut effects = vec![
434 CallEffect::ReturnPointerFromArg { arg: 0 },
435 CallEffect::ReturnNonZero,
436 ];
437 if let Some((align, ty_name)) = destination_nonnull_alignment(tcx, caller, destination) {
438 effects.push(CallEffect::ReturnAligned { align, ty_name });
439 }
440 return CallEffectSummary {
441 callee,
442 name,
443 destination,
444 effects,
445 unsupported: false,
446 };
447 }
448
449 if let Some(callee) = callee {
450 if name.contains("::intrinsics::")
453 || name.starts_with("intrinsics::")
454 || name.ends_with("::drop_in_place")
455 {
456 return CallEffectSummary::unknown(Some(callee), name, destination);
457 }
458 if let Some(must_write_args) = local_must_write_args(tcx, callee) {
459 let effects: Vec<_> = must_write_args
460 .into_iter()
461 .map(|arg| CallEffect::WriteMemory { pointer_arg: arg })
462 .collect();
463 if !effects.is_empty() {
464 return CallEffectSummary {
465 callee: Some(callee),
466 name,
467 destination,
468 effects,
469 unsupported: false,
470 };
471 }
472 }
473 if let Some(effect) = try_pointer_arith_wrapper_effect(tcx, callee, destination) {
474 return CallEffectSummary {
475 callee: Some(callee),
476 name,
477 destination,
478 effects: vec![effect],
479 unsupported: false,
480 };
481 }
482 if let Some(return_deps) = local_return_dependencies(tcx, callee) {
483 return CallEffectSummary {
484 callee: Some(callee),
485 name,
486 destination,
487 effects: return_deps
488 .into_iter()
489 .map(|arg| CallEffect::ReturnAliasArg { arg })
490 .collect(),
491 unsupported: false,
492 };
493 }
494 }
495
496 CallEffectSummary::unknown(callee, name, destination)
497}
498
499pub fn callee_def_id(func: &Operand<'_>) -> Option<DefId> {
501 let Operand::Constant(func_constant) = func else {
502 return None;
503 };
504 let TyKind::FnDef(def_id, _) = func_constant.const_.ty().kind() else {
505 return None;
506 };
507 Some(*def_id)
508}
509
510pub fn call_name(tcx: TyCtxt<'_>, func: &Operand<'_>) -> String {
512 callee_def_id(func)
513 .map(|def_id| tcx.def_path_str(def_id))
514 .unwrap_or_else(|| format!("{func:?}"))
515}
516
517pub fn is_as_ptr_call(name: &str) -> bool {
519 PrimitiveCall::classify(name) == Some(PrimitiveCall::AsPtr)
520}
521
522pub fn is_as_mut_ptr_call(name: &str) -> bool {
524 PrimitiveCall::classify(name) == Some(PrimitiveCall::AsMutPtr)
525}
526
527pub fn is_pointer_add_call(name: &str) -> bool {
529 PrimitiveCall::classify(name).is_some_and(PrimitiveCall::is_pointer_add_like)
530}
531
532pub fn is_pointer_sub_call(name: &str) -> bool {
534 PrimitiveCall::classify(name).is_some_and(PrimitiveCall::is_pointer_sub_like)
535}
536
537pub fn is_pointer_offset_call(name: &str) -> bool {
539 PrimitiveCall::classify(name) == Some(PrimitiveCall::PtrOffset)
540}
541
542pub fn is_pointer_read_call(name: &str) -> bool {
544 PrimitiveCall::classify(name) == Some(PrimitiveCall::PtrRead)
545}
546
547pub fn is_pointer_write_call(name: &str) -> bool {
549 PrimitiveCall::classify(name) == Some(PrimitiveCall::PtrWrite)
550}
551
552pub fn is_len_call(name: &str) -> bool {
554 PrimitiveCall::classify(name) == Some(PrimitiveCall::Len)
555}
556
557pub fn is_maybe_uninit_uninit_call(name: &str) -> bool {
559 PrimitiveCall::classify(name) == Some(PrimitiveCall::MaybeUninitUninit)
560}
561
562pub fn is_layout_constant_call(name: &str) -> bool {
564 PrimitiveCall::classify(name).is_some_and(PrimitiveCall::is_layout_constant)
565}
566
567fn is_from_trait_call(name: &str) -> bool {
568 name == "std::convert::From::from" || name == "core::convert::From::from"
569}
570
571fn layout_constant_effect<'tcx>(
573 tcx: TyCtxt<'tcx>,
574 caller: DefId,
575 func: &Operand<'tcx>,
576 name: &str,
577) -> Option<CallEffect> {
578 let ty = layout_call_ty(func)?;
579 let (align, size) = type_layout(tcx, caller, ty)?;
580 match PrimitiveCall::classify(name)? {
581 PrimitiveCall::AlignOf => Some(CallEffect::ReturnConst {
582 value: align,
583 label: format!("align_of::<{ty:?}>()"),
584 }),
585 PrimitiveCall::SizeOf => Some(CallEffect::ReturnConst {
586 value: size,
587 label: format!("size_of::<{ty:?}>()"),
588 }),
589 _ => None,
590 }
591}
592
593fn layout_call_ty<'tcx>(func: &Operand<'tcx>) -> Option<Ty<'tcx>> {
595 let Operand::Constant(func_constant) = func else {
596 return None;
597 };
598 let TyKind::FnDef(_, args) = func_constant.const_.ty().kind() else {
599 return None;
600 };
601 args.iter().find_map(|arg| arg.as_type())
602}
603
604fn trace_to_callee_arg<'tcx>(
607 tcx: TyCtxt<'tcx>,
608 body: &rustc_middle::mir::Body<'tcx>,
609 operand: &Operand<'_>,
610) -> Option<usize> {
611 use std::collections::{HashSet, VecDeque};
612
613 let local = match operand {
614 Operand::Copy(place) | Operand::Move(place) => place.local,
615 _ => return None,
616 };
617 let idx = local.as_usize();
618 if idx >= 1 && idx <= body.arg_count {
619 return Some(idx - 1);
620 }
621 let mut queue = VecDeque::from([local]);
622 let mut seen = HashSet::from([local]);
623 while let Some(current) = queue.pop_front() {
624 let cidx = current.as_usize();
625 if cidx >= 1 && cidx <= body.arg_count {
626 return Some(cidx - 1);
627 }
628 for bb in body.basic_blocks.iter() {
629 for stmt in &bb.statements {
630 let StatementKind::Assign(assign) = &stmt.kind else {
631 continue;
632 };
633 let dest = assign.0.local;
634 if dest != current {
635 continue;
636 }
637 let source = match &assign.1 {
638 Rvalue::Use(Operand::Copy(place), ..)
639 | Rvalue::Use(Operand::Move(place), ..)
640 | Rvalue::Cast(_, Operand::Copy(place), _)
641 | Rvalue::Cast(_, Operand::Move(place), _)
642 | Rvalue::Ref(_, _, place)
643 | Rvalue::RawPtr(_, place)
644 | Rvalue::CopyForDeref(place) => place.local,
645 _ => continue,
646 };
647 if !seen.contains(&source) {
648 seen.insert(source);
649 queue.push_back(source);
650 }
651 }
652 let Some(terminator) = &bb.terminator else {
653 continue;
654 };
655 let TerminatorKind::Call {
656 func,
657 args,
658 destination,
659 ..
660 } = &terminator.kind
661 else {
662 continue;
663 };
664 if destination.local != current {
665 continue;
666 }
667 let primitive = PrimitiveCall::classify(&call_name(tcx, func));
668 if !primitive.is_some_and(PrimitiveCall::is_as_ptr_like) {
669 continue;
670 }
671 let Some(source) = args.first().and_then(|arg| match &arg.node {
672 Operand::Copy(place) | Operand::Move(place) => Some(place.local),
673 Operand::Constant(_) => None,
674 #[cfg(rapx_rustc_ge_196)]
675 Operand::RuntimeChecks(_) => None,
676 }) else {
677 continue;
678 };
679 if !seen.contains(&source) {
680 seen.insert(source);
681 queue.push_back(source);
682 }
683 }
684 }
685 None
686}
687
688fn try_pointer_arith_wrapper_effect<'tcx>(
691 tcx: TyCtxt<'tcx>,
692 callee: DefId,
693 _destination: Option<Local>,
694) -> Option<CallEffect> {
695 use std::collections::{HashSet, VecDeque};
696
697 if !tcx.is_mir_available(callee) {
698 return None;
699 }
700
701 let body = tcx.optimized_mir(callee);
705 if body.basic_blocks.len() > 16 {
706 return None;
707 }
708 let ret = Local::from_usize(0);
709
710 for bb in body.basic_blocks.iter() {
711 let Some(terminator) = &bb.terminator else {
712 continue;
713 };
714 let TerminatorKind::Call {
715 func,
716 args,
717 destination: call_dest,
718 ..
719 } = &terminator.kind
720 else {
721 continue;
722 };
723
724 let name = call_name(tcx, func);
725 let primitive = PrimitiveCall::classify(&name);
726 let is_add = primitive.is_some_and(PrimitiveCall::is_pointer_add_like);
727 let is_sub = primitive.is_some_and(PrimitiveCall::is_pointer_sub_like);
728
729 let inner_effect = if !is_add && !is_sub {
731 callee_def_id(func).and_then(|inner_callee| {
732 let inner_name = call_name(tcx, func);
733 if inner_name.contains("::intrinsics::")
734 || inner_name.starts_with("intrinsics::")
735 || inner_name.ends_with("::drop_in_place")
736 {
737 return None;
738 }
739 try_pointer_arith_wrapper_effect(tcx, inner_callee, Some(call_dest.local))
740 })
741 } else {
742 None
743 };
744
745 if !is_add && !is_sub && inner_effect.is_none() {
746 continue;
747 }
748
749 let mut queue = VecDeque::from([call_dest.local]);
751 let mut seen = HashSet::from([call_dest.local]);
752 let mut reaches_ret = false;
753 while let Some(current) = queue.pop_front() {
754 if current == ret {
755 reaches_ret = true;
756 break;
757 }
758 for bb2 in body.basic_blocks.iter() {
759 for stmt in &bb2.statements {
760 let StatementKind::Assign(assign) = &stmt.kind else {
761 continue;
762 };
763 let dest = assign.0.local;
764 if seen.contains(&dest) {
765 continue;
766 }
767 match &assign.1 {
768 Rvalue::Use(Operand::Copy(place), ..)
769 | Rvalue::Use(Operand::Move(place), ..) => {
770 if place.local == current {
771 queue.push_back(dest);
772 seen.insert(dest);
773 }
774 }
775 Rvalue::Cast(_, Operand::Copy(place), _)
776 | Rvalue::Cast(_, Operand::Move(place), _) => {
777 if place.local == current {
778 queue.push_back(dest);
779 seen.insert(dest);
780 }
781 }
782 _ => {}
783 }
784 }
785 }
786 }
787 if !reaches_ret {
788 continue;
789 }
790
791 if let Some(effect) = inner_effect {
793 match effect {
794 CallEffect::ReturnPointerAdd {
795 base_arg: inner_base,
796 offset_arg: inner_offset,
797 stride,
798 }
799 | CallEffect::ReturnPointerSub {
800 base_arg: inner_base,
801 offset_arg: inner_offset,
802 stride,
803 } => {
804 let base_arg = trace_to_callee_arg(tcx, body, &args.get(inner_base)?.node)?;
805 let offset_arg = trace_to_callee_arg(tcx, body, &args.get(inner_offset)?.node)?;
806 return Some(match effect {
807 CallEffect::ReturnPointerSub { .. } => CallEffect::ReturnPointerSub {
808 base_arg,
809 offset_arg,
810 stride,
811 },
812 _ => CallEffect::ReturnPointerAdd {
813 base_arg,
814 offset_arg,
815 stride,
816 },
817 });
818 }
819 _ => {}
820 }
821 continue;
822 }
823
824 let base_arg = trace_to_callee_arg(tcx, body, &args[0].node)?;
827 let offset_arg = trace_to_callee_arg(tcx, body, &args[1].node)?;
828 let stride = if primitive.is_some_and(PrimitiveCall::is_byte_pointer_arithmetic) {
831 Some(1)
832 } else {
833 destination_stride(tcx, callee, Some(call_dest.local))
834 };
835
836 return if is_sub {
837 Some(CallEffect::ReturnPointerSub {
838 base_arg,
839 offset_arg,
840 stride,
841 })
842 } else {
843 Some(CallEffect::ReturnPointerAdd {
844 base_arg,
845 offset_arg,
846 stride,
847 })
848 };
849 }
850
851 None
852}
853
854fn local_return_dependencies(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
856 callee.as_local()?;
857 if !tcx.is_mir_available(callee) {
858 return None;
859 }
860 catch_unwind(AssertUnwindSafe(|| {
861 let mut analyzer = DataflowAnalyzer::new(tcx, false);
862 analyzer.build_graph(callee);
863 let deps = analyzer.get_fn_arg2ret(callee);
864 deps.iter_enumerated()
865 .filter_map(|(local, depends)| {
866 if *depends && local.as_usize() > 0 {
867 Some(local.as_usize() - 1)
868 } else {
869 None
870 }
871 })
872 .collect()
873 }))
874 .ok()
875}
876
877fn local_must_write_args(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
880 callee.as_local()?;
881 if !tcx.is_mir_available(callee) {
882 return None;
883 }
884
885 catch_unwind(AssertUnwindSafe(|| {
886 let body = tcx.optimized_mir(callee);
887 let mut graph = PathGraph::new(tcx, callee);
888 graph.find_scc();
889 let mut enumerator = PathEnumerator::new(&graph);
890 let paths = enumerator.enumerate_paths_repeat(0);
891
892 let mut must_write: Option<HashSet<usize>> = None;
893 for path in paths.iter() {
894 if !path_ends_in_return(body, &path) {
895 continue;
896 }
897 let writes = write_args_on_path(tcx, body, &path);
898 must_write = Some(match must_write {
899 Some(current) => current.intersection(&writes).copied().collect(),
900 None => writes,
901 });
902 }
903
904 must_write
905 .unwrap_or_default()
906 .into_iter()
907 .collect::<Vec<_>>()
908 }))
909 .ok()
910}
911
912fn path_ends_in_return(body: &rustc_middle::mir::Body<'_>, path: &[usize]) -> bool {
913 path.last().is_some_and(|block| {
914 body.basic_blocks
915 .get(BasicBlock::from_usize(*block))
916 .and_then(|data| data.terminator.as_ref())
917 .is_some_and(|terminator| matches!(terminator.kind, TerminatorKind::Return))
918 })
919}
920
921fn write_args_on_path<'tcx>(
922 tcx: TyCtxt<'tcx>,
923 body: &rustc_middle::mir::Body<'tcx>,
924 path: &[usize],
925) -> HashSet<usize> {
926 let mut writes = HashSet::new();
927 for block in path {
928 let Some(data) = body.basic_blocks.get(BasicBlock::from_usize(*block)) else {
929 continue;
930 };
931 let Some(terminator) = data.terminator.as_ref() else {
932 continue;
933 };
934 let TerminatorKind::Call { func, args, .. } = &terminator.kind else {
935 continue;
936 };
937 let name = call_name(tcx, func);
938 if PrimitiveCall::classify(&name) != Some(PrimitiveCall::PtrWrite) {
939 continue;
940 }
941 if let Some(pointer_arg) = args
942 .first()
943 .and_then(|arg| trace_to_callee_arg(tcx, body, &arg.node))
944 {
945 writes.insert(pointer_arg);
946 }
947 }
948 writes
949}
950
951fn destination_stride<'tcx>(
953 tcx: TyCtxt<'tcx>,
954 caller: DefId,
955 destination: Option<Local>,
956) -> Option<u64> {
957 let destination = destination?;
958 let ty = tcx.optimized_mir(caller).local_decls[destination].ty;
959 let pointee = pointee_ty(ty)?;
960 type_layout(tcx, caller, pointee).map(|(_, size)| size)
961}
962
963fn destination_pointee_alignment<'tcx>(
965 tcx: TyCtxt<'tcx>,
966 caller: DefId,
967 destination: Option<Local>,
968) -> Option<(u64, String)> {
969 let destination = destination?;
970 let ty = tcx.optimized_mir(caller).local_decls[destination].ty;
971 let pointee = pointee_ty(ty).or(Some(ty))?;
972 if let Some((align, _)) = type_layout(tcx, caller, pointee) {
973 return Some((align, format!("{pointee:?}")));
974 }
975 if let TyKind::Array(elem, _) = pointee.kind()
976 && let Some((align, _)) = type_layout(tcx, caller, *elem)
977 {
978 return Some((align, format!("{pointee:?}")));
979 }
980 Some((0, format!("{pointee:?}")))
981}
982
983fn destination_nonnull_alignment<'tcx>(
985 tcx: TyCtxt<'tcx>,
986 caller: DefId,
987 destination: Option<Local>,
988) -> Option<(u64, String)> {
989 let destination = destination?;
990 let ty = tcx.optimized_mir(caller).local_decls[destination].ty;
991 let pointee = nonnull_inner_ty(tcx, ty)?;
992 type_layout(tcx, caller, pointee).map(|(align, _)| (align, format!("{pointee:?}")))
993}
994
995fn is_nonnull_destination<'tcx>(
996 tcx: TyCtxt<'tcx>,
997 caller: DefId,
998 destination: Option<Local>,
999) -> bool {
1000 let Some(destination) = destination else {
1001 return false;
1002 };
1003 let ty = tcx.optimized_mir(caller).local_decls[destination].ty;
1004 nonnull_inner_ty(tcx, ty).is_some() || format!("{ty:?}").contains("NonNull<")
1005}
1006
1007fn pointee_ty<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
1009 match ty.kind() {
1010 TyKind::RawPtr(ty, _) | TyKind::Ref(_, ty, _) => Some(*ty),
1011 _ => None,
1012 }
1013}
1014
1015fn nonnull_inner_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
1016 let TyKind::Adt(def, args) = ty.kind() else {
1017 return None;
1018 };
1019 let path = tcx.def_path_str(def.did());
1020 if !path.contains("ptr::non_null::NonNull") {
1021 return None;
1022 }
1023 args.iter().find_map(|arg| arg.as_type())
1024}
1025
1026fn type_layout<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, ty: Ty<'tcx>) -> Option<(u64, u64)> {
1027 if ty_has_param_const(ty) {
1028 return None;
1029 }
1030 let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, caller);
1031 let input = PseudoCanonicalInput {
1032 typing_env,
1033 value: ty,
1034 };
1035 match tcx.layout_of(input) {
1036 Ok(layout) => Some((layout.align.abi.bytes(), layout.size.bytes())),
1037 Err(_) if matches!(ty.kind(), TyKind::Param(_)) => Some((0, 0)),
1038 Err(_) => None,
1039 }
1040}
1041
1042fn ty_has_param_const(ty: Ty<'_>) -> bool {
1043 for arg in ty.walk() {
1044 match arg.kind() {
1045 GenericArgKind::Const(c) if matches!(c.kind(), ConstKind::Param(_)) => return true,
1046 GenericArgKind::Type(inner_ty) if matches!(inner_ty.kind(), TyKind::Alias(..)) => {
1047 return true;
1048 }
1049 _ => {}
1050 }
1051 }
1052 false
1053}