use std::collections::VecDeque;
use crate::{
category::Category,
graph::{FuncId, Graph},
model::{EdgeKind, Loc},
solve::Solution,
util::Map,
};
#[derive(Debug, Clone)]
pub struct Hop {
pub caller: FuncId,
pub callee: FuncId,
pub loc: Option<Loc>,
pub kind: EdgeKind,
pub cleanup: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Terminal {
Site(usize),
Opaque,
Unresolved(usize),
}
#[derive(Debug, Clone)]
pub struct Witness {
pub hops: Vec<Hop>,
pub func: FuncId,
pub terminal: Terminal,
}
#[must_use]
pub fn find(
graph: &Graph,
solution: &Solution,
root: FuncId,
category: Category,
) -> Option<Witness> {
if !solution.enabled(root).contains(category) {
return None;
}
let mut came_from: Map<FuncId, Hop> = Map::default();
let mut seen = vec![false; graph.len()];
let mut queue = VecDeque::new();
seen[root.index()] = true;
queue.push_back(root);
while let Some(id) = queue.pop_front() {
let body = graph.body(id);
if body.opaque && category == Category::Unknown {
return Some(Witness {
hops: rebuild(&came_from, root, id),
func: id,
terminal: Terminal::Opaque,
});
}
let activity = solution.activity(graph, id);
for (i, site) in body.sites.iter().enumerate() {
if activity.sites.get(i).copied().unwrap_or(false)
&& site.category == category
&& !solution.policy().suppressed.contains(category)
{
return Some(Witness {
hops: rebuild(&came_from, root, id),
func: id,
terminal: Terminal::Site(i),
});
}
}
if category == Category::Unknown {
let unresolved = body.calls.iter().enumerate().find(|(i, call)| {
call.callee.is_none()
&& activity.calls.get(*i).copied().unwrap_or(false)
&& solution.follows(call)
});
if let Some((i, _)) = unresolved {
return Some(Witness {
hops: rebuild(&came_from, root, id),
func: id,
terminal: Terminal::Unresolved(i),
});
}
}
for (i, call) in body.calls.iter().enumerate() {
if !activity.calls.get(i).copied().unwrap_or(false)
|| !solution.follows(call)
{
continue;
}
let Some(key) = &call.callee else { continue };
let Some(next) = graph.id_of(key) else {
continue;
};
if seen[next.index()] || !solution.enabled(next).contains(category)
{
continue;
}
seen[next.index()] = true;
came_from.insert(
next,
Hop {
caller: id,
callee: next,
loc: call.loc.clone(),
kind: call.kind,
cleanup: !call.guard.normal,
},
);
queue.push_back(next);
}
}
None
}
fn rebuild(
came_from: &Map<FuncId, Hop>,
root: FuncId,
mut at: FuncId,
) -> Vec<Hop> {
let mut hops = Vec::new();
for _ in 0..=came_from.len() {
if at == root {
break;
}
let Some(hop) = came_from.get(&at) else { break };
hops.push(hop.clone());
at = hop.caller;
}
hops.reverse();
hops
}