Kai J. Miller

32 papers A* 1B 3Journal 20Unranked 7
YearRankTypeTitle / Venue / Authors
2025 J jnl
PLoS Comput. Biol.
Matthew R. Baker, Bryan T. Klassen, Michael A. Jensen, Gabriela Ojeda Valencia, Hossein Heydari, Nuri F. Ince, Klaus-Robert Müller, Kai J. Miller
2025 J jnl
IEEE J. Biomed. Health Informatics
Behrang Fazli Besheli, Zhiyi Sha, Amir Hossein Ayyoubi, Swamy Chandra Prakash, Thomas R. Henry, Gregory A. Worrell, Kai J. Miller, Jonathon J. Parker, David Darrow, Nuri Firat Ince
2024 conf
MWSCAS
Amir Hossein Ayyoubi, Behrang Fazli Besheli, Jhan L. Okkabaz, Swamy Chandra Prakash, Kai J. Miller, Gregory A. Worrell, Nuri F. Ince
2024 conf
EMBC
Behrang Fazli Besheli, Swamy Chandra Prakash, Amir Hossein Ayyoubi, Luciano R. F. Branco, Gregory A. Worrell, Zhiyi Sha, Jay R. Gavvala, Kai J. Miller, Nuri F. Ince
2024 J jnl
NeuroImage
Seth D. König, Sandra Safo, Kai J. Miller, Alexander B. Herman, David Darrow
2024 J jnl
PLoS Comput. Biol.
Irena Balzekas, Joshua Trzasko, Grace Yu, Thomas J. Richner, Filip Mivalt, Vladimir Sladky, Nicholas M. Gregg, Jamie Van Gompel, Kai J. Miller, Paul E. Croarkin, Václav Kremen, Gregory A. Worrell
2023 J jnl
PLoS Comput. Biol.
Kai J. Miller, Klaus-Robert Müller, Gabriela Ojeda Valencia, Harvey Huang, Nicholas M. Gregg, Gregory A. Worrell, Dora Hermes
2022 J jnl
NeuroImage
Manuel R. Mercier, Anne Sophie Dubarry, François Tadel, Pietro Avanzini, Nikolai Axmacher, Dillan Cellier, Maria Del Vecchio, Liberty S. Hamilton, Dora Hermes, Michael J. Kahana, Robert T. Knight, Anaïs Llorens, Pierre Mégevand, Lucia Melloni, Kai J. Miller, Vitória Piai, Aina Puce, Nick F. Ramsey, Caspar M. Schwiedrzik, Sydney E. Smith, Arjen Stolk, Nicole C. Swann, Mariska J. Vansteensel, Bradley Voytek, Liang Wang, Jean-Philippe Lachaux, Robert Oostenveld
2022 B conf
SMC
Filip Mivalt, Vladimir Sladky, Irena Balzekas, Tereza Pridalova, Kai J. Miller, Jamie Van Gompel, Timothy Denison, Benjamin H. Brinkmann, Václav Kremen, Gregory A. Worrell
2022 B conf
SMC
Vladimir Sladky, Václav Kremen, Kevin L. McQuown, Filip Mivalt, Benjamin H. Brinkmann, Jamie Van Gompel, Kai J. Miller, Timothy Denison, Gregory A. Worrell
2021 conf
EMBC
Harvey Huang, Gabriela Ojeda Valencia, Dora Hermes, Kai J. Miller
2021 J jnl
PLoS Comput. Biol.
Kai J. Miller, Klaus-Robert Müller, Dora Hermes
2021 J jnl
Int. J. Neural Syst.
Renée Johnston, Guillaume Doucet, Chadwick B. Boulay, Kai J. Miller, Julio C. Martinez-Trujillo, Adam J. Sachs
2021 J jnl
NeuroImage
Sivylla E. Paraskevopoulou, William G. Coon, Peter Brunner, Kai J. Miller, Gerwin Schalk
2020 conf
EMBC
Thomas J. Richner, Bryan T. Klassen, Kai J. Miller
2019 conf
BCI
Kai J. Miller, Geertjan J. M. Huiskamp, Dorien van Blooijs, Dora Hermes, Tineke A. Gebbink, Cyrille H. Ferrier, Peter C. van Rijen, Peter Gosselaar, Nick F. Ramsey, Frans S. S. Leijten
2017 conf
BCI
Kai J. Miller, Dora Hermes
2016 J jnl
PLoS Comput. Biol.
Kai J. Miller, Gerwin Schalk, Dora Hermes, Jeffrey G. Ojemann, Rajesh P. N. Rao
2015 ch.
Brain-Computer Interface Research (4)
Kai J. Miller, Gerwin Schalk, Dora Hermes, Jeffrey G. Ojemann, Rajesh P. N. Rao
2014 J jnl
NeuroImage
Kai J. Miller, Christopher J. Honey, Dora Hermes, Rajesh P. N. Rao, Marcel den Nijs, Jeffrey G. Ojemann
2014 J jnl
NeuroImage
Dora Hermes, Kai J. Miller, Mariska J. Vansteensel, Erik Edwards, Cyrille H. Ferrier, Martin G. Bleichner, Peter C. van Rijen, Erik J. Aarnoutse, Nick F. Ramsey
2014 conf
EMBC
Kai J. Miller, Jeffrey G. Ojemann, Jaimie M. Henderson
2012 J jnl
Frontiers Comput. Neurosci.
Kai J. Miller, Brett L. Foster, Christopher J. Honey
2012 J jnl
PLoS Comput. Biol.
Kai J. Miller, Dora Hermes, Christopher J. Honey, Adam O. Hebb, Nick F. Ramsey, Robert T. Knight, Jeffrey G. Ojemann, Eberhard E. Fetz
2010 B conf
ICPR
Zuoguan Wang, Qiang Ji, Kai J. Miller, Gerwin Schalk
2010 J jnl
NeuroImage
Felix Darvas, Reinhold Scherer, Jeffrey G. Ojemann, R. P. Rao, Kai J. Miller, Larry B. Sorensen
2009 J jnl
PLoS Comput. Biol.
Kai J. Miller, Larry B. Sorensen, Jeffrey G. Ojemann, Marcel den Nijs
2008 J jnl
IEEE Trans. Biomed. Eng.
Kai J. Miller, Pradeep Shenoy, Marcel den Nijs, Larry B. Sorensen, Rajesh P. N. Rao, Jeffrey G. Ojemann
2008 J jnl
IEEE Trans. Biomed. Eng.
Pradeep Shenoy, Kai J. Miller, Jeffrey G. Ojemann, Rajesh P. N. Rao
2008 J jnl
IEEE Trans. Biomed. Eng.
Pradeep Shenoy, Kai J. Miller, Beau Crawford, Rajesh P. N. Rao
2007 J jnl
NeuroImage
Kai J. Miller, Marcel den Nijs, Pradeep Shenoy, John W. Miller, Rajesh P. N. Rao, Jeffrey G. Ojemann
2005 A* conf
AAAI
Beau Crawford, Kai J. Miller, Pradeep Shenoy, Rajesh P. N. Rao
tests/unit/test_decompile_decompiler.py
← Index tests/unit/test_decompile_decompiler.py python
"""Unit tests (mocked BN) for bninja/decompiler.py — BinaryNinjaDecompiler."""
import sys
import json
import time
import pytest
from unittest.mock import MagicMock, patch, mock_open

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

