use std::collections::{HashSet, VecDeque};
use rustc_hir::def_id::DefId;
use rustc_middle::{
mir::{
BasicBlock, BinOp, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind,
TerminatorKind,
},
ty::{Ty, TyCtxt, TyKind},
};
use crate::analysis::dataflow::{DataflowAnalysis, default::DataflowAnalyzer};
use crate::analysis::path::graph::{PathEnumerator, PathGraph};
use crate::helpers::mir_utils as helpers;
use super::CallEffect;
fn trace_to_callee_arg<'tcx>(
_tcx: TyCtxt<'tcx>,
body: &rustc_middle::mir::Body<'tcx>,
operand: &Operand<'_>,
) -> Option<usize> {
let local = match operand {
Operand::Copy(place) | Operand::Move(place) => place.local,
_ => return None,
};
let idx = local.as_usize();
if idx >= 1 && idx <= body.arg_count {
return Some(idx - 1);
}
let mut queue = VecDeque::from([local]);
let mut seen = HashSet::from([local]);
while let Some(current) = queue.pop_front() {
let cidx = current.as_usize();
if cidx >= 1 && cidx <= body.arg_count {
return Some(cidx - 1);
}
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let dest = assign.0.local;
if dest != current {
continue;
}
let source = match &assign.1 {
Rvalue::Use(Operand::Copy(place), ..)
| Rvalue::Use(Operand::Move(place), ..)
| Rvalue::Cast(_, Operand::Copy(place), _)
| Rvalue::Cast(_, Operand::Move(place), _)
| Rvalue::Ref(_, _, place)
| Rvalue::RawPtr(_, place)
| Rvalue::CopyForDeref(place) => place.local,
_ => continue,
};
if !seen.contains(&source) {
seen.insert(source);
queue.push_back(source);
}
}
let Some(terminator) = &bb.terminator else {
continue;
};
let TerminatorKind::Call {
func,
args,
destination,
..
} = &terminator.kind
else {
continue;
};
if destination.local != current {
continue;
}
let callee = helpers::dep_callee_def_id(func);
let traces_base = crate::verify::api_classify::is_as_ptr(callee)
|| crate::verify::api_classify::is_pointer_add(callee)
|| crate::verify::api_classify::is_pointer_sub(callee);
if !traces_base {
continue;
}
let Some(source) = args.first().and_then(|arg| match &arg.node {
Operand::Copy(place) | Operand::Move(place) => Some(place.local),
Operand::Constant(_) => None,
#[cfg(rapx_ge_99)]
Operand::RuntimeChecks(_) => None,
}) else {
continue;
};
if !seen.contains(&source) {
seen.insert(source);
queue.push_back(source);
}
}
}
None
}
pub(super) fn try_pointer_arith_wrapper_effect<'tcx>(
tcx: TyCtxt<'tcx>,
callee: DefId,
_destination: Option<Local>,
) -> Option<CallEffect> {
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.basic_blocks.len() > 16 {
return None;
}
for bb in body.basic_blocks.iter() {
let Some(terminator) = &bb.terminator else {
continue;
};
let TerminatorKind::Call {
func,
args,
destination: call_dest,
..
} = &terminator.kind
else {
continue;
};
let callee_id = helpers::dep_callee_def_id(func);
let is_add = crate::verify::api_classify::is_pointer_add(callee_id);
let is_sub = crate::verify::api_classify::is_pointer_sub(callee_id);
let inner_effect = if !is_add && !is_sub {
helpers::dep_callee_def_id(func).and_then(|inner_callee| {
if tcx.intrinsic(inner_callee).is_some()
|| helpers::is_drop_in_place(inner_callee)
{
return None;
}
try_pointer_arith_wrapper_effect(tcx, inner_callee, Some(call_dest.local))
})
} else {
None
};
if !is_add && !is_sub && inner_effect.is_none() {
continue;
}
if !call_result_reaches_return(body, call_dest.local) {
continue;
}
if let Some(effect) = inner_effect {
match effect {
CallEffect::ReturnPointerAdd {
base_arg: inner_base,
offset_arg: inner_offset,
stride,
}
| CallEffect::ReturnPointerSub {
base_arg: inner_base,
offset_arg: inner_offset,
stride,
} => {
let base_arg = trace_to_callee_arg(tcx, body, &args.get(inner_base)?.node)?;
let offset_arg = trace_to_callee_arg(tcx, body, &args.get(inner_offset)?.node)?;
return Some(match effect {
CallEffect::ReturnPointerSub { .. } => CallEffect::ReturnPointerSub {
base_arg,
offset_arg,
stride,
},
_ => CallEffect::ReturnPointerAdd {
base_arg,
offset_arg,
stride,
},
});
}
_ => {}
}
continue;
}
let base_arg = trace_to_callee_arg(tcx, body, &args.get(0)?.node)?;
let offset_arg = trace_to_callee_arg(tcx, body, &args.get(1)?.node)?;
let stride = if crate::verify::api_classify::is_byte_ptr_arith(callee_id) {
Some(1)
} else {
helpers::destination_stride(tcx, callee, Some(call_dest.local))
};
return if is_sub {
Some(CallEffect::ReturnPointerSub {
base_arg,
offset_arg,
stride,
})
} else {
Some(CallEffect::ReturnPointerAdd {
base_arg,
offset_arg,
stride,
})
};
}
None
}
pub(super) fn callee_contains_pointer_arithmetic(tcx: TyCtxt<'_>, callee: DefId) -> bool {
let Some(_) = callee.as_local() else {
return false;
};
if !tcx.is_mir_available(callee) {
return false;
}
let body = tcx.optimized_mir(callee);
for bb in body.basic_blocks.iter() {
let Some(terminator) = &bb.terminator else {
continue;
};
let TerminatorKind::Call { func, .. } = &terminator.kind else {
continue;
};
if crate::verify::api_classify::is_pointer_add(helpers::dep_callee_def_id(func))
|| crate::verify::api_classify::is_pointer_sub(helpers::dep_callee_def_id(func))
{
return true;
}
}
false
}
pub(super) fn local_return_dependencies(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
if !tcx.is_mir_available(callee) {
return None;
}
helpers::catch_panic(|| {
let mut analyzer = DataflowAnalyzer::new(tcx, false);
analyzer.build_graph(callee);
let deps = analyzer.get_fn_arg2ret(callee);
deps.iter_enumerated()
.filter_map(|(local, depends)| {
if *depends && local.as_usize() > 0 {
Some(local.as_usize() - 1)
} else {
None
}
})
.collect()
})
.ok()
}
pub(super) fn try_from_raw_parts_wrapper_effect<'tcx>(
tcx: TyCtxt<'tcx>,
callee: DefId,
_destination: Option<Local>,
) -> Option<CallEffect> {
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.basic_blocks.len() > 8 {
return None;
}
let ret = Local::from_usize(0);
for bb in body.basic_blocks.iter() {
let Some(terminator) = &bb.terminator else {
continue;
};
let TerminatorKind::Call {
func,
args,
destination: call_dest,
..
} = &terminator.kind
else {
continue;
};
let inner_callee = helpers::dep_callee_def_id(func);
if !crate::verify::api_classify::is_from_raw_parts(inner_callee) {
continue;
}
if !call_result_reaches_return(body, call_dest.local) {
continue;
}
let pointer_arg = trace_to_callee_arg(tcx, body, &args.get(0)?.node)?;
let size_arg = trace_to_callee_arg(tcx, body, &args.get(1)?.node)?;
let elem_size =
crate::verify::call_summary::from_raw_parts_elem_size(tcx, callee, Some(ret));
return Some(CallEffect::ReturnFreshAllocation {
pointer_arg,
size_arg,
elem_size,
});
}
None
}
pub(crate) fn try_field_load_effect(tcx: TyCtxt<'_>, callee: DefId) -> Option<CallEffect> {
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.basic_blocks.len() > 4 || body.arg_count < 1 {
return None;
}
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
match &stmt.kind {
StatementKind::Assign(assign) => {
let (place, rvalue) = &**assign;
if place.local.as_usize() == 0 && place.projection.is_empty() {
let src_place = match rvalue {
Rvalue::Use(Operand::Copy(p), ..)
| Rvalue::Use(Operand::Move(p), ..) => p,
Rvalue::CopyForDeref(p) => p,
_ => return None,
};
if src_place.local.as_usize() == 1 {
let mut proj = src_place.projection.iter();
if !matches!(proj.next().map(|p| p.kind()), Some(ProjectionElem::Deref))
{
return None;
}
let Some(ProjectionElem::Field(idx, _)) = proj.next().map(|p| p.kind())
else {
return None;
};
if proj.next().is_some() {
return None;
}
return Some(CallEffect::ReturnFieldOfArg {
arg: 0,
field: idx.as_usize(),
});
}
return None;
}
return None;
}
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => {}
_ => return None,
}
}
}
None
}
pub(crate) fn try_ptr_field_return_effect(
tcx: TyCtxt<'_>,
callee: DefId,
) -> Option<CallEffect> {
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.arg_count < 1 {
return None;
}
let ret_ty = body.local_decls[Local::from_usize(0)].ty;
if !matches!(ret_ty.kind(), TyKind::RawPtr(..)) {
return None;
}
let pre_dec_offset = detect_pre_dec_end_offset(tcx, &body);
let mut queue = VecDeque::from([Local::from_usize(0)]);
let mut seen = HashSet::from([Local::from_usize(0)]);
while let Some(cur) = queue.pop_front() {
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (dest, rvalue) = &**assign;
if dest.local != cur || !dest.projection.is_empty() {
continue;
}
let src: Option<&Place<'_>> = match rvalue {
Rvalue::Use(Operand::Copy(p) | Operand::Move(p), ..) => Some(p),
Rvalue::CopyForDeref(p) => Some(p),
Rvalue::Cast(_, Operand::Copy(p) | Operand::Move(p), _) => Some(p),
_ => None,
};
let Some(src) = src else {
continue;
};
if src.local.as_usize() >= 1 && src.local.as_usize() <= body.arg_count {
let mut proj = src.projection.iter();
if matches!(proj.next().map(|p| p.kind()), Some(ProjectionElem::Deref)) {
if let Some(ProjectionElem::Field(idx, _)) = proj.next().map(|p| p.kind()) {
if proj.next().is_none() {
let arg = src.local.as_usize() - 1;
let field = idx.as_usize();
return match pre_dec_offset {
Some(offset) if offset > 0 => {
Some(CallEffect::ReturnFieldOfArgSub { arg, field, offset })
}
_ => Some(CallEffect::ReturnFieldOfArg { arg, field }),
};
}
}
}
}
if src.projection.is_empty() && seen.insert(src.local) {
queue.push_back(src.local);
}
}
}
}
None
}
fn copy_root(body: &rustc_middle::mir::Body<'_>, mut local: Local) -> Local {
let mut seen = HashSet::new();
loop {
if !seen.insert(local) {
break;
}
let mut next = None;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (dest, rvalue) = &**assign;
if dest.local != local || !dest.projection.is_empty() {
continue;
}
let Rvalue::Use(op, ..) = rvalue else {
continue;
};
let (Operand::Copy(p) | Operand::Move(p)) = op else {
continue;
};
if p.projection.is_empty() {
next = Some(p.local);
}
}
}
match next {
Some(n) => local = n,
None => break,
}
}
local
}
pub(crate) fn try_slice_bounded_return_effect(
tcx: TyCtxt<'_>,
callee: DefId,
) -> Option<CallEffect> {
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.basic_blocks.len() > 12 || body.arg_count < 1 {
return None;
}
let mut payload_root: Option<Local> = None;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (place, rvalue) = &**assign;
if place.local.as_usize() != 0 || !place.projection.is_empty() {
continue;
}
let Rvalue::Aggregate(kind, operands) = rvalue else {
continue;
};
let rustc_middle::mir::AggregateKind::Adt(adt, variant_idx, ..) = &**kind else {
continue;
};
if !tcx.is_diagnostic_item(rustc_span::sym::Option, *adt) {
continue;
}
if variant_idx.as_usize() != 1 {
continue; }
let Some(payload) = operands.iter().next() else {
continue;
};
let (Operand::Copy(p) | Operand::Move(p)) = payload else {
continue;
};
if p.projection.is_empty() {
payload_root = Some(copy_root(&body, p.local));
}
}
}
let payload_root = payload_root?;
let mut len_defs: Vec<(Local, usize)> = Vec::new();
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (place, rvalue) = &**assign;
if !place.projection.is_empty() {
continue;
}
let Rvalue::UnaryOp(op, operand) = rvalue else {
continue;
};
if !matches!(op, rustc_middle::mir::UnOp::PtrMetadata) {
continue;
}
let (Operand::Copy(p) | Operand::Move(p)) = operand else {
continue;
};
if p.projection.is_empty()
&& p.local.as_usize() >= 1
&& p.local.as_usize() <= body.arg_count
{
len_defs.push((place.local, p.local.as_usize() - 1));
}
}
}
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (_, rvalue) = &**assign;
let Rvalue::BinaryOp(op, pair) = rvalue else {
continue;
};
if !matches!(op, BinOp::Lt | BinOp::Le) {
continue;
}
let (a, b) = &**pair;
let a_root = match a {
Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => {
copy_root(&body, p.local)
}
_ => continue,
};
if a_root != payload_root {
continue;
}
let b_local = match b {
Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => Some(p.local),
_ => None,
};
let Some(&(_, arg)) = len_defs.iter().find(|(tmp, _)| Some(*tmp) == b_local) else {
continue;
};
return match op {
BinOp::Lt => Some(CallEffect::ReturnOptionSomeIndexLtArgLen { arg }),
_ => None,
};
}
}
None
}
pub(crate) fn try_branch_effect(tcx: TyCtxt<'_>, callee: DefId) -> Option<CallEffect> {
let name = tcx.def_path_str(callee);
if !name.ends_with("::branch") {
return None;
}
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.arg_count != 1 {
return None;
}
let arg_ty = body.local_decls[Local::from_usize(1)].ty;
let TyKind::Adt(arg_adt, _) = arg_ty.kind() else {
return None;
};
if !tcx.is_diagnostic_item(rustc_span::sym::Option, arg_adt.did()) {
return None;
}
let ret_ty = body.local_decls[Local::from_usize(0)].ty;
let TyKind::Adt(ret_adt, _) = ret_ty.kind() else {
return None;
};
if !tcx.def_path_str(ret_adt.did()).contains("ControlFlow") {
return None;
}
Some(CallEffect::ReturnBranchPayload { arg: 0 })
}
pub(crate) fn is_slice_get_summary(tcx: TyCtxt<'_>, callee: DefId) -> bool {
let Some(assoc) = tcx.opt_associated_item(callee) else {
return false;
};
let name = assoc.name();
let name_str = name.as_str();
if name_str != "get" && name_str != "get_mut" {
return false;
}
let path = tcx.def_path_str(callee);
path.contains("::slice::") || path.contains("slice::<impl")
}
fn block_dominates(body: &rustc_middle::mir::Body<'_>, a: BasicBlock, b: BasicBlock) -> bool {
let entry = 0usize;
let mut queue = VecDeque::from([entry]);
let mut seen = HashSet::from([entry]);
while let Some(cur) = queue.pop_front() {
if cur == b.as_usize() {
return false; }
if cur == a.as_usize() {
continue; }
for succ in body.basic_blocks[BasicBlock::from_usize(cur)].terminator().successors() {
if seen.insert(succ.as_usize()) {
queue.push_back(succ.as_usize());
}
}
}
true
}
pub(crate) fn try_decode_length_return_effect(
tcx: TyCtxt<'_>,
callee: DefId,
) -> Option<CallEffect> {
if !tcx.is_mir_available(callee) {
return None;
}
let body = tcx.optimized_mir(callee);
if body.arg_count < 1 {
return None;
}
let ret_ty = body.local_decls[Local::from_usize(0)].ty;
let TyKind::Adt(adt, substs) = ret_ty.kind() else {
return None;
};
if !tcx.is_diagnostic_item(rustc_span::sym::Option, adt.did()) {
return None;
}
let inner = substs.type_at(0);
let TyKind::Tuple(tys) = inner.kind() else {
return None;
};
let Some(field) = tys.iter().position(|t| {
matches!(t.kind(), TyKind::Uint(rustc_middle::ty::UintTy::Usize))
}) else {
return None;
};
let mut returns: Vec<(BasicBlock, u64)> = Vec::new();
let mut computed_returns: Vec<BasicBlock> = Vec::new();
for (bb, data) in body.basic_blocks.iter_enumerated() {
for stmt in &data.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (place, rvalue) = &**assign;
if place.local.as_usize() != 0 || !place.projection.is_empty() {
continue;
}
let Rvalue::Aggregate(kind, operands) = rvalue else {
continue;
};
let rustc_middle::mir::AggregateKind::Adt(adt, variant_idx, ..) = &**kind else {
continue;
};
if !tcx.is_diagnostic_item(rustc_span::sym::Option, *adt) {
continue;
}
if variant_idx.as_usize() != 1 {
continue; }
let Some(payload) = operands.iter().next() else {
continue;
};
match tuple_field_len_kind(&body, payload, field) {
Some(TupleFieldLen::Const(len)) => returns.push((bb, len)),
Some(TupleFieldLen::LenSub) => computed_returns.push(bb),
None => {}
}
}
}
if returns.is_empty() && computed_returns.is_empty() {
return None;
}
let mut gets: Vec<(BasicBlock, u64)> = Vec::new();
for (bb, data) in body.basic_blocks.iter_enumerated() {
let TerminatorKind::Call { func, args, .. } = &data.terminator().kind else {
continue;
};
let Some(get_callee) = helpers::dep_callee_def_id(func) else {
continue;
};
if !tcx.opt_associated_item(get_callee).is_some_and(|a| {
let name = a.name();
matches!(name.as_str(), "get" | "index" | "index_mut")
}) {
continue;
}
let Some(k) = args.get(1).and_then(|a| helpers::operand_const_u64(&a.node)) else {
continue;
};
gets.push((bb, k));
}
if gets.is_empty() && computed_returns.is_empty() {
return None;
}
for (bb, len) in &returns {
if *len <= 1 {
continue;
}
let k = len.checked_sub(1)?;
let Some(&(get_bb, _)) = gets.iter().find(|(_, kk)| *kk == k) else {
return None;
};
if !block_dominates(&body, get_bb, *bb) {
return None;
}
}
Some(CallEffect::ReturnOptionSomeTupleFieldLeArgLen { field, arg: 0 })
}
enum TupleFieldLen {
Const(u64),
LenSub,
}
fn tuple_field_len_kind<'tcx>(
body: &rustc_middle::mir::Body<'tcx>,
operand: &Operand<'tcx>,
field: usize,
) -> Option<TupleFieldLen> {
let tuple_local = match operand {
Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => p.local,
_ => return None,
};
let mut field_operand: Option<Operand<'tcx>> = None;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (place, rvalue) = &**assign;
if place.local != tuple_local || !place.projection.is_empty() {
continue;
}
let Rvalue::Aggregate(kind, operands) = rvalue else {
continue;
};
if !matches!(&**kind, rustc_middle::mir::AggregateKind::Tuple) {
continue;
}
field_operand = operands
.get(rustc_abi::FieldIdx::from_usize(field))
.cloned();
}
}
let mut cur = field_operand?;
loop {
if let Some(c) = helpers::operand_const_u64(&cur) {
return Some(TupleFieldLen::Const(c));
}
let local = match &cur {
Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => p.local,
Operand::Copy(p) | Operand::Move(p)
if p.projection.len() == 1
&& matches!(
p.projection[0].kind(),
rustc_middle::mir::ProjectionElem::Field(
rustc_abi::FieldIdx::ZERO,
_
)
) =>
{
p.local
}
_ => return None,
};
let mut defining: Option<&Rvalue<'tcx>> = None;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (place, rvalue) = &**assign;
if place.local != local || !place.projection.is_empty() {
continue;
}
defining = Some(rvalue);
}
}
match defining? {
Rvalue::Use(op, ..) => cur = op.clone(),
Rvalue::BinaryOp(BinOp::Sub | BinOp::SubWithOverflow, pair) => {
let (lhs, _) = &**pair;
return operand_is_ptr_metadata(body, lhs).then_some(TupleFieldLen::LenSub);
}
_ => return None,
}
}
}
fn operand_is_ptr_metadata<'tcx>(body: &rustc_middle::mir::Body<'tcx>, operand: &Operand<'tcx>) -> bool {
let mut cur = operand.clone();
loop {
let local = match &cur {
Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => p.local,
_ => return false,
};
for bb in body.basic_blocks.iter() {
let TerminatorKind::Call { func, destination, .. } = &bb.terminator().kind else {
continue;
};
if destination.local == local
&& crate::verify::api_classify::is_len(helpers::dep_callee_def_id(func))
{
return true;
}
}
let mut defining: Option<&Rvalue<'tcx>> = None;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (place, rvalue) = &**assign;
if place.local != local || !place.projection.is_empty() {
continue;
}
defining = Some(rvalue);
}
}
match defining {
Some(Rvalue::UnaryOp(op, _)) => {
return matches!(op, rustc_middle::mir::UnOp::PtrMetadata);
}
Some(Rvalue::Use(op, ..)) => cur = op.clone(),
_ => return false,
}
}
}
fn detect_pre_dec_end_offset<'tcx>(
tcx: TyCtxt<'tcx>,
body: &rustc_middle::mir::Body<'tcx>,
) -> Option<u64> {
for bb in body.basic_blocks.iter() {
let Some(term) = &bb.terminator else {
continue;
};
let TerminatorKind::Call { func, args, .. } = &term.kind else {
continue;
};
let Some(callee) = helpers::dep_callee_def_id(func) else {
continue;
};
if !crate::helpers::mir_utils::is_pre_dec_end(tcx, callee) {
continue;
}
return args.get(1).and_then(|a| helpers::operand_const_u64(&a.node));
}
None
}
pub(super) fn try_iter_constructor_effect<'tcx>(
tcx: TyCtxt<'tcx>,
callee: DefId,
) -> Option<CallEffect> {
let fn_sig = tcx.fn_sig(callee).skip_binder();
let output = fn_sig.output().skip_binder();
let TyKind::Adt(adt, substs) = output.kind() else {
return None;
};
let inputs = fn_sig.inputs().skip_binder();
let Some(arg0) = inputs.first() else {
return None;
};
let TyKind::Ref(_, inner, _) = arg0.kind() else {
return None;
};
let TyKind::Slice(elem_ty) = inner.kind() else {
return None;
};
let elem_ty = *elem_ty;
if adt.is_enum() {
return None;
}
let variant = adt.non_enum_variant();
if variant.fields.len() < 2 {
return None;
}
let is_elem_ptr = |ty: Ty<'tcx>| -> bool {
match ty.kind() {
TyKind::RawPtr(pointee, _) => *pointee == elem_ty,
TyKind::Adt(a, args)
if crate::verify::api_classify::is_std_nonnull(a.did())
&& args.type_at(0) == elem_ty =>
{
true
}
_ => false,
}
};
let mut fields = variant.fields.iter();
let (Some(f0), Some(f1)) = (fields.next(), fields.next()) else {
return None;
};
if !is_elem_ptr(helpers::field_ty(tcx, f0, substs))
|| !is_elem_ptr(helpers::field_ty(tcx, f1, substs))
{
return None;
}
if !iter_ctor_reads_slice_len(tcx, callee, 1) {
return None;
}
Some(CallEffect::ReturnIter { receiver_arg: 0 })
}
fn iter_ctor_reads_slice_len<'tcx>(tcx: TyCtxt<'tcx>, callee: DefId, depth: usize) -> bool {
if !tcx.is_mir_available(callee) {
return false;
}
let body = tcx.optimized_mir(callee);
if body_reads_slice_len(tcx, body) {
return true;
}
if depth > 0 {
if let Some(target) = single_call_wrapper_target(tcx, callee) {
return iter_ctor_reads_slice_len(tcx, target, depth - 1);
}
}
false
}
fn body_reads_slice_len<'tcx>(tcx: TyCtxt<'tcx>, body: &rustc_middle::mir::Body<'tcx>) -> bool {
for bb in body.basic_blocks.iter() {
let Some(term) = &bb.terminator else { continue };
let TerminatorKind::Call { func, args, .. } = &term.kind else {
continue;
};
if !crate::verify::api_classify::is_len(helpers::dep_callee_def_id(func)) {
continue;
}
if let Some(arg0) = args.first()
&& trace_to_callee_arg(tcx, body, &arg0.node) == Some(0)
{
return true;
}
}
false
}
fn single_call_wrapper_target<'tcx>(tcx: TyCtxt<'tcx>, callee: DefId) -> Option<DefId> {
let body = tcx.optimized_mir(callee);
let mut found: Option<DefId> = None;
for bb in body.basic_blocks.iter() {
let Some(term) = &bb.terminator else { continue };
let TerminatorKind::Call {
func, destination, ..
} = &term.kind
else {
continue;
};
if destination.local.as_usize() != 0 {
continue;
}
let Some(c) = helpers::dep_callee_def_id(func) else {
return None;
};
match found {
Some(f) if f != c => return None,
_ => found = Some(c),
}
}
found
}
pub(super) fn local_must_write_args(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
must_write_args_rec(tcx, callee, 0).map(|set| set.into_iter().collect())
}
fn must_write_args_rec(tcx: TyCtxt<'_>, callee: DefId, depth: usize) -> Option<HashSet<usize>> {
if depth > 4 {
return None;
}
if !tcx.is_mir_available(callee) {
return None;
}
if tcx.intrinsic(callee).is_some() || helpers::is_drop_in_place(callee) {
return None;
}
helpers::catch_panic(|| {
let body = tcx.optimized_mir(callee);
let mut graph = PathGraph::new(tcx, callee);
graph.find_scc();
let mut enumerator = PathEnumerator::new(&graph);
let paths = enumerator.enumerate_paths_repeat(0);
let mut must_write: Option<HashSet<usize>> = None;
for path in paths.iter() {
if !path_ends_in_return(body, &path) {
continue;
}
let writes = write_args_on_path(tcx, body, &path, depth);
must_write = Some(match must_write {
Some(current) => current.intersection(&writes).copied().collect(),
None => writes,
});
}
must_write.unwrap_or_default()
})
.ok()
}
pub(super) fn named_index_disjoint_validator(name: &str) -> Option<(usize, usize)> {
let base = name
.split('<')
.next()
.unwrap_or(name)
.trim_end_matches("::");
if base.ends_with("get_disjoint_check_valid") || base.ends_with("get_disjoint_check_valid_ext")
{
Some((0, 1))
} else {
None
}
}
pub(super) fn detect_index_disjoint_validator(
tcx: TyCtxt<'_>,
callee: DefId,
) -> Option<(usize, usize)> {
callee.as_local()?;
if !tcx.is_mir_available(callee) {
return None;
}
helpers::catch_panic(|| {
let body = tcx.optimized_mir(callee);
let arg_count = body.arg_count;
let mut elem_load_arg: HashSet<(Local, usize)> = HashSet::new();
let mut copy_of_arg: HashSet<(Local, usize)> = HashSet::new();
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (dest, rvalue) = &**assign;
if !dest.projection.is_empty() {
continue;
}
let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
continue;
};
let Some(arg) = helpers::arg_of_local(place.local, arg_count) else {
continue;
};
if place
.projection
.iter()
.any(|p| matches!(p, ProjectionElem::Index(_)))
{
elem_load_arg.insert((dest.local, arg));
} else if place.projection.is_empty() {
copy_of_arg.insert((dest.local, arg));
}
}
}
let elem_arg = |op: &Operand<'_>| -> Option<usize> {
let (Operand::Copy(p) | Operand::Move(p)) = op else {
return None;
};
if !p.projection.is_empty() {
return None;
}
elem_load_arg
.iter()
.find(|(l, _)| *l == p.local)
.map(|(_, a)| *a)
};
let scalar_arg = |op: &Operand<'_>| -> Option<usize> {
let (Operand::Copy(p) | Operand::Move(p)) = op else {
return None;
};
if !p.projection.is_empty() {
return None;
}
helpers::arg_of_local(p.local, arg_count).or_else(|| {
copy_of_arg
.iter()
.find(|(l, _)| *l == p.local)
.map(|(_, a)| *a)
})
};
let mut bounds: Option<(usize, usize)> = None;
let mut disjoint_arg: Option<usize> = None;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (_, Rvalue::BinaryOp(op, pair)) = &**assign else {
continue;
};
let (a, b) = &**pair;
match op {
BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt => {
if let (Some(idx), Some(len)) = (elem_arg(a), scalar_arg(b)) {
bounds = Some((idx, len));
} else if let (Some(idx), Some(len)) = (elem_arg(b), scalar_arg(a)) {
bounds = Some((idx, len));
}
}
BinOp::Eq | BinOp::Ne => {
if let (Some(x), Some(y)) = (elem_arg(a), elem_arg(b))
&& x == y
{
disjoint_arg = Some(x);
}
}
_ => {}
}
}
}
match (bounds, disjoint_arg) {
(Some((idx, len)), Some(dj)) if dj == idx && idx != len => Some((idx, len)),
_ => None,
}
})
.ok()
.flatten()
}
fn path_ends_in_return(body: &rustc_middle::mir::Body<'_>, path: &[usize]) -> bool {
path.last().is_some_and(|block| {
body.basic_blocks
.get(BasicBlock::from_usize(*block))
.and_then(|data| data.terminator.as_ref())
.is_some_and(|terminator| matches!(terminator.kind, TerminatorKind::Return))
})
}
fn write_args_on_path<'tcx>(
tcx: TyCtxt<'tcx>,
body: &rustc_middle::mir::Body<'tcx>,
path: &[usize],
depth: usize,
) -> HashSet<usize> {
let mut writes = HashSet::new();
for block in path {
let Some(data) = body.basic_blocks.get(BasicBlock::from_usize(*block)) else {
continue;
};
for stmt in &data.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let dest = &assign.0;
if dest.projection.first() == Some(&ProjectionElem::Deref) {
if let Some(arg) = helpers::arg_of_local(dest.local, body.arg_count) {
writes.insert(arg);
}
}
}
let Some(terminator) = data.terminator.as_ref() else {
continue;
};
let TerminatorKind::Call { func, args, .. } = &terminator.kind else {
continue;
};
if crate::verify::api_classify::is_ptr_write(helpers::dep_callee_def_id(func)) {
if let Some(pointer_arg) = args
.first()
.and_then(|arg| trace_to_callee_arg(tcx, body, &arg.node))
{
writes.insert(pointer_arg);
}
continue;
}
if let Some(nested) = helpers::dep_callee_def_id(func) {
if let Some(nested_writes) = must_write_args_rec(tcx, nested, depth + 1) {
for (i, arg) in args.iter().enumerate() {
if nested_writes.contains(&i) {
if let Some(outer) = trace_to_callee_arg(tcx, body, &arg.node) {
writes.insert(outer);
}
}
}
}
}
}
writes
}
fn call_result_reaches_return<'tcx>(
body: &rustc_middle::mir::Body<'tcx>,
call_dest: Local,
) -> bool {
let ret = Local::from_usize(0);
let mut queue = VecDeque::from([call_dest]);
let mut seen = HashSet::from([call_dest]);
while let Some(current) = queue.pop_front() {
if current == ret {
return true;
}
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let dest = assign.0.local;
if seen.contains(&dest) {
continue;
}
match &assign.1 {
Rvalue::Use(Operand::Copy(place), ..)
| Rvalue::Use(Operand::Move(place), ..)
| Rvalue::Cast(_, Operand::Copy(place), _)
| Rvalue::Cast(_, Operand::Move(place), _) => {
if place.local == current {
queue.push_back(dest);
seen.insert(dest);
}
}
_ => {}
}
}
}
}
false
}
pub(super) fn callee_calls_other_local(tcx: TyCtxt<'_>, callee: DefId) -> bool {
let body = tcx.optimized_mir(callee);
for bb in body.basic_blocks.iter() {
if matches!(
bb.terminator().kind,
rustc_middle::mir::TerminatorKind::Call { .. }
) {
return true;
}
}
false
}