1use std::collections::{HashMap, HashSet, VecDeque};
8
9use rustc_hir::def_id::DefId;
10use rustc_middle::{
11 mir::{
12 BasicBlock, BinOp, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind,
13 TerminatorKind,
14 },
15 ty::{Ty, TyCtxt, TyKind},
16};
17
18use crate::analysis::dataflow::{DataflowAnalysis, default::DataflowAnalyzer};
19use crate::analysis::path::graph::{PathEnumerator, PathGraph};
20use crate::compat::Spanned;
21use crate::helpers::mir_utils as helpers;
22use crate::limit::{
23 FIELD_LOAD_EFFECT_BLOCK_LIMIT, FROM_RAW_PARTS_WRAPPER_BLOCK_LIMIT,
24 POINTER_ARITH_WRAPPER_BLOCK_LIMIT, SLICE_BOUNDED_RETURN_BLOCK_LIMIT,
25};
26
27use super::{CallContext, CallEffect};
28
29fn trace_to_callee_arg<'tcx>(
35 body: &rustc_middle::mir::Body<'tcx>,
36 operand: &Operand<'_>,
37) -> Option<usize> {
38 let local = match operand {
39 Operand::Copy(place) | Operand::Move(place) => place.local,
40 _ => return None,
41 };
42 let idx = local.as_usize();
43 if idx >= 1 && idx <= body.arg_count {
44 return Some(idx - 1);
45 }
46 let mut queue = VecDeque::from([local]);
47 let mut seen = HashSet::from([local]);
48 while let Some(current) = queue.pop_front() {
49 let cidx = current.as_usize();
50 if cidx >= 1 && cidx <= body.arg_count {
51 return Some(cidx - 1);
52 }
53 for bb in body.basic_blocks.iter() {
54 for stmt in &bb.statements {
55 let StatementKind::Assign(assign) = &stmt.kind else {
56 continue;
57 };
58 let dest = assign.0.local;
59 if dest != current {
60 continue;
61 }
62 let source = match &assign.1 {
63 Rvalue::Use(Operand::Copy(place), ..)
64 | Rvalue::Use(Operand::Move(place), ..)
65 | Rvalue::Cast(_, Operand::Copy(place), _)
66 | Rvalue::Cast(_, Operand::Move(place), _)
67 | Rvalue::Ref(_, _, place)
68 | Rvalue::RawPtr(_, place)
69 | Rvalue::CopyForDeref(place) => place.local,
70 _ => continue,
71 };
72 if !seen.contains(&source) {
73 seen.insert(source);
74 queue.push_back(source);
75 }
76 }
77 let Some(terminator) = &bb.terminator else {
78 continue;
79 };
80 let TerminatorKind::Call {
81 func,
82 args,
83 destination,
84 ..
85 } = &terminator.kind
86 else {
87 continue;
88 };
89 if destination.local != current {
90 continue;
91 }
92 let callee = helpers::dep_callee_def_id(func);
98 let traces_base = crate::verify::api_classify::is_as_ptr(callee)
99 || crate::verify::api_classify::is_pointer_add(callee)
100 || crate::verify::api_classify::is_pointer_sub(callee);
101 if !traces_base {
102 continue;
103 }
104 let Some(source) = args.first().and_then(|arg| match &arg.node {
105 Operand::Copy(place) | Operand::Move(place) => Some(place.local),
106 Operand::Constant(_) => None,
107 #[cfg(rapx_ge_95)]
108 Operand::RuntimeChecks(_) => None,
109 }) else {
110 continue;
111 };
112 if !seen.contains(&source) {
113 seen.insert(source);
114 queue.push_back(source);
115 }
116 }
117 }
118 None
119}
120
121enum WrapperEffectMemo {
125 Computing,
126 Done(Option<CallEffect>),
127}
128
129fn resolve_wrapper_effect<'tcx>(
135 tcx: TyCtxt<'tcx>,
136 callee: DefId,
137 memo: &mut HashMap<DefId, WrapperEffectMemo>,
138 probe: &dyn Fn(
139 TyCtxt<'tcx>,
140 DefId,
141 &mut (dyn FnMut(DefId) -> Option<CallEffect> + '_),
142 ) -> Option<CallEffect>,
143) -> Option<CallEffect> {
144 if let Some(state) = memo.get(&callee) {
145 return match state {
146 WrapperEffectMemo::Computing => None,
147 WrapperEffectMemo::Done(effect) => effect.clone(),
148 };
149 }
150 memo.insert(callee, WrapperEffectMemo::Computing);
151 let result = {
152 let recurse = &mut |inner: DefId| resolve_wrapper_effect(tcx, inner, memo, probe);
153 probe(tcx, callee, recurse)
154 };
155 memo.insert(callee, WrapperEffectMemo::Done(result.clone()));
156 result
157}
158
159pub(super) fn try_pointer_arith_wrapper_effect<'tcx>(
164 tcx: TyCtxt<'tcx>,
165 callee: DefId,
166) -> Option<CallEffect> {
167 let mut memo: HashMap<DefId, WrapperEffectMemo> = HashMap::new();
168 resolve_wrapper_effect(tcx, callee, &mut memo, &pointer_arith_wrapper_probe)
169}
170
171fn pointer_arith_wrapper_probe<'tcx>(
175 tcx: TyCtxt<'tcx>,
176 callee: DefId,
177 recurse: &mut (dyn FnMut(DefId) -> Option<CallEffect> + '_),
178) -> Option<CallEffect> {
179 if !tcx.is_mir_available(callee) {
180 return None;
181 }
182 let body = tcx.optimized_mir(callee);
183 if body.basic_blocks.len() > POINTER_ARITH_WRAPPER_BLOCK_LIMIT {
184 return None;
185 }
186
187 for bb in body.basic_blocks.iter() {
188 let Some(terminator) = &bb.terminator else {
189 continue;
190 };
191 let TerminatorKind::Call {
192 func,
193 args,
194 destination: call_dest,
195 ..
196 } = &terminator.kind
197 else {
198 continue;
199 };
200
201 let callee_id = helpers::dep_callee_def_id(func);
202 let is_add = crate::verify::api_classify::is_pointer_add(callee_id);
203 let is_sub = crate::verify::api_classify::is_pointer_sub(callee_id);
204
205 let inner_effect = if !is_add && !is_sub {
206 helpers::dep_callee_def_id(func).and_then(|inner_callee| {
207 if tcx.intrinsic(inner_callee).is_some() || helpers::is_drop_in_place(inner_callee)
208 {
209 return None;
210 }
211 recurse(inner_callee)
212 })
213 } else {
214 None
215 };
216
217 if !is_add && !is_sub && inner_effect.is_none() {
218 continue;
219 }
220
221 if !call_result_reaches_return(body, call_dest.local) {
222 continue;
223 }
224
225 if let Some(effect) = inner_effect {
226 let (inner_base, inner_offset, stride, dereferenceable) = match effect {
227 CallEffect::ReturnPointerAdd {
228 base_arg,
229 offset_arg,
230 stride,
231 dereferenceable,
232 } => (base_arg, offset_arg, stride, dereferenceable),
233 CallEffect::ReturnPointerSub {
234 base_arg,
235 offset_arg,
236 stride,
237 } => (base_arg, offset_arg, stride, false),
238 _ => {
239 continue;
240 }
241 };
242 let base_arg = trace_to_callee_arg(body, &args.get(inner_base)?.node)?;
243 let offset_arg = trace_to_callee_arg(body, &args.get(inner_offset)?.node)?;
244 let is_sub = matches!(effect, CallEffect::ReturnPointerSub { .. });
245 return Some(if is_sub {
246 CallEffect::ReturnPointerSub {
247 base_arg,
248 offset_arg,
249 stride,
250 }
251 } else {
252 CallEffect::ReturnPointerAdd {
253 base_arg,
254 offset_arg,
255 stride,
256 dereferenceable,
257 }
258 });
259 }
260
261 let base_arg = trace_to_callee_arg(body, &args.get(0)?.node)?;
262 let offset_arg = trace_to_callee_arg(body, &args.get(1)?.node)?;
263 let stride = if crate::verify::api_classify::is_byte_ptr_arith(callee_id) {
264 Some(1)
265 } else {
266 helpers::destination_stride(tcx, callee, Some(call_dest.local))
267 };
268
269 return if is_sub {
270 Some(CallEffect::ReturnPointerSub {
271 base_arg,
272 offset_arg,
273 stride,
274 })
275 } else {
276 Some(CallEffect::ReturnPointerAdd {
277 base_arg,
278 offset_arg,
279 stride,
280 dereferenceable: false,
281 })
282 };
283 }
284
285 None
286}
287
288pub(super) fn callee_contains_pointer_arithmetic(tcx: TyCtxt<'_>, callee: DefId) -> bool {
290 let Some(_) = callee.as_local() else {
291 return false;
292 };
293 if !tcx.is_mir_available(callee) {
294 return false;
295 }
296 let body = tcx.optimized_mir(callee);
297 for bb in body.basic_blocks.iter() {
298 let Some(terminator) = &bb.terminator else {
299 continue;
300 };
301 let TerminatorKind::Call { func, .. } = &terminator.kind else {
302 continue;
303 };
304 if crate::verify::api_classify::is_pointer_add(helpers::dep_callee_def_id(func))
305 || crate::verify::api_classify::is_pointer_sub(helpers::dep_callee_def_id(func))
306 {
307 return true;
308 }
309 }
310 false
311}
312
313pub(super) fn local_return_dependencies(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
316 if !tcx.is_mir_available(callee) {
317 return None;
318 }
319 helpers::catch_panic(|| {
320 let mut analyzer = DataflowAnalyzer::new(tcx, false);
321 analyzer.build_graph(callee);
322 let deps = analyzer.get_fn_arg2ret(callee);
323 deps.iter_enumerated()
324 .filter_map(|(local, depends)| {
325 if *depends && local.as_usize() > 0 {
326 Some(local.as_usize() - 1)
327 } else {
328 None
329 }
330 })
331 .collect()
332 })
333 .ok()
334}
335
336pub(super) fn try_from_raw_parts_wrapper_effect<'tcx>(
339 tcx: TyCtxt<'tcx>,
340 callee: DefId,
341) -> Option<CallEffect> {
342 if !tcx.is_mir_available(callee) {
343 return None;
344 }
345 let body = tcx.optimized_mir(callee);
346 if body.basic_blocks.len() > FROM_RAW_PARTS_WRAPPER_BLOCK_LIMIT {
347 return None;
348 }
349 let ret = Local::from_usize(0);
350
351 for bb in body.basic_blocks.iter() {
352 let Some(terminator) = &bb.terminator else {
353 continue;
354 };
355 let TerminatorKind::Call {
356 func,
357 args,
358 destination: call_dest,
359 ..
360 } = &terminator.kind
361 else {
362 continue;
363 };
364
365 let inner_callee = helpers::dep_callee_def_id(func);
366 if !crate::verify::api_classify::is_from_raw_parts(inner_callee) {
367 continue;
368 }
369
370 if !call_result_reaches_return(body, call_dest.local) {
372 continue;
373 }
374
375 let pointer_arg = trace_to_callee_arg(body, &args.get(0)?.node)?;
377 let size_arg = trace_to_callee_arg(body, &args.get(1)?.node)?;
378
379 let elem_size =
381 crate::verify::call_summary::from_raw_parts_elem_size(tcx, callee, Some(ret));
382
383 return Some(CallEffect::ReturnFreshAllocation {
384 pointer_arg,
385 size_arg,
386 elem_size,
387 });
388 }
389 None
390}
391
392pub(crate) fn try_field_load_effect(tcx: TyCtxt<'_>, callee: DefId) -> Option<CallEffect> {
401 if !tcx.is_mir_available(callee) {
402 return None;
403 }
404 let body = tcx.optimized_mir(callee);
405 if body.basic_blocks.len() > FIELD_LOAD_EFFECT_BLOCK_LIMIT || body.arg_count < 1 {
406 return None;
407 }
408
409 for bb in body.basic_blocks.iter() {
410 for stmt in &bb.statements {
411 match &stmt.kind {
412 StatementKind::Assign(assign) => {
413 let (place, rvalue) = &**assign;
414 if place.local.as_usize() == 0 && place.projection.is_empty() {
416 let src_place = match rvalue {
417 Rvalue::Use(Operand::Copy(p), ..)
418 | Rvalue::Use(Operand::Move(p), ..) => p,
419 Rvalue::CopyForDeref(p) => p,
420 _ => return None,
421 };
422 if src_place.local.as_usize() == 1 {
423 let mut proj = src_place.projection.iter();
424 if !matches!(proj.next().map(|p| p.kind()), Some(ProjectionElem::Deref))
425 {
426 return None;
427 }
428 let Some(ProjectionElem::Field(idx, _)) = proj.next().map(|p| p.kind())
429 else {
430 return None;
431 };
432 if proj.next().is_some() {
433 return None;
434 }
435 return Some(CallEffect::ReturnFieldOfArg {
436 arg: 0,
437 field: idx.as_usize(),
438 });
439 }
440 return None;
441 }
442 return None;
444 }
445 StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => {}
446 _ => return None,
447 }
448 }
449 }
450 None
451}
452
453pub(crate) fn try_ptr_field_return_effect(tcx: TyCtxt<'_>, callee: DefId) -> Option<CallEffect> {
466 if !tcx.is_mir_available(callee) {
467 return None;
468 }
469 let body = tcx.optimized_mir(callee);
470 if body.arg_count < 1 {
471 return None;
472 }
473 let ret_ty = body.local_decls[Local::from_usize(0)].ty;
474 if !matches!(ret_ty.kind(), TyKind::RawPtr(..)) {
475 return None;
476 }
477 let pre_dec_offset = detect_pre_dec_end_offset(tcx, body);
483 let mut queue = VecDeque::from([Local::from_usize(0)]);
488 let mut seen = HashSet::from([Local::from_usize(0)]);
489 while let Some(cur) = queue.pop_front() {
490 for bb in body.basic_blocks.iter() {
491 for stmt in &bb.statements {
492 let StatementKind::Assign(assign) = &stmt.kind else {
493 continue;
494 };
495 let (dest, rvalue) = &**assign;
496 if dest.local != cur || !dest.projection.is_empty() {
497 continue;
498 }
499 let src: Option<&Place<'_>> = match rvalue {
500 Rvalue::Use(Operand::Copy(p) | Operand::Move(p), ..) => Some(p),
501 Rvalue::CopyForDeref(p) => Some(p),
502 Rvalue::Cast(_, Operand::Copy(p) | Operand::Move(p), _) => Some(p),
503 _ => None,
504 };
505 let Some(src) = src else {
506 continue;
507 };
508 if src.local.as_usize() >= 1 && src.local.as_usize() <= body.arg_count {
510 let mut proj = src.projection.iter();
511 if matches!(proj.next().map(|p| p.kind()), Some(ProjectionElem::Deref)) {
512 if let Some(ProjectionElem::Field(idx, _)) = proj.next().map(|p| p.kind()) {
513 if proj.next().is_none() {
514 let arg = src.local.as_usize() - 1;
515 let field = idx.as_usize();
516 return match pre_dec_offset {
517 Some(offset) if offset > 0 => {
518 Some(CallEffect::ReturnFieldOfArgSub { arg, field, offset })
519 }
520 _ => Some(CallEffect::ReturnFieldOfArg { arg, field }),
521 };
522 }
523 }
524 }
525 }
526 if src.projection.is_empty() && seen.insert(src.local) {
528 queue.push_back(src.local);
529 }
530 }
531 }
532 }
533 None
534}
535
536fn copy_root(body: &rustc_middle::mir::Body<'_>, mut local: Local) -> Local {
539 let mut seen = HashSet::new();
540 loop {
541 if !seen.insert(local) {
542 break;
543 }
544 let mut next = None;
545 for bb in body.basic_blocks.iter() {
546 for stmt in &bb.statements {
547 let StatementKind::Assign(assign) = &stmt.kind else {
548 continue;
549 };
550 let (dest, rvalue) = &**assign;
551 if dest.local != local || !dest.projection.is_empty() {
552 continue;
553 }
554 let Rvalue::Use(op, ..) = rvalue else {
555 continue;
556 };
557 let (Operand::Copy(p) | Operand::Move(p)) = op else {
558 continue;
559 };
560 if p.projection.is_empty() {
561 next = Some(p.local);
562 }
563 }
564 }
565 match next {
566 Some(n) => local = n,
567 None => break,
568 }
569 }
570 local
571}
572
573pub(crate) fn try_slice_bounded_return_effect(
578 tcx: TyCtxt<'_>,
579 callee: DefId,
580) -> Option<CallEffect> {
581 if !tcx.is_mir_available(callee) {
582 return None;
583 }
584 let body = tcx.optimized_mir(callee);
585 if body.basic_blocks.len() > SLICE_BOUNDED_RETURN_BLOCK_LIMIT || body.arg_count < 1 {
586 return None;
587 }
588
589 let mut payload_root: Option<Local> = None;
591 for bb in body.basic_blocks.iter() {
592 for stmt in &bb.statements {
593 let StatementKind::Assign(assign) = &stmt.kind else {
594 continue;
595 };
596 let (place, rvalue) = &**assign;
597 if place.local.as_usize() != 0 || !place.projection.is_empty() {
598 continue;
599 }
600 let Rvalue::Aggregate(kind, operands) = rvalue else {
601 continue;
602 };
603 let rustc_middle::mir::AggregateKind::Adt(adt, variant_idx, ..) = &**kind else {
604 continue;
605 };
606 if !tcx.is_diagnostic_item(rustc_span::sym::Option, *adt) {
607 continue;
608 }
609 if variant_idx.as_usize() != 1 {
610 continue; }
612 let Some(payload) = operands.iter().next() else {
613 continue;
614 };
615 let (Operand::Copy(p) | Operand::Move(p)) = payload else {
616 continue;
617 };
618 if p.projection.is_empty() {
619 payload_root = Some(copy_root(body, p.local));
620 }
621 }
622 }
623 let payload_root = payload_root?;
624
625 let mut len_defs: Vec<(Local, usize)> = Vec::new();
627 for bb in body.basic_blocks.iter() {
628 for stmt in &bb.statements {
629 let StatementKind::Assign(assign) = &stmt.kind else {
630 continue;
631 };
632 let (place, rvalue) = &**assign;
633 if !place.projection.is_empty() {
634 continue;
635 }
636 let Rvalue::UnaryOp(op, operand) = rvalue else {
637 continue;
638 };
639 if !matches!(op, rustc_middle::mir::UnOp::PtrMetadata) {
640 continue;
641 }
642 let (Operand::Copy(p) | Operand::Move(p)) = operand else {
643 continue;
644 };
645 if p.projection.is_empty()
646 && p.local.as_usize() >= 1
647 && p.local.as_usize() <= body.arg_count
648 {
649 len_defs.push((place.local, p.local.as_usize() - 1));
650 }
651 }
652 }
653
654 for bb in body.basic_blocks.iter() {
656 for stmt in &bb.statements {
657 let StatementKind::Assign(assign) = &stmt.kind else {
658 continue;
659 };
660 let (_, rvalue) = &**assign;
661 let Rvalue::BinaryOp(op, pair) = rvalue else {
662 continue;
663 };
664 if !matches!(op, BinOp::Lt | BinOp::Le) {
665 continue;
666 }
667 let (a, b) = &**pair;
668 let a_root = match a {
669 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => {
670 copy_root(body, p.local)
671 }
672 _ => continue,
673 };
674 if a_root != payload_root {
675 continue;
676 }
677 let b_local = match b {
678 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => Some(p.local),
679 _ => None,
680 };
681 let Some(&(_, arg)) = len_defs.iter().find(|(tmp, _)| Some(*tmp) == b_local) else {
682 continue;
683 };
684 return match op {
685 BinOp::Lt => Some(CallEffect::ReturnOptionSomeIndexLtArgLen { arg }),
686 _ => None,
687 };
688 }
689 }
690 None
691}
692
693pub(crate) fn try_branch_effect(tcx: TyCtxt<'_>, callee: DefId) -> Option<CallEffect> {
697 let name = tcx.def_path_str(callee);
698 if !name.ends_with("::branch") {
699 return None;
700 }
701 if !tcx.is_mir_available(callee) {
702 return None;
703 }
704 let body = tcx.optimized_mir(callee);
705 if body.arg_count != 1 {
706 return None;
707 }
708 let arg_ty = body.local_decls[Local::from_usize(1)].ty;
710 let TyKind::Adt(arg_adt, _) = arg_ty.kind() else {
711 return None;
712 };
713 if !tcx.is_diagnostic_item(rustc_span::sym::Option, arg_adt.did()) {
714 return None;
715 }
716 let ret_ty = body.local_decls[Local::from_usize(0)].ty;
718 let TyKind::Adt(ret_adt, _) = ret_ty.kind() else {
719 return None;
720 };
721 if !tcx.def_path_str(ret_adt.did()).contains("ControlFlow") {
722 return None;
723 }
724 Some(CallEffect::ReturnBranchPayload { arg: 0 })
725}
726
727fn block_dominates(body: &rustc_middle::mir::Body<'_>, a: BasicBlock, b: BasicBlock) -> bool {
731 let entry = 0usize;
732 let mut queue = VecDeque::from([entry]);
733 let mut seen = HashSet::from([entry]);
734 while let Some(cur) = queue.pop_front() {
735 if cur == b.as_usize() {
736 return false; }
738 if cur == a.as_usize() {
739 continue; }
741 for succ in body.basic_blocks[BasicBlock::from_usize(cur)]
742 .terminator()
743 .successors()
744 {
745 if seen.insert(succ.as_usize()) {
746 queue.push_back(succ.as_usize());
747 }
748 }
749 }
750 true
751}
752
753pub(crate) fn try_decode_length_return_effect(
760 tcx: TyCtxt<'_>,
761 callee: DefId,
762) -> Option<CallEffect> {
763 if !tcx.is_mir_available(callee) {
764 return None;
765 }
766 let body = tcx.optimized_mir(callee);
767 if body.arg_count < 1 {
768 return None;
769 }
770 let ret_ty = body.local_decls[Local::from_usize(0)].ty;
772 let TyKind::Adt(adt, substs) = ret_ty.kind() else {
773 return None;
774 };
775 if !tcx.is_diagnostic_item(rustc_span::sym::Option, adt.did()) {
776 return None;
777 }
778 let inner = substs.type_at(0);
779 let TyKind::Tuple(tys) = inner.kind() else {
780 return None;
781 };
782 let Some(field) = tys
783 .iter()
784 .position(|t| matches!(t.kind(), TyKind::Uint(rustc_middle::ty::UintTy::Usize)))
785 else {
786 return None;
787 };
788
789 let mut returns: Vec<(BasicBlock, u64)> = Vec::new();
791 let mut computed_returns: Vec<BasicBlock> = Vec::new();
792 for (bb, data) in body.basic_blocks.iter_enumerated() {
793 for stmt in &data.statements {
794 let StatementKind::Assign(assign) = &stmt.kind else {
795 continue;
796 };
797 let (place, rvalue) = &**assign;
798 if place.local.as_usize() != 0 || !place.projection.is_empty() {
799 continue;
800 }
801 let Rvalue::Aggregate(kind, operands) = rvalue else {
802 continue;
803 };
804 let rustc_middle::mir::AggregateKind::Adt(adt, variant_idx, ..) = &**kind else {
805 continue;
806 };
807 if !tcx.is_diagnostic_item(rustc_span::sym::Option, *adt) {
808 continue;
809 }
810 if variant_idx.as_usize() != 1 {
811 continue; }
813 let Some(payload) = operands.iter().next() else {
814 continue;
815 };
816 match tuple_field_len_kind(body, payload, field) {
818 Some(TupleFieldLen::Const(len)) => returns.push((bb, len)),
819 Some(TupleFieldLen::LenSub) => computed_returns.push(bb),
820 None => {}
821 }
822 }
823 }
824 if returns.is_empty() && computed_returns.is_empty() {
825 return None;
826 }
827
828 let mut gets: Vec<(BasicBlock, u64)> = Vec::new();
830 for (bb, data) in body.basic_blocks.iter_enumerated() {
831 let TerminatorKind::Call { func, args, .. } = &data.terminator().kind else {
832 continue;
833 };
834 let Some(get_callee) = helpers::dep_callee_def_id(func) else {
835 continue;
836 };
837 if !tcx.opt_associated_item(get_callee).is_some_and(|a| {
838 let name = a.name();
839 matches!(name.as_str(), "get" | "index" | "index_mut")
840 }) {
841 continue;
842 }
843 let Some(k) = args
844 .get(1)
845 .and_then(|a| helpers::operand_const_u64(&a.node))
846 else {
847 continue;
848 };
849 gets.push((bb, k));
850 }
851 if gets.is_empty() && computed_returns.is_empty() {
852 return None;
853 }
854
855 for (bb, len) in &returns {
860 if *len <= 1 {
861 continue;
862 }
863 let k = len.checked_sub(1)?;
864 let Some(&(get_bb, _)) = gets.iter().find(|(_, kk)| *kk == k) else {
865 return None;
866 };
867 if !block_dominates(body, get_bb, *bb) {
868 return None;
869 }
870 }
871
872 Some(CallEffect::ReturnOptionSomeTupleFieldLeArgLen { field, arg: 0 })
873}
874
875enum TupleFieldLen {
877 Const(u64),
879 LenSub,
882}
883
884fn tuple_field_len_kind<'tcx>(
887 body: &rustc_middle::mir::Body<'tcx>,
888 operand: &Operand<'tcx>,
889 field: usize,
890) -> Option<TupleFieldLen> {
891 let tuple_local = match operand {
892 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => p.local,
893 _ => return None,
894 };
895 let mut field_operand: Option<Operand<'tcx>> = None;
896 for bb in body.basic_blocks.iter() {
897 for stmt in &bb.statements {
898 let StatementKind::Assign(assign) = &stmt.kind else {
899 continue;
900 };
901 let (place, rvalue) = &**assign;
902 if place.local != tuple_local || !place.projection.is_empty() {
903 continue;
904 }
905 let Rvalue::Aggregate(kind, operands) = rvalue else {
906 continue;
907 };
908 if !matches!(&**kind, rustc_middle::mir::AggregateKind::Tuple) {
909 continue;
910 }
911 field_operand = operands
912 .get(rustc_abi::FieldIdx::from_usize(field))
913 .cloned();
914 }
915 }
916 let mut cur = field_operand?;
917
918 loop {
919 if let Some(c) = helpers::operand_const_u64(&cur) {
920 return Some(TupleFieldLen::Const(c));
921 }
922 let local = match &cur {
923 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => p.local,
924 Operand::Copy(p) | Operand::Move(p)
928 if p.projection.len() == 1
929 && matches!(
930 p.projection[0].kind(),
931 rustc_middle::mir::ProjectionElem::Field(rustc_abi::FieldIdx::ZERO, _)
932 ) =>
933 {
934 p.local
935 }
936 _ => return None,
937 };
938 let mut defining: Option<&Rvalue<'tcx>> = None;
939 for bb in body.basic_blocks.iter() {
940 for stmt in &bb.statements {
941 let StatementKind::Assign(assign) = &stmt.kind else {
942 continue;
943 };
944 let (place, rvalue) = &**assign;
945 if place.local != local || !place.projection.is_empty() {
946 continue;
947 }
948 defining = Some(rvalue);
949 }
950 }
951 match defining? {
952 Rvalue::Use(op, ..) => cur = op.clone(),
953 Rvalue::BinaryOp(BinOp::Sub | BinOp::SubWithOverflow, pair) => {
954 let (lhs, _) = &**pair;
957 return operand_is_ptr_metadata(body, lhs).then_some(TupleFieldLen::LenSub);
958 }
959 _ => return None,
960 }
961 }
962}
963
964fn operand_is_ptr_metadata<'tcx>(
967 body: &rustc_middle::mir::Body<'tcx>,
968 operand: &Operand<'tcx>,
969) -> bool {
970 let mut cur = operand.clone();
971 loop {
972 let local = match &cur {
973 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => p.local,
974 _ => return false,
975 };
976 for bb in body.basic_blocks.iter() {
978 let TerminatorKind::Call {
979 func, destination, ..
980 } = &bb.terminator().kind
981 else {
982 continue;
983 };
984 if destination.local == local
985 && crate::verify::api_classify::is_len(helpers::dep_callee_def_id(func))
986 {
987 return true;
988 }
989 }
990 let mut defining: Option<&Rvalue<'tcx>> = None;
991 for bb in body.basic_blocks.iter() {
992 for stmt in &bb.statements {
993 let StatementKind::Assign(assign) = &stmt.kind else {
994 continue;
995 };
996 let (place, rvalue) = &**assign;
997 if place.local != local || !place.projection.is_empty() {
998 continue;
999 }
1000 defining = Some(rvalue);
1001 }
1002 }
1003 match defining {
1004 Some(Rvalue::UnaryOp(op, _)) => {
1005 return matches!(op, rustc_middle::mir::UnOp::PtrMetadata);
1006 }
1007 Some(Rvalue::Use(op, ..)) => cur = op.clone(),
1008 _ => return false,
1009 }
1010 }
1011}
1012
1013fn detect_pre_dec_end_offset<'tcx>(
1018 tcx: TyCtxt<'tcx>,
1019 body: &rustc_middle::mir::Body<'tcx>,
1020) -> Option<u64> {
1021 for bb in body.basic_blocks.iter() {
1022 let Some(term) = &bb.terminator else {
1023 continue;
1024 };
1025 let TerminatorKind::Call { func, args, .. } = &term.kind else {
1026 continue;
1027 };
1028 let Some(callee) = helpers::dep_callee_def_id(func) else {
1029 continue;
1030 };
1031 if !crate::helpers::mir_utils::is_pre_dec_end(tcx, callee) {
1032 continue;
1033 }
1034 return args
1036 .get(1)
1037 .and_then(|a| helpers::operand_const_u64(&a.node));
1038 }
1039 None
1040}
1041
1042pub(super) fn try_iter_constructor_effect<'tcx>(
1048 tcx: TyCtxt<'tcx>,
1049 callee: DefId,
1050) -> Option<CallEffect> {
1051 let fn_sig = tcx.fn_sig(callee).skip_binder();
1052 let output = fn_sig.output().skip_binder();
1053 let TyKind::Adt(adt, substs) = output.kind() else {
1054 return None;
1055 };
1056 let inputs = fn_sig.inputs().skip_binder();
1057 let Some(arg0) = inputs.first() else {
1058 return None;
1059 };
1060 let TyKind::Ref(_, inner, _) = arg0.kind() else {
1061 return None;
1062 };
1063 let TyKind::Slice(elem_ty) = inner.kind() else {
1064 return None;
1065 };
1066 let elem_ty = *elem_ty;
1067 if adt.is_enum() {
1068 return None;
1069 }
1070 let variant = adt.non_enum_variant();
1071 if variant.fields.len() < 2 {
1072 return None;
1073 }
1074 let is_elem_ptr = |ty: Ty<'tcx>| -> bool {
1077 match ty.kind() {
1078 TyKind::RawPtr(pointee, _) => *pointee == elem_ty,
1079 TyKind::Adt(a, args)
1080 if crate::verify::api_classify::is_std_nonnull(a.did())
1081 && args.type_at(0) == elem_ty =>
1082 {
1083 true
1084 }
1085 _ => false,
1086 }
1087 };
1088 let mut fields = variant.fields.iter();
1089 let (Some(f0), Some(f1)) = (fields.next(), fields.next()) else {
1090 return None;
1091 };
1092 if !is_elem_ptr(helpers::field_ty(tcx, f0, substs))
1093 || !is_elem_ptr(helpers::field_ty(tcx, f1, substs))
1094 {
1095 return None;
1096 }
1097 if !iter_ctor_reads_slice_len(tcx, callee, 1) {
1102 return None;
1103 }
1104 Some(CallEffect::ReturnIter { receiver_arg: 0 })
1105}
1106
1107fn iter_ctor_reads_slice_len<'tcx>(tcx: TyCtxt<'tcx>, callee: DefId, depth: usize) -> bool {
1111 if !tcx.is_mir_available(callee) {
1112 return false;
1113 }
1114 let body = tcx.optimized_mir(callee);
1115 if body_reads_slice_len(body) {
1116 return true;
1117 }
1118 if depth > 0 {
1119 if let Some(target) = single_call_wrapper_target(tcx, callee) {
1120 return iter_ctor_reads_slice_len(tcx, target, depth - 1);
1121 }
1122 }
1123 false
1124}
1125
1126fn body_reads_slice_len<'tcx>(body: &rustc_middle::mir::Body<'tcx>) -> bool {
1128 for bb in body.basic_blocks.iter() {
1129 let Some(term) = &bb.terminator else { continue };
1130 let TerminatorKind::Call { func, args, .. } = &term.kind else {
1131 continue;
1132 };
1133 if !crate::verify::api_classify::is_len(helpers::dep_callee_def_id(func)) {
1134 continue;
1135 }
1136 if let Some(arg0) = args.first()
1137 && trace_to_callee_arg(body, &arg0.node) == Some(0)
1138 {
1139 return true;
1140 }
1141 }
1142 false
1143}
1144
1145fn single_call_wrapper_target<'tcx>(tcx: TyCtxt<'tcx>, callee: DefId) -> Option<DefId> {
1148 let body = tcx.optimized_mir(callee);
1149 let mut found: Option<DefId> = None;
1150 for bb in body.basic_blocks.iter() {
1151 let Some(term) = &bb.terminator else { continue };
1152 let TerminatorKind::Call {
1153 func, destination, ..
1154 } = &term.kind
1155 else {
1156 continue;
1157 };
1158 if destination.local.as_usize() != 0 {
1159 continue;
1160 }
1161 let Some(c) = helpers::dep_callee_def_id(func) else {
1162 return None;
1163 };
1164 match found {
1165 Some(f) if f != c => return None,
1166 _ => found = Some(c),
1167 }
1168 }
1169 found
1170}
1171
1172type MustWriteMemo = HashMap<(DefId, usize, Vec<(usize, i128)>), Option<HashSet<usize>>>;
1179
1180fn context_key(context: &CallContext) -> Vec<(usize, i128)> {
1183 let mut entries: Vec<(usize, i128)> = context.concrete.iter().map(|(k, v)| (*k, *v)).collect();
1184 entries.sort_unstable();
1185 entries
1186}
1187
1188pub(super) fn local_must_write_args(
1193 tcx: TyCtxt<'_>,
1194 callee: DefId,
1195 context: &CallContext,
1196) -> Option<Vec<usize>> {
1197 must_write_args_rec(tcx, callee, 0, context, &mut HashMap::new())
1198 .map(|set| set.into_iter().collect())
1199}
1200
1201fn must_write_args_rec(
1202 tcx: TyCtxt<'_>,
1203 callee: DefId,
1204 depth: usize,
1205 context: &CallContext,
1206 memo: &mut MustWriteMemo,
1207) -> Option<HashSet<usize>> {
1208 if depth > 4 {
1209 return None;
1210 }
1211 if !tcx.is_mir_available(callee) {
1212 return None;
1213 }
1214 if tcx.intrinsic(callee).is_some() || helpers::is_drop_in_place(callee) {
1215 return None;
1216 }
1217 let key = (callee, depth, context_key(context));
1218 if let Some(summary) = memo.get(&key) {
1219 return summary.clone();
1220 }
1221
1222 let summary = helpers::catch_panic(|| {
1223 let body = tcx.optimized_mir(callee);
1224 let mut graph = PathGraph::new(tcx, callee);
1225 graph.find_scc();
1226 let mut enumerator = PathEnumerator::new(&graph);
1227 let paths = enumerator.enumerate_paths_repeat(0);
1228 if paths.is_truncated() {
1231 return None;
1232 }
1233
1234 let mut must_write: Option<HashSet<usize>> = None;
1235 for path in paths.iter() {
1236 if !path_ends_in_return(body, &path) {
1237 continue;
1238 }
1239 if path_infeasible_under_context(body, &path, context) {
1240 continue;
1241 }
1242 let writes = write_args_on_path(tcx, body, &path, depth, context, memo);
1243 must_write = Some(match must_write {
1244 Some(current) => current.intersection(&writes).copied().collect(),
1245 None => writes,
1246 });
1247 }
1248
1249 Some(must_write.unwrap_or_default())
1250 })
1251 .ok()
1252 .flatten();
1253 memo.insert(key, summary.clone());
1254 summary
1255}
1256
1257fn path_infeasible_under_context(
1262 body: &rustc_middle::mir::Body<'_>,
1263 path: &[usize],
1264 context: &CallContext,
1265) -> bool {
1266 if context.concrete.is_empty() {
1267 return false;
1268 }
1269 for window in path.windows(2) {
1270 let (block, next) = (window[0], window[1]);
1271 let Some(data) = body.basic_blocks.get(BasicBlock::from_usize(block)) else {
1272 continue;
1273 };
1274 let Some(terminator) = &data.terminator else {
1275 continue;
1276 };
1277 let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind else {
1278 continue;
1279 };
1280 let Some(value) = switch_discriminant_concrete(body, discr, context) else {
1281 continue;
1282 };
1283 let expected = targets
1284 .iter()
1285 .find(|(val, _)| *val == value as u128)
1286 .map(|(_, t)| t)
1287 .unwrap_or_else(|| targets.otherwise());
1288 if expected.as_usize() != next {
1289 return true;
1290 }
1291 }
1292 false
1293}
1294
1295fn switch_discriminant_concrete(
1299 body: &rustc_middle::mir::Body<'_>,
1300 discr: &Operand<'_>,
1301 context: &CallContext,
1302) -> Option<i128> {
1303 let local = match discr {
1304 Operand::Copy(place) | Operand::Move(place) => place.local,
1305 _ => return None,
1306 };
1307 let mut queue = VecDeque::from([local]);
1308 let mut seen = HashSet::from([local]);
1309 while let Some(current) = queue.pop_front() {
1310 let cidx = current.as_usize();
1311 if cidx >= 1 && cidx <= body.arg_count {
1312 return context.concrete.get(&(cidx - 1)).copied();
1313 }
1314 for bb in body.basic_blocks.iter() {
1315 for stmt in &bb.statements {
1316 let StatementKind::Assign(assign) = &stmt.kind else {
1317 continue;
1318 };
1319 if assign.0.local != current {
1320 continue;
1321 }
1322 let source = match &assign.1 {
1323 Rvalue::Use(Operand::Copy(place), ..)
1324 | Rvalue::Use(Operand::Move(place), ..) => place.local,
1325 _ => continue,
1326 };
1327 if !seen.contains(&source) {
1328 seen.insert(source);
1329 queue.push_back(source);
1330 }
1331 }
1332 }
1333 }
1334 None
1335}
1336
1337pub(super) fn named_index_disjoint_validator(name: &str) -> Option<(usize, usize)> {
1340 let base = name
1341 .split('<')
1342 .next()
1343 .unwrap_or(name)
1344 .trim_end_matches("::");
1345 if base.ends_with("get_disjoint_check_valid") || base.ends_with("get_disjoint_check_valid_ext")
1346 {
1347 Some((0, 1))
1348 } else {
1349 None
1350 }
1351}
1352
1353pub(super) fn detect_index_disjoint_validator(
1358 tcx: TyCtxt<'_>,
1359 callee: DefId,
1360) -> Option<(usize, usize)> {
1361 callee.as_local()?;
1362 if !tcx.is_mir_available(callee) {
1363 return None;
1364 }
1365 helpers::catch_panic(|| {
1366 let body = tcx.optimized_mir(callee);
1367 let arg_count = body.arg_count;
1368 let mut elem_load_arg: HashSet<(Local, usize)> = HashSet::new();
1369 let mut copy_of_arg: HashSet<(Local, usize)> = HashSet::new();
1370
1371 for bb in body.basic_blocks.iter() {
1372 for stmt in &bb.statements {
1373 let StatementKind::Assign(assign) = &stmt.kind else {
1374 continue;
1375 };
1376 let (dest, rvalue) = &**assign;
1377 if !dest.projection.is_empty() {
1378 continue;
1379 }
1380 let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
1381 continue;
1382 };
1383 let Some(arg) = helpers::arg_of_local(place.local, arg_count) else {
1384 continue;
1385 };
1386 if place
1387 .projection
1388 .iter()
1389 .any(|p| matches!(p, ProjectionElem::Index(_)))
1390 {
1391 elem_load_arg.insert((dest.local, arg));
1392 } else if place.projection.is_empty() {
1393 copy_of_arg.insert((dest.local, arg));
1394 }
1395 }
1396 }
1397
1398 let elem_arg = |op: &Operand<'_>| -> Option<usize> {
1399 let (Operand::Copy(p) | Operand::Move(p)) = op else {
1400 return None;
1401 };
1402 if !p.projection.is_empty() {
1403 return None;
1404 }
1405 elem_load_arg
1406 .iter()
1407 .find(|(l, _)| *l == p.local)
1408 .map(|(_, a)| *a)
1409 };
1410 let scalar_arg = |op: &Operand<'_>| -> Option<usize> {
1411 let (Operand::Copy(p) | Operand::Move(p)) = op else {
1412 return None;
1413 };
1414 if !p.projection.is_empty() {
1415 return None;
1416 }
1417 helpers::arg_of_local(p.local, arg_count).or_else(|| {
1418 copy_of_arg
1419 .iter()
1420 .find(|(l, _)| *l == p.local)
1421 .map(|(_, a)| *a)
1422 })
1423 };
1424
1425 let mut bounds: Option<(usize, usize)> = None;
1426 let mut disjoint_arg: Option<usize> = None;
1427 for bb in body.basic_blocks.iter() {
1428 for stmt in &bb.statements {
1429 let StatementKind::Assign(assign) = &stmt.kind else {
1430 continue;
1431 };
1432 let (_, Rvalue::BinaryOp(op, pair)) = &**assign else {
1433 continue;
1434 };
1435 let (a, b) = &**pair;
1436 match op {
1437 BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt => {
1438 if let (Some(idx), Some(len)) = (elem_arg(a), scalar_arg(b)) {
1439 bounds = Some((idx, len));
1440 } else if let (Some(idx), Some(len)) = (elem_arg(b), scalar_arg(a)) {
1441 bounds = Some((idx, len));
1442 }
1443 }
1444 BinOp::Eq | BinOp::Ne => {
1445 if let (Some(x), Some(y)) = (elem_arg(a), elem_arg(b))
1446 && x == y
1447 {
1448 disjoint_arg = Some(x);
1449 }
1450 }
1451 _ => {}
1452 }
1453 }
1454 }
1455
1456 match (bounds, disjoint_arg) {
1457 (Some((idx, len)), Some(dj)) if dj == idx && idx != len => Some((idx, len)),
1458 _ => None,
1459 }
1460 })
1461 .ok()
1462 .flatten()
1463}
1464fn path_ends_in_return(body: &rustc_middle::mir::Body<'_>, path: &[usize]) -> bool {
1465 path.last().is_some_and(|block| {
1466 body.basic_blocks
1467 .get(BasicBlock::from_usize(*block))
1468 .and_then(|data| data.terminator.as_ref())
1469 .is_some_and(|terminator| matches!(terminator.kind, TerminatorKind::Return))
1470 })
1471}
1472
1473fn write_args_on_path<'tcx>(
1474 tcx: TyCtxt<'tcx>,
1475 body: &rustc_middle::mir::Body<'tcx>,
1476 path: &[usize],
1477 depth: usize,
1478 context: &CallContext,
1479 memo: &mut MustWriteMemo,
1480) -> HashSet<usize> {
1481 let mut writes = HashSet::new();
1482 for block in path {
1483 let Some(data) = body.basic_blocks.get(BasicBlock::from_usize(*block)) else {
1484 continue;
1485 };
1486
1487 for stmt in &data.statements {
1489 let StatementKind::Assign(assign) = &stmt.kind else {
1490 continue;
1491 };
1492 let dest = &assign.0;
1493 if dest.projection.first() == Some(&ProjectionElem::Deref) {
1494 if let Some(arg) = helpers::arg_of_local(dest.local, body.arg_count) {
1495 writes.insert(arg);
1496 }
1497 }
1498 }
1499
1500 let Some(terminator) = data.terminator.as_ref() else {
1501 continue;
1502 };
1503 let TerminatorKind::Call { func, args, .. } = &terminator.kind else {
1504 continue;
1505 };
1506
1507 if crate::verify::api_classify::is_ptr_write(helpers::dep_callee_def_id(func)) {
1509 if let Some(pointer_arg) = args
1510 .first()
1511 .and_then(|arg| trace_to_callee_arg(body, &arg.node))
1512 {
1513 writes.insert(pointer_arg);
1514 }
1515 continue;
1516 }
1517
1518 if let Some(nested) = helpers::dep_callee_def_id(func) {
1523 let nested_context = nested_call_context(body, args, context);
1524 if let Some(nested_writes) =
1525 must_write_args_rec(tcx, nested, depth + 1, &nested_context, memo)
1526 {
1527 for (i, arg) in args.iter().enumerate() {
1528 if nested_writes.contains(&i) {
1529 if let Some(outer) = trace_to_callee_arg(body, &arg.node) {
1530 writes.insert(outer);
1531 }
1532 }
1533 }
1534 }
1535 }
1536 }
1537 writes
1538}
1539
1540fn nested_call_context<'tcx>(
1547 body: &rustc_middle::mir::Body<'tcx>,
1548 args: &[Spanned<Operand<'tcx>>],
1549 context: &CallContext,
1550) -> CallContext {
1551 let mut nested_context = CallContext::default();
1552 for (i, arg) in args.iter().enumerate() {
1553 if let Some(v) = helpers::operand_const_u64(&arg.node) {
1554 nested_context.concrete.insert(i, v as i128);
1555 } else if let Some(outer) = trace_to_callee_arg(body, &arg.node) {
1556 if let Some(v) = context.concrete.get(&outer) {
1557 nested_context.concrete.insert(i, *v);
1558 }
1559 }
1560 }
1561 nested_context
1562}
1563
1564fn call_result_reaches_return<'tcx>(
1567 body: &rustc_middle::mir::Body<'tcx>,
1568 call_dest: Local,
1569) -> bool {
1570 let ret = Local::from_usize(0);
1571 let mut queue = VecDeque::from([call_dest]);
1572 let mut seen = HashSet::from([call_dest]);
1573 while let Some(current) = queue.pop_front() {
1574 if current == ret {
1575 return true;
1576 }
1577 for bb in body.basic_blocks.iter() {
1578 for stmt in &bb.statements {
1579 let StatementKind::Assign(assign) = &stmt.kind else {
1580 continue;
1581 };
1582 let dest = assign.0.local;
1583 if seen.contains(&dest) {
1584 continue;
1585 }
1586 match &assign.1 {
1587 Rvalue::Use(Operand::Copy(place), ..)
1588 | Rvalue::Use(Operand::Move(place), ..)
1589 | Rvalue::Cast(_, Operand::Copy(place), _)
1590 | Rvalue::Cast(_, Operand::Move(place), _) => {
1591 if place.local == current {
1592 queue.push_back(dest);
1593 seen.insert(dest);
1594 }
1595 }
1596 _ => {}
1597 }
1598 }
1599 }
1600 }
1601 false
1602}
1603
1604pub(super) fn callee_calls_other_local(tcx: TyCtxt<'_>, callee: DefId) -> bool {
1608 let body = tcx.optimized_mir(callee);
1609 for bb in body.basic_blocks.iter() {
1610 if matches!(
1611 bb.terminator().kind,
1612 rustc_middle::mir::TerminatorKind::Call { .. }
1613 ) {
1614 return true;
1615 }
1616 }
1617 false
1618}