Jakob Nicolaus Foerster

55 papers A* 27Journal 28
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Karen Hambardzumyan, Nicolas Mario Baldwin, Edan Toledo, Rishi Hazra, Michael Kuchnik, Bassel Al Omari, Thomas Foster, Anton Protopopov, Jean-Christophe Gagnon-Audet, Ishita Mediratta, Kelvin Niu, Michael Shvartsman, Alisia Maria Lupidi, Alexis Audran-Reiss, Parth Pathak, Tatiana Shavrina, Despoina Magka, Hela Momand, Derek Dunfield, Nicola Cancedda, Pontus Stenetorp, Carole-Jean Wu, Jakob Nicolaus Foerster, Yoram Bachrach, Martin Josifoski
2026 J jnl
CoRR
Hengyuan Hu, Tingchen Fu, Minqi Jiang, Alexander H. Miller, Yoram Bachrach, Jakob Nicolaus Foerster
2026 J jnl
CoRR
Zilin Wang, Saeed Rahmani, Daphne Cornelisse, Bidipta Sarkar, Alexander David Goldie, Jakob Nicolaus Foerster, Shimon Whiteson
2025 J jnl
CoRR
Matthew Thomas Jackson, Uljad Berdica, Jarek Liesen, Shimon Whiteson, Jakob Nicolaus Foerster
2025 A* conf
ICML
Sebastian Rene Towers, Aleksandra Kalisz, Philippe A. Robert, Alicia Higueruelo, Francesca V. Vianello, Ming-Han Chloe Tsai, Harrison Steel, Jakob Nicolaus Foerster
2025 J jnl
CoRR
Edan Toledo, Karen Hambardzumyan, Martin Josifoski, Rishi Hazra, Nicolas Mario Baldwin, Alexis Audran-Reiss, Michael Kuchnik, Despoina Magka, Minqi Jiang, Alisia Maria Lupidi, Andrei Lupu, Roberta Raileanu, Kelvin Niu, Tatiana Shavrina, Jean-Christophe Gagnon-Audet, Michael Shvartsman, Shagun Sodhani, Alexander H. Miller, Abhishek Charnalia, Derek Dunfield, Carole-Jean Wu, Pontus Stenetorp, Nicola Cancedda, Jakob Nicolaus Foerster, Yoram Bachrach
2025 A* conf
ICML
Tin Dizdarevic, Ravi Hammond, Tobias Gessler, Anisoara Calinescu, Jonathan Cook, Matteo Gallici, Andrei Lupu, Jakob Nicolaus Foerster
2025 J jnl
CoRR
Tin Dizdarevic, Ravi Hammond, Tobias Gessler, Anisoara Calinescu, Jonathan Cook, Matteo Gallici, Andrei Lupu, Darius Muglich, Johannes Forkel, Jakob Nicolaus Foerster
2025 J jnl
CoRR
J. Rosser, Jakob Nicolaus Foerster
2025 A* conf
ICLR
Davide Paglieri, Bartlomiej Cupial, Samuel Coward, Ulyana Piterbarg, Maciej Wolczyk, Akbir Khan, Eduardo Pignatelli, Lukasz Kucinski, Lerrel Pinto, Rob Fergus, Jakob Nicolaus Foerster, Jack Parker-Holder, Tim Rocktäschel
2025 A* conf
EMNLP
Qizhen Zhang, Prajjwal Bhargava, Chloe Bi, Chris X. Cai, Jakob Nicolaus Foerster, Jeremy Fu, Punit Singh Koura, Ruan Silva, Sheng Shen, Emily Dinan, Suchin Gururangan, Mike Lewis
2025 J jnl
CoRR
Bidipta Sarkar, Mattie Fellows, Juan Agustin Duque, Alistair Letcher, Antonio León Villares, Anya Sims, Dylan Cope, Jarek Liesen, Lukas Seier, Theo Wolf, Uljad Berdica, Alexander David Goldie, Aaron C. Courville, Karin Sevegnani, Shimon Whiteson, Jakob Nicolaus Foerster
2025 A* conf
ICLR
Darius Muglich, Johannes Forkel, Elise van der Pol, Jakob Nicolaus Foerster
2025 J jnl
CoRR
Alexander David Goldie, Zilin Wang, Jakob Nicolaus Foerster, Shimon Whiteson
2025 J jnl
CoRR
Ahmet Hamdi Güzel, Matthew Thomas Jackson, Jarek Liesen, Tim Rocktäschel, Jakob Nicolaus Foerster, Ilija Bogunovic, Jack Parker-Holder
2025 A* conf
ICLR
Michael T. Matthews, Michael Beukman, Chris Lu, Jakob Nicolaus Foerster
2025 A* conf
ICML
Peer Nagy, Sascha Yves Frey, Kang Li, Bidipta Sarkar, Svitlana Vyetrenko, Stefan Zohren, Ani Calinescu, Jakob Nicolaus Foerster
2025 J jnl
CoRR
Davide Paglieri, Bartlomiej Cupial, Jonathan Cook, Ulyana Piterbarg, Jens Tuyls, Edward Grefenstette, Jakob Nicolaus Foerster, Jack Parker-Holder, Tim Rocktäschel
2025 J jnl
CoRR
Bassel Al Omari, Michael T. Matthews, Alexander Rutherford, Jakob Nicolaus Foerster
2025 A* conf
ICLR
Tobias Gessler, Tin Dizdarevic, Ani Calinescu, Benjamin Ellis, Andrei Lupu, Jakob Nicolaus Foerster
2025 J jnl
CoRR
Tobias Gessler, Tin Dizdarevic, Ani Calinescu, Benjamin Ellis, Andrei Lupu, Jakob Nicolaus Foerster
2025 A* conf
ICLR
Matteo Gallici, Mattie Fellows, Benjamin Ellis, Bartomeu Pou, Ivan Masmitja, Jakob Nicolaus Foerster, Mario Martin
2024 J jnl
CoRR
Davide Paglieri, Bartlomiej Cupial, Samuel Coward, Ulyana Piterbarg, Maciej Wolczyk, Akbir Khan, Eduardo Pignatelli, Lukasz Kucinski, Lerrel Pinto, Rob Fergus, Jakob Nicolaus Foerster, Jack Parker-Holder, Tim Rocktäschel
2024 A* conf
ICLR
Andrei Lupu, Chris Lu, Jarek Liesen, Robert Tjarko Lange, Jakob Nicolaus Foerster
2024 J jnl
CoRR
Alexander David Goldie, Chris Lu, Matthew Thomas Jackson, Shimon Whiteson, Jakob Nicolaus Foerster
2024 A* conf
ICML
Michael T. Matthews, Michael Beukman, Benjamin Ellis, Mikayel Samvelyan, Matthew Thomas Jackson, Samuel Coward, Jakob Nicolaus Foerster
2024 A* conf
ICLR
Matthew Thomas Jackson, Chris Lu, Louis Kirsch, Robert Tjarko Lange, Shimon Whiteson, Jakob Nicolaus Foerster
2024 J jnl
CoRR
Matthew Thomas Jackson, Chris Lu, Louis Kirsch, Robert T. Lange, Shimon Whiteson, Jakob Nicolaus Foerster
2024 A* conf
ICML
Silvia Sapora, Gokul Swamy, Chris Lu, Yee Whye Teh, Jakob Nicolaus Foerster
2024 J jnl
CoRR
Silvia Sapora, Gokul Swamy, Chris Lu, Yee Whye Teh, Jakob Nicolaus Foerster
2024 J jnl
Trans. Mach. Learn. Res.
Usman Anwar, Abulhair Saparov, Javier Rando, Daniel Paleka, Miles Turpin, Peter Hase, Ekdeep Singh Lubana, Erik Jenner, Stephen Casper, Oliver Sourbut, Benjamin L. Edelman, Zhaowei Zhang, Mario Günther, Anton Korinek, José Hernández-Orallo, Lewis Hammond, Eric J. Bigelow, Alexander Pan, Lauro Langosco, Tomasz Korbak, Heidi Chenyu Zhang, Ruiqi Zhong, Seán Ó hÉigeartaigh, Gabriel Recchia, Giulio Corsi, Alan Chan, Markus Anderljung, Lilian Edwards, Aleksandar Petrov, Christian Schröder de Witt, Sumeet Ramesh Motwani, Yoshua Bengio, Danqi Chen, Philip Torr, Samuel Albanie, Tegan Maharaj, Jakob Nicolaus Foerster, Florian Tramèr, He He, Atoosa Kasirzadeh, Yejin Choi, David Krueger
2024 A* conf
ICLR
Tim Franzmeyer, Stephen Marcus McAleer, João F. Henriques, Jakob Nicolaus Foerster, Philip Torr, Adel Bibi, Christian Schröder de Witt
2024 J jnl
CoRR
Carlo Alfano, Silvia Sapora, Jakob Nicolaus Foerster, Patrick Rebeschini, Yee Whye Teh
2024 A* conf
ICLR
Yat Long Lo, Biswa Sengupta, Jakob Nicolaus Foerster, Michael Noukhovitch
2024 J jnl
RLJ
Timon Willi, Johan S. Obando-Ceron, Jakob Nicolaus Foerster, Gintare Karolina Dziugaite, Pablo Samuel Castro
2024 A* conf
ICML
Johan S. Obando-Ceron, Ghada Sokar, Timon Willi, Clare Lyle, Jesse Farebrother, Jakob Nicolaus Foerster, Gintare Karolina Dziugaite, Doina Precup, Pablo Samuel Castro
2024 A* conf
ICML
Ziyang Zhang, Qizhen Zhang, Jakob Nicolaus Foerster
2024 J jnl
RLJ
Matthew Thomas Jackson, Michael T. Matthews, Cong Lu, Benjamin Ellis, Shimon Whiteson, Jakob Nicolaus Foerster
2024 A* conf
ICML
Andrew Jesson, Chris Lu, Gunshi Gupta, Nicolas Beltran-Velez, Angelos Filos, Jakob Nicolaus Foerster, Yarin Gal
2024 A* conf
ICML
Michael Beukman, Samuel Coward, Michael T. Matthews, Mattie Fellows, Minqi Jiang, Michael D. Dennis, Jakob Nicolaus Foerster
2024 A* conf
ICLR
Tim Franzmeyer, Edith Elkind, Philip Torr, Jakob Nicolaus Foerster, João F. Henriques
2024 J jnl
CoRR
Matteo Gallici, Mattie Fellows, Benjamin Ellis, Bartomeu Pou, Ivan Masmitja, Jakob Nicolaus Foerster, Mario Martin
2023 A* conf
ICML
Chris Lu, Timon Willi, Alistair Letcher, Jakob Nicolaus Foerster
2023 A* conf
ICLR
Brandon Cui, Andrei Lupu, Samuel Sokota, Hengyuan Hu, David J. Wu, Jakob Nicolaus Foerster
2023 A* conf
ICLR
Yat Long Lo, Christian Schröder de Witt, Samuel Sokota, Jakob Nicolaus Foerster, Shimon Whiteson
2023 J jnl
CoRR
Yat Long Lo, Christian Schröder de Witt, Samuel Sokota, Jakob Nicolaus Foerster, Shimon Whiteson
2023 J jnl
CoRR
Matthew Thomas Jackson, Minqi Jiang, Jack Parker-Holder, Risto Vuorio, Chris Lu, Gregory Farquhar, Shimon Whiteson, Jakob Nicolaus Foerster
2023 J jnl
CoRR
Alexander Rutherford, Benjamin Ellis, Matteo Gallici, Jonathan Cook, Andrei Lupu, Garðar Ingvarsson, Timon Willi, Akbir Khan, Christian Schröder de Witt, Alexandra Souly, Saptarashmi Bandyopadhyay, Mikayel Samvelyan, Minqi Jiang, Robert Tjarko Lange, Shimon Whiteson, Bruno Lacerda, Nick Hawes, Tim Rocktäschel, Chris Lu, Jakob Nicolaus Foerster
2023 A* conf
ICML
Mingwei Ma, Jizhou Liu, Samuel Sokota, Max Kleiman-Weiner, Jakob Nicolaus Foerster
2023 J jnl
Trans. Mach. Learn. Res.
Isaac Liao, Rumen Dangovski, Jakob Nicolaus Foerster, Marin Soljacic
2023 A* conf
ICLR
Mikayel Samvelyan, Akbir Khan, Michael Dennis, Minqi Jiang, Jack Parker-Holder, Jakob Nicolaus Foerster, Roberta Raileanu, Tim Rocktäschel
2023 A* conf
ICLR
Christian Schröder de Witt, Samuel Sokota, J. Zico Kolter, Jakob Nicolaus Foerster, Martin Strohmeier
2023 J jnl
CoRR
Andrew Jesson, Chris Lu, Gunshi Gupta, Angelos Filos, Jakob Nicolaus Foerster, Yarin Gal
2022 A* conf
ICLR
Samuel Sokota, Hengyuan Hu, David J. Wu, J. Zico Kolter, Jakob Nicolaus Foerster, Noam Brown
2022 J jnl
CoRR
Stephen Zhao, Chris Lu, Roger Baker Grosse, Jakob Nicolaus Foerster
tests/unit/test_decompile_binja_extractor.py
← Index tests/unit/test_decompile_binja_extractor.py python
"""Unit tests for DecompileBinja (top-level extractor) and prepare_export_data."""
import hashlib
import json
import os
import sys
import pytest
from unittest.mock import MagicMock, patch, PropertyMock
from datetime import datetime

