#!/usr/bin/env python3 # pylint: disable=missing-class-docstring,no-self-use,no-member,protected-access from __future__ import annotations __package__ = __package__ or "tests.analyses.decompiler" # pylint:disable=redefined-builtin import unittest import angr from angr.ailment import Expr class TestConvertRendering(unittest.TestCase): """How CStructuredCodeGenerator renders Convert expressions of assorted widths.""" @classmethod def setUpClass(cls): # any decompilation will do; all we need is a codegen instance to render expressions with proj = angr.load_shellcode(b"\x31\xc0\xc3", arch="AMD64") # xor eax, eax; ret cfg = proj.analyses.CFGFast(normalize=True) cls.codegen = proj.analyses.Decompiler(cfg.functions[0], cfg=cfg).codegen def _render(self, from_bits: int, to_bits: int, value: int = 0x1234) -> str: conv = Expr.Convert(0, from_bits, to_bits, False, Expr.Const(0, value, from_bits)) return self.codegen._handle(conv).c_repr() def test_truncation_to_unrepresentable_width_is_masked(self): # No C type is 5, 3 or 1 bits wide. A cast would round up to the next real type and keep # bits the conversion discards, so these have to be spelled as a mask instead. assert self._render(32, 5) == "4660 & 31" assert self._render(32, 3) == "4660 & 7" assert self._render(32, 1) == "4660 & 1" def test_truncation_to_representable_width_is_a_cast(self): assert self._render(32, 8) == "(char)4660" assert self._render(32, 16) == "(unsigned short)4660" def test_widening_is_always_a_cast(self): # Rounding up only loses information when truncating, so widening keeps casting even to a # width no C type has. assert self._render(1, 5, value=1) == "(char)1" assert self._render(8, 12, value=3) == "(unsigned short)3" assert self._render(32, 64, value=3) == "(unsigned long long)3" if __name__ == "__main__": unittest.main()