133 lines
4.1 KiB
Python
133 lines
4.1 KiB
Python
import unittest
|
|
|
|
from h8536.pseudocode import PseudocodeOptions, generate_pseudocode
|
|
|
|
|
|
def _instruction(
|
|
address,
|
|
mnemonic,
|
|
operands="",
|
|
*,
|
|
kind="normal",
|
|
targets=None,
|
|
text=None,
|
|
):
|
|
return {
|
|
"address": address,
|
|
"text": text or f"{mnemonic} {operands}".strip(),
|
|
"mnemonic": mnemonic,
|
|
"operands": operands,
|
|
"kind": kind,
|
|
"targets": list(targets or []),
|
|
"references": [],
|
|
"comment": "",
|
|
}
|
|
|
|
|
|
def _payload(instructions):
|
|
start = min(ins["address"] for ins in instructions)
|
|
end = max(ins["address"] for ins in instructions)
|
|
return {
|
|
"vectors": [],
|
|
"call_graph": {
|
|
"nodes": [
|
|
{
|
|
"start": start,
|
|
"end": end,
|
|
"label": f"loc_{start:04X}",
|
|
"sources": [],
|
|
"instruction_count": len(instructions),
|
|
"calls": [],
|
|
}
|
|
],
|
|
"edges": [],
|
|
},
|
|
"instructions": instructions,
|
|
}
|
|
|
|
|
|
def _options(**overrides):
|
|
values = {
|
|
"include_asm": False,
|
|
"include_addresses": False,
|
|
"emit_declarations": False,
|
|
}
|
|
values.update(overrides)
|
|
return PseudocodeOptions(**values)
|
|
|
|
|
|
class PseudocodeStructuringTest(unittest.TestCase):
|
|
def test_backward_conditional_branch_becomes_do_while(self):
|
|
payload = _payload(
|
|
[
|
|
_instruction(0x0100, "MOV.B", "#H'00, R0"),
|
|
_instruction(0x0102, "ADD.B", "#H'01, R0"),
|
|
_instruction(0x0104, "CMP.B", "#H'03, R0"),
|
|
_instruction(0x0106, "BNE", "loc_0102", kind="branch", targets=[0x0102]),
|
|
_instruction(0x0108, "RTS", kind="return"),
|
|
]
|
|
)
|
|
|
|
text = generate_pseudocode(payload, options=_options())
|
|
|
|
self.assertIn("do {", text)
|
|
self.assertIn("} while (!Z);", text)
|
|
self.assertNotIn("goto loc_0102;", text)
|
|
self.assertNotIn("loc_0102:", text)
|
|
|
|
def test_forward_conditional_branch_over_small_span_becomes_if(self):
|
|
payload = _payload(
|
|
[
|
|
_instruction(0x0100, "CMP.B", "#H'00, R0"),
|
|
_instruction(0x0102, "BEQ", "loc_0108", kind="branch", targets=[0x0108]),
|
|
_instruction(0x0104, "MOV.B", "#H'01, R1"),
|
|
_instruction(0x0106, "ADD.B", "#H'02, R1"),
|
|
_instruction(0x0108, "RTS", kind="return"),
|
|
]
|
|
)
|
|
|
|
text = generate_pseudocode(payload, options=_options())
|
|
|
|
self.assertIn("if (!Z) {", text)
|
|
self.assertIn("R1 = (uint8_t)(0x01);", text)
|
|
self.assertIn("R1 += (uint8_t)(0x02);", text)
|
|
self.assertNotIn("goto loc_0108;", text)
|
|
self.assertNotIn("loc_0108:", text)
|
|
|
|
def test_structuring_can_be_disabled(self):
|
|
payload = _payload(
|
|
[
|
|
_instruction(0x0100, "CMP.B", "#H'00, R0"),
|
|
_instruction(0x0102, "BEQ", "loc_0108", kind="branch", targets=[0x0108]),
|
|
_instruction(0x0104, "MOV.B", "#H'01, R1"),
|
|
_instruction(0x0108, "RTS", kind="return"),
|
|
]
|
|
)
|
|
|
|
text = generate_pseudocode(payload, options=_options(structured=False))
|
|
|
|
self.assertIn("if (Z) goto loc_0108;", text)
|
|
self.assertIn("loc_0108:", text)
|
|
self.assertNotIn("if (!Z) {", text)
|
|
|
|
def test_ambiguous_forward_branch_keeps_goto_fallback(self):
|
|
payload = _payload(
|
|
[
|
|
_instruction(0x0100, "BEQ", "loc_0108", kind="branch", targets=[0x0108]),
|
|
_instruction(0x0102, "MOV.B", "#H'01, R1"),
|
|
_instruction(0x0104, "BRA", "loc_0108", kind="jump", targets=[0x0108]),
|
|
_instruction(0x0108, "RTS", kind="return"),
|
|
]
|
|
)
|
|
|
|
text = generate_pseudocode(payload, options=_options())
|
|
|
|
self.assertIn("if (Z) goto loc_0108;", text)
|
|
self.assertIn("goto loc_0108;", text)
|
|
self.assertIn("loc_0108:", text)
|
|
self.assertNotIn("if (!Z) {", text)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|