# Install binaryninja stubs
from tests.unit.conftest_binja_stubs import install_binja_stubs
install_binja_stubs()

from redb.extractors.enum import Tag


# ============================================================================
# 7a. DecompileBinja — methods that don't require a live Binary Ninja session
# ============================================================================


class TestDecompileBinjaCalculateMD5:
    def test_calculate_md5(self):
        """calculate_md5("test") returns expected MD5."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = object.__new__(DecompileBinja)
            obj.log = MagicMock()
            result = obj.calculate_md5("test")
            expected = hashlib.md5(b"test").hexdigest()
            assert result == expected


class TestDecompileBinjaTimeouts:
    def _make_extractor(self, env_vars=None):
        """Create a DecompileBinja with mocked Extractor.__init__."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            if env_vars:
                with patch.dict(os.environ, env_vars, clear=False):
                    obj = DecompileBinja.__new__(DecompileBinja)
                    obj.log = MagicMock()
                    obj.analysis_results = None
                    obj.binja_decompiler = None
                    obj.filetype = None
                    obj.goresym_data = None
                    obj.goresym_output_path = None
                    # Re-run timeout logic
                    try:
                        obj.BINJA_TIMEOUT = int(os.getenv("BINJA_TIMEOUT", "1200"))
                    except ValueError:
                        obj.BINJA_TIMEOUT = 1200
                    try:
                        obj.DECOMPILE_EXTRACTOR_TIMEOUT = int(
                            os.getenv("DECOMPILE_EXTRACTOR_TIMEOUT", "2580")
                        )
                    except ValueError:
                        obj.DECOMPILE_EXTRACTOR_TIMEOUT = 2580
                    return obj
            else:
                obj = DecompileBinja.__new__(DecompileBinja)
                obj.log = MagicMock()
                obj.analysis_results = None
                obj.binja_decompiler = None
                obj.filetype = None
                obj.goresym_data = None
                obj.goresym_output_path = None
                obj.BINJA_TIMEOUT = 1200
                obj.DECOMPILE_EXTRACTOR_TIMEOUT = 2580
                return obj

    def test_timeout_defaults(self):
        obj = self._make_extractor()
        assert obj.BINJA_TIMEOUT == 1200
        assert obj.DECOMPILE_EXTRACTOR_TIMEOUT == 2580

    def test_timeout_from_env(self):
        obj = self._make_extractor(env_vars={"BINJA_TIMEOUT": "600"})
        assert obj.BINJA_TIMEOUT == 600

    def test_timeout_invalid_env(self):
        obj = self._make_extractor(env_vars={"BINJA_TIMEOUT": "not_a_number"})
        assert obj.BINJA_TIMEOUT == 1200


