pub mod lattice;
pub mod residue;
pub mod state;
pub use crate::analysis::cpa::residue::{CFG, TerminatingReducer, VEC};
use tracing::{Level, span};
use crate::analysis::cpa::residue::{Residue, ResidueWrapper};
use crate::analysis::cpa::state::{AbstractState, LocationState};
use crate::analysis::pcode_store::PcodeStore;
use std::borrow::Borrow;
use std::collections::VecDeque;
use std::fmt::Debug;
pub trait ConfigurableProgramAnalysis: Sized {
type State: AbstractState + Debug;
type Reducer<'op>: residue::Residue<'op, Self::State>;
}
pub trait RunnableConfigurableProgramAnalysis: ConfigurableProgramAnalysis
where
Self::State: LocationState,
{
fn run_cpa<'op, I: Borrow<Self::State>, P: PcodeStore<'op> + ?Sized>(
&self,
initial: I,
pcode_store: &'op P,
) -> <<Self as ConfigurableProgramAnalysis>::Reducer<'op> as residue::Residue<'op, Self::State>>::Output
where
Self::State: 'op,
{
let initial = initial.borrow();
let mut reducer = <Self::Reducer<'op> as residue::Residue<'op, Self::State>>::new();
let mut waitlist: VecDeque<usize> = VecDeque::new();
let mut reached: Vec<Self::State> = vec![initial.clone()];
waitlist.push_back(0);
tracing::debug!("CPA started with initial state: {:?}", initial);
tracing::debug!("Initial waitlist size: 1, reached size: 1");
let mut iteration = 0;
while let Some(state_idx) = waitlist.pop_front() {
let span = span!(Level::DEBUG, "cpa", iteration);
let _enter = span.enter();
iteration += 1;
let reached_len = reached.len();
tracing::trace!("Processing state at index {}", state_idx);
tracing::trace!(
" Waitlist size: {}, Reached size: {}",
waitlist.len(),
reached_len
);
let op = reached[state_idx].get_operation(pcode_store);
tracing::debug!(
" Operation at state: {:?}",
op.as_ref().map(|p| format!("{:x}", p.as_ref()))
);
let mut new_states = 0;
let mut merged_states = 0;
let mut stopped_states = 0;
let dest_states: Vec<_> = op
.iter()
.flat_map(|op| reached[state_idx].transfer(op.as_ref()).into_iter())
.collect();
for dest_state in dest_states {
tracing::trace!(" Transfer produced dest_state: {}", dest_state);
let mut was_merged = false;
for (idx, reached_state) in reached.iter_mut().enumerate() {
if reached_state.merge(&dest_state).is_merged() {
tracing::debug!(" Merged dest_state into existing reached_state");
tracing::debug!(" Merged state: {}", reached_state);
reducer.merged_state(state_idx, idx, &op);
waitlist.push_back(idx);
merged_states += 1;
was_merged = true;
}
}
if was_merged {
continue;
}
tracing::debug!("Adding new state without merging: {}", dest_state);
if !dest_state.stop(reached.iter()) {
tracing::trace!(" Adding new state to waitlist and reached");
let new_idx = reached.len();
reached.push(dest_state);
reducer.new_state(state_idx, new_idx, &op);
waitlist.push_back(new_idx);
new_states += 1;
} else {
tracing::trace!(" State stopped (already covered)");
stopped_states += 1;
}
}
if new_states > 0 || merged_states > 0 || stopped_states > 0 {
tracing::debug!(
"Iteration {} summary: {} new state(s), {} merge(s), {} stopped",
iteration,
new_states,
merged_states,
stopped_states
);
}
}
tracing::debug!(
"CPA completed after {} iterations. Total states reached: {}",
iteration,
reached.len()
);
reducer.finalize(reached)
}
fn with_residue<F>(self, f: F) -> ResidueWrapper<Self, F>
where
for<'op> F: crate::analysis::cpa::residue::ReducerFactoryForState<Self>,
{
ResidueWrapper::wrap(self, f)
}
}
pub trait IntoState<C: ConfigurableProgramAnalysis>: Sized {
fn into_state(self, c: &C) -> C::State;
}
impl<T> RunnableConfigurableProgramAnalysis for T
where
T: ConfigurableProgramAnalysis,
T::State: LocationState,
{
}