mod relations;
pub mod space;
use crate::JingleError;
use crate::JingleError::{
ConstantWrite, IndirectConstantRead, MismatchedWordSize, UnexpectedArraySort, UnmodeledSpace,
ZeroSizedVarnode,
};
use crate::modeling::machine::cpu::concrete::ConcretePcodeAddress;
use crate::modeling::machine::memory::space::BMCModeledSpace;
use crate::varnode::ResolvedVarnode;
use jingle_sleigh::{
GeneralizedVarNode, IndirectVarNode, SleighArchInfo, SpaceInfo, SpaceType, VarNode,
};
use std::borrow::Borrow;
use std::ops::Add;
use z3::ast::{Array, Ast, BV, Bool};
use z3::{Context, Model, Solvable, Translate};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MemoryState {
info: SleighArchInfo,
spaces: Vec<BMCModeledSpace>,
}
impl MemoryState {
pub fn fresh<T: Borrow<SleighArchInfo>>(info: T) -> Self {
let info = info.borrow().clone();
let spaces: Vec<BMCModeledSpace> = info
.spaces()
.iter()
.map(BMCModeledSpace::fresh_const)
.collect();
Self { info, spaces }
}
pub fn new_const<T: Borrow<SleighArchInfo>, S: AsRef<str>>(name: S, info: T) -> Self {
let info = info.borrow().clone();
let spaces: Vec<BMCModeledSpace> = info
.spaces()
.iter()
.map(|space| BMCModeledSpace::new_const(name.as_ref(), space))
.collect();
Self { info, spaces }
}
pub fn fresh_for_address<T: Borrow<ConcretePcodeAddress>, S: Borrow<SleighArchInfo>>(
info: S,
addr: T,
) -> Self {
let addr = addr.borrow();
let info = info.borrow().clone();
let spaces: Vec<BMCModeledSpace> = info
.spaces()
.iter()
.map(|s| BMCModeledSpace::new_for_address(s, addr))
.collect();
Self { info, spaces }
}
pub fn get_space(&self, idx: usize) -> Result<&Array, JingleError> {
self.spaces
.get(idx)
.map(|u| u.get_space())
.ok_or(UnmodeledSpace)
}
fn read_varnode(&self, varnode: &VarNode) -> Result<BV, JingleError> {
let space = self
.info
.get_space(varnode.space_index())
.ok_or(UnmodeledSpace)?;
match space._type {
SpaceType::IPTR_CONSTANT => Ok(BV::from_i64(
varnode.offset() as i64,
(varnode.size() * 8) as u32,
)),
_ => {
let offset = BV::from_i64(varnode.offset() as i64, space.index_size_bytes * 8);
let arr = self
.spaces
.get(varnode.space_index())
.ok_or(UnmodeledSpace)?;
arr.read(&offset, varnode.size())
}
}
}
fn read_varnode_indirect(&self, indirect: &IndirectVarNode) -> Result<BV, JingleError> {
let pointer_space_info = self
.info
.get_space(indirect.pointer_space_index())
.ok_or(UnmodeledSpace)?;
if pointer_space_info._type == SpaceType::IPTR_CONSTANT {
return Err(IndirectConstantRead);
}
let ptr = self.read_varnode(indirect.pointer_location())?;
let space = self
.spaces
.get(indirect.pointer_space_index())
.ok_or(UnmodeledSpace)?;
space.read(&ptr, indirect.access_size_bytes())
}
#[expect(unused)]
fn read_varnode_metadata_indirect(
&self,
indirect: &IndirectVarNode,
) -> Result<BV, JingleError> {
let pointer_space_info = self
.info
.get_space(indirect.pointer_space_index())
.ok_or(UnmodeledSpace)?;
if pointer_space_info._type == SpaceType::IPTR_CONSTANT {
return Err(IndirectConstantRead);
}
let ptr = self.read_varnode(indirect.pointer_location())?;
let space = self
.spaces
.get(indirect.pointer_space_index())
.ok_or(UnmodeledSpace)?;
space.read(&ptr, indirect.access_size_bytes())
}
pub fn read<T: Into<GeneralizedVarNode>>(&self, vn: T) -> Result<BV, JingleError> {
let gen_varnode: GeneralizedVarNode = vn.into();
match gen_varnode {
GeneralizedVarNode::Direct(d) => self.read_varnode(&d),
GeneralizedVarNode::Indirect(i) => self.read_varnode_indirect(&i),
}
}
pub fn write<T: Into<GeneralizedVarNode>>(
&mut self,
dest: T,
val: BV,
) -> Result<(), JingleError> {
let gen_varnode: GeneralizedVarNode = dest.into();
match gen_varnode {
GeneralizedVarNode::Direct(d) => self.write_varnode(&d, val),
GeneralizedVarNode::Indirect(i) => self.write_varnode_indirect(&i, val),
}
}
fn write_varnode(&mut self, dest: &VarNode, val: BV) -> Result<(), JingleError> {
if dest.size() as u32 * 8 != val.get_size() {
return Err(MismatchedWordSize);
}
match self.info.get_space(dest.space_index()).unwrap() {
SpaceInfo {
_type: SpaceType::IPTR_CONSTANT,
..
} => Err(ConstantWrite),
info => {
let space = self
.spaces
.get_mut(dest.space_index())
.ok_or(UnmodeledSpace)?;
space.write(
&val,
&BV::from_u64(dest.offset(), info.index_size_bytes * 8),
)?;
Ok(())
}
}
}
fn write_varnode_indirect(
&mut self,
dest: &IndirectVarNode,
val: BV,
) -> Result<(), JingleError> {
if self
.info
.get_space(dest.pointer_space_index())
.unwrap()
._type
== SpaceType::IPTR_CONSTANT
{
return Err(ConstantWrite);
}
let ptr = self.read_varnode(dest.pointer_location())?;
self.spaces[dest.pointer_space_index()].write(&val, &ptr)?;
Ok(())
}
#[expect(unused)]
fn write_varnode_metadata_indirect(
&mut self,
dest: &IndirectVarNode,
val: BV,
) -> Result<(), JingleError> {
if self
.info
.get_space(dest.pointer_space_index())
.unwrap()
._type
== SpaceType::IPTR_CONSTANT
{
return Err(ConstantWrite);
}
let ptr = self.read_varnode(dest.pointer_location())?;
self.spaces[dest.pointer_space_index()].write(&val, &ptr)?;
Ok(())
}
pub fn read_resolved(&self, vn: &ResolvedVarnode) -> Result<BV, JingleError> {
match vn {
ResolvedVarnode::Direct(d) => self.read_varnode(d),
ResolvedVarnode::Indirect(indirect) => {
let array = self.get_space(indirect.pointer_space_idx)?;
(0..indirect.access_size_bytes)
.map(|i| {
array
.select(&indirect.pointer.clone().add(i as u64))
.as_bv()
.ok_or(UnexpectedArraySort)
})
.reduce(|c, d| Ok(d?.concat(&c?)))
.ok_or(ZeroSizedVarnode)?
}
}
}
pub fn _eq(&self, other: &MemoryState, machine_eq: &Bool) -> Bool {
let mut terms = vec![];
for (ours, theirs) in self.spaces.iter().zip(&other.spaces).skip(1) {
if !ours._meta_eq(theirs) {
return Bool::from_bool(false);
}
if ours.get_type() == SpaceType::IPTR_INTERNAL {
terms.push(machine_eq.simplify().implies(ours._eq(theirs)).simplify())
} else {
terms.push(ours._eq(theirs))
}
}
let eq_terms: Vec<&Bool> = terms.iter().collect();
Bool::and(eq_terms.as_slice())
}
pub fn simplify(&self) -> Self {
let spaces = self.spaces.iter().map(|s| s.simplify()).collect();
Self {
info: self.info.clone(),
spaces,
}
}
}
impl AsRef<SleighArchInfo> for MemoryState {
fn as_ref(&self) -> &SleighArchInfo {
&self.info
}
}
unsafe impl Translate for MemoryState {
fn translate(&self, dest: &Context) -> Self {
let spaces = self.spaces.translate(dest);
Self {
info: self.info.clone(),
spaces,
}
}
}
impl Solvable for MemoryState {
type ModelInstance = Self;
fn read_from_model(
&self,
model: &Model,
model_completion: bool,
) -> Option<Self::ModelInstance> {
let spaces = self
.spaces
.iter()
.map(|s| s.read_from_model(model, model_completion))
.collect::<Option<Vec<_>>>()?;
Some(Self {
info: self.info.clone(),
spaces,
})
}
fn generate_constraint(&self, model: &Self::ModelInstance) -> Bool {
let space_constraints: Vec<Bool> = self
.spaces
.iter()
.zip(&model.spaces)
.map(|(s, m)| s.generate_constraint(m))
.collect();
Bool::or(space_constraints.iter().collect::<Vec<_>>().as_slice())
}
}