install_binja_stubs()

from redb.extractors.decompiler.bninja.decompiler import (
    BinaryNinjaDecompiler,
    is_lib_or_thunk,
)
from redb.extractors.decompiler.bninja.function_type import FunctionType


# ============================================================================
# 10a. BinaryNinjaDecompiler
# ============================================================================


class TestBinaryNinjaDecompilerLogError:
    def _make_decompiler(self):
        with patch.object(BinaryNinjaDecompiler, "__init__", lambda self, *a, **kw: None):
            d = BinaryNinjaDecompiler.__new__(BinaryNinjaDecompiler)
            d.log = MagicMock()
            d.errors = []
            d.filepath = "/fake/binary"
            d.bv = None
            d.analysis_results = None
            d.exporters = []
            d.index_prefix = None
            d.filetype = None
            d.goresym = None
            d.BNINJA_TIMEOUT = 60
            return d

    def test_log_error_appends(self):
        d = self._make_decompiler()
        d.log_error("test error", "func1", 0x1000, Exception("boom"), "extract")
        assert len(d.errors) == 1

    def test_log_error_schema(self):
        d = self._make_decompiler()
        d.log_error("test error", "func1", 0x1000, Exception("boom"), "extract")
        error = d.errors[0]
        expected_keys = [
            "function_name",
            "function_address",
            "error_location",
            "error_message",
            "error_details",
            "error_type",
            "timestamp",
        ]
        for key in expected_keys:
            assert key in error, f"Missing key: {key}"


