angr/tests/analyses/decompiler/test_rust_pattern_match_simplifier.py
Fish 9235f7fd27
Migrate AIL classes to Rust (#5967)
This PR migrates AIL classes (Block, Statement, Expression, etc.) to Rust. Key changes include:

- Block, Statement, and Expression are native Rust objects. Every access to their properties will create a new Python class object. Therefore, `expr.dst is expr.dst` will always evaluate to False. You'll want to do `expr.dst == expr.dst` instead.

- However, keep in mind that `expr_0 == expr_1` can be expensive because equality checks may potentially go over two expression trees and compare every single node. In this case, you may want to resort to `expr_0.idx == expr_1.idx`. There are still a few places where `idx` is reused; we plan to remove all such cases and guarantee the uniqueness of `idx` for all Statements and Expressions during a single decompilation run.

- `type(expr) is Const` no longer works. You must use `isinstance(expr, Const)` instead. `isinstance(..., ExpressionCls/StatementCls)` is also more expensive than before due to the use of custom meta classes.

- New changes to AIL requires rebuilding the Rust component to land. You can do `python setup.py build_rust --inplace --release` to rebuild the angr Rust component in-place in an editable install.

---------

Co-authored-by: Kevin Phoenix <kevin@kphoenix.us>
2026-07-08 02:10:07 -07:00

119 lines
4.3 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import unittest
from angr.ailment.expression import BinaryOp, Const, Convert, UnaryOp, VirtualVariable, VirtualVariableCategory
from angr.rust.optimization_passes.pre_pattern_match_simplifier import PrePatternMatchSimplifier
from angr.rust.sim_type import RustSimTypeInt, RustSimTypeResult
class TestRustPatternMatchSimplifier(unittest.TestCase):
"""Tests for Rust pattern match simplification."""
def test_extracts_sign_bit_niche_discriminant(self):
vvar = VirtualVariable(None, 0, 64, VirtualVariableCategory.STACK, -0x20)
cast = Convert(None, 64, 64, True, vvar)
zero = Const(None, 0, 64)
condition = Convert(
None,
64,
8,
False,
BinaryOp(
None,
"Shr",
[
BinaryOp(
None,
"And",
[
BinaryOp(None, "Xor", [zero, cast], bits=64),
BinaryOp(None, "Xor", [zero, UnaryOp(None, "Neg", cast)], bits=64),
],
bits=64,
),
Const(None, 63, 8),
],
bits=64,
),
)
scrutinee, discriminant, cmp_op, leftover = PrePatternMatchSimplifier.extract_scrutinee_and_discriminant(
condition
)
self.assertTrue(scrutinee is not None and scrutinee.likes(vvar))
self.assertEqual(discriminant, -(1 << 63))
self.assertEqual(cmp_op, "CmpEQ")
self.assertIsNone(leftover)
def test_extracts_truthiness_discriminant(self):
# `Conv(N -> 1, disc)`, rendered `if result as i8`, means `disc != 0`.
vvar = VirtualVariable(None, 0, 64, VirtualVariableCategory.STACK, -0x20)
condition = Convert(None, 64, 1, False, vvar)
scrutinee, discriminant, cmp_op, leftover = PrePatternMatchSimplifier.extract_scrutinee_and_discriminant(
condition
)
self.assertEqual(scrutinee, vvar)
self.assertEqual(discriminant, 0)
self.assertEqual(cmp_op, "CmpNE")
self.assertIsNone(leftover)
def test_extracts_negated_truthiness_discriminant(self):
# `Not(Conv(N -> 1, disc))`, rendered `if !(result as i8)`, means `disc == 0`.
vvar = VirtualVariable(None, 0, 64, VirtualVariableCategory.STACK, -0x20)
condition = UnaryOp(None, "Not", Convert(None, 64, 1, False, vvar))
scrutinee, discriminant, cmp_op, leftover = PrePatternMatchSimplifier.extract_scrutinee_and_discriminant(
condition
)
self.assertEqual(scrutinee, vvar)
self.assertEqual(discriminant, 0)
self.assertEqual(cmp_op, "CmpEQ")
self.assertIsNone(leftover)
def test_truthiness_discriminant_preserves_logical_and_leftover(self):
vvar = VirtualVariable(None, 0, 64, VirtualVariableCategory.STACK, -0x20)
rest = Const(None, 1, 8)
condition = BinaryOp(None, "LogicalAnd", [Convert(None, 64, 1, False, vvar), rest], bits=8)
scrutinee, discriminant, cmp_op, leftover = PrePatternMatchSimplifier.extract_scrutinee_and_discriminant(
condition
)
self.assertEqual(scrutinee, vvar)
self.assertEqual(discriminant, 0)
self.assertEqual(cmp_op, "CmpNE")
self.assertEqual(leftover, rest)
def test_non_boolean_conversion_is_not_a_discriminant(self):
# A wider truncation (`to_bits != 1`) is an ordinary cast, not a truthiness test.
vvar = VirtualVariable(None, 0, 64, VirtualVariableCategory.STACK, -0x20)
condition = Convert(None, 64, 8, False, vvar)
result = PrePatternMatchSimplifier.extract_scrutinee_and_discriminant(condition)
self.assertEqual(result, (None, None, None, None))
def test_enum_variant_lookup_matches_signed_and_unsigned_discriminants(self):
enum_ty = RustSimTypeResult(
RustSimTypeInt(64, signed=False),
None,
0,
RustSimTypeInt(16, signed=False),
-(1 << 63),
8,
)
variant = enum_ty.get_variant(1 << 63)
self.assertIsNotNone(variant)
assert variant is not None
self.assertEqual(variant.name, "Err")
if __name__ == "__main__":
unittest.main()