use serde::{Deserialize, Serialize};
use super::ops::{AssignmentOp, BinaryOp, UnaryOp};
use crate::address::Address;
use crate::arena::Idx;
use crate::types::TypeId;
pub type ExpressionId = Idx<ExpressionNode>;
pub type StatementId = Idx<StatementNode>;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
#[doc(alias("citem_t"))]
pub enum NodeRef {
Expression(ExpressionId),
Statement(StatementId),
}
impl NodeRef {
#[inline]
#[must_use]
pub fn as_expression(self) -> Option<ExpressionId> {
match self {
Self::Expression(e) => Some(e),
Self::Statement(_) => None,
}
}
#[inline]
#[must_use]
pub fn as_statement(self) -> Option<StatementId> {
match self {
Self::Statement(s) => Some(s),
Self::Expression(_) => None,
}
}
#[inline]
#[must_use]
pub fn is_expression(self) -> bool {
matches!(self, Self::Expression(_))
}
#[inline]
#[must_use]
pub fn is_statement(self) -> bool {
matches!(self, Self::Statement(_))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
#[doc(alias("lvar_t"))]
pub struct LocalId(
pub u32,
);
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
#[doc(alias("argloc_t"))]
pub enum LocalLocation {
#[doc(alias("ALOC_REG1"))]
Register(u32),
#[doc(alias("ALOC_REG2"))]
RegisterPair {
low: u32,
high: u32,
},
#[doc(alias("ALOC_STACK"))]
Stack(i64),
#[doc(alias("ALOC_RREL"))]
RegisterRelative {
reg: u32,
offset: i64,
},
#[doc(alias("ALOC_STATIC"))]
Static(Address),
#[doc(alias("ALOC_DIST"))]
Scattered(Vec<LocationPiece>),
#[doc(alias("ALOC_CUSTOM"))]
Custom,
#[doc(alias("ALOC_NONE"))]
Unallocated,
}
impl LocalLocation {
pub(crate) fn from_argloc(
atype: u32,
reg1: u32,
reg2: u32,
sval: i64,
pieces: Vec<LocationPiece>,
) -> Self {
match atype {
0 => Self::Unallocated,
1 => Self::Stack(sval),
2 => Self::Scattered(pieces),
3 => Self::Register(reg1),
4 => Self::RegisterPair {
low: reg1,
high: reg2,
},
5 => Self::RegisterRelative {
reg: reg1,
offset: sval,
},
6 => Self::Static(Address::new_const(sval as u64)),
_ => Self::Custom,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct LocationPiece {
pub location: LocalLocation,
pub offset: u32,
pub size: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("lvar_t"))]
pub struct Local {
pub name: String,
pub ty: TypeId,
pub is_arg: bool,
pub is_result: bool,
pub is_byref: bool,
pub width: u32,
pub comment: Option<String>,
pub location: LocalLocation,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExpressionNode {
pub address: Option<Address>,
pub ty: TypeId,
pub parent: Option<NodeRef>,
pub kind: ExpressionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct StatementNode {
pub address: Option<Address>,
pub parent: Option<NodeRef>,
pub kind: StatementKind,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[doc(alias("ctype_t", "cexpr_t"))]
pub enum ExpressionKind {
#[doc(alias("cot_add", "cot_sub"))]
Binary {
op: BinaryOp,
x: ExpressionId,
y: ExpressionId,
},
#[doc(alias("cot_asg"))]
Assign {
op: AssignmentOp,
x: ExpressionId,
y: ExpressionId,
},
#[doc(alias("cot_neg", "cot_lnot"))]
Unary {
op: UnaryOp,
x: ExpressionId,
},
Ternary {
cond: ExpressionId,
then_: ExpressionId,
else_: ExpressionId,
},
#[doc(alias("cot_call"))]
Call {
callee: ExpressionId,
args: Vec<ExpressionId>,
},
#[doc(alias("cot_idx"))]
Index {
array: ExpressionId,
index: ExpressionId,
},
#[doc(alias("cot_memref"))]
MemberRef {
obj: ExpressionId,
byte_offset: u32,
},
#[doc(alias("cot_memptr"))]
MemberPtr {
obj: ExpressionId,
byte_offset: u32,
},
#[doc(alias("cot_cast"))]
Cast {
x: ExpressionId,
},
#[doc(alias("cot_ptr"))]
Deref {
x: ExpressionId,
size: u32,
},
#[doc(alias("cot_sizeof"))]
Sizeof(ExpressionId),
#[doc(alias("cot_num"))]
Num(u64),
#[doc(alias("cot_fnum"))]
Fnum(f64),
#[doc(alias("cot_str"))]
Str(String),
#[doc(alias("cot_obj"))]
Obj {
address: Address,
name: Option<String>,
},
#[doc(alias("cot_var"))]
Var(LocalId),
#[doc(alias("cot_helper"))]
Helper(String),
#[doc(alias("cot_type"))]
TypeExpression,
#[doc(alias("cot_empty"))]
Empty,
Internal,
}
macro_rules! expression_accessors {
( $( $(#[$m:meta])* $fn:ident : $variant:ident $pat:tt => $ret:ty = $build:expr ; )* ) => {
impl ExpressionKind {
$(
$(#[$m])*
#[inline]
#[must_use]
pub fn $fn(&self) -> Option<$ret> {
if let ExpressionKind::$variant $pat = self {
Some($build)
} else {
None
}
}
)*
}
};
}
expression_accessors! {
as_binary: Binary { op, x, y } => (BinaryOp, ExpressionId, ExpressionId) = (*op, *x, *y);
as_assign: Assign { op, x, y } => (AssignmentOp, ExpressionId, ExpressionId) = (*op, *x, *y);
as_unary: Unary { op, x } => (UnaryOp, ExpressionId) = (*op, *x);
as_ternary: Ternary { cond, then_, else_ } => (ExpressionId, ExpressionId, ExpressionId) = (*cond, *then_, *else_);
as_call: Call { callee, args } => (ExpressionId, &[ExpressionId]) = (*callee, args.as_slice());
as_index: Index { array, index } => (ExpressionId, ExpressionId) = (*array, *index);
as_member_ref: MemberRef { obj, byte_offset } => (ExpressionId, u32) = (*obj, *byte_offset);
as_member_ptr: MemberPtr { obj, byte_offset } => (ExpressionId, u32) = (*obj, *byte_offset);
as_cast: Cast { x } => ExpressionId = *x;
as_deref: Deref { x, size } => (ExpressionId, u32) = (*x, *size);
as_sizeof: Sizeof(x) => ExpressionId = *x;
as_num: Num(v) => u64 = *v;
as_fnum: Fnum(v) => f64 = *v;
as_var: Var(v) => LocalId = *v;
as_str: Str(s) => &str = s.as_str();
as_obj: Obj { address, name } => (Address, Option<&str>) = (*address, name.as_deref());
as_helper: Helper(s) => &str = s.as_str();
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("ccase_t"))]
pub struct Case {
pub values: Vec<u64>,
pub body: StatementId,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("cinsn_t"))]
pub enum StatementKind {
#[doc(alias("cit_block"))]
Block(Vec<StatementId>),
#[doc(alias("cit_expr"))]
Expression(ExpressionId),
#[doc(alias("cit_if"))]
If {
cond: ExpressionId,
then_: StatementId,
else_: Option<StatementId>,
},
#[doc(alias("cit_for"))]
For {
init: Option<ExpressionId>,
cond: Option<ExpressionId>,
step: Option<ExpressionId>,
body: StatementId,
},
#[doc(alias("cit_while"))]
While {
cond: ExpressionId,
body: StatementId,
},
#[doc(alias("cit_do"))]
Do {
body: StatementId,
cond: ExpressionId,
},
#[doc(alias("cit_switch"))]
Switch {
expression: ExpressionId,
cases: Vec<Case>,
},
#[doc(alias("cit_break"))]
Break,
#[doc(alias("cit_continue"))]
Continue,
#[doc(alias("cit_return"))]
Return(Option<ExpressionId>),
#[doc(alias("cit_goto"))]
Goto {
label: i32,
},
#[doc(alias("cit_asm"))]
Asm(Vec<Address>),
#[doc(alias("cit_try"))]
Try {
body: StatementId,
catches: Vec<StatementId>,
},
#[doc(alias("cit_throw"))]
Throw(Option<ExpressionId>),
#[doc(alias("cit_empty"))]
Empty,
}
impl ExpressionKind {
pub(crate) fn for_each_child(&self, mut f: impl FnMut(NodeRef)) {
use ExpressionKind::{
Assign, Binary, Call, Cast, Deref, Index, MemberPtr, MemberRef, Sizeof, Ternary, Unary,
};
match self {
Binary { x, y, .. } | Assign { x, y, .. } => {
f(NodeRef::Expression(*x));
f(NodeRef::Expression(*y));
}
Index { array, index } => {
f(NodeRef::Expression(*array));
f(NodeRef::Expression(*index));
}
Unary { x, .. } | Cast { x } | Deref { x, .. } | Sizeof(x) => {
f(NodeRef::Expression(*x));
}
MemberRef { obj, .. } | MemberPtr { obj, .. } => f(NodeRef::Expression(*obj)),
Ternary { cond, then_, else_ } => {
f(NodeRef::Expression(*cond));
f(NodeRef::Expression(*then_));
f(NodeRef::Expression(*else_));
}
Call { callee, args } => {
f(NodeRef::Expression(*callee));
for a in args {
f(NodeRef::Expression(*a));
}
}
Self::Num(_)
| Self::Fnum(_)
| Self::Str(_)
| Self::Obj { .. }
| Self::Var(_)
| Self::Helper(_)
| Self::TypeExpression
| Self::Empty
| Self::Internal => {}
}
}
}
impl StatementKind {
pub(crate) fn for_each_child(&self, mut f: impl FnMut(NodeRef)) {
use StatementKind::{Block, Do, Expression, For, If, Return, Switch, Throw, Try, While};
match self {
Block(statements) => {
for s in statements {
f(NodeRef::Statement(*s));
}
}
Expression(e) => f(NodeRef::Expression(*e)),
If { cond, then_, else_ } => {
f(NodeRef::Expression(*cond));
f(NodeRef::Statement(*then_));
if let Some(s) = else_ {
f(NodeRef::Statement(*s));
}
}
For {
init,
cond,
step,
body,
} => {
if let Some(e) = init {
f(NodeRef::Expression(*e));
}
if let Some(e) = cond {
f(NodeRef::Expression(*e));
}
if let Some(e) = step {
f(NodeRef::Expression(*e));
}
f(NodeRef::Statement(*body));
}
While { cond, body } => {
f(NodeRef::Expression(*cond));
f(NodeRef::Statement(*body));
}
Do { body, cond } => {
f(NodeRef::Statement(*body));
f(NodeRef::Expression(*cond));
}
Switch { expression, cases } => {
f(NodeRef::Expression(*expression));
for c in cases {
f(NodeRef::Statement(c.body));
}
}
Return(e) | Throw(e) => {
if let Some(x) = e {
f(NodeRef::Expression(*x));
}
}
Try { body, catches } => {
f(NodeRef::Statement(*body));
for s in catches {
f(NodeRef::Statement(*s));
}
}
Self::Break | Self::Continue | Self::Goto { .. } | Self::Asm(_) | Self::Empty => {}
}
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use rstest::rstest;
use super::*;
fn e(n: u32) -> ExpressionId {
Idx::from_raw(n)
}
fn s(n: u32) -> StatementId {
Idx::from_raw(n)
}
#[test]
fn expression_accessors_project_their_variant() {
let (a, b, c) = (e(0), e(1), e(2));
assert!(
ExpressionKind::Binary {
op: BinaryOp::Add,
x: a,
y: b
}
.as_binary()
== Some((BinaryOp::Add, a, b))
);
assert!(
ExpressionKind::Assign {
op: AssignmentOp::Assign,
x: a,
y: b
}
.as_assign()
== Some((AssignmentOp::Assign, a, b))
);
assert!(
ExpressionKind::Unary {
op: UnaryOp::Neg,
x: a
}
.as_unary()
== Some((UnaryOp::Neg, a))
);
assert!(
ExpressionKind::Ternary {
cond: a,
then_: b,
else_: c
}
.as_ternary()
== Some((a, b, c))
);
assert!(ExpressionKind::Index { array: a, index: b }.as_index() == Some((a, b)));
assert!(
ExpressionKind::MemberRef {
obj: a,
byte_offset: 8
}
.as_member_ref()
== Some((a, 8))
);
assert!(
ExpressionKind::MemberPtr {
obj: a,
byte_offset: 8
}
.as_member_ptr()
== Some((a, 8))
);
assert!(ExpressionKind::Cast { x: a }.as_cast() == Some(a));
assert!(ExpressionKind::Deref { x: a, size: 4 }.as_deref() == Some((a, 4)));
assert!(ExpressionKind::Sizeof(a).as_sizeof() == Some(a));
assert!(ExpressionKind::Num(7).as_num() == Some(7));
assert!(ExpressionKind::Fnum(3.5).as_fnum() == Some(3.5));
assert!(ExpressionKind::Var(LocalId(3)).as_var() == Some(LocalId(3)));
assert!(ExpressionKind::Str("hi".into()).as_str() == Some("hi"));
assert!(ExpressionKind::Helper("h".into()).as_helper() == Some("h"));
let call = ExpressionKind::Call {
callee: a,
args: vec![b, c],
};
assert!(let Some((callee, args)) = call.as_call());
assert!(callee == a && args.len() == 2 && args[0] == b && args[1] == c);
let obj = ExpressionKind::Obj {
address: Address::new_const(0x10),
name: Some("g".into()),
};
assert!(obj.as_obj() == Some((Address::new_const(0x10), Some("g"))));
assert!(let None = ExpressionKind::Num(1).as_binary());
}
#[test]
fn node_ref_projections() {
let expression = NodeRef::Expression(e(0));
let statement = NodeRef::Statement(Idx::from_raw(0));
assert!(expression.as_expression() == Some(e(0)));
assert!(let None = expression.as_statement());
assert!(expression.is_expression() && !expression.is_statement());
assert!(statement.as_statement() == Some(Idx::from_raw(0)));
assert!(statement.is_statement() && !statement.is_expression());
}
#[rstest]
#[case::unallocated(0, 0, 0, 0, LocalLocation::Unallocated)]
#[case::stack(1, 0, 0, -8, LocalLocation::Stack(-8))]
#[case::register(3, 5, 0, 0, LocalLocation::Register(5))]
#[case::register_pair(4, 5, 6, 0, LocalLocation::RegisterPair { low: 5, high: 6 })]
#[case::register_relative(5, 5, 0, 16, LocalLocation::RegisterRelative { reg: 5, offset: 16 })]
#[case::static_(6, 0, 0, 0x1000, LocalLocation::Static(Address::new_const(0x1000)))]
#[case::custom_lower_bound(7, 0, 0, 0, LocalLocation::Custom)]
#[case::custom_above_lower_bound(42, 0, 0, 0, LocalLocation::Custom)]
fn from_argloc_maps_known_atypes(
#[case] atype: u32,
#[case] reg1: u32,
#[case] reg2: u32,
#[case] sval: i64,
#[case] expected: LocalLocation,
) {
assert!(LocalLocation::from_argloc(atype, reg1, reg2, sval, vec![]) == expected);
}
#[test]
fn from_argloc_scattered_carries_its_fragments() {
let piece = LocationPiece {
location: LocalLocation::Stack(16),
offset: 0,
size: 8,
};
assert!(
LocalLocation::from_argloc(2, 0, 0, 0, vec![piece.clone()])
== LocalLocation::Scattered(vec![piece])
);
}
#[test]
fn local_id_ord_sorts_by_index() {
let mut ids = vec![LocalId(2), LocalId(0), LocalId(1)];
ids.sort();
assert!(ids == [LocalId(0), LocalId(1), LocalId(2)]);
assert!(LocalId(0) < LocalId(1));
}
#[test]
fn node_ref_ord_orders_expressions_before_statements() {
let mut refs = vec![
NodeRef::Statement(s(0)),
NodeRef::Expression(e(5)),
NodeRef::Expression(e(1)),
];
refs.sort();
assert!(
refs == [
NodeRef::Expression(e(1)),
NodeRef::Expression(e(5)),
NodeRef::Statement(s(0)),
]
);
}
#[test]
fn hash_targets_key_a_hash_set() {
use std::collections::HashSet;
let mut locations = HashSet::new();
locations.insert(LocalLocation::Register(1));
locations.insert(LocalLocation::Stack(-8));
assert!(locations.contains(&LocalLocation::Register(1)));
assert!(!locations.contains(&LocalLocation::Register(2)));
let mut pieces = HashSet::new();
pieces.insert(LocationPiece {
location: LocalLocation::Stack(0),
offset: 0,
size: 4,
});
assert!(pieces.contains(&LocationPiece {
location: LocalLocation::Stack(0),
offset: 0,
size: 4,
}));
let local = Local {
name: "v1".into(),
ty: Idx::from_raw(0),
is_arg: true,
is_result: false,
is_byref: false,
width: 8,
comment: None,
location: LocalLocation::Register(0),
};
let mut locals = HashSet::new();
locals.insert(local.clone());
assert!(locals.contains(&local));
let case = Case {
values: vec![1, 2],
body: s(0),
};
let mut cases = HashSet::new();
cases.insert(case.clone());
assert!(cases.contains(&case));
let mut statements = HashSet::new();
statements.insert(StatementKind::Break);
statements.insert(StatementKind::Goto { label: 3 });
assert!(statements.contains(&StatementKind::Break));
assert!(!statements.contains(&StatementKind::Continue));
}
#[test]
fn serde_round_trips() {
let node_ref = NodeRef::Expression(e(3));
let json = serde_json::to_string(&node_ref).unwrap();
assert!(serde_json::from_str::<NodeRef>(&json).unwrap() == node_ref);
let local_id = LocalId(7);
let json = serde_json::to_string(&local_id).unwrap();
assert!(serde_json::from_str::<LocalId>(&json).unwrap() == local_id);
let location = LocalLocation::RegisterRelative { reg: 2, offset: 16 };
let json = serde_json::to_string(&location).unwrap();
assert!(serde_json::from_str::<LocalLocation>(&json).unwrap() == location);
let piece = LocationPiece {
location: LocalLocation::Stack(-4),
offset: 4,
size: 4,
};
let json = serde_json::to_string(&piece).unwrap();
assert!(serde_json::from_str::<LocationPiece>(&json).unwrap() == piece);
let local = Local {
name: "arg1".into(),
ty: Idx::from_raw(1),
is_arg: true,
is_result: false,
is_byref: false,
width: 4,
comment: Some("note".into()),
location: LocalLocation::Stack(-4),
};
let json = serde_json::to_string(&local).unwrap();
assert!(serde_json::from_str::<Local>(&json).unwrap() == local);
let expression_node = ExpressionNode {
address: Some(Address::new_const(0x1000)),
ty: Idx::from_raw(0),
parent: None,
kind: ExpressionKind::Num(42),
};
let json = serde_json::to_string(&expression_node).unwrap();
assert!(serde_json::from_str::<ExpressionNode>(&json).unwrap() == expression_node);
let statement_node = StatementNode {
address: None,
parent: Some(NodeRef::Statement(s(0))),
kind: StatementKind::Break,
};
let json = serde_json::to_string(&statement_node).unwrap();
assert!(serde_json::from_str::<StatementNode>(&json).unwrap() == statement_node);
let expression_kind = ExpressionKind::Str("hi".into());
let json = serde_json::to_string(&expression_kind).unwrap();
assert!(serde_json::from_str::<ExpressionKind>(&json).unwrap() == expression_kind);
let statement_kind = StatementKind::Return(Some(e(0)));
let json = serde_json::to_string(&statement_kind).unwrap();
assert!(serde_json::from_str::<StatementKind>(&json).unwrap() == statement_kind);
let case = Case {
values: vec![1, 2, 3],
body: s(1),
};
let json = serde_json::to_string(&case).unwrap();
assert!(serde_json::from_str::<Case>(&json).unwrap() == case);
}
mod proptests {
use proptest::prelude::*;
use super::*;
proptest! {
#[test]
fn atype_at_or_above_seven_is_always_custom(
atype in 7u32..=u32::MAX,
reg1 in any::<u32>(),
reg2 in any::<u32>(),
sval in any::<i64>(),
) {
prop_assert_eq!(
LocalLocation::from_argloc(atype, reg1, reg2, sval, vec![]),
LocalLocation::Custom
);
}
}
}
}