use std::fmt;
use serde::{Deserialize, Serialize};
use super::node::{
ExpressionId, ExpressionKind, ExpressionNode, Local, LocalId, NodeRef, StatementId,
StatementKind, StatementNode,
};
use super::ops::{AssignmentOp, BinaryOp, UnaryOp};
use crate::address::Address;
use crate::arena::Arena;
use crate::types::{TypeBuilder, TypeId, TypeTable, TypeValue};
#[inline]
fn for_each_child(
expressions: &Arena<ExpressionNode>,
statements: &Arena<StatementNode>,
node: NodeRef,
f: impl FnMut(NodeRef),
) {
match node {
NodeRef::Expression(id) => expressions[id].kind.for_each_child(f),
NodeRef::Statement(id) => statements[id].kind.for_each_child(f),
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[doc(alias("cfunc_t"))]
pub struct Ctree {
expressions: Arena<ExpressionNode>,
statements: Arena<StatementNode>,
types: TypeTable,
locals: Vec<Local>,
root: StatementId,
}
impl Ctree {
#[inline]
#[must_use]
pub fn root(&self) -> StatementId {
self.root
}
#[inline]
#[must_use]
pub fn expression(&self, id: ExpressionId) -> &ExpressionNode {
&self.expressions[id]
}
#[inline]
#[must_use]
pub fn statement(&self, id: StatementId) -> &StatementNode {
&self.statements[id]
}
#[inline]
#[must_use]
pub fn kind(&self, id: ExpressionId) -> &ExpressionKind {
&self.expressions[id].kind
}
#[inline]
#[must_use]
pub fn statement_kind(&self, id: StatementId) -> &StatementKind {
&self.statements[id].kind
}
#[inline]
#[must_use]
pub fn type_of(&self, id: TypeId) -> &TypeValue {
self.types.get(id)
}
#[inline]
#[must_use]
#[doc(alias("lvar_t"))]
pub fn local(&self, id: LocalId) -> &Local {
&self.locals[id.0 as usize]
}
#[must_use]
#[doc(alias("get_lvars"))]
pub fn locals(&self) -> impl ExactSizeIterator<Item = &Local> {
self.locals.iter()
}
#[must_use]
pub fn this_local(&self) -> Option<LocalId> {
self.locals
.iter()
.position(|lv| lv.is_arg)
.map(|i| LocalId(i as u32))
}
#[must_use]
pub fn expressions(&self) -> impl ExactSizeIterator<Item = (ExpressionId, &ExpressionNode)> {
self.expressions.iter()
}
#[must_use]
pub fn statements(&self) -> impl ExactSizeIterator<Item = (StatementId, &StatementNode)> {
self.statements.iter()
}
pub fn calls(&self) -> impl Iterator<Item = (ExpressionId, ExpressionId, &[ExpressionId])> {
self.expressions()
.filter_map(|(id, node)| node.kind.as_call().map(|(callee, args)| (id, callee, args)))
}
pub fn assigns(
&self,
) -> impl Iterator<Item = (ExpressionId, AssignmentOp, ExpressionId, ExpressionId)> {
self.expressions()
.filter_map(|(id, node)| node.kind.as_assign().map(|(op, x, y)| (id, op, x, y)))
}
pub fn vars(&self) -> impl Iterator<Item = (ExpressionId, LocalId)> {
self.expressions()
.filter_map(|(id, node)| node.kind.as_var().map(|v| (id, v)))
}
#[must_use]
pub fn types(&self) -> impl ExactSizeIterator<Item = (TypeId, &TypeValue)> {
self.types.iter()
}
#[must_use]
pub fn expression_at(&self, address: Address) -> Option<ExpressionId> {
self.expressions()
.find(|(_, node)| node.address == Some(address))
.map(|(id, _)| id)
}
#[must_use]
pub fn statement_at(&self, address: Address) -> Option<StatementId> {
self.statements()
.find(|(_, node)| node.address == Some(address))
.map(|(id, _)| id)
}
pub fn items_at(&self, address: Address) -> impl Iterator<Item = NodeRef> + '_ {
let expressions = self
.expressions()
.filter(move |(_, node)| node.address == Some(address))
.map(|(id, _)| NodeRef::Expression(id));
let statements = self
.statements()
.filter(move |(_, node)| node.address == Some(address))
.map(|(id, _)| NodeRef::Statement(id));
expressions.chain(statements)
}
#[inline]
#[must_use]
pub fn parent(&self, node: NodeRef) -> Option<NodeRef> {
match node {
NodeRef::Expression(id) => self.expressions[id].parent,
NodeRef::Statement(id) => self.statements[id].parent,
}
}
#[must_use]
pub fn children(&self, node: NodeRef) -> Vec<NodeRef> {
let mut v = Vec::new();
for_each_child(&self.expressions, &self.statements, node, |c| v.push(c));
v
}
pub fn children_for_each(&self, node: NodeRef, f: impl FnMut(NodeRef)) {
for_each_child(&self.expressions, &self.statements, node, f);
}
#[must_use]
pub fn descendants(&self, node: NodeRef) -> Descendants<'_> {
Descendants {
tree: self,
stack: vec![node],
}
}
pub fn expression_descendants(&self, node: NodeRef) -> impl Iterator<Item = ExpressionId> + '_ {
self.descendants(node).filter_map(NodeRef::as_expression)
}
}
impl fmt::Display for Ctree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_pseudocode())
}
}
pub struct Descendants<'a> {
tree: &'a Ctree,
stack: Vec<NodeRef>,
}
impl fmt::Debug for Descendants<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Descendants")
.field("stack", &self.stack)
.finish_non_exhaustive()
}
}
impl Iterator for Descendants<'_> {
type Item = NodeRef;
fn next(&mut self) -> Option<NodeRef> {
let node = self.stack.pop()?;
let base = self.stack.len();
for_each_child(&self.tree.expressions, &self.tree.statements, node, |c| {
self.stack.push(c);
});
self.stack[base..].reverse();
Some(node)
}
}
#[derive(Debug)]
pub struct CtreeBuilder {
expressions: Arena<ExpressionNode>,
statements: Arena<StatementNode>,
types: TypeBuilder,
locals: Vec<Local>,
}
impl CtreeBuilder {
#[must_use]
pub fn new() -> Self {
Self {
expressions: Arena::new(),
statements: Arena::new(),
types: TypeBuilder::new(),
locals: Vec::new(),
}
}
pub(crate) fn types(&self) -> &TypeBuilder {
&self.types
}
pub(crate) fn types_mut(&mut self) -> &mut TypeBuilder {
&mut self.types
}
pub fn intern_type(&mut self, data: TypeValue) -> TypeId {
self.types.intern(data)
}
pub fn alloc_type_placeholder(&mut self) -> TypeId {
self.types.alloc_placeholder()
}
pub fn fill_type(&mut self, id: TypeId, data: TypeValue) {
self.types.fill(id, data);
}
#[must_use]
pub fn type_size(&self, id: TypeId) -> Option<u64> {
self.types.type_size(id)
}
pub fn push_local(&mut self, local: Local) -> LocalId {
let id = LocalId(u32::try_from(self.locals.len()).expect("ctree exceeded u32 locals"));
self.locals.push(local);
id
}
pub fn var(&mut self, ty: TypeId, local: LocalId) -> ExpressionId {
self.expression(ty, ExpressionKind::Var(local)).call()
}
pub fn num(&mut self, ty: TypeId, value: u64) -> ExpressionId {
self.expression(ty, ExpressionKind::Num(value)).call()
}
pub fn fnum(&mut self, ty: TypeId, value: f64) -> ExpressionId {
self.expression(ty, ExpressionKind::Fnum(value)).call()
}
pub fn obj(&mut self, ty: TypeId, address: Address, name: Option<&str>) -> ExpressionId {
self.expression(
ty,
ExpressionKind::Obj {
address,
name: name.map(str::to_owned),
},
)
.call()
}
pub fn string(&mut self, ty: TypeId, s: impl Into<String>) -> ExpressionId {
self.expression(ty, ExpressionKind::Str(s.into())).call()
}
pub fn helper(&mut self, ty: TypeId, s: impl Into<String>) -> ExpressionId {
self.expression(ty, ExpressionKind::Helper(s.into())).call()
}
pub fn cast(&mut self, ty: TypeId, x: ExpressionId) -> ExpressionId {
self.expression(ty, ExpressionKind::Cast { x }).call()
}
pub fn deref(&mut self, ty: TypeId, x: ExpressionId, size: u32) -> ExpressionId {
self.expression(ty, ExpressionKind::Deref { x, size })
.call()
}
pub fn unary(&mut self, ty: TypeId, op: UnaryOp, x: ExpressionId) -> ExpressionId {
self.expression(ty, ExpressionKind::Unary { op, x }).call()
}
pub fn binary(
&mut self,
ty: TypeId,
op: BinaryOp,
x: ExpressionId,
y: ExpressionId,
) -> ExpressionId {
self.expression(ty, ExpressionKind::Binary { op, x, y })
.call()
}
pub fn assign(
&mut self,
ty: TypeId,
op: AssignmentOp,
x: ExpressionId,
y: ExpressionId,
) -> ExpressionId {
self.expression(ty, ExpressionKind::Assign { op, x, y })
.call()
}
pub fn ternary(
&mut self,
ty: TypeId,
cond: ExpressionId,
then_: ExpressionId,
else_: ExpressionId,
) -> ExpressionId {
self.expression(ty, ExpressionKind::Ternary { cond, then_, else_ })
.call()
}
pub fn index(&mut self, ty: TypeId, array: ExpressionId, index: ExpressionId) -> ExpressionId {
self.expression(ty, ExpressionKind::Index { array, index })
.call()
}
pub fn member_ref(&mut self, ty: TypeId, obj: ExpressionId, byte_offset: u32) -> ExpressionId {
self.expression(ty, ExpressionKind::MemberRef { obj, byte_offset })
.call()
}
pub fn member_ptr(&mut self, ty: TypeId, obj: ExpressionId, byte_offset: u32) -> ExpressionId {
self.expression(ty, ExpressionKind::MemberPtr { obj, byte_offset })
.call()
}
pub fn call_expression(
&mut self,
ty: TypeId,
callee: ExpressionId,
args: Vec<ExpressionId>,
) -> ExpressionId {
self.expression(ty, ExpressionKind::Call { callee, args })
.call()
}
pub fn sizeof(&mut self, ty: TypeId, x: ExpressionId) -> ExpressionId {
self.expression(ty, ExpressionKind::Sizeof(x)).call()
}
pub fn expression_statement(&mut self, e: ExpressionId) -> StatementId {
self.statement(StatementKind::Expression(e)).call()
}
pub fn block(&mut self, statements: Vec<StatementId>) -> StatementId {
self.statement(StatementKind::Block(statements)).call()
}
pub fn ret(&mut self, value: Option<ExpressionId>) -> StatementId {
self.statement(StatementKind::Return(value)).call()
}
#[must_use]
pub fn finish(mut self, root: StatementId) -> Ctree {
let total = self.expressions.len() + self.statements.len();
let mut stack = vec![NodeRef::Statement(root)];
let mut kids: Vec<NodeRef> = Vec::new();
let mut visited = 0usize;
while let Some(node) = stack.pop() {
visited += 1;
assert!(
visited <= total,
"ctree walk revisited more nodes ({visited}) than were allocated ({total}); \
the tree is cyclic or over-linked"
);
kids.clear();
for_each_child(&self.expressions, &self.statements, node, |c| kids.push(c));
for &child in &kids {
match child {
NodeRef::Expression(id) => self.expressions[id].parent = Some(node),
NodeRef::Statement(id) => self.statements[id].parent = Some(node),
}
stack.push(child);
}
}
debug_assert_eq!(visited, total, "ctree has nodes unreachable from the root");
Ctree {
expressions: self.expressions,
statements: self.statements,
types: self.types.into_table(),
locals: self.locals,
root,
}
}
}
#[bon::bon]
impl CtreeBuilder {
#[builder]
pub fn expression(
&mut self,
#[builder(start_fn)] ty: TypeId,
#[builder(start_fn)] kind: ExpressionKind,
address: Option<Address>,
) -> ExpressionId {
self.expressions.alloc(ExpressionNode {
address,
ty,
parent: None,
kind,
})
}
#[builder]
pub fn statement(
&mut self,
#[builder(start_fn)] kind: StatementKind,
address: Option<Address>,
) -> StatementId {
self.statements.alloc(StatementNode {
address,
parent: None,
kind,
})
}
}
impl Default for CtreeBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decompiler::ctree::node::{Local, LocalId, LocalLocation};
use crate::decompiler::ctree::ops::{AssignmentOp, BinaryOp};
use crate::types::TypeShape;
use assert2::assert;
fn int32() -> TypeValue {
TypeValue {
shape: TypeShape::Int {
bytes: 4,
signed: true,
},
size: Some(4),
}
}
fn local(name: &str, ty: TypeId, is_arg: bool) -> Local {
Local {
name: name.into(),
ty,
is_arg,
is_result: false,
is_byref: false,
width: 4,
comment: None,
location: LocalLocation::Register(0),
}
}
fn sample() -> (
Ctree,
StatementId,
StatementId,
ExpressionId,
ExpressionId,
ExpressionId,
) {
let mut b = CtreeBuilder::new();
let int = b.intern_type(int32());
let va = b.var(int, LocalId(0));
let vb = b.var(int, LocalId(1));
let add = b.binary(int, BinaryOp::Add, va, vb);
let ret = b.ret(Some(add));
let block = b.block(vec![ret]);
let tree = b.finish(block);
(tree, block, ret, add, va, vb)
}
#[test]
fn finish_wires_parent_links() {
let (tree, block, ret, add, va, vb) = sample();
assert!(tree.root() == block);
assert!(let None = tree.parent(NodeRef::Statement(block)));
assert!(tree.parent(NodeRef::Statement(ret)) == Some(NodeRef::Statement(block)));
assert!(tree.parent(NodeRef::Expression(add)) == Some(NodeRef::Statement(ret)));
assert!(tree.parent(NodeRef::Expression(va)) == Some(NodeRef::Expression(add)));
assert!(tree.parent(NodeRef::Expression(vb)) == Some(NodeRef::Expression(add)));
}
#[test]
fn descendants_are_pre_order() {
let (tree, block, ret, add, va, vb) = sample();
let walk: Vec<NodeRef> = tree.descendants(NodeRef::Statement(block)).collect();
assert!(
walk == vec![
NodeRef::Statement(block),
NodeRef::Statement(ret),
NodeRef::Expression(add),
NodeRef::Expression(va),
NodeRef::Expression(vb),
]
);
}
#[test]
fn children_of_a_leaf_are_empty() {
let (tree, _block, _ret, _add, va, _vb) = sample();
assert!(tree.children(NodeRef::Expression(va)).is_empty());
}
#[test]
fn expression_descendants_skips_statements() {
let (tree, block, _ret, add, va, vb) = sample();
let expressions: Vec<ExpressionId> = tree
.expression_descendants(NodeRef::Statement(block))
.collect();
assert!(expressions == vec![add, va, vb]);
}
#[test]
fn kind_resolves_handles_to_their_node_kind() {
let (tree, block, ret, add, va, _vb) = sample();
assert!(let ExpressionKind::Binary { .. } = tree.kind(add));
assert!(let ExpressionKind::Var(_) = tree.kind(va));
assert!(let StatementKind::Block(_) = tree.statement_kind(block));
assert!(let StatementKind::Return(_) = tree.statement_kind(ret));
}
#[test]
fn semantic_iterators_enumerate_their_kind() {
let mut b = CtreeBuilder::new();
let int = b.intern_type(int32());
let x = b.var(int, LocalId(0));
let a = b.var(int, LocalId(1));
let f = b.obj(int, Address::new_const(0x40), Some("f"));
let call = b.call_expression(int, f, vec![a]);
let asg = b.assign(int, AssignmentOp::Assign, x, call);
let st = b.expression_statement(asg);
let block = b.block(vec![st]);
let tree = b.finish(block);
let calls: Vec<_> = tree.calls().collect();
assert!(calls == vec![(call, f, [a].as_slice())]);
assert!(tree.assigns().collect::<Vec<_>>() == vec![(asg, AssignmentOp::Assign, x, call)]);
assert!(tree.vars().map(|(_, v)| v).collect::<Vec<_>>() == vec![LocalId(0), LocalId(1)]);
}
#[test]
fn flat_iteration_covers_every_node() {
let (tree, _block, _ret, _add, _va, _vb) = sample();
assert!(tree.expressions().count() == 3);
assert!(tree.statements().count() == 2);
assert!(tree.types().count() == 1);
let binaries = tree
.expressions()
.filter(|(_, e)| matches!(e.kind, ExpressionKind::Binary { .. }))
.count();
assert!(binaries == 1);
}
#[test]
fn flat_queries_find_nodes_by_address() {
let mut b = CtreeBuilder::new();
let int = b.intern_type(int32());
let a0 = Address::new_const(0x1000);
let a1 = Address::new_const(0x1004);
let v = b
.expression(int, ExpressionKind::Var(LocalId(0)))
.address(a0)
.call();
let s0 = b.statement(StatementKind::Expression(v)).address(a0).call();
let s1 = b.statement(StatementKind::Return(None)).address(a1).call();
let block = b.block(vec![s0, s1]);
let tree = b.finish(block);
assert!(tree.expression_at(a0) == Some(v));
assert!(tree.statement_at(a0) == Some(s0));
assert!(tree.statement_at(a1) == Some(s1));
assert!(tree.expression_at(a1).is_none());
assert!(tree.statement_at(Address::new_const(0x2000)).is_none());
assert!(
tree.items_at(a0).collect::<Vec<_>>()
== vec![NodeRef::Expression(v), NodeRef::Statement(s0)]
);
assert!(tree.items_at(a1).collect::<Vec<_>>() == vec![NodeRef::Statement(s1)]);
assert!(tree.items_at(Address::new_const(0x2000)).next().is_none());
}
#[test]
fn fill_type_writes_the_placeholder_type_size_reads() {
let mut b = CtreeBuilder::new();
let id = b.alloc_type_placeholder();
assert!(let None = b.type_size(id));
b.fill_type(id, int32());
assert!(b.type_size(id) == Some(4));
let v = b.var(id, LocalId(0));
let st = b.expression_statement(v);
let block = b.block(vec![st]);
let tree = b.finish(block);
assert!(tree.type_of(id).shape == int32().shape);
}
#[test]
fn expression_carries_its_resolved_type() {
let (tree, _block, _ret, add, _va, _vb) = sample();
let ty = tree.expression(add).ty;
assert!(
tree.type_of(ty).shape
== TypeShape::Int {
bytes: 4,
signed: true
}
);
}
#[test]
fn this_local_is_the_first_argument() {
let mut b = CtreeBuilder::new();
let int = b.intern_type(int32());
b.push_local(local("local", int, false));
let this = b.push_local(local("this", int, true));
b.push_local(local("arg2", int, true));
let v = b.var(int, this);
let st = b.expression_statement(v);
let block = b.block(vec![st]);
let tree = b.finish(block);
assert!(tree.this_local() == Some(this));
}
#[test]
fn this_local_is_none_without_arguments() {
let mut b = CtreeBuilder::new();
let int = b.intern_type(int32());
b.push_local(local("local", int, false));
let block = b.block(vec![]);
let tree = b.finish(block);
assert!(let None = tree.this_local());
}
#[test]
fn ctree_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Ctree>();
}
#[test]
fn ctree_clone_and_partial_eq() {
let (tree, ..) = sample();
let (other, ..) = sample();
assert!(tree.clone() == tree);
assert!(tree == other);
}
#[test]
fn ctree_display_matches_to_pseudocode() {
let (tree, ..) = sample();
assert!(tree.to_string() == tree.to_pseudocode());
}
#[test]
fn ctree_serde_round_trips() {
let (tree, ..) = sample();
let json = serde_json::to_string(&tree).unwrap();
let back: Ctree = serde_json::from_str(&json).unwrap();
assert!(back == tree);
}
#[test]
fn descendants_debug_does_not_panic() {
let (tree, block, ..) = sample();
let descendants = tree.descendants(NodeRef::Statement(block));
let rendered = format!("{descendants:?}");
assert!(rendered.contains("Descendants"));
}
mod proptests {
use proptest::prelude::*;
use strum::VariantArray;
use super::*;
#[derive(Debug, Clone)]
enum ExprSpec {
Num(u64),
Var(u32),
Unary(UnaryOp, Box<Self>),
Binary(BinaryOp, Box<Self>, Box<Self>),
Cast(Box<Self>),
Call(Box<Self>, Vec<Self>),
}
fn expr_spec() -> impl Strategy<Value = ExprSpec> {
let leaf = prop_oneof![
any::<u64>().prop_map(ExprSpec::Num),
(0u32..4).prop_map(ExprSpec::Var),
];
leaf.prop_recursive(4, 32, 4, |inner| {
prop_oneof![
(
prop::sample::select(UnaryOp::VARIANTS.to_vec()),
inner.clone()
)
.prop_map(|(op, x)| ExprSpec::Unary(op, Box::new(x))),
(
prop::sample::select(BinaryOp::VARIANTS.to_vec()),
inner.clone(),
inner.clone(),
)
.prop_map(|(op, x, y)| ExprSpec::Binary(
op,
Box::new(x),
Box::new(y)
)),
inner.clone().prop_map(|x| ExprSpec::Cast(Box::new(x))),
(inner.clone(), prop::collection::vec(inner, 0..3))
.prop_map(|(callee, args)| ExprSpec::Call(Box::new(callee), args)),
]
})
}
fn stmt_spec() -> impl Strategy<Value = Vec<ExprSpec>> {
prop::collection::vec(expr_spec(), 1..6)
}
fn build_expr(b: &mut CtreeBuilder, ty: TypeId, spec: &ExprSpec) -> ExpressionId {
match spec {
ExprSpec::Num(v) => b.num(ty, *v),
ExprSpec::Var(i) => b.var(ty, LocalId(*i)),
ExprSpec::Unary(op, x) => {
let x = build_expr(b, ty, x);
b.unary(ty, *op, x)
}
ExprSpec::Binary(op, x, y) => {
let x = build_expr(b, ty, x);
let y = build_expr(b, ty, y);
b.binary(ty, *op, x, y)
}
ExprSpec::Cast(x) => {
let x = build_expr(b, ty, x);
b.cast(ty, x)
}
ExprSpec::Call(callee, args) => {
let callee = build_expr(b, ty, callee);
let args = args.iter().map(|a| build_expr(b, ty, a)).collect();
b.call_expression(ty, callee, args)
}
}
}
fn build_tree(specs: &[ExprSpec]) -> Ctree {
let mut b = CtreeBuilder::new();
let ty = b.intern_type(int32());
let statements = specs
.iter()
.map(|spec| {
let e = build_expr(&mut b, ty, spec);
b.expression_statement(e)
})
.collect();
let block = b.block(statements);
b.finish(block)
}
proptest! {
#[test]
fn parent_links_mirror_children(specs in stmt_spec()) {
let tree = build_tree(&specs);
let root = NodeRef::Statement(tree.root());
prop_assert_eq!(tree.parent(root), None);
for node in tree.descendants(root) {
for child in tree.children(node) {
prop_assert_eq!(
tree.parent(child),
Some(node),
"child {:?} of {:?} does not point back",
child,
node
);
}
}
}
#[test]
fn for_each_child_matches_children(specs in stmt_spec()) {
let tree = build_tree(&specs);
let root = NodeRef::Statement(tree.root());
for node in tree.descendants(root) {
let mut via_callback = Vec::new();
tree.children_for_each(node, |c| via_callback.push(c));
prop_assert_eq!(via_callback, tree.children(node));
}
}
#[test]
fn descendants_visit_every_node_exactly_once(specs in stmt_spec()) {
let tree = build_tree(&specs);
let root = NodeRef::Statement(tree.root());
let visited: Vec<NodeRef> = tree.descendants(root).collect();
let total = tree.expressions().count() + tree.statements().count();
prop_assert_eq!(
visited.len(),
total,
"traversal should terminate having seen every allocated node"
);
let mut seen = std::collections::HashSet::new();
for node in &visited {
prop_assert!(seen.insert(*node), "node {:?} visited twice", node);
}
}
#[test]
fn handles_stay_within_their_arena(specs in stmt_spec()) {
let tree = build_tree(&specs);
let n_expr = tree.expressions().count();
let n_stmt = tree.statements().count();
for (id, _) in tree.expressions() {
prop_assert!(id.index() < n_expr);
}
for (id, _) in tree.statements() {
prop_assert!(id.index() < n_stmt);
}
}
}
}
}