Hana Chockler

131 papers A* 14A 6B 15C 3Misc 1Journal 59Unranked 23
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Kurt Butler, Stephanie Riley, Damian Machlanski, Edward Moroshko, Panagiotis Dimitrakopoulos, Thomas Melistas, Akchunya Chanchal, Konstantinos Vilouras, Zhihua Liu, Steven McDonagh, Hana Chockler, Ben Glocker, Niccoló Tempini, Matthew Sperrin, Sotirios A. Tsaftaris, Ricardo Silva
2026 B conf
FASE
Kaveh Aryan, Hana Chockler, Mohammad Reza Mousavi
2026 J jnl
CoRR
David A. Kelly, Hana Chockler
2025 J jnl
CoRR
Melane Navaratnarajah, Sophie A. Martin, David A. Kelly, Nathan Blake, Hana Chockler
2025 J jnl
CoRR
Akchunya Chanchal, David A. Kelly, Hana Chockler
2025 J jnl
CoRR
David A. Kelly, Hana Chockler
2025 J jnl
CoRR
Hugo Araújo, Hana Chockler, Mohammad Reza Mousavi, Gustavo Carvalho, Augusto Sampaio
2025 conf
CLeaR
Milad Kazemi, Jessica Lally, Ekaterina Tishchenko, Hana Chockler, Nicola Paoletti
2025 J jnl
CoRR
Aditi Ramaswamy, Hana Chockler, Melane Navaratnarajah
2025 J jnl
CoRR
Stav Armoni-Friedmann, Hana Chockler, David A. Kelly
2025 A conf
UAI
David A. Kelly, Hana Chockler, Nathan Blake
2025 A conf
ECAI
Hana Chockler, David A. Kelly, Daniel Kroening
2025 J jnl
CoRR
Melane Navaratnarajah, David A. Kelly, Hana Chockler
2025 J jnl
CoRR
Nathan Blake, David A. Kelly, Akchunya Chanchal, Sarah Kapllani-Mucaj, Geraint Thomas, Hana Chockler
2024 J jnl
Minds Mach.
Sander Beckers, Hana Chockler, Joseph Y. Halpern
2024 J jnl
CoRR
Hana Chockler, David A. Kelly, Daniel Kroening, Youcheng Sun
2024 J jnl
CoRR
Milad Kazemi, Jessica Lally, Ekaterina Tishchenko, Hana Chockler, Nicola Paoletti
2024 A* conf
KR
Hana Chockler, Joseph Y. Halpern
2024 J jnl
CoRR
Hana Chockler, Joseph Y. Halpern
2024 J jnl
CoRR
Aditi Ramaswamy, Melane Navaratnarajah, Hana Chockler
2024 J jnl
CoRR
Santiago Calderon Pena, Hana Chockler, David A. Kelly
2024 A* conf
NeurIPS
Stefan Pranger, Hana Chockler, Martin Tappler, Bettina Könighofer
2024 J jnl
CoRR
Stefan Pranger, Hana Chockler, Martin Tappler, Bettina Könighofer
2024 J jnl
Dagstuhl Reports
Vaishak Belle, Hana Chockler, Shannon Vallor, Kush R. Varshney, Joost Vennekens, Sander Beckers
2023 conf
EWAF
Sander Beckers, Hana Chockler, Joseph Y. Halpern
2023 conf
TAS
Bénédicte Legastelois, Amy Rafferty, Paul Brennan, Hana Chockler, Ajitha Rajan, Vaishak Belle
2023 J jnl
CoRR
Mark Levin, Hana Chockler
2023 J jnl
CoRR
Nathan Blake, Hana Chockler, David A. Kelly, Santiago Calderon Pena, Akchunya Chanchal
2023 J jnl
CoRR
Hana Chockler, David A. Kelly, Daniel Kroening
2023 A* conf
IJCAI
Sander Beckers, Hana Chockler, Joseph Y. Halpern
2023 J jnl
CoRR
David A. Kelly, Hana Chockler, Daniel Kroening, Nathan Blake, Aditi Ramaswamy, Melane Navaratnarajah, Aaditya Shivakumar
2022 A* conf
NeurIPS
Sander Beckers, Hana Chockler, Joseph Y. Halpern
2022 J jnl
CoRR
Sander Beckers, Hana Chockler, Joseph Y. Halpern
2022 J jnl
CoRR
Sander Beckers, Hana Chockler, Joseph Y. Halpern
2022 conf
MLCN@MICCAI
Stefanos Ioannou, Hana Chockler, Alexander Hammers, Andrew P. King
2022 J jnl
CoRR
Stefanos Ioannou, Hana Chockler, Alexander Hammers, Andrew P. King
2022 J jnl
CoRR
Francesca E. D. Raimondi, Tadhg O'Keeffe, Hana Chockler, Andrew R. Lawrence, Tamara Stemberga, Andre Franca, Maksim Sipos, Javed Butler, Shlomo Ben-Haim
2022 J jnl
CoRR
Steven Kleinegesse, Andrew R. Lawrence, Hana Chockler
2022 J jnl
CoRR
Francesca E. D. Raimondi, Andrew R. Lawrence, Hana Chockler
2022 A* conf
AAAI
Hana Chockler, Joseph Y. Halpern
2022 J jnl
Formal Methods Syst. Des.
Roderick Bloem, Hana Chockler, Masoud Ebrahimi, Ofer Strichman
2022 J jnl
CoRR
Xin Du, Bénédicte Legastelois, Bhargavi Ganesh, Ajitha Rajan, Hana Chockler, Vaishak Belle, Stuart Anderson, Subramanian Ramamoorthy
2022 B conf
FMCAD
Hana Chockler
2021 J jnl
CoRR
Daniel C. McNamee, Hana Chockler
2021 J jnl
CoRR
Hana Chockler, Daniel Kroening, Youcheng Sun
2021 A* conf
ICCV
Hana Chockler, Daniel Kroening, Youcheng Sun
2021 J jnl
Formal Methods Syst. Des.
Hana Chockler, Georg Weissenbacher
2021 A* conf
NeurIPS
Hadrien Pouget, Hana Chockler, Youcheng Sun, Daniel Kroening
2021 J jnl
CoRR
Ayman Boustati, Hana Chockler, Daniel C. McNamee
2021 J jnl
Formal Methods Syst. Des.
Roderick Bloem, Hana Chockler, Masoud Ebrahimi, Ofer Strichman
2020 J jnl
CoRR
Dalal Alrajeh, Hana Chockler, Joseph Y. Halpern
2020 J jnl
Artif. Intell.
Dalal Alrajeh, Hana Chockler, Joseph Y. Halpern
2020 conf
ECCV (28)
Youcheng Sun, Hana Chockler, Xiaowei Huang, Daniel Kroening
2020 J jnl
J. Artif. Intell. Res.
Hana Chockler, Pascal Kesseli, Daniel Kroening, Ofer Strichman
2020 J jnl
CoRR
Hadrien Pouget, Hana Chockler, Youcheng Sun, Daniel Kroening
2020 J jnl
CoRR
Roderick Bloem, Hana Chockler, Masoud Ebrahimi, Dana Fisman, Heinz Riener
2019 J jnl
CoRR
Youcheng Sun, Hana Chockler, Xiaowei Huang, Daniel Kroening
2019 C conf
MEMOCODE
Karine Even-Mendoza, Antti E. J. Hyvärinen, Hana Chockler, Natasha Sharygina
2019 B conf
FMCAD
Roderick Bloem, Hana Chockler, Masoud Ebrahimi, Ofer Strichman
2018 A* conf
AAAI
Dalal Alrajeh, Hana Chockler, Joseph Y. Halpern
2018 ed.
CAV (1)
Hana Chockler, Georg Weissenbacher
2018 ed.
CAV (2)
Hana Chockler, Georg Weissenbacher
2018 B conf
LPAR
Sepideh Asadi, Martin Blicha, Grigory Fedyukovich, Antti E. J. Hyvärinen, Karine Even-Mendoza, Natasha Sharygina, Hana Chockler
2018 conf
VSTTE
Karine Even-Mendoza, Sepideh Asadi, Antti E. J. Hyvärinen, Hana Chockler, Natasha Sharygina
2018 B conf
LPAR
Antti E. J. Hyvärinen, Matteo Marescotti, Parvin Sadigova, Hana Chockler, Natasha Sharygina
2018 B conf
FM
Hana Chockler, Shibashis Guha, Orna Kupferman
2017 conf
TACAS (2)
Leonardo Alt, Sepideh Asadi, Hana Chockler, Karine Even-Mendoza, Grigory Fedyukovich, Antti E. J. Hyvärinen, Natasha Sharygina
2017 B conf
VMCAI
Roderick Bloem, Hana Chockler, Masoud Ebrahimi, Ofer Strichman
2017 J jnl
J. Artif. Intell. Res.
Gadi Aleksandrowicz, Hana Chockler, Joseph Y. Halpern, Alexander Ivrii
2017 A conf
SAT
Antti E. J. Hyvärinen, Sepideh Asadi, Karine Even-Mendoza, Grigory Fedyukovich, Hana Chockler, Natasha Sharygina
2016 conf
CREST
Hana Chockler
2016 conf
Haifa Verification Conference
David Landsberg, Hana Chockler, Daniel Kroening
2015 C conf
ICAIL
Hana Chockler, Norman E. Fenton, Jeroen Keppens, David A. Lagnado
2015 ch.
Validation of Evolving Software
Hana Chockler, Daniel Kroening, Leonardo Mariani, Natasha Sharygina
2015 ch.
Validation of Evolving Software
Hana Chockler, Daniel Kroening, Leonardo Mariani, Natasha Sharygina
2015 B conf
FASE
David Landsberg, Hana Chockler, Daniel Kroening, Matt Lewis
2015 ch.
Validation of Evolving Software
Hana Chockler, Daniel Kroening, Leonardo Mariani, Natasha Sharygina
2015 B conf
ATVA
Martin Chapman, Hana Chockler, Pascal Kesseli, Daniel Kroening, Ofer Strichman, Michael Tautschnig
2015 ch.
Validation of Evolving Software
Hana Chockler, Sitvanit Ruah
2015 book
Hana Chockler, Daniel Kroening, Leonardo Mariani, Natasha Sharygina
2014 A* conf
AAAI
Gadi Aleksandrowicz, Hana Chockler, Joseph Y. Halpern, Alexander Ivrii
2014 J jnl
CoRR
Gadi Aleksandrowicz, Hana Chockler, Joseph Y. Halpern, Alexander Ivrii
2013 conf
Haifa Verification Conference
Shoham Ben-David, Hana Chockler, Orna Kupferman
2013 J jnl
Formal Methods Syst. Des.
Hana Chockler, Arie Gurfinkel, Ofer Strichman
2013 A conf
ISSTA
Hana Chockler, Karine Even, Eran Yahav
2013 conf
Haifa Verification Conference
Hana Chockler, Dmitry Pidan, Sitvanit Ruah
2013 conf
CSMR
Hana Chockler, Giovanni Denaro, Meijia Ling, Grigory Fedyukovich, Antti Eero Johannes Hyvärinen, Leonardo Mariani, Ali Muhammad, Manuel Oriol, Ajitha Rajan, Ondrej Sery, Natasha Sharygina, Michael Tautschnig
2013 conf
VSSE
Hana Chockler
2013 ed.
VSSE
Hana Chockler
2013 Misc conf
SAC
Hana Chockler, Alexander Ivrii, Arie Matsliah, Simone Fulvio Rollini, Natasha Sharygina
2013 J jnl
ACM SIGOPS Oper. Syst. Rev.
Sara Bouchenak, Gregory V. Chockler, Hana Chockler, Gabriela Gheorghe, Nuno Santos, Alexander Shraer
2012 conf
Haifa Verification Conference
Hana Chockler, Alexander Ivrii, Arie Matsliah
2012 J jnl
IEEE Trans. Comput. Aided Des. Integr. Circuits Syst.
Hana Chockler, Daniel Kroening, Mitra Purandare
2012 J jnl
Formal Methods Syst. Des.
Ilan Beer, Shoham Ben-David, Hana Chockler, Avigail Orni, Richard J. Trefler
2012 conf
HotSWUp
Hana Chockler, Sitvanit Ruah
2011 B conf
FMCAD
Hana Chockler, Alexander Ivrii, Arie Matsliah, Shiri Moran, Ziv Nevo
2011 J jnl
Int. J. Softw. Tools Technol. Transf.
Hana Chockler, Alan J. Hu
2010 A* conf
DAC
Hana Chockler, Daniel Kroening, Mitra Purandare
2010 J jnl
ACM Trans. Comput. Log.
Hana Chockler, Joseph Y. Halpern, Orna Kupferman
2010 B conf
FMCAD
Hana Chockler
2010 conf
Haifa Verification Conference
Hana Chockler, Arie Gurfinkel, Ofer Strichman
2009 J jnl
Formal Methods Syst. Des.
Hana Chockler, Ofer Strichman
2009 B conf
FASE
Hana Chockler, Eitan Farchi, Benny Godlin, Sergey Novikov
2009 A* conf
CAV
Ilan Beer, Shoham Ben-David, Hana Chockler, Avigail Orni, Richard J. Trefler
2009 ed.
Haifa Verification Conference
Hana Chockler, Alan J. Hu
2008 B conf
FMCAD
Hana Chockler, Arie Gurfinkel, Ofer Strichman
2008 A conf
TACAS
Hana Chockler, Orna Grumberg, Avi Yadgar
2008 J jnl
ACM Trans. Comput. Log.
Hana Chockler, Joseph Y. Halpern, Orna Kupferman
2007 B conf
FMCAD
Hana Chockler, Eitan Farchi, Benny Godlin, Sergey Novikov
2007 C conf
MEMOCODE
Hana Chockler, Ofer Strichman
2006 conf
CBSE
Paul C. Attie, David H. Lorenz, Aleksandra Portnova, Hana Chockler
2006 J jnl
Int. J. Softw. Tools Technol. Transf.
Hana Chockler, Orna Kupferman, Moshe Y. Vardi
2006 J jnl
Formal Methods Syst. Des.
Hana Chockler, Orna Kupferman, Moshe Y. Vardi
2006 conf
PADTAD
Hana Chockler, Eitan Farchi, Ziv Glazberg, Benny Godlin, Yarden Nir-Buchbinder, Ishai Rabinovitz
2005 conf
INFINITY
Paul C. Attie, Hana Chockler
2005 B conf
VMCAI
Paul C. Attie, Hana Chockler
2005 conf
CHARME
Hana Chockler, Kathi Fisler
2004 J jnl
Inf. Process. Lett.
Hana Chockler, Dan Gutfreund
2004 J jnl
J. Artif. Intell. Res.
Hana Chockler, Joseph Y. Halpern
2004 J jnl
Theor. Comput. Sci.
Hana Chockler, Orna Kupferman
2003 conf
CHARME
Hana Chockler, Orna Kupferman, Moshe Y. Vardi
2003
Hana Chockler
2003 A* conf
IJCAI
Hana Chockler, Joseph Y. Halpern
2003 J jnl
CoRR
Hana Chockler, Joseph Y. Halpern
2003 J jnl
CoRR
Hana Chockler, Joseph Y. Halpern, Orna Kupferman
2002 conf
IFIP TCS
Hana Chockler, Orna Kupferman
2002 conf
RANDOM
Hana Chockler, Orna Kupferman
2001 A* conf
CAV
Hana Chockler, Orna Kupferman, Robert P. Kurshan, Moshe Y. Vardi
2001 A conf
TACAS
Hana Chockler, Orna Kupferman, Moshe Y. Vardi
2001 J jnl
Comput. Complex.
Hana Chockler, Uri Zwick
2001 A* conf
SODA
Hana Chockler, Uri Zwick
tests/unit/test_decompile_analysis.py
← Index tests/unit/test_decompile_analysis.py python
"""Unit tests (mocked Binary Ninja) for analysis modules:
- bninja/analysis/cfg.py — CFGAnalysis
- bninja/analysis/disassembly.py — DisassemblyAnalysis
- bninja/analysis/low_level.py — LowLevelAnalysis
"""
import sys
import pytest
from unittest.mock import MagicMock

