use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::DefId;
use rustc_middle::{mir::BasicBlock, ty::TyCtxt};
use crate::analysis::path_analysis::graph::PathGraph;
use super::helpers::{Callsite, CallsiteLocation};
pub(crate) const PATH_LIMIT: usize = 1024;
pub struct PathExtractor<'tcx> {
tcx: TyCtxt<'tcx>,
def_id: DefId,
callsites: Vec<Callsite<'tcx>>,
paths: FxHashMap<CallsiteLocation, Vec<Path>>,
path_graph: Option<PathGraph<'tcx>>,
allow_repeat: usize,
}
impl<'tcx> PathExtractor<'tcx> {
pub fn new(
tcx: TyCtxt<'tcx>,
def_id: DefId,
callsites: Vec<Callsite<'tcx>>,
allow_repeat: usize,
) -> Self {
Self {
tcx,
def_id,
callsites,
paths: FxHashMap::default(),
path_graph: None,
allow_repeat,
}
}
fn path_graph(&mut self) -> &mut PathGraph<'tcx> {
self.path_graph.get_or_insert_with(|| {
let mut pg = PathGraph::new(self.tcx, self.def_id);
pg.find_scc();
pg
})
}
pub fn run(mut self) -> FunctionPaths<'tcx> {
self.path_graph();
self.find_paths();
FunctionPaths {
callsite_paths: CallsitePaths::new(self.callsites, self.paths),
}
}
fn find_paths(&mut self) {
for index in 0..self.callsites.len() {
let callsite = self.callsites[index].clone();
let paths = self.find_paths_for_callsite(&callsite);
self.paths.insert(callsite.location(), paths);
}
}
fn find_paths_for_callsite(&mut self, callsite: &Callsite<'tcx>) -> Vec<Path> {
let target = callsite.location();
let target_block = callsite.block.as_usize();
let callee_name = callsite.callee_name(self.tcx);
let allow_repeat = self.allow_repeat;
let pg = self.path_graph();
let all_paths = pg.enumerate_paths_repeat(allow_repeat);
rap_debug!(
"Callsite at bb{} -> {}: {} whole-cfg paths",
target_block,
callee_name,
all_paths.len()
);
let mut results = Vec::new();
let mut seen_prefixes = FxHashSet::default();
for (idx, path) in all_paths.iter().enumerate() {
if results.len() >= PATH_LIMIT {
break;
}
let Some(pos) = path.iter().position(|&b| b == target_block) else {
continue;
};
let prefix: Vec<usize> = path[..=pos].to_vec();
if !seen_prefixes.insert(prefix.clone()) {
continue;
}
let reachable = pg.is_path_reachable(&prefix);
rap_debug!(
" verify path {}: {:?} | {}",
idx, prefix,
if reachable { "reachable" } else { "unreachable" }
);
if !reachable {
continue;
}
results.push(Path {
target,
start: PathStart::FunctionEntry,
steps: prefix
.into_iter()
.map(|b| PathStep::Block(BasicBlock::from(b)))
.chain(std::iter::once(PathStep::Callsite(target)))
.collect(),
});
}
results
}
}
pub struct FunctionPaths<'tcx> {
callsite_paths: CallsitePaths<'tcx>,
}
impl<'tcx> FunctionPaths<'tcx> {
pub fn paths_for(&self, location: CallsiteLocation) -> &[Path] {
self.callsite_paths.paths_for(location)
}
pub fn callsites(&self) -> &[Callsite<'tcx>] {
self.callsite_paths.callsites()
}
}
pub struct CallsitePaths<'tcx> {
callsites: Vec<Callsite<'tcx>>,
paths_by_callsite: FxHashMap<CallsiteLocation, Vec<Path>>,
}
impl<'tcx> CallsitePaths<'tcx> {
fn new(
callsites: Vec<Callsite<'tcx>>,
paths_by_callsite: FxHashMap<CallsiteLocation, Vec<Path>>,
) -> Self {
Self {
callsites,
paths_by_callsite,
}
}
pub fn callsites(&self) -> &[Callsite<'tcx>] {
&self.callsites
}
pub fn paths_for(&self, location: CallsiteLocation) -> &[Path] {
self.paths_by_callsite
.get(&location)
.map(Vec::as_slice)
.unwrap_or(&[])
}
}
#[derive(Clone, Debug)]
pub struct Path {
pub target: CallsiteLocation,
pub start: PathStart,
pub steps: Vec<PathStep>,
}
impl Path {
pub fn describe(&self) -> String {
self.describe_body()
}
pub fn describe_body(&self) -> String {
self.steps
.iter()
.filter_map(|step| match step {
PathStep::Block(bb) => Some(format!("{}", bb.as_usize())),
PathStep::Callsite(_) => None,
})
.collect::<Vec<_>>()
.join(" -> ")
}
pub fn describe_indices(&self) -> String {
let mut indices: Vec<usize> = Vec::new();
for step in &self.steps {
match step {
PathStep::Block(b) => indices.push(b.as_usize()),
PathStep::Callsite(l) => {
let bb = l.block.as_usize();
if indices.last() != Some(&bb) {
indices.push(bb);
}
}
}
}
format!("{:?}", indices)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PathStart {
FunctionEntry,
}
#[derive(Clone, Debug)]
pub enum PathStep {
Block(BasicBlock),
Callsite(CallsiteLocation),
}