class TestDecompileBinjaDotnet:
    def _make_extractor(self, filetype=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = filetype
            obj.filepath = "/tmp/fake.bin"
            obj.binary = b"\x00" * 100
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            return obj

    def test_is_dotnet_non_pe(self):
        obj = self._make_extractor(filetype="elf")
        assert obj.is_dotnet() is False

    def test_is_dotnet_pe_without_com(self):
        """PE without .NET COM descriptor -> False."""
        obj = self._make_extractor(filetype="pebin")
        # Mock magic to not have .Net
        with patch("redb.extractors.decompiler.DecompileBinja.magic") as mock_magic:
            mock_magic.from_buffer.return_value = "PE32 executable"
            with patch("redb.extractors.decompiler.DecompileBinja.pefile") as mock_pefile:
                mock_pe = MagicMock()
                entry = MagicMock()
                entry.name = "IMAGE_DIRECTORY_ENTRY_IMPORT"
                entry.Size = 100
                mock_pe.OPTIONAL_HEADER.DATA_DIRECTORY = [entry]
                mock_pefile.PE.return_value = mock_pe
                assert obj.is_dotnet() is False

    def test_is_dotnet_pe_with_com_descriptor(self):
        """PE with COM descriptor -> True."""
        obj = self._make_extractor(filetype="pebin")
        with patch("redb.extractors.decompiler.DecompileBinja.magic") as mock_magic:
            mock_magic.from_buffer.return_value = "PE32 executable"
            with patch("redb.extractors.decompiler.DecompileBinja.pefile") as mock_pefile:
                mock_pe = MagicMock()
                entry = MagicMock()
                entry.name = "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR"
                entry.Size = 72
                mock_pe.OPTIONAL_HEADER.DATA_DIRECTORY = [entry]
                mock_pefile.PE.return_value = mock_pe
                assert obj.is_dotnet() is True


class TestDecompileBinjaGolang:
    def _make_extractor(self, filetype=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = filetype
            obj.filepath = "/tmp/fake.bin"
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            return obj

    def test_is_golang_non_elf_pe(self):
        obj = self._make_extractor(filetype=None)
        assert obj.is_golang() is False

    def test_is_golang_elf_with_go_sections(self):
        """ELF with .gopclntab section -> True."""
        obj = self._make_extractor(filetype="elf")
        with patch("builtins.open", MagicMock()):
            with patch("redb.extractors.decompiler.DecompileBinja.ELFFile") as mock_elf_cls:
                mock_elf = MagicMock()
                sec1 = MagicMock()
                sec1.name = ".text"
                sec1.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec1.data.return_value = b""
                sec2 = MagicMock()
                sec2.name = ".gopclntab"
                sec2.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec2.data.return_value = b""
                mock_elf.iter_sections.return_value = [sec1, sec2]
                mock_elf_cls.return_value = mock_elf
                assert obj.is_golang() is True

    def test_is_golang_elf_without_go(self):
        obj = self._make_extractor(filetype="elf")
        with patch("builtins.open", MagicMock()):
            with patch("redb.extractors.decompiler.DecompileBinja.ELFFile") as mock_elf_cls:
                mock_elf = MagicMock()
                sec = MagicMock()
                sec.name = ".text"
                sec.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec.data.return_value = b"no go signatures here"
                mock_elf.iter_sections.return_value = [sec]
                mock_elf_cls.return_value = mock_elf
                assert obj.is_golang() is False


class TestDecompileBinjaTag:
    def test_tag_returns_decompiled(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            assert obj.tag() == Tag.DECOMPILED.value


class TestDecompileBinjaContextManager:
    def test_context_manager_protocol(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            assert obj.__enter__() is obj
            # __exit__ calls cleanup_run
            obj.__exit__(None, None, None)

    def test_cleanup_run_clears_state(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.binja_decompiler = MagicMock()
            obj.goresym_output_path = None
            obj.cleanup_run()
            assert obj.binja_decompiler is None


class TestDecompileBinjaExtract:
    def test_extract_timeout_handling(self):
        """Thread timeout -> returns None and calls cleanup."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.analysis_results = None
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            obj.DECOMPILE_EXTRACTOR_TIMEOUT = 0  # Immediate timeout
            obj.sha256 = "a" * 64
            obj.sha1 = "b" * 40
            obj.md5 = "c" * 32
            # Mock analyze_binary to block
            import threading
            obj.analyze_binary = lambda: None
            result = obj.extract()
            assert result is None

    def test_analyze_binary_skips_dotnet(self):
        """Dotnet binary -> analyze_binary returns None."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = "pebin"
            obj.filepath = "/fake"
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            obj.goresym_data = None
            obj.BINJA_TIMEOUT = 60
            obj.exporters = []
            obj.index_prefix = None
            with patch.object(obj, "is_dotnet", return_value=True):
                result = obj.analyze_binary()
                assert result is None


# ============================================================================
# 7b. prepare_export_data (ClickHouse schema)
# ============================================================================


class TestPrepareExportData:
    def _make_extractor_with_results(self, goresym_data=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = goresym_data
            obj.analysis_results = {
                "sha256": "a" * 64,
                "sha1": "b" * 40,
                "md5": "c" * 32,
                "decompiled": [
                    {
                        "decompiled_function_hash": "h" * 64,
                        "decompiled_function": "void test() {}",
                        "decompiled_function_name": "test",
                        "decompiled_function_prototype": "void test()",
                        "decompiled_function_address": 0x1000,
                        "function_type": "USER",
                        "functions_caller": [],
                        "functions_call": [],
                        "disassembled_function_hash": "d" * 64,
                        "flattened_score": 0.0,
                        "mba_score": 0.0,
                    }
                ],
                "disassembled": [
                    {
                        "disassembled_function_hash": "d" * 64,
                        "disassembled_function": "0x1000: push rbp",
                        "disassembled_function_no_addresses": "push rbp",
                        "disassembled_function_name": "test",
                        "disassembled_function_address": 0x1000,
                        "function_type": "USER",
                        "instructions_count": 5,
                        "instructions_types": ["DATA_MOVEMENT"],
                        "control_flow_count": 1,
                        "memory_access_pattern": ["MEM_STACK"],
                        "register_usage": ["GPR"],
                        "data_references_count": 0,
                        "max_block_size": 5,
                        "num_calls": 1,
                        "stack_size": -8,
                        "decompiled_function_hash": "h" * 64,
                        "tlsh_disassembly": None,
                        "tlsh_llil": None,
                        "minhash": [1, 2, 3],
                        "cyclomatic_complexity": 2,
                    }
                ],
                "cfg": [
                    {
                        "function_address": 0x1000,
                        "disassembled_function_hash": "d" * 64,
                        "cfg_topology_hash": b'\x00' * 16,
                        "block_count": 3,
                        "edge_count": 4,
                        "llil_total_operations": 20,
                        "call_count": 1,
                        "cyclomatic_complexity": 3,
                        "loop_count": 1,
                        "max_depth": 2,
                        "max_fan_out": 2,
                        "md_index_topdown": 12345,
                        "md_index_bottomup": 67890,
                        "prime_product_llil": 99999,
                        "cfg_feature_tlsh": None,
                        "wl_minhash": [0] * 128,
                        "bb_features": [[5, 1, 0, 2, 0, 1, 1, 2]] * 3,
                        "cfg_adjacency": [(0 << 16) | 1, (0 << 16) | 2, (1 << 16) | 2],
                    }
                ],
                "llil": [
                    {
                        "sha256_llil": "l" * 64,
                        "function_type": "USER",
                        "instructions_types_llil": [],
                        "control_flow_count_llil": 0,
                        "memory_access_pattern_llil": [],
                        "register_usage": {},
                        "total_reg_reads": 0,
                        "total_reg_written": 0,
                        "data_references_count": 0,
                        "max_block_size": 5,
                        "num_calls": 0,
                        "stack_size": -8,
                        "body_llil_vector": [],
                        "function_address": 0x1000,
                        "disassembled_function_hash": "d" * 64,
                        "tlsh_disassembly": None,
                        "tlsh_llil": None,
                    }
                ],
                "errors": [],
                "strings": [
                    {
                        "string": "Hello",
                        "string_raw": "Hello",
                        "string_encoding": "Utf8String",
                        "string_offset": 0,
                        "string_length": 5,
                        "string_raw_length": 5,
                        "string_entropy": 2.32,
                    }
                ],
            }
            return obj

    def test_prepare_export_returns_none_no_results(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.analysis_results = None
            result = obj.prepare_export_data("ClickHouseExporter")
            assert result is None

    def test_prepare_export_multi_table_flag(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        assert result["multi_table"] is True

    def test_prepare_export_all_tables_present(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        expected_tables = [
            "decompiled_content",
            "decompiled_refs",
            "disassembled_content",
            "disassembled_refs",
            "llil_content",
            "llil_refs",
            "cfg_functions",
            "function_similarity_metrics",
            "strings_raw",
        ]
        for table in expected_tables:
            assert table in result, f"Missing table: {table}"

    def test_prepare_export_decompiled_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["decompiled_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert "decompiled_function_hash" in table["column_names"]

    def test_prepare_export_decompiled_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["decompiled_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert "sha256" in table["column_names"]

    def test_prepare_export_disassembled_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["disassembled_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_disassembled_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["disassembled_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_llil_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["llil_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_llil_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["llil_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_cfg_functions_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["cfg_functions"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert table["table"] == "code_binja_cfg_functions"
        assert "cfg_topology_hash" in table["column_names"]
        assert "wl_minhash" in table["column_names"]
        assert "bb_features" in table["column_names"]

    def test_prepare_export_function_similarity_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["function_similarity_metrics"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_strings_raw_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["strings_raw"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_errors_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["function_analysis_errors"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_golang_metadata_present(self):
        obj = self._make_extractor_with_results(goresym_data={"UserFunctions": []})
        result = obj.prepare_export_data("ClickHouseExporter")
        assert "golang_metadata" in result

    def test_prepare_export_golang_metadata_absent(self):
        obj = self._make_extractor_with_results(goresym_data=None)
        result = obj.prepare_export_data("ClickHouseExporter")
        assert "golang_metadata" not in result


class TestPrepareHelperFunctions:
    """Test the helper functions inside prepare_export_data."""

    def test_prepare_array_field_none(self):
        # The prepare_array_field function converts None -> []
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [], "cfg": [], "llil": [],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            # Verify the export succeeds (function works even with empty lists)
            assert result is not None

    def test_prepare_array_field_list(self):
        """List values pass through."""
        # Verified through the export data containing arrays
        pass

    def test_prepare_register_usage_map(self):
        """Dict converted to {reg: (reads, writes)} tuples."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [],
                "cfg": [],
                "llil": [{
                    "sha256_llil": "l" * 64, "function_type": "USER",
                    "instructions_types_llil": [], "control_flow_count_llil": 0,
                    "memory_access_pattern_llil": [],
                    "register_usage": {"rbx": {"reads": 3, "writes": 1}},
                    "total_reg_reads": 3, "total_reg_written": 1,
                    "data_references_count": 0, "max_block_size": 5,
                    "num_calls": 0, "stack_size": 0,
                    "body_llil_vector": [],
                    "function_address": 0x1000,
                    "disassembled_function_hash": "d" * 64,
                    "tlsh_disassembly": None,
                    "tlsh_llil": None,
                }],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            # The LLIL content data should have register_usage_llil as a map
            llil_data = result["llil_content"]["data"]
            assert len(llil_data) == 1
            # Column index 5 is register_usage_llil
            reg_map = llil_data[0][5]
            assert reg_map == {"rbx": (3, 1)}

    def test_prepare_register_usage_map_empty(self):
        """Empty/None register usage -> {}."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [],
                "cfg": [],
                "llil": [{
                    "sha256_llil": "l" * 64, "function_type": "USER",
                    "instructions_types_llil": [], "control_flow_count_llil": 0,
                    "memory_access_pattern_llil": [],
                    "register_usage": None,
                    "total_reg_reads": 0, "total_reg_written": 0,
                    "data_references_count": 0, "max_block_size": 0,
                    "num_calls": 0, "stack_size": 0,
                    "body_llil_vector": [],
                    "function_address": 0x1000,
                    "disassembled_function_hash": "d" * 64,
                    "tlsh_disassembly": None,
                    "tlsh_llil": None,
                }],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            llil_data = result["llil_content"]["data"]
            reg_map = llil_data[0][5]
            assert reg_map == {}