#include #include "fold/ir/binary_expr.hpp" namespace fold::ir { namespace { TEST_CASE("BinaryExprKind round-trips through string conversion", "[binary_expr]") { struct Case { BinaryExprKind kind; std::string_view expected; }; const std::vector cases = { {BinaryExprKind::HardcodedString, "HardcodedString"}, {BinaryExprKind::ImportedSymbol, "ImportedSymbol"}, {BinaryExprKind::SectionData, "SectionData"}, {BinaryExprKind::BasicBlock, "BasicBlock"}, {BinaryExprKind::CallSite, "CallSite"}, {BinaryExprKind::IndirectCall, "IndirectCall"}, {BinaryExprKind::StringXref, "StringXref"}, {BinaryExprKind::DataXref, "DataXref"}, {BinaryExprKind::CodeXref, "CodeXref"}, {BinaryExprKind::ExportEntry, "ExportEntry"}, {BinaryExprKind::VtableEntry, "VtableEntry"}, {BinaryExprKind::SwitchTable, "SwitchTable"}, }; for (const auto& tc : cases) { INFO("kind string: " << tc.expected); const auto str = binaryKindToString(tc.kind); REQUIRE(str == tc.expected); const auto parsed = binaryKindFromString(str); REQUIRE(parsed == tc.kind); } } TEST_CASE("BinaryExprKind unknown round-trips", "[binary_expr]") { REQUIRE(binaryKindToString(BinaryExprKind::Unknown) == "Unknown"); REQUIRE(binaryKindFromString("Unknown") == BinaryExprKind::Unknown); REQUIRE(binaryKindFromString("InvalidKindName") == BinaryExprKind::Unknown); } TEST_CASE("BinaryExprNode default is Unknown with zero fields", "[binary_expr]") { BinaryExprNode node; REQUIRE(node.kind == BinaryExprKind::Unknown); REQUIRE(node.address == 0); REQUIRE(node.size == 0); REQUIRE(node.label.empty()); REQUIRE(node.symbol_name.empty()); REQUIRE(node.section_name.empty()); REQUIRE(node.string_value.empty()); REQUIRE(node.xref_target == 0); REQUIRE(node.xref_source == 0); REQUIRE(node.call_target.empty()); REQUIRE(node.call_args.empty()); REQUIRE(node.method_name.empty()); REQUIRE(node.method_addr == 0); REQUIRE(node.slot_index == -1); REQUIRE(node.switch_entries.empty()); } TEST_CASE("BinaryExprNode equality", "[binary_expr]") { BinaryExprNode a; a.kind = BinaryExprKind::HardcodedString; a.address = 0x1000; a.size = 16; a.label = "test_string"; a.string_value = "AKIA1234567890ABCD"; BinaryExprNode b = a; REQUIRE(a == b); b.address = 0x2000; REQUIRE(a != b); } TEST_CASE("BinaryIRTree empty", "[binary_expr]") { BinaryIRTree tree; REQUIRE(tree.empty()); REQUIRE(tree.size() == 0); REQUIRE(toJson(tree).is_array()); REQUIRE(toJson(tree).empty()); } TEST_CASE("BinaryIRTree fromJson of empty array", "[binary_expr]") { const auto j = nlohmann::json::array(); const auto tree = binaryIRTreeFromJson(j); REQUIRE(tree.empty()); } TEST_CASE("BinaryIRTree fromJson ignores non-array input", "[binary_expr]") { const auto j = nlohmann::json::object(); const auto tree = binaryIRTreeFromJson(j); REQUIRE(tree.empty()); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — HardcodedString", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::HardcodedString; node.address = 0x4000; node.size = 32; node.label = "secret_token"; node.string_value = "sk_live_abc123def456"; const auto j = toJson(node); REQUIRE(j.at("kind") == "HardcodedString"); REQUIRE(j.at("address") == 0x4000); REQUIRE(j.at("size") == 32); REQUIRE(j.at("label") == "secret_token"); REQUIRE(j.at("string_value") == "sk_live_abc123def456"); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — ImportedSymbol", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::ImportedSymbol; node.address = 0x2000; node.size = 8; node.label = "system"; node.symbol_name = "system@GLIBC_2.2.5"; const auto j = toJson(node); REQUIRE(j.at("kind") == "ImportedSymbol"); REQUIRE(j.at("address") == 0x2000); REQUIRE(j.at("label") == "system"); REQUIRE(j.at("symbol_name") == "system@GLIBC_2.2.5"); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — ExportEntry", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::ExportEntry; node.address = 0x5000; node.size = 128; node.label = "main"; node.symbol_name = "main"; const auto j = toJson(node); REQUIRE(j.at("kind") == "ExportEntry"); REQUIRE(j.at("symbol_name") == "main"); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — SectionData", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::SectionData; node.address = 0x1000; node.size = 4096; node.label = ".text"; node.section_name = ".text"; const auto j = toJson(node); REQUIRE(j.at("kind") == "SectionData"); REQUIRE(j.at("section_name") == ".text"); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — BasicBlock", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::BasicBlock; node.address = 0x4010; node.size = 24; node.label = "bb_0x4010"; const auto j = toJson(node); REQUIRE(j.at("kind") == "BasicBlock"); REQUIRE(j.at("address") == 0x4010); REQUIRE(j.at("size") == 24); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — CallSite", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::CallSite; node.address = 0x4020; node.size = 4; node.label = "call puts"; node.call_target = "puts"; node.call_args = {0x6000}; const auto j = toJson(node); REQUIRE(j.at("kind") == "CallSite"); REQUIRE(j.at("call_target") == "puts"); REQUIRE(j.at("call_args").is_array()); REQUIRE(j.at("call_args").size() == 1); REQUIRE(j.at("call_args")[0] == 0x6000); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — IndirectCall", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::IndirectCall; node.address = 0x4030; node.size = 4; node.label = "blr x8"; node.call_target = ""; // unresolved node.call_args = {0x6000, 0x6008, 0x6010}; const auto j = toJson(node); REQUIRE(j.at("kind") == "IndirectCall"); REQUIRE(j.at("call_args").size() == 3); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — StringXref", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::StringXref; node.address = 0x4040; node.label = "xref -> secret"; node.xref_target = 0x7000; // address of the string node.xref_source = 0x4040; // address of the instruction doing the xref const auto j = toJson(node); REQUIRE(j.at("kind") == "StringXref"); REQUIRE(j.at("xref_target") == 0x7000); REQUIRE(j.at("xref_source") == 0x4040); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — DataXref", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::DataXref; node.address = 0x4050; node.xref_target = 0x8000; node.xref_source = 0x4050; const auto j = toJson(node); REQUIRE(j.at("kind") == "DataXref"); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — CodeXref", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::CodeXref; node.address = 0x4060; node.xref_target = 0x9000; node.xref_source = 0x4060; const auto j = toJson(node); REQUIRE(j.at("kind") == "CodeXref"); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — VtableEntry", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::VtableEntry; node.address = 0xA000; node.size = 8; node.label = "vtable[3] MyClass::foo"; node.method_name = "MyClass::foo"; node.method_addr = 0x5100; node.slot_index = 3; const auto j = toJson(node); REQUIRE(j.at("kind") == "VtableEntry"); REQUIRE(j.at("method_name") == "MyClass::foo"); REQUIRE(j.at("method_addr") == 0x5100); REQUIRE(j.at("slot_index") == 3); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryExprNode toJson/fromJson round-trip — SwitchTable", "[binary_expr]") { BinaryExprNode node; node.kind = BinaryExprKind::SwitchTable; node.address = 0xB000; node.size = 64; node.label = "switch_table"; node.switch_entries = {0x5200, 0x5300, 0x5400, 0x5500}; const auto j = toJson(node); REQUIRE(j.at("kind") == "SwitchTable"); REQUIRE(j.at("switch_entries").is_array()); REQUIRE(j.at("switch_entries").size() == 4); const auto parsed = fromJson(j); REQUIRE(parsed == node); } TEST_CASE("BinaryIRTree round-trip through JSON", "[binary_expr]") { BinaryIRTree tree; { BinaryExprNode n1; n1.kind = BinaryExprKind::HardcodedString; n1.address = 0x4000; n1.size = 10; n1.label = "str1"; n1.string_value = "password123"; tree.nodes.push_back(n1); } { BinaryExprNode n2; n2.kind = BinaryExprKind::CallSite; n2.address = 0x4020; n2.size = 4; n2.label = "call"; n2.call_target = "strcpy"; n2.call_args = {0x6000, 0x4000}; tree.nodes.push_back(n2); } { BinaryExprNode n3; n3.kind = BinaryExprKind::VtableEntry; n3.address = 0xA000; n3.size = 8; n3.method_name = "Base::vfunc"; n3.method_addr = 0x5100; n3.slot_index = 0; tree.nodes.push_back(n3); } REQUIRE(tree.size() == 3); const auto j = toJson(tree); REQUIRE(j.is_array()); REQUIRE(j.size() == 3); const auto parsed = binaryIRTreeFromJson(j); REQUIRE(parsed.size() == 3); REQUIRE(parsed.nodes[0] == tree.nodes[0]); REQUIRE(parsed.nodes[1] == tree.nodes[1]); REQUIRE(parsed.nodes[2] == tree.nodes[2]); } TEST_CASE("BinaryIRTree round-trip handles empty nodes mixed in", "[binary_expr]") { BinaryIRTree tree; { BinaryExprNode n; n.kind = BinaryExprKind::SectionData; n.address = 0x1000; n.size = 4096; n.section_name = ".rodata"; tree.nodes.push_back(n); } // A node with all zero/default fields should round-trip as Unknown. tree.nodes.push_back(BinaryExprNode{}); const auto j = toJson(tree); REQUIRE(j.size() == 2); const auto parsed = binaryIRTreeFromJson(j); REQUIRE(parsed.size() == 2); REQUIRE(parsed.nodes[0] == tree.nodes[0]); REQUIRE(parsed.nodes[1] == tree.nodes[1]); } TEST_CASE("json_keys constants are valid for serialization", "[binary_expr]") { using namespace binary_json_keys; BinaryExprNode node; node.kind = BinaryExprKind::SectionData; node.address = 0x1000; node.size = 4096; node.section_name = ".text"; const auto j = toJson(node); // Verify all the canonical keys exist. REQUIRE(j.contains(kKind)); REQUIRE(j.contains(kAddress)); REQUIRE(j.contains(kSize)); REQUIRE(j.contains(kSectionName)); // Verify that using these constants for access works. REQUIRE(j.at(kKind) == "SectionData"); REQUIRE(j.at(kAddress) == 0x1000); REQUIRE(j.at(kSize) == 4096); REQUIRE(j.at(kSectionName) == ".text"); } TEST_CASE("BinaryExprNode fromJson gracefully handles missing fields", "[binary_expr]") { // Missing kind auto j = nlohmann::json::object(); auto parsed = fromJson(j); REQUIRE(parsed.kind == BinaryExprKind::Unknown); REQUIRE(parsed.address == 0); REQUIRE(parsed.size == 0); // Only kind provided, no payload j = nlohmann::json{{"kind", "BasicBlock"}}; parsed = fromJson(j); REQUIRE(parsed.kind == BinaryExprKind::BasicBlock); REQUIRE(parsed.address == 0); // Kind + address + size, no label j = nlohmann::json{{"kind", "BasicBlock"}, {"address", 0x4200}, {"size", 64}}; parsed = fromJson(j); REQUIRE(parsed.kind == BinaryExprKind::BasicBlock); REQUIRE(parsed.address == 0x4200); REQUIRE(parsed.size == 64); REQUIRE(parsed.label.empty()); } TEST_CASE("BinaryExprNode fromJson handles invalid kind gracefully", "[binary_expr]") { const auto j = nlohmann::json{{"kind", "NonExistentKind"}, {"address", 0x1234}}; const auto parsed = fromJson(j); REQUIRE(parsed.kind == BinaryExprKind::Unknown); REQUIRE(parsed.address == 0x1234); } } // namespace } // namespace fold::ir