use crate::compat::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_middle::{mir::BasicBlock, ty::TyCtxt};
use crate::analysis::path_analysis::{
PathTree,
graph::{PathEnumerator, 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>>,
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,
path_graph: None,
allow_repeat,
}
}
fn path_graph(&mut self) -> &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();
let allow_repeat = self.allow_repeat;
let tree = {
let graph = self.path_graph();
let mut enumerator = PathEnumerator::new(graph);
enumerator.enumerate_paths_repeat(allow_repeat)
};
let paths = self.find_paths_in_tree(&tree);
FunctionPaths {
callsite_paths: CallsitePaths::new(self.callsites, paths),
path_tree: tree,
}
}
fn find_paths_in_tree(&mut self, tree: &PathTree) -> FxHashMap<CallsiteLocation, Vec<Path>> {
let mut paths = FxHashMap::default();
for index in 0..self.callsites.len() {
let callsite = self.callsites[index].clone();
let per_callsite = self.collect_prefixes_from_tree(tree, &callsite);
paths.insert(callsite.location(), per_callsite);
}
paths
}
fn collect_prefixes_from_tree(&self, tree: &PathTree, callsite: &Callsite<'tcx>) -> Vec<Path> {
let target = callsite.location();
let target_block = callsite.block.as_usize();
let callee_name = callsite.callee_name(self.tcx);
rap_debug!(
"Callsite at bb{} -> {}: {} whole-cfg paths",
target_block,
callee_name,
tree.len()
);
let mut results = Vec::new();
let mut idx = 0usize;
let _ = tree.walk_prefixes(target_block, &mut |prefix: &[usize]| -> bool {
if results.len() >= PATH_LIMIT {
return false;
}
rap_debug!(" verify path {}: {:?}", idx, prefix);
idx += 1;
results.push(Path {
target,
start: PathStart::FunctionEntry,
steps: prefix
.iter()
.map(|&b| PathStep::Block(BasicBlock::from(b)))
.chain(std::iter::once(PathStep::Callsite(target)))
.collect(),
});
true
});
results
}
}
pub struct FunctionPaths<'tcx> {
callsite_paths: CallsitePaths<'tcx>,
path_tree: PathTree,
}
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 fn path_tree(&self) -> &PathTree {
&self.path_tree
}
}
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),
}