use std::ops::Range;
use crate::execution::{Binary, DynamicValueType, VariableId};
use crate::semantic::execution::{
Assignment as FinalAssignment, AssignmentOp as FinalAssignmentOp,
AssignmentWrite as FinalAssignmentType, AssignmentWriteVariable as FinalAssignmentValueWrite,
};
use crate::semantic::inner::execution::TableExportType;
use crate::semantic::inner::{Sleigh, SolverStatus};
use crate::{
AttachVarnodeId, BitrangeId, ExecutionError, NumberNonZeroUnsigned, NumberUnsigned, Span,
TableId, VarSizeError, VarnodeId,
};
use super::{
len, Execution, Expr, ExprBinaryOp, ExprElement, ExprNumber, ExprUnaryOp, ExprValue, FieldSize,
FieldSizeMut, FieldSizeTableExport, FieldSizeUnmutable, MemoryLocation, Unary,
};
#[derive(Clone, Debug)]
pub struct Assignment {
pub location: Span,
pub var_location: Span,
pub var: AssignmentWrite,
pub right: Expr,
}
#[derive(Clone, Debug)]
pub enum AssignmentWrite {
Variable {
value: AssignmentWriteVariable,
op: Option<AssignmentOp>,
},
Memory {
mem: MemoryLocation,
addr: Expr,
},
TableExport {
table_id: TableId,
op: Option<AssignmentOp>,
},
TableReferenceExport {
table_id: TableId,
size: FieldSize,
},
}
impl AssignmentWrite {
fn convert(self) -> FinalAssignmentType {
match self {
AssignmentWrite::Variable { value, op } => FinalAssignmentType::Variable {
value: value.convert(),
op: op.map(AssignmentOp::convert),
},
AssignmentWrite::Memory { mem, addr } => FinalAssignmentType::Memory {
mem: mem.convert(),
addr: addr.convert(),
},
AssignmentWrite::TableExport { table_id, op } => FinalAssignmentType::TableExport {
table_id,
op: op.map(AssignmentOp::convert),
size: None,
},
AssignmentWrite::TableReferenceExport { table_id, size } => {
FinalAssignmentType::TableExport {
table_id,
op: None,
size: size.possible_value(),
}
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum AssignmentWriteVariable {
Varnode(VarnodeId),
Bitrange(BitrangeId),
DynVarnode {
value_id: DynamicValueType,
attach_id: AttachVarnodeId,
},
Local {
id: VariableId,
creation: bool,
},
}
impl AssignmentWriteVariable {
fn convert(self) -> FinalAssignmentValueWrite {
match self {
AssignmentWriteVariable::Varnode(var) => FinalAssignmentValueWrite::Varnode(var),
AssignmentWriteVariable::Bitrange(bit) => FinalAssignmentValueWrite::Bitrange(bit),
AssignmentWriteVariable::DynVarnode {
value_id,
attach_id,
} => FinalAssignmentValueWrite::DynVarnode {
value_id,
attach_id,
},
AssignmentWriteVariable::Local { id, creation: _ } => {
FinalAssignmentValueWrite::Variable(id)
}
}
}
}
impl Assignment {
pub fn new(var_location: Span, var: AssignmentWrite, location: Span, right: Expr) -> Self {
Self {
var_location,
var,
location,
right,
}
}
pub fn solve(
&mut self,
sleigh: &Sleigh,
execution: &Execution,
solved: &mut impl SolverStatus,
) -> Result<(), Box<ExecutionError>> {
self.right.solve(sleigh, execution, solved)?;
if hack_solve_table_reference_export_size(self, sleigh, execution) {
solved.i_did_a_thing();
}
if hack_solve_simple_bin_ands(self, sleigh, execution)? {
solved.i_did_a_thing();
}
if hack_auto_fix_bitrange_with_bitand(self, sleigh, execution) {
solved.i_did_a_thing();
}
if hack_extra_var_info_creation(self, sleigh, execution) {
solved.i_did_a_thing();
}
if hack_auto_zext_right_side(self, sleigh, execution) {
solved.i_did_a_thing();
}
if hack_1_byte_varnode_assign_to_bit(self, sleigh, execution) {
solved.i_did_a_thing();
}
if hack_auto_trunkate_right_side(self, sleigh, execution) {
solved.i_did_a_thing();
}
if hack_force_solve_usercall(self, sleigh, execution)? {
solved.i_did_a_thing();
}
let modified = {
let (mut left, mut right) = self.left_and_right_size_mut(sleigh, execution);
len::a_receive_b(&mut *left, &mut *right)
};
if modified.ok_or_else(|| VarSizeError::AssignmentSides {
left: self.left_size(sleigh, execution),
right: self.right.size(sleigh, execution),
location: self.location.clone(),
backtrace: format!("{}:{}", file!(), line!()),
})? {
solved.i_did_a_thing()
}
match &mut self.var {
AssignmentWrite::Variable { value, op } => {
if let Some(op) = op {
if op.output_size().is_undefined() {
solved.iam_not_finished(&self.var_location, file!(), line!())
}
}
match value {
AssignmentWriteVariable::Local { id, creation: _ } => {
let var = execution.variable(*id);
if var.size.get().is_undefined() {
solved.iam_not_finished(&self.var_location, file!(), line!())
}
}
AssignmentWriteVariable::Varnode(_)
| AssignmentWriteVariable::Bitrange(_)
| AssignmentWriteVariable::DynVarnode { .. } => {}
}
}
AssignmentWrite::Memory { mem, addr } => {
if mem.size.is_undefined() {
solved.iam_not_finished(&self.var_location, file!(), line!())
}
addr.solve(sleigh, execution, solved)?;
}
AssignmentWrite::TableExport { table_id: _, op } => {
if let Some(op) = op {
if op.output_size().is_undefined() {
solved.iam_not_finished(&self.var_location, file!(), line!())
}
}
}
AssignmentWrite::TableReferenceExport { table_id: _, size } => {
if size.is_undefined() {
solved.iam_not_finished(&self.var_location, file!(), line!())
}
}
}
Ok(())
}
pub fn left_size(&self, sleigh: &Sleigh, execution: &Execution) -> FieldSize {
match &self.var {
AssignmentWrite::TableExport { table_id, op: None } => {
let table = sleigh.table(*table_id);
let table_export = *table.export.borrow();
*table_export.unwrap().size().unwrap()
}
AssignmentWrite::Memory { mem, .. } => mem.size,
AssignmentWrite::Variable { op: Some(op), .. }
| AssignmentWrite::TableExport { op: Some(op), .. } => op.output_size(),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::Varnode(var),
} => FieldSize::new_bytes(sleigh.varnode(*var).len_bytes),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::Bitrange(bit),
} => FieldSize::new_bits(sleigh.bitrange(*bit).bits.len()),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::DynVarnode { attach_id, .. },
} => FieldSize::new_bytes(sleigh.attach_varnodes_len_bytes(*attach_id)),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::Local { id, creation: _ },
} => execution.variable(*id).size.get(),
AssignmentWrite::TableReferenceExport { size, .. } => *size,
}
}
pub fn left_and_right_size_mut<'a>(
&'a mut self,
sleigh: &'a Sleigh,
execution: &'a Execution,
) -> (Box<dyn FieldSizeMut + 'a>, Box<dyn FieldSizeMut + 'a>) {
let left = match &mut self.var {
AssignmentWrite::TableReferenceExport { size, .. } => Box::new(size),
AssignmentWrite::Variable { op: Some(op), .. }
| AssignmentWrite::TableExport { op: Some(op), .. } => op.output_size_mut(),
AssignmentWrite::Memory { mem, .. } => Box::new(&mut mem.size),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::Varnode(var),
} => Box::new(FieldSizeUnmutable(FieldSize::new_bytes(
sleigh.varnode(*var).len_bytes,
))),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::Bitrange(bit),
} => Box::new(FieldSizeUnmutable(FieldSize::new_bits(
sleigh.bitrange(*bit).bits.len(),
))),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::DynVarnode { attach_id, .. },
} => Box::new(FieldSizeUnmutable(FieldSize::new_bytes(
sleigh.attach_varnodes_len_bytes(*attach_id),
))),
AssignmentWrite::Variable {
op: None,
value: AssignmentWriteVariable::Local { id, creation: _ },
} => Box::new(&execution.variable(*id).size),
AssignmentWrite::TableExport { table_id, op: None } => {
let table = sleigh.table(*table_id);
Box::new(FieldSizeTableExport(&table.export))
}
};
let right = self.right.size_mut(sleigh, execution);
(left, right)
}
pub fn convert(self) -> FinalAssignment {
FinalAssignment {
location: self.location,
var: self.var.convert(),
right: self.right.convert(),
}
}
fn swap_right(&mut self, mut new_right: impl FnMut(Expr) -> Expr) {
let mut swap_right = Expr::Value(ExprElement::Value {
location: Span::File(crate::FileSpan {
start: crate::FileLocation {
file: std::rc::Rc::from(std::path::Path::new("")),
line: 0,
column: 0,
},
end_line: 0,
end_column: 0,
}),
value: ExprValue::Int(ExprNumber {
size: FieldSize::default(),
number: crate::Number::Positive(0),
}),
});
core::mem::swap(&mut self.right, &mut swap_right);
self.right = new_right(swap_right);
}
}
#[derive(Clone, Debug)]
pub struct MacroParamAssignment {
pub var: VariableId,
pub right: Expr,
}
impl MacroParamAssignment {
pub fn new(var: VariableId, right: Expr) -> Self {
Self { var, right }
}
pub fn solve(
&mut self,
sleigh: &Sleigh,
execution: &Execution,
solved: &mut impl SolverStatus,
) -> Result<(), Box<ExecutionError>> {
self.right.solve(sleigh, execution, solved)?;
let var = execution.variable(self.var);
let result =
len::a_equivalent_b(&mut &var.size, &mut *self.right.size_mut(sleigh, execution));
let did_something = result.ok_or_else(|| VarSizeError::AssignmentSides {
left: var.size.get(),
right: self.right.size(sleigh, execution),
location: self.right.src().clone(),
backtrace: format!("{}:{}", file!(), line!()),
})?;
if did_something {
solved.i_did_a_thing();
}
Ok(())
}
pub fn convert(self) -> FinalAssignment {
FinalAssignment {
location: self.right.src().clone(),
var: FinalAssignmentType::Variable {
value: FinalAssignmentValueWrite::Variable(self.var),
op: None,
},
right: self.right.convert(),
}
}
}
#[derive(Clone, Debug)]
pub enum AssignmentOp {
TakeLsb(NumberNonZeroUnsigned),
TrunkLsb {
bytes: NumberUnsigned,
output_size: FieldSize,
},
BitRange(Range<NumberUnsigned>),
}
impl AssignmentOp {
pub fn output_size_mut(&mut self) -> Box<dyn FieldSizeMut + '_> {
match self {
AssignmentOp::TakeLsb(bytes) => {
Box::new(len::FieldSizeUnmutable(FieldSize::new_bytes(*bytes)))
}
AssignmentOp::TrunkLsb {
bytes: _,
output_size,
} => Box::new(output_size),
AssignmentOp::BitRange(bits) => Box::new(len::FieldSizeUnmutable(FieldSize::new_bits(
(bits.end - bits.start).try_into().unwrap(),
))),
}
}
pub fn output_size(&self) -> FieldSize {
match self {
AssignmentOp::TakeLsb(bytes) => FieldSize::new_bytes(*bytes),
AssignmentOp::TrunkLsb {
bytes: _,
output_size,
} => *output_size,
AssignmentOp::BitRange(bits) => {
FieldSize::new_bits((bits.end - bits.start).try_into().unwrap())
}
}
}
pub fn convert(self) -> FinalAssignmentOp {
match self {
AssignmentOp::TakeLsb(x) => FinalAssignmentOp::TakeLsb(x),
AssignmentOp::TrunkLsb {
bytes,
output_size: _,
} => FinalAssignmentOp::TrunkLsb(bytes),
AssignmentOp::BitRange(x) => FinalAssignmentOp::BitRange(x),
}
}
}
fn hack_solve_table_reference_export_size(
ass: &mut Assignment,
sleigh: &Sleigh,
execution: &Execution,
) -> bool {
let AssignmentWrite::TableExport { table_id, op: None } = &ass.var else {
return false;
};
let table = sleigh.table(*table_id);
let table_export = *table.export.borrow();
let Some(TableExportType::Reference {
len: left_size,
space: _,
also_values: _,
}) = table_export
else {
return false;
};
let Some(left_size) = left_size.final_value() else {
return false;
};
let Some(right_size) = ass.right.size(sleigh, execution).final_value() else {
return false;
};
if left_size == right_size {
return false;
}
ass.var = AssignmentWrite::TableReferenceExport {
table_id: *table_id,
size: FieldSize::new_bits(right_size),
};
true
}
fn hack_solve_simple_bin_ands(
ass: &mut Assignment,
sleigh: &Sleigh,
execution: &Execution,
) -> Result<bool, Box<VarSizeError>> {
let Some(ass_size) = ass.left_size(sleigh, execution).final_value() else {
return Ok(false);
};
fn get_simple_value_size(value: &mut ExprValue) -> Option<&mut FieldSize> {
Some(match value {
ExprValue::Int(var) => &mut var.size,
ExprValue::TokenField(tf) => &mut tf.size,
ExprValue::Context(ctx) => &mut ctx.size,
ExprValue::Bitrange(bt) => &mut bt.size,
ExprValue::DisVar(dis) => &mut dis.size,
_ => return None,
})
}
match &mut ass.right {
Expr::Value(ExprElement::Value { location: _, value }) => {
let Some(var_size) = get_simple_value_size(value) else {
return Ok(false);
};
var_size
.update_action(|var| var.set_final_value(ass_size))
.ok_or_else(|| {
Box::new(VarSizeError::AssignmentSides {
left: FieldSize::new_bits(ass_size),
right: ass.right.size(sleigh, execution),
location: ass.location.clone(),
backtrace: format!("{}:{}", file!(), line!()),
})
})
}
Expr::Op(ExprBinaryOp {
location: _,
output_size,
op: Binary::BitOr | Binary::BitAnd | Binary::BitXor,
left,
right,
}) => {
let (
Expr::Value(ExprElement::Value {
location: _,
value: expr_left,
}),
Expr::Value(ExprElement::Value {
location: _,
value: expr_right,
}),
) = (left.as_mut(), right.as_mut())
else {
return Ok(false);
};
let Some(left_size) = get_simple_value_size(expr_left) else {
return Ok(false);
};
let Some(right_size) = get_simple_value_size(expr_right) else {
return Ok(false);
};
let result_right = right_size.update_action(|x| x.set_final_value(ass_size));
let result_right = result_right.ok_or_else(|| {
Box::new(VarSizeError::AssignmentSides {
left: FieldSize::new_bits(ass_size),
right: right.size(sleigh, execution),
location: ass.location.clone(),
backtrace: format!("{}:{}", file!(), line!()),
})
})?;
let result_left = left_size.update_action(|x| x.set_final_value(ass_size));
let result_left = result_left.ok_or_else(|| {
Box::new(VarSizeError::AssignmentSides {
left: FieldSize::new_bits(ass_size),
right: right.size(sleigh, execution),
location: ass.location.clone(),
backtrace: format!("{}:{}", file!(), line!()),
})
})?;
let result_output = output_size.update_action(|x| x.set_final_value(ass_size));
let result_output = result_output.ok_or_else(|| {
Box::new(VarSizeError::AssignmentSides {
left: FieldSize::new_bits(ass_size),
right: ass.right.size(sleigh, execution),
location: ass.location.clone(),
backtrace: format!("{}:{}", file!(), line!()),
})
})?;
Ok(result_left | result_right | result_output)
}
_ => Ok(false),
}
}
fn hack_auto_fix_bitrange_with_bitand(
ass: &mut Assignment,
sleigh: &Sleigh,
execution: &Execution,
) -> bool {
let AssignmentWrite::Variable {
op: Some(AssignmentOp::BitRange(bitrange)),
value: _,
} = &ass.var
else {
return false;
};
let range = bitrange.end - bitrange.start;
let Some(right_size) = ass.right.size(sleigh, execution).final_value() else {
return false;
};
if right_size.get() <= range {
return false;
}
ass.swap_right(|swap_right| {
Expr::Value(ExprElement::new_op(
swap_right.src().clone(),
Unary::BitRange {
range: 0..range,
size: FieldSize::Value(range.try_into().unwrap()),
},
swap_right,
))
});
true
}
fn hack_extra_var_info_creation(
ass: &mut Assignment,
_sleigh: &Sleigh,
execution: &Execution,
) -> bool {
let AssignmentWrite::Variable {
op: _,
value: AssignmentWriteVariable::Local { id, creation: true },
} = &ass.var
else {
return false;
};
let var = execution.variable(*id);
if !var.size.get().is_fully_undefined() {
return false;
}
match &ass.right {
Expr::Value(ExprElement::Op(ExprUnaryOp {
location: _,
op:
Unary::Dereference(MemoryLocation {
space: _,
size,
location: _,
}),
input: _,
})) => {
var.size.set(*size);
true
}
_ => false,
}
}
fn hack_auto_trunkate_right_side(
ass: &mut Assignment,
sleigh: &Sleigh,
execution: &Execution,
) -> bool {
let Some(left_size) = ass.left_size(sleigh, execution).final_value() else {
return false;
};
let right_size = ass.right.size(sleigh, execution);
let Some(right_size) = right_size.final_value() else {
return false;
};
if left_size >= right_size {
return false;
}
match &mut ass.right {
Expr::Value(ExprElement::Op(ExprUnaryOp {
location: _,
op: Unary::Dereference(MemoryLocation { size, .. }),
input: _,
})) if left_size.get() % 8 == 0 => {
*size = FieldSize::new_bits(left_size);
}
_ => ass.swap_right(|swap_right| {
Expr::Value(ExprElement::new_op(
swap_right.src().clone(),
Unary::BitRange {
range: 0..left_size.get(),
size: FieldSize::Value(left_size),
},
swap_right,
))
}),
}
true
}
fn hack_auto_zext_right_side(ass: &mut Assignment, sleigh: &Sleigh, execution: &Execution) -> bool {
let left_size = match &mut ass.var {
AssignmentWrite::Variable {
value: AssignmentWriteVariable::Varnode(id),
op: None,
} => {
let var = sleigh.varnode(*id);
let len = var.len_bytes.get() * 8;
len.try_into().unwrap()
}
AssignmentWrite::Variable {
value: AssignmentWriteVariable::Local { id, creation: _ },
op: None,
} => {
let var = execution.variable(*id);
let Some(bits) = var.size.get().final_value() else {
return false;
};
bits
}
AssignmentWrite::Memory { mem, addr: _ } => {
let Some(size) = mem.size.final_value() else {
return false;
};
size
}
_ => return false,
};
let Some(right_size) = ass.right.size(sleigh, execution).max_bits() else {
return false;
};
if left_size <= right_size {
return false;
}
ass.swap_right(|swap_right| {
Expr::new_value(ExprElement::new_op(
swap_right.src().clone(),
Unary::Zext(FieldSize::new_bits(left_size)),
swap_right,
))
});
true
}
fn hack_1_byte_varnode_assign_to_bit(
ass: &mut Assignment,
sleigh: &Sleigh,
execution: &Execution,
) -> bool {
if ass.right.size(sleigh, execution).final_value() != Some(8.try_into().unwrap()) {
return false;
}
if ass.left_size(sleigh, execution).final_value() != Some(1.try_into().unwrap()) {
return false;
}
ass.swap_right(|swap_right| {
Expr::Value(ExprElement::new_op(
swap_right.src().clone(),
Unary::BitRange {
range: 0..1,
size: FieldSize::Value(1.try_into().unwrap()),
},
swap_right,
))
});
true
}
fn hack_force_solve_usercall(
ass: &mut Assignment,
sleigh: &Sleigh,
execution: &Execution,
) -> Result<bool, Box<VarSizeError>> {
let Expr::Value(ExprElement::UserCall(usercall)) = &ass.right else {
return Ok(false);
};
let location = usercall.location.clone();
let (mut left_size, mut right_size) = ass.left_and_right_size_mut(sleigh, execution);
len::a_equivalent_b(&mut *left_size, &mut *right_size).ok_or_else(|| {
Box::new(VarSizeError::AssignmentSides {
left: left_size.get(),
right: right_size.get(),
location: location.clone(),
backtrace: format!("{}:{}", file!(), line!()),
})
})
}