class TestBinaryNinjaDecompilerIsTooFewBlocks:
    def _make_decompiler(self):
        with patch.object(BinaryNinjaDecompiler, "__init__", lambda self, *a, **kw: None):
            d = BinaryNinjaDecompiler.__new__(BinaryNinjaDecompiler)
            d.log = MagicMock()
            d.errors = []
            d.MIN_FUNCTION_SIZE = 10
            return d

    def test_is_too_few_blocks_none(self):
        d = self._make_decompiler()
        func = MagicMock()
        func.basic_blocks = None
        assert d.is_too_few_blocks(func) is False

    def test_is_too_few_blocks_empty(self):
        # Empty basic_blocks now returns True because Binja creates function
        # stubs with empty blocks before analysis completes — we no longer
        # skip these so they get filtered downstream instead.
        d = self._make_decompiler()
        func = MagicMock()
        func.basic_blocks = []
        assert d.is_too_few_blocks(func) is True

    def test_is_too_few_blocks_small_single(self):
        d = self._make_decompiler()
        block = MagicMock()
        block.disassembly_text = [MagicMock() for _ in range(5)]  # < 10
        func = MagicMock()
        func.basic_blocks = [block]
        assert d.is_too_few_blocks(func) is False

    def test_is_too_few_blocks_large_single(self):
        d = self._make_decompiler()
        block = MagicMock()
        block.disassembly_text = [MagicMock() for _ in range(15)]  # >= 10
        func = MagicMock()
        func.basic_blocks = [block]
        assert d.is_too_few_blocks(func) is True

    def test_is_too_few_blocks_multiple(self):
        d = self._make_decompiler()
        func = MagicMock()
        func.basic_blocks = [MagicMock(), MagicMock()]
        assert d.is_too_few_blocks(func) is True


class TestIsLibOrThunk:
    def test_is_lib_or_thunk_user(self):
        func = MagicMock()
        func.symbol.type = SymbolType.FunctionSymbol
        func.is_thunk = False
        assert is_lib_or_thunk(func) is True  # NOT filtered

    def test_is_lib_or_thunk_thunk(self):
        func = MagicMock()
        func.symbol.type = SymbolType.FunctionSymbol
        func.is_thunk = True
        assert is_lib_or_thunk(func) is False  # Filtered out

    def test_is_lib_or_thunk_external(self):
        func = MagicMock()
        func.symbol.type = SymbolType.ImportedFunctionSymbol
        func.is_thunk = False
        assert is_lib_or_thunk(func) is False  # Filtered out


