use crate::cfg::{BlockId, Cfg};
use petgraph::graph::NodeIndex;
use std::collections::{BTreeSet, HashMap, HashSet};
use super::{classify_path_precomputed, EnumerationContext, Path, PathLimits};
struct ContextPathEnumerator<'a> {
cfg: &'a Cfg,
limits: &'a PathLimits,
ctx: &'a EnumerationContext,
paths: Vec<Path>,
current_path: Vec<BlockId>,
visited: HashSet<NodeIndex>,
loop_iterations: HashMap<NodeIndex, usize>,
}
impl<'a> ContextPathEnumerator<'a> {
fn new(cfg: &'a Cfg, limits: &'a PathLimits, ctx: &'a EnumerationContext) -> Self {
Self {
cfg,
limits,
ctx,
paths: Vec::new(),
current_path: Vec::new(),
visited: HashSet::new(),
loop_iterations: HashMap::new(),
}
}
fn enumerate(mut self, entry: NodeIndex) -> Vec<Path> {
self.dfs(entry);
self.paths
}
fn dfs(&mut self, current: NodeIndex) {
let block_id = match self.cfg.node_weight(current) {
Some(block) => block.id,
None => return,
};
self.current_path.push(block_id);
if self.current_path.len() > self.limits.max_length {
self.current_path.pop();
return;
}
if self.ctx.is_exit(current) {
let kind =
classify_path_precomputed(self.cfg, &self.current_path, &self.ctx.reachable_blocks);
self.paths.push(Path::new(self.current_path.clone(), kind));
self.current_path.pop();
return;
}
if self.paths.len() >= self.limits.max_paths {
self.current_path.pop();
return;
}
if self.visited.contains(¤t) && !self.ctx.is_loop_header(current) {
self.current_path.pop();
return;
}
self.visited.insert(current);
let is_loop_header = self.ctx.is_loop_header(current);
if is_loop_header {
let count = self.loop_iterations.entry(current).or_insert(0);
if *count >= self.limits.loop_unroll_limit {
self.visited.remove(¤t);
self.current_path.pop();
return;
}
*count += 1;
}
let neighbors: Vec<_> = self.cfg.neighbors(current).collect();
for next in neighbors {
self.dfs(next);
}
if is_loop_header {
if let Some(count) = self.loop_iterations.get_mut(¤t) {
*count = count.saturating_sub(1);
}
}
self.visited.remove(¤t);
self.current_path.pop();
}
}
struct PathEnumerator<'a> {
cfg: &'a Cfg,
exits: HashSet<NodeIndex>,
limits: &'a PathLimits,
loop_headers: HashSet<NodeIndex>,
reachable_blocks: HashSet<BlockId>,
paths: Vec<Path>,
current_path: Vec<BlockId>,
visited: HashSet<NodeIndex>,
loop_iterations: HashMap<NodeIndex, usize>,
}
impl<'a> PathEnumerator<'a> {
fn new(
cfg: &'a Cfg,
exits: HashSet<NodeIndex>,
limits: &'a PathLimits,
loop_headers: HashSet<NodeIndex>,
reachable_blocks: HashSet<BlockId>,
) -> Self {
Self {
cfg,
exits,
limits,
loop_headers,
reachable_blocks,
paths: Vec::new(),
current_path: Vec::new(),
visited: HashSet::new(),
loop_iterations: HashMap::new(),
}
}
fn enumerate(mut self, entry: NodeIndex) -> Vec<Path> {
self.dfs(entry);
self.paths
}
fn dfs(&mut self, current: NodeIndex) {
let block_id = match self.cfg.node_weight(current) {
Some(block) => block.id,
None => return,
};
self.current_path.push(block_id);
if self.current_path.len() > self.limits.max_length {
self.current_path.pop();
return;
}
if self.exits.contains(¤t) {
let kind =
classify_path_precomputed(self.cfg, &self.current_path, &self.reachable_blocks);
self.paths.push(Path::new(self.current_path.clone(), kind));
self.current_path.pop();
return;
}
if self.paths.len() >= self.limits.max_paths {
self.current_path.pop();
return;
}
let is_loop_header = self.loop_headers.contains(¤t);
if is_loop_header {
let count = self.loop_iterations.entry(current).or_insert(0);
if *count >= self.limits.loop_unroll_limit {
self.current_path.pop();
return;
}
*count += 1;
}
let was_visited = self.visited.insert(current);
let mut successors: Vec<NodeIndex> = self.cfg.neighbors(current).collect();
successors.sort_by_key(|n| n.index());
if successors.is_empty() {
let kind =
classify_path_precomputed(self.cfg, &self.current_path, &self.reachable_blocks);
self.paths.push(Path::new(self.current_path.clone(), kind));
} else {
for succ in successors {
let is_back_edge =
self.loop_headers.contains(&succ) && self.loop_iterations.contains_key(&succ);
if self.visited.contains(&succ) && !is_back_edge {
continue;
}
if is_back_edge {
let count = self.loop_iterations.get(&succ).copied().unwrap_or(0);
if count >= self.limits.loop_unroll_limit {
continue;
}
}
self.dfs(succ);
if self.paths.len() >= self.limits.max_paths {
break;
}
}
}
if was_visited {
self.visited.remove(¤t);
}
if is_loop_header {
self.loop_iterations
.entry(current)
.and_modify(|count| *count = count.saturating_sub(1));
}
self.current_path.pop();
}
}
pub fn enumerate_paths_with_context(
cfg: &Cfg,
limits: &PathLimits,
ctx: &EnumerationContext,
) -> Vec<Path> {
let entry = match crate::cfg::analysis::find_entry(cfg) {
Some(e) => e,
None => return vec![],
};
if ctx.exits.is_empty() {
return vec![];
}
ContextPathEnumerator::new(cfg, limits, ctx).enumerate(entry)
}
pub fn enumerate_paths(cfg: &Cfg, limits: &PathLimits) -> Vec<Path> {
let entry = match crate::cfg::analysis::find_entry(cfg) {
Some(e) => e,
None => return vec![],
};
let mut exits: HashSet<NodeIndex> = crate::cfg::analysis::find_exits(cfg).into_iter().collect();
if exits.is_empty() {
for node in cfg.node_indices() {
if cfg.neighbors(node).next().is_none() {
exits.insert(node);
}
}
}
if exits.is_empty() {
return vec![];
}
let reachable_nodes = crate::cfg::reachability::find_reachable(cfg);
let reachable_blocks: HashSet<BlockId> =
reachable_nodes.iter().map(|&idx| cfg[idx].id).collect();
let loop_headers = crate::cfg::loops::find_loop_headers(cfg);
PathEnumerator::new(cfg, exits, limits, loop_headers, reachable_blocks).enumerate(entry)
}
pub fn enumerate_paths_iterative(cfg: &Cfg, limits: &PathLimits) -> Vec<Path> {
let entry = match crate::cfg::analysis::find_entry(cfg) {
Some(e) => e,
None => return vec![],
};
let mut exits: HashSet<NodeIndex> = crate::cfg::analysis::find_exits(cfg).into_iter().collect();
if exits.is_empty() {
for node in cfg.node_indices() {
if cfg.neighbors(node).next().is_none() {
exits.insert(node);
}
}
}
if exits.is_empty() {
return vec![];
}
let reachable_nodes = crate::cfg::reachability::find_reachable(cfg);
let reachable_blocks: HashSet<BlockId> =
reachable_nodes.iter().map(|&idx| cfg[idx].id).collect();
let loop_headers = crate::cfg::loops::find_loop_headers(cfg);
let mut seen_paths: BTreeSet<Vec<BlockId>> = BTreeSet::new();
struct StackFrame {
node: NodeIndex,
path: Vec<BlockId>,
visited: HashSet<NodeIndex>,
loop_iterations: HashMap<NodeIndex, usize>,
}
let mut stack = Vec::new();
let mut paths = Vec::new();
let entry_block_id = cfg[entry].id;
let mut initial_visited = HashSet::new();
initial_visited.insert(entry);
stack.push(StackFrame {
node: entry,
path: vec![entry_block_id],
visited: initial_visited,
loop_iterations: HashMap::new(),
});
while let Some(frame) = stack.pop() {
let StackFrame {
node: current,
path: current_path,
visited: current_visited,
mut loop_iterations,
} = frame;
if current_path.len() > limits.max_length {
continue;
}
if exits.contains(¤t) {
if seen_paths.insert(current_path.clone()) {
let kind = classify_path_precomputed(cfg, ¤t_path, &reachable_blocks);
let path = Path::new(current_path, kind);
paths.push(path);
}
continue;
}
if paths.len() >= limits.max_paths {
break;
}
let mut successors: Vec<NodeIndex> = cfg.neighbors(current).collect();
successors.sort_by_key(|n| n.index());
for succ in successors {
let is_back_edge = loop_headers.contains(&succ)
&& loop_iterations.get(&succ).copied().unwrap_or(0) < limits.loop_unroll_limit;
if current_visited.contains(&succ) && !is_back_edge {
continue;
}
if is_back_edge {
let count = loop_iterations.entry(succ).or_insert(0);
if *count >= limits.loop_unroll_limit {
continue;
}
*count += 1;
}
let mut new_path = current_path.clone();
let block_id = cfg[succ].id;
new_path.push(block_id);
let mut new_visited = current_visited.clone();
new_visited.insert(succ);
stack.push(StackFrame {
node: succ,
path: new_path,
visited: new_visited,
loop_iterations: loop_iterations.clone(),
});
}
}
paths
}