from tests.unit.conftest_binja_stubs import (
    install_binja_stubs,
    BranchType,
    InstructionTextTokenType,
    MockBasicBlock,
    MockEdge,
    MockFunction,
    MockToken,
    MockDisassemblyLine,
    MockBinaryView,
    MockSymbol,
    SymbolType,
    LowLevelILOperation,
)

install_binja_stubs()

from redb.extractors.decompiler.bninja.analysis.cfg import CFGAnalysis
from redb.extractors.decompiler.bninja.analysis.disassembly import DisassemblyAnalysis
from redb.extractors.decompiler.bninja.arch.x86 import Arch_x86


# ============================================================================
# 9a. CFGAnalysis
# ============================================================================


class TestCFGCyclomaticComplexity:
    def test_cyclomatic_complexity_linear(self):
        """Single block, no edges: E - N + 2 = 0 - 1 + 2 = 1."""
        block = MockBasicBlock(start=0x1000, end=0x1010, outgoing_edges=[])
        func = MockFunction(start=0x1000, basic_blocks=[block])
        cfg = CFGAnalysis(func)
        result = cfg.extract_function_cfg()
        assert result["cyclomatic_complexity"] == 1

    def test_cyclomatic_complexity_branch(self):
        """Diamond: 4 blocks, 4 edges -> 4 - 4 + 2 = 2."""
        entry = MockBasicBlock(start=0x1000, end=0x1010)
        true_b = MockBasicBlock(start=0x1010, end=0x1020)
        false_b = MockBasicBlock(start=0x1020, end=0x1030)
        merge = MockBasicBlock(start=0x1030, end=0x1040)

        entry.outgoing_edges = [MockEdge(target=true_b), MockEdge(target=false_b)]
        true_b.outgoing_edges = [MockEdge(target=merge)]
        false_b.outgoing_edges = [MockEdge(target=merge)]
        merge.outgoing_edges = []

        func = MockFunction(start=0x1000, basic_blocks=[entry, true_b, false_b, merge])
        cfg = CFGAnalysis(func)
        result = cfg.extract_function_cfg()
        assert result["cyclomatic_complexity"] == 2

    def test_cyclomatic_complexity_loop(self):
        """Loop: 3 blocks, 3 edges -> 3 - 3 + 2 = 2."""
        header = MockBasicBlock(start=0x1000, end=0x1010)
        body = MockBasicBlock(start=0x1010, end=0x1020)
        exit_b = MockBasicBlock(start=0x1020, end=0x1030)

        header.outgoing_edges = [MockEdge(target=body), MockEdge(target=exit_b)]
        body.outgoing_edges = [MockEdge(target=header)]
        exit_b.outgoing_edges = []

        func = MockFunction(start=0x1000, basic_blocks=[header, body, exit_b])
        cfg = CFGAnalysis(func)
        result = cfg.extract_function_cfg()
        assert result["cyclomatic_complexity"] == 2