class TestBinaryNinjaDecompilerExtract:
    def _make_decompiler(self):
        with patch.object(BinaryNinjaDecompiler, "__init__", lambda self, *a, **kw: None):
            d = BinaryNinjaDecompiler.__new__(BinaryNinjaDecompiler)
            d.log = MagicMock()
            d.errors = []
            d.filepath = "/fake/binary"
            d.bv = MagicMock()
            d.analysis_results = None
            d.exporters = []
            d.index_prefix = None
            d.filetype = None
            d.goresym = None
            d.BNINJA_TIMEOUT = 60
            d.arch = MagicMock()
            return d

    def test_extract_success(self):
        d = self._make_decompiler()
        mock_results = {
            "decompiled": [{"test": True}],
            "disassembled": [],
            "cfg": [],
            "llil": [],
            "errors": [],
            "strings": [],
            "mlil": [],
        }
        with patch.object(d, "analyze_binary", return_value=mock_results):
            result = d.extract()
            assert result is True
            assert d.analysis_results == mock_results

    def test_extract_failure(self):
        d = self._make_decompiler()
        with patch.object(d, "analyze_binary", return_value=None):
            result = d.extract()
            assert result is False

    def test_tag(self):
        d = self._make_decompiler()
        assert d.tag() == "DECOMPILED"


class TestBinaryNinjaDecompilerAnalyzeBinary:
    def _make_decompiler(self):
        with patch.object(BinaryNinjaDecompiler, "__init__", lambda self, *a, **kw: None):
            d = BinaryNinjaDecompiler.__new__(BinaryNinjaDecompiler)
            d.log = MagicMock()
            d.errors = []
            d.filepath = "/fake/binary"
            d.analysis_results = None
            d.exporters = []
            d.index_prefix = None
            d.filetype = None
            d.goresym = None
            d.BNINJA_TIMEOUT = 60
            d.arch = MagicMock()
            d.MIN_FUNCTION_SIZE = 10
            return d

    def test_analyze_binary_links_hlil_disasm(self):
        """Bi-directional hash linkage between decompiled and disassembled."""
        d = self._make_decompiler()
        d.bv = MagicMock()
        d.bv.functions = []
        # Mock methods to return controlled data
        with patch.object(d, "extract_hlil") as mock_hlil, \
             patch.object(d, "extract_disasm") as mock_disasm, \
             patch.object(d, "extract_cfg", return_value=None), \
             patch.object(d, "extract_lowlevel", return_value=None):

            mock_hlil.return_value = {
                "decompiled_function_hash": "HLIL_HASH",
                "decompiled_function_name": "test",
                "decompiled_function": "code",
            }
            mock_disasm.return_value = {
                "disassembled_function_hash": "DISASM_HASH",
                "disassembled_function_no_addresses": "asm",
            }

            # Manually feed one function
            mock_func = MagicMock()
            mock_func.symbol.type = SymbolType.FunctionSymbol
            mock_func.is_thunk = False
            mock_func.basic_blocks = [MagicMock(), MagicMock()]
            d.bv.functions = [mock_func]

            results = d.analyze_binary()
            if results and results["decompiled"]:
                hlil_r = results["decompiled"][0]
                disasm_r = results["disassembled"][0]
                assert hlil_r["disassembled_function_hash"] == "DISASM_HASH"
                assert disasm_r["decompiled_function_hash"] == "HLIL_HASH"

    def test_analyze_binary_links_llil_disasm(self):
        """Bi-directional linkage between LLIL and disassembly."""
        d = self._make_decompiler()
        d.bv = MagicMock()

        with patch.object(d, "extract_hlil", return_value=None), \
             patch.object(d, "extract_disasm") as mock_disasm, \
             patch.object(d, "extract_cfg", return_value=None), \
             patch.object(d, "extract_lowlevel") as mock_llil:

            mock_disasm.return_value = {
                "disassembled_function_hash": "DISASM_HASH",
                "disassembled_function_no_addresses": "asm",
            }
            mock_llil.return_value = {
                "sha256_llil": "LLIL_HASH",
                "tlsh_llil": "tlsh_val",
            }

            mock_func = MagicMock()
            mock_func.symbol.type = SymbolType.FunctionSymbol
            mock_func.is_thunk = False
            mock_func.basic_blocks = [MagicMock(), MagicMock()]
            d.bv.functions = [mock_func]

            results = d.analyze_binary()
            if results and results["llil"]:
                llil_r = results["llil"][0]
                assert llil_r["disassembled_function_hash"] == "DISASM_HASH"

    def test_analyze_binary_links_cfg_disasm(self):
        """CFG gets disassembled_function_hash."""
        d = self._make_decompiler()
        d.bv = MagicMock()

        with patch.object(d, "extract_hlil", return_value=None), \
             patch.object(d, "extract_disasm") as mock_disasm, \
             patch.object(d, "extract_cfg") as mock_cfg, \
             patch.object(d, "extract_lowlevel", return_value=None):

            mock_disasm.return_value = {
                "disassembled_function_hash": "DISASM_HASH",
                "disassembled_function_no_addresses": "asm",
            }
            mock_cfg.return_value = {
                "function_address": 0x1000,
                "cyclomatic_complexity": 3,
            }

            mock_func = MagicMock()
            mock_func.symbol.type = SymbolType.FunctionSymbol
            mock_func.is_thunk = False
            mock_func.basic_blocks = [MagicMock(), MagicMock()]
            d.bv.functions = [mock_func]

            results = d.analyze_binary()
            if results and results["cfg"]:
                cfg_r = results["cfg"][0]
                assert cfg_r["disassembled_function_hash"] == "DISASM_HASH"

    def test_analyze_binary_sets_cyclomatic(self):
        """cyclomatic_complexity set on disasm from CFG."""
        d = self._make_decompiler()
        d.bv = MagicMock()

        with patch.object(d, "extract_hlil", return_value=None), \
             patch.object(d, "extract_disasm") as mock_disasm, \
             patch.object(d, "extract_cfg") as mock_cfg, \
             patch.object(d, "extract_lowlevel", return_value=None):

            mock_disasm.return_value = {
                "disassembled_function_hash": "HASH",
                "disassembled_function_no_addresses": "asm",
            }
            mock_cfg.return_value = {
                "function_address": 0x1000,
                "cyclomatic_complexity": 7,
            }

            mock_func = MagicMock()
            mock_func.symbol.type = SymbolType.FunctionSymbol
            mock_func.is_thunk = False
            mock_func.basic_blocks = [MagicMock(), MagicMock()]
            d.bv.functions = [mock_func]

            results = d.analyze_binary()
            if results and results["disassembled"]:
                disasm_r = results["disassembled"][0]
                assert disasm_r.get("cyclomatic_complexity") == 7


