use std::collections::{HashMap, HashSet, VecDeque};
use rustc_hir::def_id::DefId;
use rustc_middle::{
mir::{
BasicBlock, Operand, ProjectionElem, Rvalue, StatementKind, TerminatorKind,
},
ty::TyCtxt,
};
use crate::analysis::dataflow::{DataflowAnalysis, default::DataflowAnalyzer};
use crate::analysis::path::graph::{PathEnumerator, PathGraph};
use crate::compat::Spanned;
use crate::helpers::mir_utils as helpers;
use super::CallContext;
fn trace_to_callee_arg<'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_95)]
Operand::RuntimeChecks(_) => None,
}) else {
continue;
};
if !seen.contains(&source) {
seen.insert(source);
queue.push_back(source);
}
}
}
None
}
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()
}
type MustWriteMemo = HashMap<(DefId, usize, Vec<(usize, i128)>), Option<HashSet<usize>>>;
fn context_key(context: &CallContext) -> Vec<(usize, i128)> {
let mut entries: Vec<(usize, i128)> = context.concrete.iter().map(|(k, v)| (*k, *v)).collect();
entries.sort_unstable();
entries
}
pub(super) fn local_must_write_args(
tcx: TyCtxt<'_>,
callee: DefId,
context: &CallContext,
) -> Option<Vec<usize>> {
must_write_args_rec(tcx, callee, 0, context, &mut HashMap::new())
.map(|set| set.into_iter().collect())
}
fn must_write_args_rec(
tcx: TyCtxt<'_>,
callee: DefId,
depth: usize,
context: &CallContext,
memo: &mut MustWriteMemo,
) -> 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;
}
let key = (callee, depth, context_key(context));
if let Some(summary) = memo.get(&key) {
return summary.clone();
}
let summary = 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);
if paths.is_truncated() {
return None;
}
let mut must_write: Option<HashSet<usize>> = None;
for path in paths.iter() {
if !path_ends_in_return(body, &path) {
continue;
}
if path_infeasible_under_context(body, &path, context) {
continue;
}
let writes = write_args_on_path(tcx, body, &path, depth, context, memo);
must_write = Some(match must_write {
Some(current) => current.intersection(&writes).copied().collect(),
None => writes,
});
}
Some(must_write.unwrap_or_default())
})
.ok()
.flatten();
memo.insert(key, summary.clone());
summary
}
fn path_infeasible_under_context(
body: &rustc_middle::mir::Body<'_>,
path: &[usize],
context: &CallContext,
) -> bool {
if context.concrete.is_empty() {
return false;
}
for window in path.windows(2) {
let (block, next) = (window[0], window[1]);
let Some(data) = body.basic_blocks.get(BasicBlock::from_usize(block)) else {
continue;
};
let Some(terminator) = &data.terminator else {
continue;
};
let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind else {
continue;
};
let Some(value) = switch_discriminant_concrete(body, discr, context) else {
continue;
};
let expected = targets
.iter()
.find(|(val, _)| *val == value as u128)
.map(|(_, t)| t)
.unwrap_or_else(|| targets.otherwise());
if expected.as_usize() != next {
return true;
}
}
false
}
fn switch_discriminant_concrete(
body: &rustc_middle::mir::Body<'_>,
discr: &Operand<'_>,
context: &CallContext,
) -> Option<i128> {
let local = match discr {
Operand::Copy(place) | Operand::Move(place) => place.local,
_ => return None,
};
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 context.concrete.get(&(cidx - 1)).copied();
}
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
if assign.0.local != current {
continue;
}
let source = match &assign.1 {
Rvalue::Use(Operand::Copy(place), ..)
| Rvalue::Use(Operand::Move(place), ..) => place.local,
_ => continue,
};
if !seen.contains(&source) {
seen.insert(source);
queue.push_back(source);
}
}
}
}
None
}
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,
context: &CallContext,
memo: &mut MustWriteMemo,
) -> 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)
&& 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(body, &arg.node))
{
writes.insert(pointer_arg);
}
continue;
}
if let Some(nested) = helpers::dep_callee_def_id(func) {
let nested_context = nested_call_context(body, args, context);
if let Some(nested_writes) =
must_write_args_rec(tcx, nested, depth + 1, &nested_context, memo)
{
for (i, arg) in args.iter().enumerate() {
if nested_writes.contains(&i)
&& let Some(outer) = trace_to_callee_arg(body, &arg.node) {
writes.insert(outer);
}
}
}
}
}
writes
}
fn nested_call_context<'tcx>(
body: &rustc_middle::mir::Body<'tcx>,
args: &[Spanned<Operand<'tcx>>],
context: &CallContext,
) -> CallContext {
let mut nested_context = CallContext::default();
for (i, arg) in args.iter().enumerate() {
if let Some(v) = helpers::operand_const_u64(&arg.node) {
nested_context.concrete.insert(i, v as i128);
} else if let Some(outer) = trace_to_callee_arg(body, &arg.node)
&& let Some(v) = context.concrete.get(&outer) {
nested_context.concrete.insert(i, *v);
}
}
nested_context
}