class TestCFGExtractFunctionCFG:
    def _make_simple_cfg(self):
        """Create a simple two-block CFG for testing structure."""
        entry = MockBasicBlock(start=0x1000, end=0x1010)
        exit_b = MockBasicBlock(start=0x1010, end=0x1020)

        entry.outgoing_edges = [MockEdge(source=entry, target=exit_b, edge_type=BranchType.UnconditionalBranch)]
        exit_b.incoming_edges = [MockEdge(source=entry, target=exit_b)]
        exit_b.outgoing_edges = []
        entry.incoming_edges = []

        func = MockFunction(start=0x1000, basic_blocks=[entry, exit_b])
        return func

    def test_extract_function_cfg_structure(self):
        func = self._make_simple_cfg()
        cfg = CFGAnalysis(func)
        result = cfg.extract_function_cfg()
        assert "function_address" not in result
        # New schema: no "blocks" or "measures" nesting
        assert "blocks" not in result
        assert "measures" not in result

    def test_function_cfg_new_keys(self):
        """Assert all expected keys are present in the new output dict."""
        func = self._make_simple_cfg()
        cfg = CFGAnalysis(func)
        result = cfg.extract_function_cfg()
        expected_keys = [
            "cfg_topology_hash",
            "block_count",
            "edge_count",
            "llil_total_operations",
            "call_count",
            "cyclomatic_complexity",
            "loop_count",
            "max_depth",
            "max_fan_out",
            "md_index_topdown",
            "md_index_bottomup",
            "prime_product_llil",
            "cfg_feature_tlsh",
            "wl_minhash",
            "bb_features",
            "cfg_adjacency",
        ]
        for key in expected_keys:
            assert key in result, f"Missing key: {key}"

    def test_returns_none_for_empty_blocks(self):
        func = MockFunction(start=0x1000, basic_blocks=[])
        cfg = CFGAnalysis(func)
        assert cfg.extract_function_cfg() is None