class TestApplyGoReSym:
    def _make_decompiler(self):
        with patch.object(BinaryNinjaDecompiler, "__init__", lambda self, *a, **kw: None):
            d = BinaryNinjaDecompiler.__new__(BinaryNinjaDecompiler)
            d.log = MagicMock()
            d.errors = []
            d.bv = MagicMock()
            return d

    def test_apply_goresym_user_functions(self):
        d = self._make_decompiler()
        d.goresym = "/tmp/goresym.json"
        data = {
            "UserFunctions": [{"Start": 0x1000, "FullName": "main.main"}],
        }
        with patch("builtins.open", mock_open(read_data=json.dumps(data))):
            d._BinaryNinjaDecompiler__apply_goresym()
            d.bv.define_user_symbol.assert_called()

    def test_apply_goresym_std_functions(self):
        d = self._make_decompiler()
        d.goresym = "/tmp/goresym.json"
        data = {
            "StdFunctions": [{"Start": 0x2000, "FullName": "runtime.main"}],
        }
        with patch("builtins.open", mock_open(read_data=json.dumps(data))):
            d._BinaryNinjaDecompiler__apply_goresym()
            d.bv.define_user_symbol.assert_called()

    def test_apply_goresym_invalid_json(self):
        d = self._make_decompiler()
        d.goresym = "/tmp/goresym.json"
        with patch("builtins.open", mock_open(read_data="not json {")):
            d._BinaryNinjaDecompiler__apply_goresym()
            # Should log error but not crash
            d.log.log_error if hasattr(d.log, 'log_error') else None
            # Just verify no exception raised