use boolector::option::{BtorOption, ModelGen};
use boolector::BVSolution;
use either::Either;
use itertools::Itertools;
use llvm_ir::types::{FPType, NamedStructDef, Typed};
use llvm_ir::*;
use log::{debug, info, warn};
use reduce::Reduce;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use crate::alloc::Alloc;
use crate::backend::*;
use crate::config::{Config, NullPointerChecking};
use crate::demangling::Demangling;
use crate::error::*;
use crate::function_hooks::{self, FunctionHooks};
use crate::global_allocations::*;
use crate::hooks;
use crate::project::Project;
use crate::solver_utils::{self, PossibleSolutions};
use crate::varmap::{RestoreInfo, VarMap};
use crate::watchpoints::{Watchpoint, Watchpoints};
#[derive(Clone)]
pub struct State<'p, B: Backend> {
pub solver: B::SolverRef,
pub config: Config<'p, B>,
pub cur_loc: Location<'p>,
proj: &'p Project,
varmap: VarMap<B::BV>,
mem: RefCell<B::Memory>,
alloc: Alloc,
global_allocations: GlobalAllocations<'p, B>,
pointer_size_bits: u32,
pub(crate) intrinsic_hooks: FunctionHooks<'p, B>,
stack: Vec<StackFrame<'p, B::BV>>,
backtrack_points: RefCell<Vec<BacktrackPoint<'p, B>>>,
path: Vec<PathEntry<'p>>,
mem_watchpoints: Watchpoints,
function_ptr_cache: HashMap<Location<'p>, u64>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct LocationDescription<'p> {
pub modname: String,
pub funcname: String,
pub bbname: Name,
pub instr: BBInstrIndex,
pub source_loc: Option<&'p DebugLoc>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
pub enum BBInstrIndex {
Instr(usize),
Terminator,
}
impl fmt::Display for BBInstrIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BBInstrIndex::Instr(i) => write!(f, "instr {}", i),
BBInstrIndex::Terminator => write!(f, "terminator"),
}
}
}
fn pretty_source_loc(source_loc: &DebugLoc) -> String {
source_loc.to_string()
}
impl<'p> fmt::Debug for LocationDescription<'p> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_string_with_module())
}
}
impl<'p> LocationDescription<'p> {
pub(crate) fn to_string_with_module(&self) -> String {
format!(
"{{{}: {}, bb {}, {}}}",
self.modname, self.funcname, self.bbname, self.instr
)
}
pub(crate) fn to_string_no_module(&self) -> String {
format!("{{{}, bb {}, {}}}", self.funcname, self.bbname, self.instr)
}
}
#[derive(PartialEq, Eq, Clone)]
pub struct PathEntry<'p>(pub Location<'p>);
impl<'p> fmt::Debug for PathEntry<'p> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_string_with_module())
}
}
impl<'p> PathEntry<'p> {
pub(crate) fn to_string_with_module(&self) -> String {
format!(
"{{{}: {}, bb {}, starting at {}}}",
self.0.module.name, self.0.func.name, self.0.bb.name, self.0.instr
)
}
pub(crate) fn to_string_no_module(&self) -> String {
format!(
"{{{}, bb {}, starting at {}}}",
self.0.func.name, self.0.bb.name, self.0.instr
)
}
pub(crate) fn get_all_source_locs(&self) -> impl Iterator<Item = &'p DebugLoc> {
self.0
.bb
.instrs
.iter()
.filter_map(|instr| instr.get_debug_loc().as_ref())
.dedup()
}
}
#[derive(Clone)]
pub struct Location<'p> {
pub module: &'p Module,
pub func: &'p Function,
pub bb: &'p BasicBlock,
pub instr: BBInstrIndex,
pub source_loc: Option<&'p DebugLoc>,
}
impl<'p> PartialEq for Location<'p> {
fn eq(&self, other: &Self) -> bool {
self.module.name == other.module.name
&& self.func.name == other.func.name
&& self.bb.name == other.bb.name
&& self.instr == other.instr
}
}
impl<'p> Eq for Location<'p> {}
impl<'p> Hash for Location<'p> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.module.name.hash(state);
self.func.name.hash(state);
self.bb.name.hash(state);
self.instr.hash(state);
}
}
impl<'p> fmt::Debug for Location<'p> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{{{}}}", self.to_string_with_module())
}
}
impl<'p> Location<'p> {
pub fn to_string_with_module(&self) -> String {
format!(
"{}: {}, bb {}, {}",
self.module.name, self.func.name, self.bb.name, self.instr
)
}
pub fn to_string_no_module(&self) -> String {
format!("{}, bb {}, {}", self.func.name, self.bb.name, self.instr)
}
pub fn to_string_short_module(&self) -> String {
let short_module_name = self
.module
.name
.rsplit('/')
.next()
.unwrap_or(&self.module.name);
format!(
"{}: {}, bb {}, {}",
short_module_name, self.func.name, self.bb.name, self.instr
)
}
}
impl<'p> From<Location<'p>> for LocationDescription<'p> {
fn from(loc: Location<'p>) -> LocationDescription {
LocationDescription {
modname: loc.module.name.clone(),
funcname: loc.func.name.clone(),
bbname: loc.bb.name.clone(),
instr: loc.instr,
source_loc: loc.source_loc,
}
}
}
impl<'p> Location<'p> {
pub(crate) fn move_to_start_of_bb(&mut self, bb: &'p BasicBlock) {
self.bb = bb;
self.instr = BBInstrIndex::Instr(0);
}
pub(crate) fn move_to_start_of_bb_by_name(&mut self, bbname: &Name) {
self.move_to_start_of_bb(self.func.get_bb_by_name(bbname).unwrap_or_else(|| {
panic!(
"Failed to find bb named {} in function {:?}",
bbname, self.func.name
)
}))
}
pub(crate) fn inc(&mut self) {
match self.instr {
BBInstrIndex::Instr(i) => {
if i + 1 >= self.bb.instrs.len() {
self.instr = BBInstrIndex::Terminator;
} else {
self.instr = BBInstrIndex::Instr(i + 1);
}
},
BBInstrIndex::Terminator => {
panic!("called inc() on a Location pointing to a terminator")
},
}
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct Callsite<'p> {
pub loc: Location<'p>,
pub instr: Either<&'p instruction::Call, &'p terminator::Invoke>,
}
#[derive(PartialEq, Clone, Debug)]
struct StackFrame<'p, V: BV> {
callsite: Callsite<'p>,
restore_info: RestoreInfo<V>,
}
#[derive(Clone)]
struct BacktrackPoint<'p, B: Backend> {
loc: Location<'p>,
stack: Vec<StackFrame<'p, B::BV>>,
constraint: B::BV,
varmap: VarMap<B::BV>,
mem: B::Memory,
path_len: usize,
}
impl<'p, B: Backend> fmt::Display for BacktrackPoint<'p, B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"<BacktrackPoint to execute bb {} with constraint {:?} and {} frames on the callstack>",
self.loc.bb.name,
self.constraint,
self.stack.len()
)
}
}
impl<'p, B: Backend> State<'p, B>
where
B: 'p,
{
pub fn new(project: &'p Project, start_loc: Location<'p>, mut config: Config<'p, B>) -> Self {
let solver = B::SolverRef::new();
solver.set_opt(BtorOption::SolverTimeout(config.solver_query_timeout));
if config.demangling.is_none() {
config.demangling = Some(Demangling::autodetect(project));
}
let mut state = Self {
cur_loc: start_loc.clone(),
pointer_size_bits: project.pointer_size_bits(),
proj: project,
varmap: VarMap::new(solver.clone(), config.loop_bound),
mem: RefCell::new(Memory::new_uninitialized(
solver.clone(),
match config.null_pointer_checking {
NullPointerChecking::Simple => true,
NullPointerChecking::SplitPath => true,
NullPointerChecking::None => false,
},
None,
project.pointer_size_bits(),
)),
alloc: Alloc::new(),
global_allocations: GlobalAllocations::new(),
intrinsic_hooks: {
let mut intrinsic_hooks = FunctionHooks::new();
intrinsic_hooks.add("intrinsic: llvm.memset", &hooks::intrinsics::symex_memset);
intrinsic_hooks.add(
"intrinsic: llvm.memcpy/memmove",
&hooks::intrinsics::symex_memcpy,
);
intrinsic_hooks.add("intrinsic: llvm.bswap", &hooks::intrinsics::symex_bswap);
intrinsic_hooks.add("intrinsic: llvm.ctlz", &hooks::intrinsics::symex_ctlz);
intrinsic_hooks.add("intrinsic: llvm.cttz", &hooks::intrinsics::symex_cttz);
intrinsic_hooks.add(
"intrinsic: llvm.objectsize",
&hooks::intrinsics::symex_objectsize,
);
intrinsic_hooks.add("intrinsic: llvm.assume", &hooks::intrinsics::symex_assume);
intrinsic_hooks.add(
"intrinsic: llvm.uadd.with.overflow",
&hooks::intrinsics::symex_uadd_with_overflow,
);
intrinsic_hooks.add(
"intrinsic: llvm.sadd.with.overflow",
&hooks::intrinsics::symex_sadd_with_overflow,
);
intrinsic_hooks.add(
"intrinsic: llvm.usub.with.overflow",
&hooks::intrinsics::symex_usub_with_overflow,
);
intrinsic_hooks.add(
"intrinsic: llvm.ssub.with.overflow",
&hooks::intrinsics::symex_ssub_with_overflow,
);
intrinsic_hooks.add(
"intrinsic: llvm.umul.with.overflow",
&hooks::intrinsics::symex_umul_with_overflow,
);
intrinsic_hooks.add(
"intrinsic: llvm.smul.with.overflow",
&hooks::intrinsics::symex_smul_with_overflow,
);
intrinsic_hooks.add(
"intrinsic: llvm.uadd.sat",
&hooks::intrinsics::symex_uadd_sat,
);
intrinsic_hooks.add(
"intrinsic: llvm.sadd.sat",
&hooks::intrinsics::symex_sadd_sat,
);
intrinsic_hooks.add(
"intrinsic: llvm.usub.sat",
&hooks::intrinsics::symex_usub_sat,
);
intrinsic_hooks.add(
"intrinsic: llvm.ssub.sat",
&hooks::intrinsics::symex_ssub_sat,
);
intrinsic_hooks.add(
"intrinsic: generic_stub_hook",
&function_hooks::generic_stub_hook,
);
intrinsic_hooks.add("intrinsic: abort_hook", &function_hooks::abort_hook);
intrinsic_hooks
},
stack: Vec::new(),
backtrack_points: RefCell::new(Vec::new()),
path: Vec::new(),
mem_watchpoints: config.initial_mem_watchpoints.clone().into_iter().collect(),
function_ptr_cache: HashMap::new(),
solver,
config,
};
info!("Allocating global variables and functions");
debug!("Allocating global variables");
for (var, module) in project
.all_global_vars()
.filter(|(var, _)| var.initializer.is_some())
{
if let Type::PointerType { pointee_type, .. } = var.ty.as_ref() {
let size_bits = state.size_in_bits(&pointee_type).expect(
"Global variable has a struct type which is opaque in the entire Project",
);
let size_bits = if size_bits == 0 {
debug!(
"Global {:?} has size 0 bits; allocating 8 bits for it anyway",
var.name
);
8
} else {
size_bits
};
let addr = state.allocate(size_bits as u64);
debug!("Allocated {:?} at {:?}", var.name, addr);
state
.global_allocations
.allocate_global_var(var, module, addr);
} else {
panic!("Global variable has non-pointer type {:?}", &var.ty);
}
}
debug!("Allocating functions");
for (func, module) in project.all_functions() {
let addr: u64 = state.alloc.alloc(64_u64);
let addr_bv = state.bv_from_u64(addr, project.pointer_size_bits());
debug!("Allocated {:?} at {:?}", func.name, addr_bv);
state
.global_allocations
.allocate_function(func, module, addr, addr_bv);
}
debug!("Allocating function hooks");
for (funcname, hook) in state.config.function_hooks.get_all_hooks() {
let addr: u64 = state.alloc.alloc(64_u64);
let addr_bv = state.bv_from_u64(addr, project.pointer_size_bits());
debug!("Allocated hook for {:?} at {:?}", funcname, addr_bv);
state
.global_allocations
.allocate_function_hook((*hook).clone(), addr, addr_bv);
}
debug!("Done allocating global variables and functions");
state
}
pub fn fork(&self) -> Self {
let mut cloned = self.clone();
let new_solver = cloned.solver.duplicate();
cloned.varmap.change_solver(new_solver.clone());
cloned.mem.borrow_mut().change_solver(new_solver.clone());
cloned.global_allocations.change_solver(new_solver.clone());
cloned.solver = new_solver;
cloned
}
pub fn sat(&self) -> Result<bool> {
solver_utils::sat(&self.solver)
}
pub fn sat_with_extra_constraints<'b>(
&'b self,
constraints: impl IntoIterator<Item = &'b B::BV>,
) -> Result<bool> {
solver_utils::sat_with_extra_constraints(&self.solver, constraints)
}
pub fn bvs_must_be_equal(&self, a: &B::BV, b: &B::BV) -> Result<bool> {
solver_utils::bvs_must_be_equal(&self.solver, a, b)
}
pub fn bvs_can_be_equal(&self, a: &B::BV, b: &B::BV) -> Result<bool> {
solver_utils::bvs_can_be_equal(&self.solver, a, b)
}
pub fn get_a_solution_for_bv(&self, bv: &B::BV) -> Result<Option<BVSolution>> {
match bv.as_binary_str() {
Some(bstr) => Ok(Some(BVSolution::from_01x_str(bstr))),
None => {
warn!("A call to get_a_solution_for_bv() is resulting in a call to sat() with model generation enabled. Experimentally, these types of calls can be very slow. The BV is {:?}", bv);
self.solver.set_opt(BtorOption::ModelGen(ModelGen::All));
let solution = if self.sat()? {
bv.get_a_solution().map(Some)
} else {
Ok(None)
};
self.solver
.set_opt(BtorOption::ModelGen(ModelGen::Disabled));
solution
},
}
}
#[allow(clippy::ptr_arg)]
pub fn get_a_solution_for_irname(
&mut self,
funcname: &String,
name: &Name,
) -> Result<Option<BVSolution>> {
let bv = self.varmap.lookup_var(funcname, name);
self.get_a_solution_for_bv(bv)
}
pub fn get_possible_solutions_for_bv(
&self,
bv: &B::BV,
n: usize,
) -> Result<PossibleSolutions<BVSolution>> {
solver_utils::get_possible_solutions_for_bv(self.solver.clone(), bv, n)
}
#[allow(clippy::ptr_arg)]
pub fn get_possible_solutions_for_irname(
&mut self,
funcname: &String,
name: &Name,
n: usize,
) -> Result<PossibleSolutions<BVSolution>> {
let bv = self.varmap.lookup_var(funcname, name);
self.get_possible_solutions_for_bv(bv, n)
}
pub fn max_possible_solution_for_bv_as_u64(&self, bv: &B::BV) -> Result<Option<u64>> {
solver_utils::max_possible_solution_for_bv_as_u64(self.solver.clone(), bv)
}
#[allow(clippy::ptr_arg)]
pub fn max_possible_solution_for_irname_as_u64(
&mut self,
funcname: &String,
name: &Name,
) -> Result<Option<u64>> {
let bv = self.varmap.lookup_var(funcname, name);
solver_utils::max_possible_solution_for_bv_as_u64(self.solver.clone(), bv)
}
pub fn min_possible_solution_for_bv_as_u64(&self, bv: &B::BV) -> Result<Option<u64>> {
solver_utils::min_possible_solution_for_bv_as_u64(self.solver.clone(), bv)
}
#[allow(clippy::ptr_arg)]
pub fn min_possible_solution_for_irname_as_u64(
&self,
funcname: &String,
name: &Name,
) -> Result<Option<u64>> {
let bv = self.varmap.lookup_var(funcname, name);
solver_utils::min_possible_solution_for_bv_as_u64(self.solver.clone(), bv)
}
pub fn bv_from_bool(&self, b: bool) -> B::BV {
B::BV::from_bool(self.solver.clone(), b)
}
pub fn bv_from_i32(&self, i: i32, width: u32) -> B::BV {
B::BV::from_i32(self.solver.clone(), i, width)
}
pub fn bv_from_u32(&self, u: u32, width: u32) -> B::BV {
B::BV::from_u32(self.solver.clone(), u, width)
}
pub fn bv_from_i64(&self, i: i64, width: u32) -> B::BV {
B::BV::from_i64(self.solver.clone(), i, width)
}
pub fn bv_from_u64(&self, u: u64, width: u32) -> B::BV {
B::BV::from_u64(self.solver.clone(), u, width)
}
pub fn zero(&self, width: u32) -> B::BV {
B::BV::zero(self.solver.clone(), width)
}
pub fn one(&self, width: u32) -> B::BV {
B::BV::one(self.solver.clone(), width)
}
pub fn ones(&self, width: u32) -> B::BV {
B::BV::ones(self.solver.clone(), width)
}
pub fn new_bv_with_name(&mut self, name: Name, bits: u32) -> Result<B::BV> {
self.varmap
.new_bv_with_name(self.cur_loc.func.name.clone(), name, bits)
}
pub fn assign_bv_to_name(&mut self, name: Name, bv: B::BV) -> Result<()> {
self.varmap
.assign_bv_to_name(self.cur_loc.func.name.clone(), name, bv)
}
#[cfg(debug_assertions)]
pub fn record_bv_result(
&mut self,
thing: &impl instruction::HasResult,
resultval: B::BV,
) -> Result<()> {
let thing_size_in_bits = self.size_in_bits(&self.type_of(thing)).ok_or_else(|| {
Error::MalformedInstruction("Instruction result type is an opaque struct type".into())
})?;
if thing_size_in_bits != resultval.get_width() {
Err(Error::OtherError(format!(
"Computed result for an instruction has the wrong size: instruction {:?} with result size {}, but got result {:?} with size {}",
thing,
thing_size_in_bits,
resultval,
resultval.get_width()
)))
} else {
self.assign_bv_to_name(thing.get_result().clone(), resultval)
}
}
#[cfg(not(debug_assertions))]
pub fn record_bv_result(
&mut self,
thing: &impl instruction::HasResult,
resultval: B::BV,
) -> Result<()> {
self.assign_bv_to_name(thing.get_result().clone(), resultval)
}
pub fn overwrite_latest_version_of_bv(&mut self, name: &Name, bv: B::BV) {
self.varmap
.overwrite_latest_version_of_bv(&self.cur_loc.func.name, name, bv)
}
pub fn type_of<T: Typed + ?Sized>(&self, t: &T) -> TypeRef {
self.cur_loc.module.type_of(t)
}
pub fn operand_to_bv(&self, op: &Operand) -> Result<B::BV> {
match op {
Operand::ConstantOperand(c) => self.const_to_bv(c),
Operand::LocalOperand { name, .. } => Ok(self
.varmap
.lookup_var(&self.cur_loc.func.name, name)
.clone()),
Operand::MetadataOperand => panic!("Can't convert {:?} to BV", op),
}
}
pub fn const_to_bv(&self, c: &Constant) -> Result<B::BV> {
match c {
Constant::Int { bits, value } => Ok(self.bv_from_u64(*value, *bits)),
Constant::Null(ty) | Constant::AggregateZero(ty) | Constant::Undef(ty) => {
let size_bits = self.size_in_bits(ty).ok_or_else(|| {
Error::OtherError(format!(
"const_to_bv on a constant with opaque struct type: {:?}",
c
))
})?;
Ok(self.zero(size_bits))
},
Constant::Struct {
values: elements, ..
}
| Constant::Array { elements, .. }
| Constant::Vector(elements) => elements
.iter()
.map(|c| self.const_to_bv(c))
.reduce(|a, b| Ok(b?.concat(&a?)))
.unwrap(),
Constant::GlobalReference { name, .. } => {
if let Some(ga) = self
.global_allocations
.get_global_allocation(name, self.cur_loc.module)
{
match ga {
GlobalAllocation::Function { addr, .. } => Ok(addr.clone()),
GlobalAllocation::GlobalVariable {
addr,
initializer,
initialized,
} => {
if !initialized.get() {
debug!(
"Initializing {:?} with initializer {:?}",
name, &initializer
);
initialized.set(true);
if let Some(bv) = self.const_to_bv_maybe_zerowidth(initializer)? {
self.write_without_mut(addr, bv)?;
}
}
Ok(addr.clone())
},
}
} else if let Some(alias) = self
.cur_loc
.module
.global_aliases
.iter()
.find(|a| &a.name == name)
{
self.const_to_bv(&alias.aliasee)
} else {
Err(Error::OtherError(format!("const_to_bv: GlobalReference to {:?} which was not found (current module is {:?})", name, &self.cur_loc.module.name)))
}
},
Constant::Add(a) => Ok(self
.const_to_bv(&a.operand0)?
.add(&self.const_to_bv(&a.operand1)?)),
Constant::Sub(s) => Ok(self
.const_to_bv(&s.operand0)?
.sub(&self.const_to_bv(&s.operand1)?)),
Constant::Mul(m) => Ok(self
.const_to_bv(&m.operand0)?
.mul(&self.const_to_bv(&m.operand1)?)),
Constant::UDiv(u) => Ok(self
.const_to_bv(&u.operand0)?
.udiv(&self.const_to_bv(&u.operand1)?)),
Constant::SDiv(s) => Ok(self
.const_to_bv(&s.operand0)?
.sdiv(&self.const_to_bv(&s.operand1)?)),
Constant::URem(u) => Ok(self
.const_to_bv(&u.operand0)?
.urem(&self.const_to_bv(&u.operand1)?)),
Constant::SRem(s) => Ok(self
.const_to_bv(&s.operand0)?
.srem(&self.const_to_bv(&s.operand1)?)),
Constant::And(a) => Ok(self
.const_to_bv(&a.operand0)?
.and(&self.const_to_bv(&a.operand1)?)),
Constant::Or(o) => Ok(self
.const_to_bv(&o.operand0)?
.or(&self.const_to_bv(&o.operand1)?)),
Constant::Xor(x) => Ok(self
.const_to_bv(&x.operand0)?
.xor(&self.const_to_bv(&x.operand1)?)),
Constant::Shl(s) => Ok(self
.const_to_bv(&s.operand0)?
.sll(&self.const_to_bv(&s.operand1)?)),
Constant::LShr(s) => Ok(self
.const_to_bv(&s.operand0)?
.srl(&self.const_to_bv(&s.operand1)?)),
Constant::AShr(s) => Ok(self
.const_to_bv(&s.operand0)?
.sra(&self.const_to_bv(&s.operand1)?)),
Constant::ExtractElement(ee) => match &ee.index.as_ref() {
Constant::Int { value: index, .. } => match &ee.vector.as_ref() {
Constant::Vector(els) => {
let el = els.get(*index as usize).ok_or_else(|| {
Error::MalformedInstruction(
"Constant::ExtractElement index out of range".to_owned(),
)
})?;
self.const_to_bv(el)
},
c => Err(Error::MalformedInstruction(format!(
"Expected ExtractElement.vector to be a Constant::Vector, got {:?}",
c
))),
},
index => Err(Error::MalformedInstruction(format!(
"Expected ExtractElement.index to be a Constant::Int, but got {:?}",
index
))),
},
Constant::InsertElement(ie) => match &ie.index.as_ref() {
Constant::Int { value: index, .. } => match &ie.vector.as_ref() {
Constant::Vector(els) => {
let mut els = els.clone();
let el: &mut ConstantRef =
els.get_mut(*index as usize).ok_or_else(|| {
Error::MalformedInstruction(
"Constant::InsertElement index out of range".to_owned(),
)
})?;
*el = ie.element.clone();
self.const_to_bv(&Constant::Vector(els))
},
c => Err(Error::MalformedInstruction(format!(
"Expected InsertElement.vector to be a Constant::Vector, got {:?}",
c
))),
},
index => Err(Error::MalformedInstruction(format!(
"Expected InsertElement.index to be a Constant::Int, but got {:?}",
index
))),
},
Constant::ExtractValue(ev) => self.const_to_bv(Self::simplify_const_ev(
&ev.aggregate,
ev.indices.iter().copied(),
)?),
Constant::InsertValue(iv) => {
let c = Self::simplify_const_iv(
&iv.aggregate,
(*iv.element).clone(),
iv.indices.iter().copied(),
)?;
self.const_to_bv(&c)
},
Constant::GetElementPtr(gep) => {
let bvbase = self.const_to_bv(&gep.address)?;
let offset = self.get_offset_recursive(
gep.indices.iter(),
&self.type_of(&gep.address),
bvbase.get_width(),
)?;
Ok(bvbase.add(&offset))
},
Constant::Trunc(t) => {
let to_size_bits = self.size_in_bits(&t.to_type).ok_or_else(|| {
Error::OtherError(format!(
"const_to_bv on a constant with opaque struct type {:?}",
c
))
})?;
self.const_to_bv(&t.operand)
.map(|bv| bv.slice(to_size_bits - 1, 0))
},
Constant::ZExt(z) => {
let to_size_bits = self.size_in_bits(&z.to_type).ok_or_else(|| {
Error::OtherError(format!(
"const_to_bv on a constant with opaque struct type {:?}",
c
))
})?;
self.const_to_bv(&z.operand)
.map(|bv| bv.zero_extend_to_bits(to_size_bits))
},
Constant::SExt(s) => {
let to_size_bits = self.size_in_bits(&s.to_type).ok_or_else(|| {
Error::OtherError(format!(
"const_to_bv on a constant with opaque struct type {:?}",
c
))
})?;
self.const_to_bv(&s.operand)
.map(|bv| bv.sign_extend_to_bits(to_size_bits))
},
Constant::PtrToInt(pti) => {
let bv = self.const_to_bv(&pti.operand)?;
assert_eq!(
bv.get_width(),
self.size_in_bits(&pti.to_type)
.ok_or_else(|| Error::MalformedInstruction(
"PtrToInt result type is opaque struct type".into()
))?
);
Ok(bv)
},
Constant::IntToPtr(itp) => {
let bv = self.const_to_bv(&itp.operand)?;
assert_eq!(
bv.get_width(),
self.size_in_bits(&itp.to_type)
.ok_or_else(|| Error::MalformedInstruction(
"IntToPtr result type is opaque struct type".into()
))?
);
Ok(bv)
},
Constant::BitCast(bc) => {
let bv = self.const_to_bv(&bc.operand)?;
assert_eq!(
bv.get_width(),
self.size_in_bits(&bc.to_type)
.ok_or_else(|| Error::MalformedInstruction(
"BitCast result type is opaque struct type".into()
))?
);
Ok(bv)
},
Constant::AddrSpaceCast(ac) => {
let bv = self.const_to_bv(&ac.operand)?;
assert_eq!(
bv.get_width(),
self.size_in_bits(&ac.to_type)
.ok_or_else(|| Error::MalformedInstruction(
"AddrSpaceCast result type is opaque struct type".into()
))?
);
Ok(bv)
},
Constant::ICmp(icmp) => {
let bv0 = self.const_to_bv(&icmp.operand0)?;
let bv1 = self.const_to_bv(&icmp.operand1)?;
Ok(match icmp.predicate {
IntPredicate::EQ => bv0._eq(&bv1),
IntPredicate::NE => bv0._ne(&bv1),
IntPredicate::UGT => bv0.ugt(&bv1),
IntPredicate::UGE => bv0.ugte(&bv1),
IntPredicate::ULT => bv0.ult(&bv1),
IntPredicate::ULE => bv0.ulte(&bv1),
IntPredicate::SGT => bv0.sgt(&bv1),
IntPredicate::SGE => bv0.sgte(&bv1),
IntPredicate::SLT => bv0.slt(&bv1),
IntPredicate::SLE => bv0.slte(&bv1),
})
},
Constant::Select(s) => {
let b = self.const_to_bv(&s.condition)?;
match b.as_bool() {
None => Err(Error::MalformedInstruction(
"Constant::Select: Expected a constant condition".to_owned(),
)),
Some(true) => self.const_to_bv(&s.true_value),
Some(false) => self.const_to_bv(&s.false_value),
}
},
_ => unimplemented!("const_to_bv for {:?}", c),
}
}
fn const_to_bv_maybe_zerowidth(&self, c: &Constant) -> Result<Option<B::BV>> {
match c {
Constant::Null(ty) | Constant::AggregateZero(ty) | Constant::Undef(ty) => {
match self.size_in_bits(ty) {
None => Err(Error::OtherError(format!(
"const_to_bv on a constant with opaque struct type: {:?}",
c
))),
Some(0) => Ok(None),
Some(bits) => Ok(Some(self.zero(bits))),
}
},
Constant::Struct { values, .. } => {
values
.iter()
.map(|val| {
self.size_in_bits(&self.type_of(val.as_ref()))
.map(|bits| (val, bits))
.ok_or_else(|| {
Error::OtherError(format!(
"const_to_bv: encountered an opaque struct type: {:?}",
val
))
})
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.filter(|&(_val, bits)| bits > 0)
.map(|(val, _bits)| val)
.map(|val| self.const_to_bv_maybe_zerowidth(val).transpose().unwrap())
.reduce(|a, b| Ok(b?.concat(&a?)))
.transpose()
},
Constant::Array { elements, .. } if elements.is_empty() => Ok(None),
_ => self.const_to_bv(c).map(|bv| Some(bv)),
}
}
fn simplify_const_ev(
s: &Constant,
mut indices: impl Iterator<Item = u32>,
) -> Result<&Constant> {
match indices.next() {
None => Ok(s),
Some(index) => {
if let Constant::Struct { values, .. } = s {
let val = values.get(index as usize).ok_or_else(|| {
Error::MalformedInstruction(
"Constant::ExtractValue index out of range".to_owned(),
)
})?;
Self::simplify_const_ev(val, indices)
} else {
panic!("simplify_const_ev: not a Constant::Struct: {:?}", s)
}
},
}
}
fn simplify_const_iv(
s: &Constant,
val: Constant,
mut indices: impl Iterator<Item = u32>,
) -> Result<ConstantRef> {
match indices.next() {
None => Ok(ConstantRef::new(val)),
Some(index) => {
if let Constant::Struct {
name,
values,
is_packed,
} = s
{
let to_replace = values
.get(index as usize)
.ok_or_else(|| {
Error::MalformedInstruction(
"Constant::InsertValue index out of range".to_owned(),
)
})?
.clone();
let mut values = values.clone();
values[index as usize] = Self::simplify_const_iv(&to_replace, val, indices)?;
Ok(ConstantRef::new(Constant::Struct {
name: name.clone(),
values,
is_packed: *is_packed,
}))
} else {
panic!("simplify_const_iv: not a Constant::Struct: {:?}", s)
}
},
}
}
fn get_offset_recursive<'a>(
&self,
mut indices: impl Iterator<Item = &'a ConstantRef>,
base_type: &Type,
result_bits: u32,
) -> Result<B::BV> {
if let Type::NamedStructType { name } = base_type {
match self.cur_loc.module.types.named_struct_def(name) {
None => {
return Err(Error::MalformedInstruction(format!("get_offset on a struct type not defined in the current module (struct name {:?})", name)));
},
Some(NamedStructDef::Opaque) => {
return Err(Error::MalformedInstruction(format!(
"get_offset on an opaque struct type ({:?})",
name
)));
},
Some(NamedStructDef::Defined(ty)) => {
return self.get_offset_recursive(indices, &ty, result_bits);
},
}
}
match indices.next() {
None => Ok(self.zero(result_bits)),
Some(index) => match base_type {
Type::PointerType { .. } | Type::ArrayType { .. } | Type::VectorType { .. } => {
let index = self.const_to_bv(index)?.zero_extend_to_bits(result_bits);
let (offset, nested_ty) =
self.get_offset_bv_index(base_type, &index, self.solver.clone())?;
self.get_offset_recursive(indices, nested_ty, result_bits)
.map(|bv| bv.add(&offset))
},
Type::StructType { .. } => match index.as_ref() {
Constant::Int { value: index, .. } => {
let (offset, nested_ty) =
self.get_offset_constant_index(base_type, *index as usize)?;
self.get_offset_recursive(indices, &nested_ty, result_bits)
.map(|bv| bv.add(&self.bv_from_u64(offset as u64, result_bits)))
},
_ => Err(Error::MalformedInstruction(format!(
"Expected index into struct type to be a constant int, but got index {:?}",
index
))),
},
Type::NamedStructType { .. } => {
panic!("NamedStructType case should have been handled above")
},
_ => panic!("get_offset_recursive with base type {:?}", base_type),
},
}
}
pub(crate) fn interpret_as_function_ptr(
&mut self,
bv: B::BV,
n: usize,
) -> Result<PossibleSolutions<Callable<'p, B>>> {
if n == 0 {
unimplemented!("n == 0 in interpret_as_function_ptr")
}
let addrs: Vec<u64> = match bv.as_u64() {
Some(addr) => vec![addr],
None => {
match self.function_ptr_cache.get(&self.cur_loc) {
Some(addr)
if self
.bvs_must_be_equal(&bv, &self.bv_from_u64(*addr, bv.get_width()))? =>
{
vec![*addr]
},
_ => {
match self
.get_possible_solutions_for_bv(&bv, n)?
.as_u64_solutions()
.unwrap()
{
PossibleSolutions::Exactly(v) => v.into_iter().collect(),
PossibleSolutions::AtLeast(v) => v.into_iter().collect(),
}
},
}
},
};
if addrs.len() == 1 {
self.function_ptr_cache
.insert(self.cur_loc.clone(), addrs[0]);
}
let callables = addrs
.into_iter()
.map(|addr| {
self.global_allocations
.get_func_for_address(addr, self.cur_loc.module)
.ok_or_else(|| Error::FailedToResolveFunctionPointer(addr))
})
.collect::<Result<HashSet<_>>>()?;
if callables.len() > n {
Ok(PossibleSolutions::AtLeast(callables))
} else {
Ok(PossibleSolutions::Exactly(callables))
}
}
pub fn get_pointer_to_function(&self, funcname: impl Into<String>) -> Option<&B::BV> {
self.global_allocations
.get_global_allocation(&Name::from(funcname.into()), self.cur_loc.module)
.map(|ga| ga.get_addr())
}
pub fn get_pointer_to_function_hook(&self, funcname: &str) -> Option<&B::BV> {
self.global_allocations
.get_function_hook_address(self.config.function_hooks.get_hook_for(funcname)?)
}
pub fn get_func_by_name(
&self,
funcname: impl Into<String>,
) -> Option<(&'p Function, &'p Module)> {
let funcname = funcname.into();
self.global_allocations
.get_global_allocation(&Name::from(funcname.clone()), self.cur_loc.module)
.and_then(|ga| match ga {
GlobalAllocation::Function { func, module, .. } => Some((*func, *module)),
GlobalAllocation::GlobalVariable { .. } => panic!(
"get_func_by_name: {} refers to a global variable, not a function",
funcname
),
})
}
pub fn read(&self, addr: &B::BV, bits: u32) -> Result<B::BV> {
let retval = match self.mem.borrow().read(addr, bits) {
Ok(val) => val,
e @ Err(Error::NullPointerDereference) => {
if self.config.null_pointer_checking == NullPointerChecking::SplitPath {
self.save_backtracking_point_at_location(
self.cur_loc.clone(),
addr._ne(&self.zero(addr.get_width())),
);
}
return e;
},
e @ Err(_) => return e,
};
for (name, watchpoint) in self.mem_watchpoints.get_triggered_watchpoints(addr, bits)? {
let pretty_loc = if self.config.print_module_name {
self.cur_loc.to_string_with_module()
} else {
self.cur_loc.to_string_no_module()
};
info!(
"Memory watchpoint {:?} {} read by {{{}}}",
name, watchpoint, pretty_loc
);
}
Ok(retval)
}
pub fn write(&mut self, addr: &B::BV, val: B::BV) -> Result<()> {
self.write_without_mut(addr, val)
}
fn write_without_mut(&self, addr: &B::BV, val: B::BV) -> Result<()> {
let write_width = val.get_width();
let result = self.mem.borrow_mut().write(addr, val);
match result {
Ok(()) => (),
e @ Err(Error::NullPointerDereference) => {
if self.config.null_pointer_checking == NullPointerChecking::SplitPath {
self.save_backtracking_point_at_location(
self.cur_loc.clone(),
addr._ne(&self.zero(addr.get_width())),
);
}
return e;
},
e @ Err(_) => return e,
};
for (name, watchpoint) in self
.mem_watchpoints
.get_triggered_watchpoints(addr, write_width)?
{
let pretty_loc = if self.config.print_module_name {
self.cur_loc.to_string_with_module()
} else {
self.cur_loc.to_string_no_module()
};
let watchpoint_low =
self.bv_from_u64(watchpoint.get_lower_bound(), self.pointer_size_bits);
let watchpoint_size_bits =
(watchpoint.get_upper_bound() - watchpoint.get_lower_bound() + 1) * 8;
let new_value = self
.mem
.borrow()
.read(&watchpoint_low, watchpoint_size_bits as u32)?;
info!(
"Memory watchpoint {:?} {} written by {{{}}}; new value is {:?}",
name, watchpoint, pretty_loc, new_value
);
}
Ok(())
}
#[deprecated = "Prefer size_in_bits()"]
pub fn size(&self, ty: &Type) -> u32 {
self.proj
.size_in_bits(ty)
.expect("state.size() encountered a struct with no definition in the entire Project")
}
#[deprecated = "Renamed to size_in_bits()"]
pub fn size_opaque_aware(&self, ty: &Type, _proj: &'p Project) -> Option<u32> {
self.proj.size_in_bits(ty)
}
pub fn size_in_bits(&self, ty: &Type) -> Option<u32> {
self.proj.size_in_bits(ty)
}
#[deprecated = "Renamed to fp_size_in_bits"]
pub fn fp_size(fpt: FPType) -> u32 {
Self::fp_size_in_bits(fpt)
}
pub fn fp_size_in_bits(fpt: FPType) -> u32 {
match fpt {
FPType::Half => 16,
FPType::Single => 32,
FPType::Double => 64,
FPType::FP128 => 128,
FPType::X86_FP80 => 80,
FPType::PPC_FP128 => 128,
}
}
pub fn get_offset_constant_index(
&self,
base_type: &Type,
index: usize,
) -> Result<(u32, TypeRef)> {
match base_type {
Type::PointerType {
pointee_type: element_type,
..
}
| Type::ArrayType { element_type, .. }
| Type::VectorType { element_type, .. } => {
let el_size_bits = self.size_in_bits(element_type).ok_or_else(|| {
Error::MalformedInstruction(format!(
"get_offset encountered an opaque struct type: {:?}",
element_type
))
})?;
if el_size_bits % 8 != 0 {
Err(Error::UnsupportedInstruction(format!(
"Encountered a type with size {} bits",
el_size_bits
)))
} else {
let el_size_bytes = el_size_bits / 8;
let index: u32 = index.try_into().unwrap();
Ok((index * el_size_bytes, element_type.clone()))
}
},
Type::StructType { element_types, .. } => {
let mut offset_bits = 0;
for ty in element_types.iter().take(index) {
let element_size_bits = self.size_in_bits(ty).ok_or_else(|| {
Error::MalformedInstruction(format!(
"get_offset encountered an opaque struct type: {:?}",
ty
))
})?;
offset_bits += element_size_bits;
}
if offset_bits % 8 != 0 {
Err(Error::UnsupportedInstruction(format!(
"Struct offset of {} bits",
offset_bits
)))
} else {
Ok((offset_bits / 8, element_types[index].clone()))
}
},
Type::NamedStructType { name } => {
match self.cur_loc.module.types.named_struct_def(name) {
None => Err(Error::MalformedInstruction(format!(
"get_offset on a struct type not found in the current module: {:?}",
name
))),
Some(NamedStructDef::Opaque) => Err(Error::MalformedInstruction(format!(
"get_offset on an opaque struct type: {:?}",
name
))),
Some(NamedStructDef::Defined(ty)) => self.get_offset_constant_index(&ty, index),
}
},
_ => panic!("get_offset_constant_index with base type {:?}", base_type),
}
}
pub fn get_offset_bv_index<'t, V: BV>(
&self,
base_type: &'t Type,
index: &V,
solver: V::SolverRef,
) -> Result<(V, &'t Type)> {
match base_type {
Type::PointerType { pointee_type: element_type, .. }
| Type::ArrayType { element_type, .. }
| Type::VectorType { element_type, .. }
=> {
let el_size_bits = self.size_in_bits(element_type)
.ok_or_else(|| Error::OtherError(format!("get_offset encountered an opaque struct type: {:?}", element_type)))?;
if el_size_bits % 8 != 0 {
Err(Error::UnsupportedInstruction(format!("Encountered a type with size {} bits", el_size_bits)))
} else {
let el_size_bytes = el_size_bits / 8;
Ok((index.mul(&V::from_u64(solver, el_size_bytes as u64, index.get_width())), &element_type))
}
},
Type::StructType { .. } | Type::NamedStructType { .. } => {
Err(Error::MalformedInstruction("Index into struct type must be constant; consider using `get_offset_constant_index` instead of `get_offset_bv_index`".to_owned()))
},
_ => panic!("get_offset_bv_index with base type {:?}", base_type),
}
}
pub fn add_mem_watchpoint(&mut self, name: impl Into<String>, watchpoint: Watchpoint) -> bool {
self.mem_watchpoints.add(name, watchpoint)
}
pub fn rm_mem_watchpoint(&mut self, name: &str) -> bool {
self.mem_watchpoints.remove(name)
}
pub fn disable_watchpoint(&mut self, name: &str) -> bool {
self.mem_watchpoints.disable(name)
}
pub fn enable_watchpoint(&mut self, name: &str) -> bool {
self.mem_watchpoints.enable(name)
}
pub fn allocate(&mut self, bits: impl Into<u64>) -> B::BV {
let raw_ptr = self.alloc.alloc(bits);
self.bv_from_u64(raw_ptr, self.pointer_size_bits)
}
pub fn get_allocation_size(&mut self, addr: &B::BV) -> Result<Option<u64>> {
match addr.as_u64() {
Some(addr) => Ok(self.alloc.get_allocation_size(addr)),
None => {
match self.get_possible_solutions_for_bv(addr, 1)? {
PossibleSolutions::AtLeast(_) => Err(Error::OtherError(format!(
"get_allocation_size: address is not a constant: {:?}",
addr
))),
PossibleSolutions::Exactly(v) => {
let addr =
v.iter()
.next()
.ok_or(Error::Unsat)?
.as_u64()
.ok_or_else(|| {
Error::OtherError(
"get_allocation_size: address is more than 64 bits wide"
.to_owned(),
)
})?;
Ok(self.alloc.get_allocation_size(addr))
},
}
},
}
}
pub fn record_path_entry(&mut self) {
let entry = PathEntry(self.cur_loc.clone());
debug!("Recording a path entry {:?}", entry);
self.path.push(entry);
}
pub fn get_path(&self) -> &Vec<PathEntry<'p>> {
&self.path
}
pub fn push_callsite(&mut self, call: &'p instruction::Call) {
self.push_generic_callsite(Either::Left(call))
}
pub fn push_invokesite(&mut self, invoke: &'p terminator::Invoke) {
self.push_generic_callsite(Either::Right(invoke))
}
fn push_generic_callsite(
&mut self,
instr: Either<&'p instruction::Call, &'p terminator::Invoke>,
) {
self.stack.push(StackFrame {
callsite: Callsite {
loc: self.cur_loc.clone(),
instr,
},
restore_info: self
.varmap
.get_restore_info_for_fn(self.cur_loc.func.name.clone()),
})
}
pub fn pop_callsite(&mut self) -> Option<Callsite<'p>> {
if let Some(StackFrame {
callsite,
restore_info,
}) = self.stack.pop()
{
self.varmap.restore_fn_vars(restore_info);
Some(callsite)
} else {
None
}
}
pub fn current_callstack_depth(&self) -> usize {
self.stack.len()
}
pub fn save_backtracking_point(&mut self, bb_to_enter: &Name, constraint: B::BV) {
debug!(
"Saving a backtracking point, which would enter bb {:?} with constraint {:?}",
bb_to_enter, constraint
);
let bb_to_enter = self
.cur_loc
.func
.get_bb_by_name(&bb_to_enter)
.unwrap_or_else(|| {
panic!(
"Failed to find bb named {} in function {:?}",
bb_to_enter, self.cur_loc.func.name
)
});
let backtrack_loc = Location {
module: self.cur_loc.module,
func: self.cur_loc.func,
bb: bb_to_enter,
instr: BBInstrIndex::Instr(0),
source_loc: None,
};
self.save_backtracking_point_at_location(backtrack_loc, constraint);
}
fn save_backtracking_point_at_location(
&self,
loc_to_start_at: Location<'p>,
constraint: B::BV,
) {
self.solver.push(1);
self.backtrack_points.borrow_mut().push(BacktrackPoint {
loc: loc_to_start_at,
stack: self.stack.clone(),
constraint,
varmap: self.varmap.clone(),
mem: self.mem.borrow().clone(),
path_len: self.path.len(),
});
}
pub fn revert_to_backtracking_point(&mut self) -> Result<bool> {
if let Some(bp) = self.backtrack_points.borrow_mut().pop() {
debug!("Reverting to backtracking point {}", bp);
self.solver.pop(1);
self.varmap = bp.varmap;
self.mem.replace(bp.mem);
self.stack = bp.stack;
self.path.truncate(bp.path_len);
self.cur_loc = bp.loc;
bp.constraint.assert()?;
Ok(true)
} else {
Ok(false)
}
}
pub fn count_backtracking_points(&self) -> usize {
self.backtrack_points.borrow().len()
}
pub fn pretty_backtrace(&self) -> String {
let mut locdescrs = std::iter::once(LocationDescription::from(self.cur_loc.clone()))
.chain(
self.stack
.iter()
.rev()
.map(|frame| LocationDescription::from(frame.callsite.loc.clone())),
)
.collect::<Vec<LocationDescription>>();
for locdescr in locdescrs.iter_mut() {
self.demangle_locdescr(locdescr);
}
locdescrs
.into_iter()
.zip(1 ..)
.map(|(locdescr, framenum)| {
let pretty_locdescr = if self.config.print_module_name {
locdescr.to_string_with_module()
} else {
locdescr.to_string_no_module()
};
let mut frame_string = format!(" #{}: {}\n", framenum, pretty_locdescr);
match locdescr.source_loc {
Some(source_loc) if self.config.print_source_info => {
frame_string
.push_str(&format!(" ({})\n", pretty_source_loc(source_loc)));
},
_ => {},
};
frame_string
})
.collect()
}
pub fn pretty_path_llvm(&self) -> String {
let mut path_str = String::new();
for path_entry in self.get_path() {
path_str.push_str(&format!(
" {}\n",
if self.config.print_module_name {
path_entry.to_string_with_module()
} else {
path_entry.to_string_no_module()
},
));
}
path_str
}
pub fn pretty_path_source(&self) -> String {
let mut path_str = String::new();
let mut source_locs = self
.get_path()
.iter()
.flat_map(|path_entry| path_entry.get_all_source_locs());
match source_locs.next() {
None => {
path_str.push_str(" No source locations available in the path.\n");
path_str.push_str(
" This may be because the LLVM bitcode was not compiled with debuginfo.\n",
);
path_str.push_str(
" To compile C/C++ or Rust sources with debuginfo, pass the `-g` flag\n",
);
path_str.push_str(" to `clang`, `clang++`, or `rustc`.\n");
},
Some(first_source_loc) => {
path_str.push_str(&format!(" {}\n", pretty_source_loc(first_source_loc)))
},
}
for source_loc in source_locs {
path_str.push_str(&format!(" {}\n", pretty_source_loc(source_loc)));
}
path_str
}
pub fn pretty_path_interleaved(&self) -> String {
let mut path_str = String::new();
for path_entry in self.get_path() {
path_str.push_str(&format!(
" {}:\n",
if self.config.print_module_name {
path_entry.to_string_with_module()
} else {
path_entry.to_string_no_module()
},
));
let mut source_locs = path_entry.get_all_source_locs();
match source_locs.next() {
None => path_str.push_str(" (no source locations available)\n"),
Some(first_source_loc) => {
path_str.push_str(&format!(" {}\n", pretty_source_loc(first_source_loc)))
},
}
for source_loc in source_locs {
path_str.push_str(&format!(" {}\n", pretty_source_loc(source_loc)));
}
}
path_str
}
pub fn demangle(&self, funcname: &str) -> String {
match self.config.demangling {
Some(demangling) => demangling.maybe_demangle(funcname),
None => panic!("Demangling shouldn't be None here"),
}
}
fn demangle_locdescr(&self, locdescr: &mut LocationDescription) {
locdescr.funcname = self.demangle(&locdescr.funcname);
}
pub fn all_vars_in_cur_fn(&self) -> impl Iterator<Item = (&Name, &B::BV)> {
self.varmap.get_all_vars_in_fn(&self.cur_loc.func.name)
}
pub fn current_assignments_as_pretty_string(&self) -> Result<String> {
self.solver.set_opt(BtorOption::ModelGen(ModelGen::All));
let string = if self.sat()? {
let printed = self.solver.print_model();
let sorted = itertools::sorted(printed.lines());
sorted.fold(String::new(), |s, line| s + "\n" + line)
} else {
"<state is unsatisfiable>".to_owned()
};
self.solver
.set_opt(BtorOption::ModelGen(ModelGen::Disabled));
Ok(string)
}
pub fn full_error_message_with_context(&self, e: Error) -> String {
let mut err_msg = format!("{}\n\n", e);
err_msg.push_str(&format!("Backtrace:\n{}\n", self.pretty_backtrace()));
match PathDumpType::get_from_env_var() {
PathDumpType::None => {
err_msg.push_str("note: For a dump of the path that led to this error, rerun with the environment variable `HAYBALE_DUMP_PATH` set to:\n");
err_msg
.push_str(" `LLVM` for a list of the LLVM basic blocks in the path\n");
err_msg.push_str(
" `SRC` for a list of the source-language locations in the path\n",
);
err_msg.push_str(" `BOTH` for both of the above\n");
err_msg.push_str(
" To get source-language locations, the LLVM bitcode must also contain\n",
);
err_msg.push_str(" debuginfo. For example, C/C++ or Rust sources must be compiled with the\n");
err_msg.push_str(" `-g` flag to `clang`, `clang++`, or `rustc`.\n");
},
PathDumpType::LLVM => {
err_msg.push_str("LLVM path to error:\n");
err_msg.push_str(&self.pretty_path_llvm());
err_msg.push_str("note: to also get a dump of the source-language locations in this path, rerun with `HAYBALE_DUMP_PATH=BOTH`.\n");
},
PathDumpType::Source => {
err_msg.push_str("Source-language path to error:\n");
err_msg.push_str(&self.pretty_path_source());
err_msg.push_str("note: to also get a dump of the LLVM basic blocks in this path, rerun with `HAYBALE_DUMP_PATH=BOTH`.\n");
},
PathDumpType::Interleaved => {
err_msg.push_str("Full path to error:\n");
err_msg.push_str(&self.pretty_path_interleaved());
},
}
if std::env::var("HAYBALE_DUMP_VARS") == Ok("1".to_owned()) {
err_msg
.push_str("\nLatest values of variables at time of error, in current function:\n");
err_msg.push_str("(Ignore any values from past the point of error, they may be from other paths)\n\n");
for (varname, value) in self.all_vars_in_cur_fn() {
err_msg.push_str(&format!(" {}: {:?}\n", varname, value));
}
} else {
err_msg.push_str("\nnote: For a dump of variable values at time of error, rerun with `HAYBALE_DUMP_VARS=1` environment variable.\n");
}
err_msg.push_str("\nnote: to enable detailed logs, ensure that debug-level logging messages are visible.\n");
err_msg.push_str(" See the documentation for your chosen logging backend (e.g., env_logger, log4rs, etc)\n");
err_msg.push_str(" for help with configuration.\n");
err_msg.push_str("\n");
err_msg
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
enum PathDumpType {
None,
LLVM,
Source,
Interleaved,
}
impl PathDumpType {
fn get_from_env_var() -> Self {
match std::env::var("HAYBALE_DUMP_PATH") {
Err(_) => Self::None,
Ok(mut val) => {
val.make_ascii_uppercase();
match val.deref() {
"" => Self::None,
"LLVM" => Self::LLVM,
"SRC" => Self::Source,
"BOTH" => Self::Interleaved,
"1" => Self::Interleaved,
_ => Self::Interleaved,
}
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::solver_utils::SolutionCount;
use crate::test_utils::*;
#[test]
fn sat() -> Result<()> {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
assert_eq!(state.sat(), Ok(true));
state.bv_from_bool(true).assert();
assert_eq!(state.sat(), Ok(true));
let x = state.new_bv_with_name(Name::from("x"), 64)?;
x.sgt(&state.zero(64)).assert();
assert_eq!(state.sat(), Ok(true));
Ok(())
}
#[test]
fn unsat() -> Result<()> {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let state = blank_state(&project, "test_func");
state.bv_from_bool(false).assert();
assert_eq!(state.sat(), Ok(false));
Ok(())
}
#[test]
fn unsat_with_extra_constraints() -> Result<()> {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
let x = state.new_bv_with_name(Name::from("x"), 64)?;
x.ugt(&state.bv_from_u64(3, 64)).assert();
assert_eq!(state.sat(), Ok(true));
let bad_constraint = x.ult(&state.bv_from_u64(3, 64));
assert_eq!(
state.sat_with_extra_constraints(std::iter::once(&bad_constraint)),
Ok(false)
);
assert_eq!(state.sat(), Ok(true));
Ok(())
}
#[test]
fn get_a_solution() -> Result<()> {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
let x = state.new_bv_with_name(Name::from("x"), 64)?;
x.ugt(&state.bv_from_u64(3, 64)).assert();
let x_value = state
.get_a_solution_for_bv(&x)
.unwrap()
.expect("Expected a solution for x")
.as_u64()
.unwrap();
assert!(x_value > 3);
Ok(())
}
#[test]
fn possible_solutions() -> Result<()> {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
let x = state.new_bv_with_name(Name::from("x"), 64)?;
x.ugt(&state.bv_from_u64(3, 64)).assert();
let num_solutions = state.get_possible_solutions_for_bv(&x, 2).unwrap().count();
assert_eq!(num_solutions, SolutionCount::AtLeast(3));
x.ult(&state.bv_from_u64(6, 64)).assert();
let solutions = state
.get_possible_solutions_for_bv(&x, 2)
.unwrap()
.as_u64_solutions();
assert_eq!(solutions, Some([4, 5].iter().copied().collect()));
x.ult(&state.bv_from_u64(5, 64)).assert();
let solutions = state
.get_possible_solutions_for_bv(&x, 2)
.unwrap()
.as_u64_solutions();
assert_eq!(solutions, Some(PossibleSolutions::exactly_one(4)));
x.ult(&state.bv_from_u64(3, 64)).assert();
let solutions = state
.get_possible_solutions_for_bv(&x, 2)
.unwrap()
.as_u64_solutions();
assert_eq!(solutions, Some(PossibleSolutions::empty()));
Ok(())
}
#[test]
fn lookup_vars_via_operand() {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
let name1 = Name::from("val");
let name2 = Name::from(2);
let var1 = state.new_bv_with_name(name1.clone(), 64).unwrap();
let var2 = state.new_bv_with_name(name2.clone(), 1).unwrap();
let op1 = Operand::LocalOperand {
name: name1,
ty: state.cur_loc.module.types.i32(),
};
let op2 = Operand::LocalOperand {
name: name2,
ty: state.cur_loc.module.types.bool(),
};
assert_eq!(state.operand_to_bv(&op1), Ok(var1));
assert_eq!(state.operand_to_bv(&op2), Ok(var2));
}
#[test]
fn const_bv() {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let state = blank_state(&project, "test_func");
let constint = Constant::Int { bits: 64, value: 3 };
let bv = state
.operand_to_bv(&Operand::ConstantOperand(ConstantRef::new(constint)))
.unwrap();
let solution = state
.get_a_solution_for_bv(&bv)
.unwrap()
.expect("Expected a solution for the bv")
.as_u64()
.unwrap();
assert_eq!(solution, 3);
}
#[test]
fn const_bool() {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let state = blank_state(&project, "test_func");
let consttrue = Constant::Int { bits: 1, value: 1 };
let constfalse = Constant::Int { bits: 1, value: 0 };
let bvtrue = state
.operand_to_bv(&Operand::ConstantOperand(ConstantRef::new(consttrue)))
.unwrap();
let bvfalse = state
.operand_to_bv(&Operand::ConstantOperand(ConstantRef::new(constfalse)))
.unwrap();
assert_eq!(
state
.get_a_solution_for_bv(&bvtrue)
.unwrap()
.expect("Expected a solution for bvtrue")
.as_bool()
.unwrap(),
true,
);
assert_eq!(
state
.get_a_solution_for_bv(&bvfalse)
.unwrap()
.expect("Expected a solution for bvfalse")
.as_bool()
.unwrap(),
false,
);
bvtrue.assert();
assert_eq!(state.sat(), Ok(true));
bvfalse.assert();
assert_eq!(state.sat(), Ok(false));
}
#[test]
fn backtracking() -> Result<()> {
let func = blank_function(
"test_func",
vec![Name::from("bb_start"), Name::from("bb_target")],
);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
state.record_path_entry();
let x = state.new_bv_with_name(Name::from("x"), 64)?;
x.sgt(&state.bv_from_i64(11, 64)).assert();
let y = state.new_bv_with_name(Name::from("y"), 64)?;
let constraint = y.sgt(&state.bv_from_i64(5, 64));
let bb = project
.get_func_by_name("test_func")
.map(|(func, _)| func)
.expect("Expected to find function named 'test_func'")
.get_bb_by_name(&Name::from("bb_target"))
.expect("Expected to find bb named 'bb_target'");
state.save_backtracking_point(&bb.name, constraint);
assert_eq!(
state.sat_with_extra_constraints(std::iter::once(&y.slt(&state.bv_from_i64(4, 64)))),
Ok(true),
);
x.slt(&state.bv_from_i64(8, 64)).assert();
assert_eq!(state.sat(), Ok(false));
let pre_rollback = state.cur_loc.clone();
assert!(state.revert_to_backtracking_point().unwrap());
assert_eq!(state.cur_loc.func, pre_rollback.func);
assert_eq!(state.cur_loc.bb.name, bb.name);
let path = state.get_path();
assert_eq!(path.len(), 1);
let path_entry = &path[0];
assert_eq!(path_entry.0.module.name, "test_mod");
assert_eq!(path_entry.0.func.name, "test_func");
assert_eq!(path_entry.0.bb.name, Name::from("bb_start"));
assert_eq!(path_entry.0.instr, BBInstrIndex::Instr(0));
assert_eq!(state.sat(), Ok(true));
assert!(
state
.get_a_solution_for_bv(&y)
.unwrap()
.expect("Expected a solution for y")
.as_u64()
.unwrap()
> 5
);
assert!(
state
.get_a_solution_for_bv(&x)
.unwrap()
.expect("Expected a solution for x")
.as_u64()
.unwrap()
> 11
);
assert!(!state.revert_to_backtracking_point().unwrap());
Ok(())
}
#[test]
fn fork() {
let func = blank_function("test_func", vec![Name::from("test_bb")]);
let project = blank_project("test_mod", func);
let mut state = blank_state(&project, "test_func");
let x = state.new_bv_with_name(Name::from("x"), 64).unwrap();
x.ult(&state.bv_from_u32(42, 64)).assert();
let y = x.add(&state.bv_from_u32(7, 64));
state.assign_bv_to_name(Name::from("y"), y).unwrap();
let mut state_2 = state.fork();
let op_x = Operand::LocalOperand {
name: Name::from("x"),
ty: state.cur_loc.module.types.i64(),
};
let op_y = Operand::LocalOperand {
name: Name::from("y"),
ty: state.cur_loc.module.types.i64(),
};
let x_1 = state.operand_to_bv(&op_x).unwrap();
let x_2 = state_2.operand_to_bv(&op_x).unwrap();
let y_1 = state.operand_to_bv(&op_y).unwrap();
let y_2 = state_2.operand_to_bv(&op_y).unwrap();
x_1.ugt(&state.bv_from_u32(3, 64)).assert();
x_2.ult(&state_2.bv_from_u32(3, 64)).assert();
let y_solution = state
.get_a_solution_for_bv(&y_1)
.unwrap()
.expect("Expected a solution for y")
.as_u64()
.unwrap();
assert!(y_solution > 10);
let y_solution = state
.get_a_solution_for_irname(&"test_func".to_owned(), &Name::from("y"))
.unwrap()
.expect("Expected a solution for y")
.as_u64()
.unwrap();
assert!(y_solution > 10);
let y_2_solution = state_2
.get_a_solution_for_bv(&y_2)
.unwrap()
.expect("Expected a solution for y_2")
.as_u64()
.unwrap();
assert!(y_2_solution < 10);
let y_2_solution = state_2
.get_a_solution_for_irname(&"test_func".to_owned(), &Name::from("y"))
.unwrap()
.expect("Expected a solution for y_2")
.as_u64()
.unwrap();
assert!(y_2_solution < 10);
}
}