class TestCFGTopologyHash:
    def _make_two_block_cfg(self):
        entry = MockBasicBlock(start=0x1000, end=0x1010)
        exit_b = MockBasicBlock(start=0x1010, end=0x1020)
        entry.outgoing_edges = [MockEdge(target=exit_b)]
        exit_b.outgoing_edges = []
        return MockFunction(start=0x1000, basic_blocks=[entry, exit_b])

    def test_topology_hash_is_16_bytes(self):
        func = self._make_two_block_cfg()
        cfg = CFGAnalysis(func)
        result = cfg.extract_function_cfg()
        assert isinstance(result["cfg_topology_hash"], bytes)
        assert len(result["cfg_topology_hash"]) == 16

    def test_topology_hash_deterministic(self):
        func = self._make_two_block_cfg()
        r1 = CFGAnalysis(func).extract_function_cfg()
        r2 = CFGAnalysis(func).extract_function_cfg()
        assert r1["cfg_topology_hash"] == r2["cfg_topology_hash"]


class TestCFGLoopCount:
    def test_no_loops(self):
        entry = MockBasicBlock(start=0x1000, end=0x1010)
        exit_b = MockBasicBlock(start=0x1010, end=0x1020)
        entry.outgoing_edges = [MockEdge(target=exit_b)]
        exit_b.outgoing_edges = []
        func = MockFunction(start=0x1000, basic_blocks=[entry, exit_b])
        result = CFGAnalysis(func).extract_function_cfg()
        assert result["loop_count"] == 0

    def test_single_loop(self):
        header = MockBasicBlock(start=0x1000, end=0x1010)
        body = MockBasicBlock(start=0x1010, end=0x1020)
        exit_b = MockBasicBlock(start=0x1020, end=0x1030)
        header.outgoing_edges = [MockEdge(target=body), MockEdge(target=exit_b)]
        body.outgoing_edges = [MockEdge(target=header)]
        exit_b.outgoing_edges = []
        func = MockFunction(start=0x1000, basic_blocks=[header, body, exit_b])
        result = CFGAnalysis(func).extract_function_cfg()
        assert result["loop_count"] == 1


