Vangelis Sakkalis

81 papers A 1B 1C 10Journal 22Unranked 47
YearRankTypeTitle / Venue / Authors
2025 C conf
BIBE
Chryssalenia Koumpouzi, Matthew Pediaditis, Vangelis Sakkalis
2025 conf
IST
Emmanouil G. Spanakis, Antonio Kung, Amélie Gyrard, Diana Jiménez, Cécile Rabrait, Vangelis Sakkalis
2023 C conf
BIBE
Marios Spanakis, Eleftheria Tzamali, Georgios Tzedakis, Emmanouil G. Spanakis, Aristides Tsatsakis, Vangelis Sakkalis
2023 J jnl
ERCIM News
Emmanouil Spanakis, Evangelos Markakis, Dimitra Papatsaroucha, Ilias Politis, Vangelis Sakkalis
2023 conf
IST
Georgios Tzedakis, Eleftheria Tzamali, Emmanouil G. Spanakis, Marios Antonakakis, Michalis E. Zervakis, Vangelis Sakkalis
2023 conf
EMBC
Ioannis Torakis, Marios Antonakakis, Ekaterini S. Bei, Petros Gikas, Vangelis Sakkalis, Michalis E. Zervakis
2023 conf
EMBC
Nikolaos Haliasos, Matthew Pediaditis, Dimitrios Giakoumettis, Cleanthe Spanaki, Antonis Vakis, Vangelis Sakkalis
2023 conf
EMBC
Emmanouil G. Spanakis, Ilias Politis, Evangelos Markakis, Dimitra Papatsaroucha, Athanasios Vasileios Grammatopoulos, Vaios Bolgouras, Anna Angelogianni, Christos Xenakis, Vangelis Sakkalis
2022 conf
IST
Cristina Farmaki, Nikolaos Zacharioudakis, Matthew Pediaditis, Myrto Krana, Vangelis Sakkalis
2022 C conf
BIBE
Maria Samara, Cristina Farmaki, Nikolaos Zacharioudakis, Matthew Pediaditis, Myrto Krana, Vangelis Sakkalis
2022 conf
IST
Matthew Pediaditis, Cristina Farmaki, Sophia Schiza, Nikolaos Tzanakis, Emmanouil Galanakis, Vangelis Sakkalis
2022 J jnl
ERCIM News
Cristina Farmaki, Matthew Pediaditis, Vangelis Sakkalis
2022 conf
IST
Marios Antonakakis, Aimilios Tzavaras, Konstantinos Tsakos, Emmanouil G. Spanakis, Vangelis Sakkalis, Michalis E. Zervakis, Euripides G. M. Petrakis
2021 C conf
BIBE
Marios Antonakakis, Konstantinos Politof, Georgios A. Klados, Glykeria Sdoukopoulou, Sophia Schiza, Maria Papadogiorgaki, Christina Farmaki, Matthew Pediaditis, Michalis E. Zervakis, Vangelis Sakkalis
2021 conf
EMBC
Matthew Pediaditis, Emmanouil Spanakis, Giannis Zacharakis, Vangelis Sakkalis
2021 J jnl
Frontiers Digit. Health
Emmanouil G. Spanakis, Stelios Sfakianakis, Silvia Bonomi, Claudio Ciccotelli, Sabina Magalini, Vangelis Sakkalis
2021 C conf
BIBE
Maria Papadogiorgaki, Maria Venianaki, Paulos Charonyktakis, Marios Antonakakis, Ioannis Tsamardinos, Michalis E. Zervakis, Vangelis Sakkalis
2021 conf
EMBC
Stelios Sfakianakis, Emmanouil G. Spanakis, Pasquale Mari, Ivan Tesfai Ogbu, Martina Bossini Baroggi, Sabina Magalini, Vangelis Sakkalis
2021 conf
IST
Emmanouil G. Spanakis, Vangelis Sakkalis
2021 conf
EMBC
Myrto Krana, Cristina Farmaki, Matthew Pediaditis, Vangelis Sakkalis
2020 conf
EMBC
Emmanouil G. Spanakis, Silvia Bonomi, Stelios Sfakianakis, Giuseppe Santucci, Simone Lenti, Mara Sorella, Florin Dragos Tanasache, Alessia Palleschi, Claudio Ciccotelli, Vangelis Sakkalis, Sabina Magalini
2020 conf
ICEGOV
Kalliopi Anastasopoulou, Pasquale Mari, Aimilia Magkanaraki, Emmanouil G. Spanakis, Matteo Merialdo, Vangelis Sakkalis, Sabina Magalini
2019 J jnl
IEEE J. Biomed. Health Informatics
Eleftheria Tzamali, Georgios Tzedakis, Vangelis Sakkalis
2019 J jnl
J. Biomed. Informatics
Marios Spanakis, Stelios Sfakianakis, George Kallergis, Emmanouil G. Spanakis, Vangelis Sakkalis
2019 J jnl
ERCIM News
Emmanouil G. Spanakis, Vangelis Sakkalis
2019 C conf
BIBE
Cristina Farmaki, Myrto Krana, Matthew Pediaditis, Emmanouil Spanakis, Vangelis Sakkalis
2019 conf
IST
Vangelis Sakkalis, Matthew Pediaditis, Stelios Sfakianakis
2018 conf
EMBC
Emmanouil G. Spanakis, Maria Psaraki, Vangelis Sakkalis
2018 conf
EMBC
Stylianos E. Psycharakis, Evangelos Liapis, Athanasios Zacharopoulos, Mariam-Eleni Oraiopoulou, Joseph Papamatheakis, Vangelis Sakkalis, Giannis Zacharakis
2018 J jnl
ERCIM News
Cristina Farmaki, Vangelis Sakkalis
2018 conf
BHI
Marios Spanakis, Stelios Sfakianakis, Emmanouil G. Spanakis, George Kallergis, Vangelis Sakkalis
2017 B conf
CBMS
Katerina Giannakaki, Giorgos A. Giannakakis, Christina Farmaki, Vangelis Sakkalis
2016 conf
CGI (Short Papers)
Eleftherios Kontopodis, Ioannis Karatzanis, Vangelis Sakkalis, Francesca Buffa, Kostas Marias
2016 conf
pHealth
Giorgos A. Giannakakis, Matthew Pediaditis, George Stavrinidis, George Konstantinidis, Vangelis Kritsotakis, Vassilios D. Tsakanikas, Michael Ligerakis, Vangelis Sakkalis, Pelagia Vorgia, Manolis Tsiknakis
2016 conf
EMBC
Giorgos Tzedakis, Evangelos Liapis, Eleftheria Tzamali, Giannis Zacharakis, Vangelis Sakkalis
2016 conf
EMBC
Marios Spanakis, Emmanouil G. Spanakis, Haridimos Kondylakis, Stelios Sfakianakis, Irini Genitsaridi, Vangelis Sakkalis, Manolis Tsiknakis, Kostas Marias
2016 conf
EMBC
Christina Farmaki, Georgios Christodoulakis, Vangelis Sakkalis
2015 conf
EMBC
Chryssa H. Thermolia, Ekaterini S. Bei, Euripides G. M. Petrakis, Vangelis Kritsotakis, Manolis Tsiknakis, Vangelis Sakkalis
2015 conf
EMBC
Haridimos Kondylakis, Emmanouil G. Spanakis, Stelios Sfakianakis, Vangelis Sakkalis, Manolis Tsiknakis, Kostas Marias, Xia Zhao, Hongqing Yu, Feng Dong
2015 J jnl
J. Clin. Bioinform.
Ioannis Karatzanis, Kostas Marias, Vangelis Sakkalis
2015 conf
NER
Dimitra Makri, Christina Farmaki, Vangelis Sakkalis
2014 conf
IARWISOI
Ioannis Karatzanis, Athanasios Iliopoulos, Manolis Tsiknakis, Vangelis Sakkalis, Kostas Marias
2014 conf
IARWISOI
Georgios S. Stamatakos, Dimitra D. Dionysiou, Fay Misichroni, Norbert M. Graf, Stefaan W. Van Gool, Rainer Bohle, Feng Dong, Marco Viceconti, Kostas Marias, Vangelis Sakkalis, Nikolaus Forgó, Ravi Radhakrishnan, Helen M. Byrne, Caterina Guiot, Philippe Büchler, Elias Neri, Anca I. D. Bucur, Bernard de Bono, Debora Testi, Manolis Tsiknakis
2014 J jnl
Entropy
Emmanouil Spanakis, Vangelis Sakkalis, Kostas Marias, Apostolos Traganitis
2014 conf
MobiHealth
Emmanouil Spanakis, Dimitris Kafetzopoulos, Po Yang, Kostas Marias, Zhikun Deng, Manolis Tsiknakis, Vangelis Sakkalis, Feng Dong
2014 J jnl
Sensors
Anna Hristoskova, Vangelis Sakkalis, Giorgos Zacharioudakis, Manolis Tsiknakis, Filip De Turck
2014 conf
IARWISOI
Giorgios Tzedakis, Giorgos Grekas, Eleftheria Tzamali, Kostas Marias, Vangelis Sakkalis
2014 J jnl
IEEE J. Biomed. Health Informatics
Vangelis Sakkalis, Stelios Sfakianakis, Eleftheria Tzamali, Kostas Marias, Georgios S. Stamatakos, Fay Misichroni, Eleftherios Ouzounoglou, Eleni A. Kolokotroni, Dimitra D. Dionysiou, David Johnson, Steve McKeever, Norbert M. Graf
2013 conf
EMBC
Vangelis Sakkalis, Giorgos A. Giannakakis, Christina Farmaki, Abdou Mousas, Matthew Pediaditis, Pelagia Vorgia, Manolis Tsiknakis
2013 conf
EMBC
Giorgos A. Giannakakis, Vangelis Sakkalis, Matthew Pediaditis, Christina Farmaki, Pelagia Vorgia, Manolis Tsiknakis
2013 conf
EMBC
Eleftheria Tzamali, Rosy Favicchio, Alexandros Roniotis, Giorgios Tzedakis, Giorgos Grekas, Jorge Ripoll, Kostas Marias, Giannis Zacharakis, Vangelis Sakkalis
2013 C conf
BIBE
Marios Spanakis, Efrosini Papadaki, Dimitris Kafetzopoulos, Apostolos Karantanas, Thomas G. Maris, Vangelis Sakkalis, Konstantinos Marias
2012 conf
EMBC
Vangelis Sakkalis, Georgios C. Manikis, Nikolaos Papanikolaou, Ioannis Karatzanis, Kostas Marias
2012 conf
EMBC
Stelios Sfakianakis, Vangelis Sakkalis, Kostas Marias, Georgios S. Stamatakos, Steve McKeever, Thomas S. Deisboeck, Norbert M. Graf
2012 C conf
BIBE
Jonathan Zepp, Norbert M. Graf, Holger Stenzhorn, Wolfgang Reith, Ioannis Karatzanis, Georgios C. Manikis, Vangelis Sakkalis, Konstantinos Marias, Georgios S. Stamatakos
2012 conf
MobiHealth
Vangelis Sakkalis, Stelios Sfakianakis, Kostas Marias
2012 conf
IARWISOI
Ruslan David, Norbert M. Graf, Ioannis Karatzanis, Holger Stenzhorn, Georgios C. Manikis, Vangelis Sakkalis, Georgios S. Stamatakos, Konstantinos Marias
2012 J jnl
Sensors
Sokratis Kartakis, Vangelis Sakkalis, Panagiotis Tourlakis, Giorgos Zacharioudakis, Constantine Stephanidis
2012 J jnl
IEEE Trans. Inf. Technol. Biomed.
Alexandros Roniotis, Georgios C. Manikis, Vangelis Sakkalis, Michalis E. Zervakis, Ioannis Karatzanis, Kostas Marias
2012 conf
IARWISOI
Giorgios Tzedakis, Eleftheria Tzamali, Vangelis Sakkalis, Alexandros Roniotis, Kostas Marias
2012 J jnl
IEEE Trans. Inf. Technol. Biomed.
Alexandros Roniotis, Vangelis Sakkalis, Ioannis Karatzanis, Michalis E. Zervakis, Kostas Marias
2012 conf
IARWISOI
Stelios Sfakianakis, Vangelis Sakkalis, Kostas Marias
2012 conf
IARWISOI
Alexandros Roniotis, Vangelis Sakkalis, Eleftheria Tzamali, Georgios Tzedakis, Michalis E. Zervakis, Kostas Marias
2012 C conf
BIBE
Eleftheria Tzamali, Vangelis Sakkalis, Konstantinos Marias
2011 J jnl
Comput. Intell. Neurosci.
Eleni G. Christodoulou, Vangelis Sakkalis, Vassilis Tsiaras, Ioannis G. Tollis
2011 conf
MobiHealth
Anna Hristoskova, Vangelis Sakkalis, George Zacharioudakis, Manolis Tsiknakis, Filip De Turck
2011 J jnl
Comput. Biol. Medicine
Vangelis Sakkalis
2011 conf
MobiHealth
Ioannis Basdekis, Vangelis Sakkalis, Constantine Stephanidis
2010 J jnl
ERCIM News
Alexandros Roniotis, Konstantinos Marias, Vangelis Sakkalis
2010 J jnl
Int. J. Comput. Assist. Radiol. Surg.
Cristina Farmaki, Konstantinos Marias, Vangelis Sakkalis, Norbert M. Graf
2009 J jnl
IEEE Trans. Inf. Technol. Biomed.
Vangelis Sakkalis, Ciprian Doru Giurcaneanu, Petros Xanthopoulos, Michalis E. Zervakis, Vassilis Tsiaras, Yinghua Yang, Eleni Karakonstantaki, Sifis Micheloyannis
2009 conf
ICANN (2)
Vasiliki Iordanidou, Kostas Michalopoulos, Vangelis Sakkalis, Michalis E. Zervakis
2009 C conf
ISDA
Vangelis Sakkalis, Konstantinos Marias, Alexandros Roniotis, Emmanouil Skounakis
2009 J jnl
Oper. Res.
Kleanthi Lakiotaki, Pavlos Delias, Vangelis Sakkalis, Nikolaos F. Matsatsinis
2008 A conf
GD
Vassilis Tsiaras, Ioannis G. Tollis, Vangelis Sakkalis
2008 J jnl
Comput. Intell. Neurosci.
Vangelis Sakkalis, Tracey Cassar, Michalis E. Zervakis, Kenneth P. Camilleri, Simon G. Fabri, Cristin Bigan, Eleni Karakonstantaki, Sifis Micheloyannis
2007 J jnl
ERCIM News
Vangelis Sakkalis, Ioannis G. Tollis
2006 conf
EMBC
Petros Xanthopoulos, Spyretta Golemati, Vangelis Sakkalis, Periklis Y. Ktonas, Michalis E. Zervakis, Constantin R. Soldatos
2006 conf
ISBMDA
Theofanis Oikonomou, Vangelis Sakkalis, Ioannis G. Tollis, Sifis Micheloyannis
2006 conf
EMBC
Vangelis Sakkalis, Theofanis Oikonomou, Ellie Pachou, Ioannis G. Tollis, Sifis Micheloyannis, Michalis E. Zervakis
2000 conf
EuroPACS
Stavros Kostomanolakis, G. Kavlentakis, Vangelis Sakkalis, Catherine E. Chronaki, Manolis Tsiknakis, Stelios C. Orphanoudakis
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