Walter Ukovich

103 papers A* 1B 18C 9Journal 39Unranked 36
YearRankTypeTitle / Venue / Authors
2020 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2019 B conf
KES
Alfredo Cuzzocrea, Massimiliano Nolich, Walter Ukovich
2019 conf
ICCSA (1)
Alfredo Cuzzocrea, Massimiliano Nolich, Walter Ukovich
2019 J jnl
IEEE Trans Autom. Sci. Eng.
Lorenzo Abbatecola, Maria Pia Fanti, Giovanni Pedroncelli, Walter Ukovich
2019 B conf
SMC
Maria Pia Fanti, Alberto Locatelli, Gabriella Stecco, Walter Ukovich
2018 B conf
SMC
Maria Pia Fanti, Giorgio Iacobellis, Beatrice Di Pierro, Walter Ukovich, Agostino Marcello Mangini
2018 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Monica Clemente, Maria Pia Fanti, Giorgio Iacobellis, Massimiliano Nolich, Walter Ukovich
2018 conf
CASE
Lorenzo Abbatecola, Maria Pia Fanti, Giovanni Pedroncelli, Walter Ukovich
2018 conf
ICCSA (5)
Alfredo Cuzzocrea, Massimiliano Nolich, Walter Ukovich
2018 J jnl
Internet Technol. Lett.
Michele Roccotelli, Massimiliano Nolich, Maria Pia Fanti, Walter Ukovich
2018 B conf
SMC
Maria Pia Fanti, Agostino Marcello Mangini, Michele Roccotelli, Massimiliano Nolich, Walter Ukovich
2018 C conf
CoDIT
Maria Pia Fanti, Massimiliano Nolich, Michele Roccotelli, Walter Ukovich
2017 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Giorgio Iacobellis, Massimiliano Nolich, Andrea Rusich, Walter Ukovich
2017 conf
CASE
Maria Pia Fanti, Giorgio Iacobellis, Massimiliano Nolich, Andrea Rusich, Walter Ukovich
2017 conf
SOLI
Maria Pia Fanti, Giorgio Iacobellis, Agostino Marcello Mangini, Ilario Precchiazzi, Walter Ukovich
2017 conf
SOLI
Maria Pia Fanti, Giovanni Pedroncelli, Michele Roccotelli, Stefano Mininel, Gabriella Stecco, Walter Ukovich
2017 conf
CASE
Monica Clemente, Maria Pia Fanti, Massimiliano Nolich, Gabriella Stecco, Walter Ukovich
2017 conf
ICNSC
Maria Pia Fanti, Agostino Marcello Mangini, Michele Roccotelli, Walter Ukovich
2017 conf
SOLI
Beatrice Di Pierro, Giorgio Iacobellis, Biagio Turchiano, Walter Ukovich
2016 J jnl
IEEE Trans Autom. Sci. Eng.
Lorenzo Abbatecola, Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2016 conf
WODES
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2016 conf
CASE
Lorenzo Abbatecola, Maria Pia Fanti, Walter Ukovich
2016 C conf
CoDIT
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2016 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Giuliana Rotunno, Gabriella Stecco, Walter Ukovich, Stefano Mininel
2016 C conf
ACC
Maria Pia Fanti, Agostino Marcello Mangini, Lorenzo Abbatecola, Walter Ukovich
2016 conf
CDC
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2016 conf
ECC
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Edward W. Tunstel, Walter Ukovich
2016 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Walter Ukovich, Reha Uzsoy
2016 B conf
SMC
Maria Pia Fanti, Massimiliano Nolich, Stella Simic, Walter Ukovich
2016 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Gabriella Stecco, Walter Ukovich
2015 B conf
SMC
Monica Clemente, Massimiliano Nolich, Walter Ukovich, Maria Pia Fanti
2015 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Agostino Marcello Mangini, Michele Roccotelli, Walter Ukovich
2015 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich
2015 conf
CASE
Maria Pia Fanti, Agostino Marcello Mangini, Michele Roccotelli, Walter Ukovich, Stefano Pizzuti
2015 conf
CASE
Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich
2015 J jnl
Autom.
Maria Pia Fanti, Agostino Marcello Mangini, Francesca Mazzia, Walter Ukovich
2015 J jnl
J. Comput. Sci.
Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich, Valentina Boschian, George K. Georgoulas, Chrysostomos D. Stylios
2015 C conf
ACC
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2015 B conf
SMC
Maria Pia Fanti, Agostino Marcello Mangini, Mauro Franceschelli, Giovanni Pedroncelli, Walter Ukovich
2014 conf
CASE
Maria Pia Fanti, R. Laraspata, Giorgio Iacobellis, Agostino Marcello Mangini, Walter Ukovich, Lorenzo Abbatecola
2014 conf
CASE
Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich, Jean-Jacques Lesage, Kevin Viard
2014 conf
ECC
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2014 C conf
ETFA
Maria Pia Fanti, Walter Ukovich
2014 B conf
SMC
Maria Pia Fanti, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2014 C conf
ACC
Maria Pia Fanti, Stefano Mininel, Massimiliano Nolich, Gabriella Stecco, Walter Ukovich, M. Bernabo, Giovanni Serafino
2014 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Giorgio Iacobellis, Agostino Marcello Mangini, Walter Ukovich
2013 B conf
SMC
Monica Clemente, Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich
2013 conf
CDC
Maria Pia Fanti, Gabriella Stecco, Walter Ukovich
2013 B conf
SMC
Maria Pia Fanti, Walter Ukovich, Agostino Marcello Mangini, Giovanni Pedroncelli
2013 conf
MIM
Valentina Boschian, Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich, Noemi Augenti
2013 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Maria Pia Fanti, Agostino Marcello Mangini, Mariagrazia Dotoli, Walter Ukovich
2013 J jnl
Comput. Ind. Eng.
Nicola Costantino, Mariagrazia Dotoli, Marco Falagario, Maria Pia Fanti, Agostino Marcello Mangini, Fabio Sciancalepore, Walter Ukovich
2013 conf
CASE
Maria Pia Fanti, Giorgio Iacobellis, Giuliana Rotunno, Walter Ukovich
2013 conf
CDC
Maria Pia Fanti, Mauro Franceschelli, Agostino Marcello Mangini, Giovanni Pedroncelli, Walter Ukovich
2013 J jnl
IEEE Trans Autom. Sci. Eng.
Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2013 C conf
ACC
Luca Coslovich, Maria Pia Fanti, Paolo Mania, Giovanni Piccoli, Walter Ukovich
2013 C conf
ETFA
Maria Pia Fanti, Giuliana Rotunno, Agostino Marcello Mangini, Walter Ukovich
2013 A* conf
ICRA
Maria Pia Fanti, Giuliana Rotunno, Gabriella Stecco, Walter Ukovich
2013 B conf
Petri Nets
Monica Clemente, Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2012 conf
WODES
Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich
2012 conf
CDC
Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2012 conf
CASE
Maria Pia Fanti, Giorgio Iacobellis, Agostino Marcello Mangini, Walter Ukovich
2012 conf
ADHS
Maria Pia Fanti, Giorgio Iacobellis, Agostino Marcello Mangini, Walter Ukovich
2012 B conf
SMC
Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2012 J jnl
IEEE Trans. Syst. Man Cybern. Part A
Maria Pia Fanti, Walter Ukovich, Xiaolan Xie, MengChu Zhou
2012 conf
SoSE
Maria Pia Fanti, Giovanni Pedroncelli, Gabriella Stecco, Walter Ukovich
2012 J jnl
Eng. Appl. Artif. Intell.
Maria Pia Fanti, Stefano Mininel, Walter Ukovich, Federica Vatta
2012 C conf
ACC
Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich, Valentina Boschian
2012 J jnl
J. Intell. Manuf.
Agostino Villa, Teresa Taurino, Walter Ukovich
2011 B conf
SMC
Giampaolo Centrone, Walter Ukovich, Maria Pia Fanti, Giorgio Iacobellis
2011 J jnl
IEEE Trans Autom. Sci. Eng.
Valentina Boschian, Mariagrazia Dotoli, Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich
2011 conf
CASE
Nicola Costantino, Mariagrazia Dotoli, Marco Falagario, Maria Pia Fanti, Agostino Marcello Mangini, Fabio Sciancalepore, Walter Ukovich
2011 B conf
SMC
Mariagrazia Dotoli, Maria Pia Fanti, Giuliana Rotunno, Walter Ukovich
2011 conf
CDC/ECC
Valentina Boschian, Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2011 conf
CASE
Maria Pia Fanti, Gabriella Stecco, Walter Ukovich
2010 J jnl
Ann. Oper. Res.
Lorenzo Castelli, Raffaele Pesenti, Walter Ukovich
2010 B conf
SMC
Valentina Boschian, Walter Ukovich, Mariagrazia Dotoli, Maria Pia Fanti, Giorgio Iacobellis
2010 conf
CASE
Nicola Costantino, Mariagrazia Dotoli, Marco Falagario, Maria Pia Fanti, Agostino Marcello Mangini, Fabio Sciancalepore, Walter Ukovich
2010 conf
CASE
Maria Pia Fanti, Gabriella Stecco, Walter Ukovich
2010 B conf
SMC
Valentina Boschian, Maria Pia Fanti, Giorgio Iacobellis, Walter Ukovich
2009 conf
ADHS
Mariagrazia Dotoli, Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2009 B conf
SMC
Stefano Mininel, Federica Vatta, S. Gaion, Walter Ukovich, Maria Pia Fanti
2009 conf
ECC
Romeo Danielis, Mariagrazia Dotoli, Maria Pia Fanti, Agostino Marcello Mangini, Raffaele Pesenti, Gabriella Stecco, Walter Ukovich
2009 J jnl
Autom.
Mariagrazia Dotoli, Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2009 conf
CASE
Mariagrazia Dotoli, Maria Pia Fanti, Agostino Marcello Mangini, Walter Ukovich
2009 conf
CASE
Mariagrazia Dotoli, Maria Pia Fanti, Agostino Marcello Mangini, Gabriella Stecco, Walter Ukovich
2009 B conf
SMC
Maria Pia Fanti, Valentina Boschian, Giorgio Iacobellis, Walter Ukovich
2006 J jnl
Eur. J. Oper. Res.
Luca Coslovich, Raffaele Pesenti, Walter Ukovich
2006 J jnl
Eur. J. Oper. Res.
Luca Coslovich, Raffaele Pesenti, Walter Ukovich
2005 J jnl
Eur. J. Oper. Res.
Marta Omero, Lorenzo D'Ambrosio, Raffaele Pesenti, Walter Ukovich
2004 J jnl
Networks
Raffaele Pesenti, Franca Rinaldi, Walter Ukovich
2004 J jnl
Eur. J. Oper. Res.
Lorenzo Castelli, Raffaele Pesenti, Walter Ukovich
2004 J jnl
Eur. J. Oper. Res.
Lorenzo Castelli, Raffaele Pesenti, Walter Ukovich
2004 J jnl
Transp. Sci.
Lorenzo Castelli, Giovanni Longo, Raffaele Pesenti, Walter Ukovich
2001 J jnl
Eur. J. Oper. Res.
Lorenzo Castelli, Raffaele Pesenti, Walter Ukovich
2000 J jnl
Comput. Oper. Res.
F. Baita, Raffaele Pesenti, Walter Ukovich, Daniela Favaretto
2000 J jnl
IEEE Trans. Autom. Control.
Franco Blanchini, Stefano Miani, Walter Ukovich
2000 J jnl
IEEE Trans. Robotics Autom.
Franco Blanchini, Raffaele Pesenti, Franca Rinaldi, Walter Ukovich
1997 J jnl
SIAM J. Optim.
Franco Blanchini, Franca Rinaldi, Walter Ukovich
1997 J jnl
IEEE Trans. Robotics Autom.
Franco Blanchini, Franca Rinaldi, Walter Ukovich
1996 J jnl
Networks
Franco Blanchini, Maurice Queyranne, Franca Rinaldi, Walter Ukovich
1994 J jnl
Oper. Res.
Maria Grazia Speranza, Walter Ukovich
1989 J jnl
SIAM J. Discret. Math.
Paolo Serafini, Walter Ukovich
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