class TestCFGMaxDepth:
    def test_max_depth_linear(self):
        entry = MockBasicBlock(start=0x1000, end=0x1010)
        b1 = MockBasicBlock(start=0x1010, end=0x1020)
        b2 = MockBasicBlock(start=0x1020, end=0x1030)
        entry.outgoing_edges = [MockEdge(target=b1)]
        b1.outgoing_edges = [MockEdge(target=b2)]
        b2.outgoing_edges = []
        func = MockFunction(start=0x1000, basic_blocks=[entry, b1, b2])
        result = CFGAnalysis(func).extract_function_cfg()
        assert result["max_depth"] == 2

    def test_max_depth_single_block(self):
        block = MockBasicBlock(start=0x1000, end=0x1010, outgoing_edges=[])
        func = MockFunction(start=0x1000, basic_blocks=[block])
        result = CFGAnalysis(func).extract_function_cfg()
        assert result["max_depth"] == 0


class TestCFGCollectBlockLlilOps:
    """Test that _collect_block_llil_ops correctly maps LLIL data to native blocks."""

    def test_llil_fields_nonzero_with_mock_llil(self):
        """When LLIL is available, llil_total_operations and call_count should be non-zero."""
        # Two native blocks
        entry = MockBasicBlock(start=0x1000, end=0x1010)
        exit_b = MockBasicBlock(start=0x1010, end=0x1020)
        entry.outgoing_edges = [MockEdge(target=exit_b)]
        exit_b.outgoing_edges = []

        # LLIL instructions: SET_REG, CALL in first block; STORE, RET in second
        llil_instrs_1 = [
            MockLLILInstruction(LowLevelILOperation.LLIL_SET_REG),
            MockLLILInstruction(LowLevelILOperation.LLIL_CALL),
        ]
        llil_instrs_2 = [
            MockLLILInstruction(LowLevelILOperation.LLIL_STORE),
            MockLLILInstruction(LowLevelILOperation.LLIL_RET),
        ]

        # LLIL basic blocks map back to native blocks via source_block
        llil_bb1 = MockLLILBasicBlock(llil_instrs_1, source_block=entry)
        llil_bb2 = MockLLILBasicBlock(llil_instrs_2, source_block=exit_b)
        llil_func = MockLLILFunction([llil_bb1, llil_bb2])

        func = MockFunction(start=0x1000, basic_blocks=[entry, exit_b], llil=llil_func)
        result = CFGAnalysis(func, llil_function=llil_func).extract_function_cfg()

        assert result["llil_total_operations"] == 4
        assert result["call_count"] == 1
        assert result["prime_product_llil"] != 0

    def test_llil_none_gives_zero_fields(self):
        """Without LLIL, LLIL-dependent fields should be zero."""
        block = MockBasicBlock(start=0x1000, end=0x1010, outgoing_edges=[])
        func = MockFunction(start=0x1000, basic_blocks=[block])
        result = CFGAnalysis(func).extract_function_cfg()

        assert result["llil_total_operations"] == 0
        assert result["call_count"] == 0
        assert result["prime_product_llil"] == 0

    def test_bb_features_with_llil(self):
        """bb_features should reflect LLIL instruction categories when LLIL is available."""
        block = MockBasicBlock(start=0x1000, end=0x1010, outgoing_edges=[])

        llil_instrs = [
            MockLLILInstruction(LowLevelILOperation.LLIL_ADD),
            MockLLILInstruction(LowLevelILOperation.LLIL_LOAD),
            MockLLILInstruction(LowLevelILOperation.LLIL_CALL),
        ]
        llil_bb = MockLLILBasicBlock(llil_instrs, source_block=block)
        llil_func = MockLLILFunction([llil_bb])

        func = MockFunction(start=0x1000, basic_blocks=[block])
        result = CFGAnalysis(func, llil_function=llil_func).extract_function_cfg()

        feats = result["bb_features"]
        assert len(feats) == 1
        assert feats[0][0] == 3  # instruction count = 3
        # At least one non-zero category count (not all OTHER)
        category_counts = feats[0][1:7]
        assert sum(category_counts) > 0


# ============================================================================
# 9b. DisassemblyAnalysis
# ============================================================================


class TestDisassemblyAnalysisGetJson:
    def _make_analysis(self, instructions=None, basic_blocks=None):
        arch = Arch_x86()
        if instructions is None:
            instructions = [
                (
                    [
                        MockToken("push", InstructionTextTokenType.InstructionToken),
                        MockToken(" ", InstructionTextTokenType.TextToken),
                        MockToken("rbp", InstructionTextTokenType.RegisterToken),
                    ],
                    0x1000,
                ),
                (
                    [
                        MockToken("mov", InstructionTextTokenType.InstructionToken),
                        MockToken(" ", InstructionTextTokenType.TextToken),
                        MockToken("rsp", InstructionTextTokenType.RegisterToken),
                    ],
                    0x1003,
                ),
            ]
        if basic_blocks is None:
            basic_blocks = [MockBasicBlock(
                start=0x1000, end=0x1010,
                disassembly_text=[MockDisassemblyLine([MockToken("push rbp")])]
            )]

        func = MockFunction(
            name="test_func",
            start=0x1000,
            basic_blocks=basic_blocks,
            instructions=instructions,
            symbol=MockSymbol(symbol_type=SymbolType.FunctionSymbol, name="test_func"),
            stack_adjustment=MagicMock(value=-8),
            mlil=None,
        )
        bv = MockBinaryView()
        logger = MagicMock()
        return DisassemblyAnalysis(arch, func, bv, logger)

    def test_get_json_basic_structure(self):
        da = self._make_analysis()
        result, errors = da.get_json()
        expected_keys = [
            "disassembled_function_hash",
            "disassembled_function",
            "disassembled_function_no_addresses",
            "disassembled_function_name",
            "disassembled_function_address",
            "instructions_count",
            "function_type",
            "instructions_types",
            "control_flow_count",
            "memory_access_pattern",
            "register_usage",
            "data_references_count",
        ]
        for key in expected_keys:
            assert key in result, f"Missing key: {key}"

    def test_get_json_hash_deterministic(self):
        da = self._make_analysis()
        r1, _ = da.get_json()
        da2 = self._make_analysis()
        r2, _ = da2.get_json()
        assert r1["disassembled_function_hash"] == r2["disassembled_function_hash"]


class TestDisassemblyCollectInstructionTypes:
    def test_collect_instruction_types(self):
        arch = Arch_x86()
        instructions = [
            ([MockToken("MOV", InstructionTextTokenType.InstructionToken)], 0x1000),
            ([MockToken("ADD", InstructionTextTokenType.InstructionToken)], 0x1001),
            ([MockToken("MOV", InstructionTextTokenType.InstructionToken)], 0x1002),
        ]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        types = da.collect_instruction_types()
        assert "DATA_MOVEMENT" in types
        assert "ARITHMETIC" in types

    def test_collect_instruction_types_empty(self):
        arch = Arch_x86()
        func = MockFunction(start=0x1000, instructions=[], symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        types = da.collect_instruction_types()
        assert types == {}


class TestDisassemblyMemoryPatterns:
    def _make_memory_instruction(self, tokens):
        return ([t for t in tokens], 0x1000)

    def test_collect_memory_patterns_stack(self):
        arch = Arch_x86()
        tokens = [
            MockToken("[", InstructionTextTokenType.BeginMemoryOperandToken),
            MockToken("RSP", InstructionTextTokenType.RegisterToken),
            MockToken("+0x8", InstructionTextTokenType.TextToken),
            MockToken("]", InstructionTextTokenType.EndMemoryOperandToken),
        ]
        instructions = [self._make_memory_instruction(tokens)]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        patterns = da.collect_memory_patterns()
        assert "MEM_STACK" in patterns

    def test_collect_memory_patterns_direct(self):
        arch = Arch_x86()
        tokens = [
            MockToken("[", InstructionTextTokenType.BeginMemoryOperandToken),
            MockToken("0x402000", InstructionTextTokenType.TextToken),
            MockToken("]", InstructionTextTokenType.EndMemoryOperandToken),
        ]
        instructions = [self._make_memory_instruction(tokens)]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        patterns = da.collect_memory_patterns()
        assert "MEM_DIRECT" in patterns

    def test_collect_memory_patterns_scaled(self):
        arch = Arch_x86()
        tokens = [
            MockToken("[", InstructionTextTokenType.BeginMemoryOperandToken),
            MockToken("RAX+RCX*4", InstructionTextTokenType.TextToken),
            MockToken("]", InstructionTextTokenType.EndMemoryOperandToken),
        ]
        instructions = [self._make_memory_instruction(tokens)]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        patterns = da.collect_memory_patterns()
        assert "MEM_SCALED_INDEX" in patterns

    def test_collect_memory_patterns_base_offset(self):
        arch = Arch_x86()
        tokens = [
            MockToken("[", InstructionTextTokenType.BeginMemoryOperandToken),
            MockToken("RAX+0x10", InstructionTextTokenType.TextToken),
            MockToken("]", InstructionTextTokenType.EndMemoryOperandToken),
        ]
        instructions = [self._make_memory_instruction(tokens)]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        patterns = da.collect_memory_patterns()
        assert "MEM_BASE_OFFSET" in patterns


class TestDisassemblyRegisterUsage:
    def test_collect_register_usage_gpr(self):
        arch = Arch_x86()
        instructions = [
            ([MockToken("RAX", InstructionTextTokenType.RegisterToken)], 0x1000),
        ]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        regs = da.collect_register_usage()
        assert "GPR" in regs

    def test_collect_register_usage_simd(self):
        arch = Arch_x86()
        instructions = [
            ([MockToken("XMM0", InstructionTextTokenType.RegisterToken)], 0x1000),
        ]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        regs = da.collect_register_usage()
        assert "SIMD" in regs

    def test_collect_register_usage_fpu(self):
        arch = Arch_x86()
        instructions = [
            ([MockToken("ST0", InstructionTextTokenType.RegisterToken)], 0x1000),
        ]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        regs = da.collect_register_usage()
        assert "FPU" in regs


class TestDisassemblyMisc:
    def test_count_data_references(self):
        arch = Arch_x86()
        func = MockFunction(start=0x1000, instructions=[], symbol=MockSymbol(), mlil=None)
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        assert da.count_data_references() == 0

    def test_compute_max_block_size(self):
        arch = Arch_x86()
        blocks = [
            MockBasicBlock(disassembly_text=[MockDisassemblyLine([]) for _ in range(3)]),
            MockBasicBlock(disassembly_text=[MockDisassemblyLine([]) for _ in range(5)]),
        ]
        func = MockFunction(start=0x1000, basic_blocks=blocks, instructions=[], symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        assert da.compute_max_block_size() == 5

    def test_compute_num_calls(self):
        arch = Arch_x86()
        instructions = [
            ([MockToken("CALL", InstructionTextTokenType.InstructionToken)], 0x1000),
            ([MockToken("MOV", InstructionTextTokenType.InstructionToken)], 0x1005),
            ([MockToken("CALL", InstructionTextTokenType.InstructionToken)], 0x1010),
        ]
        func = MockFunction(start=0x1000, instructions=instructions, symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        assert da.compute_num_calls() == 2

    def test_estimate_stack_size_value(self):
        arch = Arch_x86()
        stack = MagicMock()
        stack.value = -16
        func = MockFunction(start=0x1000, instructions=[], symbol=MockSymbol(), stack_adjustment=stack)
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        assert da.estimate_stack_size() == -16

    def test_estimate_stack_size_int(self):
        arch = Arch_x86()
        func = MockFunction(start=0x1000, instructions=[], symbol=MockSymbol(), stack_adjustment=-8)
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        assert da.estimate_stack_size() == -8

    def test_normalize_opcode(self):
        arch = Arch_x86()
        func = MockFunction(start=0x1000, instructions=[], symbol=MockSymbol())
        da = DisassemblyAnalysis(arch, func, MockBinaryView(), MagicMock())
        assert da.normalize_opcode("mov") == "MOV"
        assert da.normalize_opcode("PUSH") == "PUSH"


# ============================================================================
# 9c. LowLevelAnalysis (basic tests with mocked LLIL)
# ============================================================================


class MockLLILInstruction:
    """Mock LLIL instruction for low_level.py tests."""
    def __init__(self, operation, operands=None, address=0):
        self.operation = operation
        self.operands = operands or []
        self.address = address

    def __str__(self):
        return f"LLIL_{self.operation}"


class MockLLILBasicBlock:
    def __init__(self, instructions, source_block=None):
        self._instructions = instructions
        self.source_block = source_block

    def __iter__(self):
        return iter(self._instructions)


class MockLLILFunction:
    def __init__(self, basic_blocks):
        self.basic_blocks = basic_blocks
        self._instructions = []
        for bb in basic_blocks:
            self._instructions.extend(bb._instructions)

    @property
    def instructions(self):
        return iter(self._instructions)

    @property
    def source_function(self):
        mock = MagicMock()
        mock.start = 0x1000
        return mock


class TestLowLevelAnalysisCountControlFlow:
    def test_count_control_flow_instructions(self):
        from redb.extractors.decompiler.bninja.analysis.low_level import LowLevelAnalysis
        instrs = [
            MockLLILInstruction(LowLevelILOperation.LLIL_IF),
            MockLLILInstruction(LowLevelILOperation.LLIL_SET_REG),
            MockLLILInstruction(LowLevelILOperation.LLIL_CALL),
            MockLLILInstruction(LowLevelILOperation.LLIL_GOTO),
        ]
        bb = MockLLILBasicBlock(instrs)
        llil_func = MockLLILFunction([bb])

        func = MockFunction(start=0x1000, llil=llil_func, symbol=MockSymbol())
        func.low_level_il = None
        bv = MockBinaryView()
        bv.arch = MagicMock()
        bv.arch.stack_pointer = "sp"
        la = LowLevelAnalysis(func, bv, MagicMock())
        assert la.count_control_flow_instructions() == 3  # IF, CALL, GOTO


class TestLowLevelAnalysisNumCalls:
    def test_compute_num_calls_llil(self):
        from redb.extractors.decompiler.bninja.analysis.low_level import LowLevelAnalysis
        instrs = [
            MockLLILInstruction(LowLevelILOperation.LLIL_CALL),
            MockLLILInstruction(LowLevelILOperation.LLIL_TAILCALL),
            MockLLILInstruction(LowLevelILOperation.LLIL_SET_REG),
        ]
        bb = MockLLILBasicBlock(instrs)
        llil_func = MockLLILFunction([bb])

        func = MockFunction(start=0x1000, llil=llil_func, symbol=MockSymbol())
        func.low_level_il = None
        bv = MockBinaryView()
        la = LowLevelAnalysis(func, bv, MagicMock())
        assert la.compute_num_calls() == 2


class TestLowLevelAnalysisCollectNormalization:
    def test_collect_low_level(self):
        from redb.extractors.decompiler.bninja.analysis.low_level import LowLevelAnalysis
        instrs = [
            MockLLILInstruction(LowLevelILOperation.LLIL_SET_REG, address=0x1000),
            MockLLILInstruction(LowLevelILOperation.LLIL_STORE, address=0x1004),
        ]
        bb = MockLLILBasicBlock(instrs)
        llil_func = MockLLILFunction([bb])

        func = MockFunction(start=0x1000, llil=llil_func, symbol=MockSymbol())
        func.low_level_il = None
        bv = MockBinaryView()
        la = LowLevelAnalysis(func, bv, MagicMock())
        result, _ = la._collect_low_level_and_with_addr()
        assert len(result) == 2
        # Each item is a list of operation ints
        assert isinstance(result[0], list)

    def test_collect_low_level_with_addr_offset_clamping(self):
        from redb.extractors.decompiler.bninja.analysis.low_level import LowLevelAnalysis
        instrs = [
            MockLLILInstruction(LowLevelILOperation.LLIL_SET_REG, address=0x0FFF),  # Before function start
        ]
        bb = MockLLILBasicBlock(instrs)
        llil_func = MockLLILFunction([bb])

        func = MockFunction(start=0x1000, llil=llil_func, symbol=MockSymbol())
        func.low_level_il = None
        bv = MockBinaryView()
        la = LowLevelAnalysis(func, bv, MagicMock())
        _, result = la._collect_low_level_and_with_addr()
        assert len(result) == 1
        offset, _ = result[0]
        assert offset == 0  # Clamped to 0