Hanni Muukkonen

32 papers B 1Journal 13Unranked 17
YearRankTypeTitle / Venue / Authors
2026 J jnl
Comput. Educ.
Anceli Kaveri, Ismail Celik, Egle Gedrimiene, Anni Silvola, Hanni Muukkonen
2025 J jnl
Br. J. Educ. Technol.
Anni Silvola, Anu Kajamaa, Joonas Merikko, Hanni Muukkonen
2025 J jnl
Educ. Inf. Technol.
Anceli Kaveri, Jenni Korpi, Anni Silvola, Hanni Muukkonen
2025 conf
CSEDU (2)
Barbi Svetec, Blazenka Divjak, Bart Rienties, Hanni Muukkonen
2024 J jnl
Educ. Inf. Technol.
Egle Gedrimiene, Ismail Celik, Antti Kaasila, Kati Mäkitalo, Hanni Muukkonen
2023 J jnl
J. Learn. Anal.
Anceli Kaveri, Anni Silvola, Hanni Muukkonen
2023 J jnl
J. Learn. Anal.
Egle Gedrimiene, Ismail Celik, Kati Mäkitalo, Hanni Muukkonen
2022 J jnl
Technol. Knowl. Learn.
Muhterem Dindar, Ismail Celik, Hanni Muukkonen
2022 conf
FLAIEC
Anni Silvola, Jenni Kunnari, Egle Gedrimiene, Hanni Muukkonen
2021 J jnl
Comput. Educ.
Anni Silvola, Piia Näykki, Anceli Kaveri, Hanni Muukkonen
2020 conf
ICLS
Minna Lakkala, Hanni Muukkonen, Liisa Ilomäki, Auli Toom
2020 J jnl
Br. J. Educ. Technol.
Suvi Kauppi, Hanni Muukkonen, Teemu Suorsa, Marjatta Takala
2020 conf
ICLS
Lina Markauskaite, Hanni Muukkonen, Crina Damsa, Kate Thompson, Natasha Arthars, Ismail Celik, Molly Sutphen, Rachelle Esterhazy, Tone Dyrdal Solbrekke, Ciaran Sugrue, Velda McCune, Penny Wheeler, Daniela Vasco, Yael Kali
2014 conf
HCI (9)
Mikko Heiskala, Eero Palomäki, Matti Vartiainen, Kai Hakkarainen, Hanni Muukkonen
2014 conf
ICLS
Crina Damsa, Hanni Muukkonen, Sten R. Ludvigsen, Monika Nerland, Minna Lakkala, Auli Toom, Kari Kosonen, Liisa Ilomäki, Lina Markauskaite, Peter Goodyear, Agnieszka Bachfischer
2014 conf
HCI (9)
Hanni Muukkonen, Kai Hakkarainen, Shupin Li, Matti Vartiainen
2011 conf
CSCL
Sten R. Ludvigsen, Crina Damsa, Hanni Muukkonen
2011 conf
CSCL
Sami Paavola, Merja Bauters, Christoph Richter, Crina Damsa, Klas Karlgren, Eini Saarivesi, Seppo Toikka, Minna Lakkala, Hanni Muukkonen, Liisa Ilomäki
2010 conf
SaITE
Jaana Holvikivi, Minna Lakkala, Hanni Muukkonen
2010 J jnl
Res. Pract. Technol. Enhanc. Learn.
Hanni Muukkonen, Minna Lakkala, Jyrki Kaistinen, Göte Nyman
2009 J jnl
Int. J. Comput. Support. Collab. Learn.
Hanni Muukkonen, Minna Lakkala
2009 conf
CSCL (1)
Minna Lakkala, Sami Paavola, Kari Kosonen, Hanni Muukkonen, Merja Bauters, Hannu Markkanen
2009 conf
CSCL (1)
Hanni Muukkonen, Mikko Inkinen, Kari Kosonen, Kai Hakkarainen, Petri Vesikivi, Hanna Lachmann, Klas Karlgren
2008 conf
ICLS (2)
Hanni Muukkonen, Kai Hakkarainen, Mikko Inkinen, Kirsti Lonka, Katariina Salmela-Aro
2008 J jnl
Res. Pract. Technol. Enhanc. Learn.
Minna Lakkala, Hanni Muukkonen, Sami Paavola, Kai Hakkarainen
2007 conf
CSCL
Hanni Muukkonen, Kai Hakkarainen, Kari Kosonen, Satu Jalonen, Annamari Heikkilä, Kirsti Lonka, Katariina Salmela-Aro, Juha Linnanen, Kari Salo
2006 B conf
EC-TEL
Kai Hakkarainen, Liisa Ilomäki, Sami Paavola, Hanni Muukkonen, Hanna Toiviainen, Hannu Markkanen, Christoph Richter
2006 conf
ICLS
Kai Hakkarainen, Hanni Muukkonen, Hannu Markkanen
2006 conf
ICLS
Hanni Muukkonen, Minna Lakkala
2005 ch.
Encyclopedia of Information Science and Technology (V)
Hanni Muukkonen, Minna Lakkala, Kai Hakkarainen
2000 J jnl
Comput. Educ.
Kai Hakkarainen, Liisa Ilomäki, Lasse Lipponen, Hanni Muukkonen, Marjaana Rahikainen, Taneli Tuominen, Minna Lakkala, Erno Lehtinen
1999 conf
CSCL
Hanni Muukkonen, Kai Hakkarainen, Minna Lakkala
tests/unit/test_decompile_medium_level.py
← Index tests/unit/test_decompile_medium_level.py python
# tests/unit/test_decompile_medium_level.py
"""Unit tests (mocked BN) for bninja/analysis/medium_level.py
   and bninja/analysis/medium_level_normalization.py."""
# tests/unit/test_decompile_medium_level.py
import sys
from unittest.mock import MagicMock, patch

# Installa gli stubs BN
from tests.unit.conftest_binja_stubs import install_binja_stubs
install_binja_stubs()

# ── Definisci MockMLILInstruction PRIMA di importare il modulo ──
class MockMLILInstruction:
    def __init__(self, operation, address=0, operands=None):
        self.operation = operation
        self.address = address
        self.operands = operands or []

# ── Patcha il modulo BN in modo che isinstance() funzioni ──
sys.modules["binaryninja"].MediumLevelILInstruction = MockMLILInstruction
sys.modules["binaryninja"].SSAVariable = type("SSAVariable", (), {})
sys.modules["binaryninja"].Variable = type("Variable", (), {})
sys.modules["binaryninja"].ILIntrinsic = type("ILIntrinsic", (), {})

# Ora importa il modulo — vede già i tipi corretti
from redb.extractors.decompiler.bninja.analysis.medium_level_normalization import (
    MediumLevelNormalization,
)	

class MockMLILFunction:
    def __init__(self, instructions):
        self._instructions = instructions

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

    @property
    def basic_blocks(self):
        # one block containing all instructions, good enough for MinHasher
        block = MagicMock()
        block.__iter__ = lambda self_: iter([])  # not used by MediumLevelAnalysis
        return [block]


class MockFunction:
    def __init__(self, name="func", start=0x1000, mlil=None):
        self.name = name
        self.start = start
        self.mlil = mlil



class TestMediumLevelNormalization:
    def setup_method(self):
        from redb.extractors.decompiler.bninja.analysis.medium_level_normalization import (
            MediumLevelNormalization,
        )
        self.norm = MediumLevelNormalization()

    def test_normalize_skeleton_single_instruction(self):
        il = MockMLILInstruction(operation=42, operands=[])
        result = self.norm.normalize_instruction_all_levels(il)
        assert result == [42]

    def test_normalize_skeleton_nested(self):
        inner = MockMLILInstruction(operation=7, operands=[])
        outer = MockMLILInstruction(operation=1, operands=[inner])
        result = self.norm.normalize_instruction_all_levels(outer)
        assert result == [1, 7]

    def test_normalize_skeleton_with_list_operand(self):
        inner_a = MockMLILInstruction(operation=10, operands=[])
        inner_b = MockMLILInstruction(operation=11, operands=[])
        outer = MockMLILInstruction(operation=2, operands=[[inner_a, inner_b]])
        result = self.norm.normalize_instruction_all_levels(outer)
        assert result == [2, 10, 11]

    def test_normalize_skeleton_none(self):
        result = self.norm.normalize_instruction_all_levels(None)
        # collect on None should leave ops empty
        assert result == []

    def test_normalize_typed_appends_leaf_types(self):
        # operand is a plain int -> "CONST"
        il = MockMLILInstruction(operation=3, operands=[42])
        result = self.norm.normalize_instr_with_operands(il)
        assert result == [3, "CONST"]

    def test_normalize_typed_bool_before_int(self):
        # bool must be detected before int (since bool is an int subclass)
        il = MockMLILInstruction(operation=4, operands=[True])
        result = self.norm.normalize_instr_with_operands(il)
        assert result == [4, "BOOL"]

    def test_normalize_typed_float(self):
        il = MockMLILInstruction(operation=5, operands=[1.5])
        result = self.norm.normalize_instr_with_operands(il)
        assert result == [5, "FLOAT_CONST"]

    def test_normalize_typed_str(self):
        il = MockMLILInstruction(operation=6, operands=["hello"])
        result = self.norm.normalize_instr_with_operands(il)
        assert result == [6, "STR"]

    def test_normalize_typed_unknown_falls_back_to_typename(self):
        class Weird:
            pass
        il = MockMLILInstruction(operation=8, operands=[Weird()])
        result = self.norm.normalize_instr_with_operands(il)
        assert result == [8, "WEIRD"]

    def test_normalize_typed_nested_mlil(self):
        inner = MockMLILInstruction(operation=99, operands=[7])
        outer = MockMLILInstruction(operation=1, operands=[inner])
        result = self.norm.normalize_instr_with_operands(outer)
        assert result == [1, 99, "CONST"]

    def test_normalize_typed_list_mixed(self):
        inner = MockMLILInstruction(operation=50, operands=[])
        il = MockMLILInstruction(operation=2, operands=[[inner, 99]])
        result = self.norm.normalize_instr_with_operands(il)
        assert result == [2, 50, "CONST"]


class TestMediumLevelAnalysis:
    def _make_analysis(self, instructions=None, mlil=True, start=0x1000):
        from redb.extractors.decompiler.bninja.analysis.medium_level import (
            MediumLevelAnalysis,
        )
        mlil_func = MockMLILFunction(instructions or []) if mlil else None
        func = MockFunction(name="testfunc", start=start, mlil=mlil_func)
        bv = MagicMock()
        return MediumLevelAnalysis(func, bv, MagicMock())

    def test_collect_returns_empty_when_no_mlil(self):
        a = self._make_analysis(mlil=False)
        sk, sk_addr, ty, ty_addr = a._collect_mlil_skeleton_and_typed()
        assert sk == [] and sk_addr == [] and ty == [] and ty_addr == []

    def test_collect_skeleton_and_typed_basic(self):
        instrs = [
            MockMLILInstruction(operation=1, address=0x1000, operands=[]),
            MockMLILInstruction(operation=2, address=0x1004, operands=[42]),
        ]
        a = self._make_analysis(instructions=instrs, start=0x1000)
        sk, sk_addr, ty, ty_addr = a._collect_mlil_skeleton_and_typed()

        assert sk == [[1], [2]]
        assert ty == [[1], [2, "CONST"]]
        assert sk_addr == [(0, [1]), (4, [2])]
        assert ty_addr == [(0, [1]), (4, [2, "CONST"])]

    def test_collect_negative_offset_clamped_to_zero(self):
        instrs = [
            MockMLILInstruction(operation=1, address=0x900, operands=[]),
        ]
        a = self._make_analysis(instructions=instrs, start=0x1000)
        _, sk_addr, _, ty_addr = a._collect_mlil_skeleton_and_typed()
        assert sk_addr[0][0] == 0
        assert ty_addr[0][0] == 0

    def test_log_error_records_entry(self):
        a = self._make_analysis()
        a.log_error("boom", "fname", 0x1234, ValueError("x"), "loc")
        assert len(a.errors) == 1
        err = a.errors[0]
        assert err["function_name"] == "fname"
        assert err["function_address"] == "4660"  # hex 0x1234
        assert err["error_location"] == "loc"
        assert err["error_message"] == "boom"
        assert err["error_type"] == "ValueError"
        assert "timestamp" in err

    @patch(
        "redb.extractors.decompiler.bninja.analysis.medium_level.MinHasher"
    )
    def test_analyze_returns_expected_keys(self, mock_minhasher):
        mock_minhasher.return_value.calculateMinHash.return_value = [1, 2, 3]

        instrs = [
            MockMLILInstruction(operation=1, address=0x1000, operands=[]),
            MockMLILInstruction(operation=2, address=0x1004, operands=[42]),
            MockMLILInstruction(operation=3, address=0x1008, operands=[]),
        ]
        a = self._make_analysis(instructions=instrs, start=0x1000)
        result, errors = a.analyze()

        expected_keys = {
            "function_address",
            "body_mlil_skeleton_vector",
            "sha256_mlil_skeleton",
            "tlsh_mlil_skeleton",
            "minhash_mlil_skeleton",
            "body_mlil_typed_vector",
            "sha256_mlil_typed",
            "tlsh_mlil_typed",
            "minhash_mlil_typed",
        }
        assert set(result.keys()) == expected_keys
        assert result["function_address"] == 0x1000
        assert result["minhash_mlil_skeleton"] == [1, 2, 3]
        assert result["minhash_mlil_typed"] == [1, 2, 3]
        assert errors == []

    @patch(
        "redb.extractors.decompiler.bninja.analysis.medium_level.MinHasher"
    )
    def test_analyze_empty_mlil(self, mock_minhasher):
        mock_minhasher.return_value.calculateMinHash.return_value = []
        a = self._make_analysis(mlil=False)
        result, errors = a.analyze()
        assert result["body_mlil_skeleton_vector"] == []
        assert result["body_mlil_typed_vector"] == []
        assert errors == []

    @patch(
        "redb.extractors.decompiler.bninja.analysis.medium_level.MinHasher"
    )
    def test_analyze_sha256_differs_skeleton_vs_typed(self, mock_minhasher):
        mock_minhasher.return_value.calculateMinHash.return_value = []

        instrs = [
            MockMLILInstruction(operation=1, address=0x1000, operands=[42]),
            MockMLILInstruction(operation=2, address=0x1004, operands=["foo"]),
            MockMLILInstruction(operation=3, address=0x1008, operands=[True]),
        ]
        a = self._make_analysis(instructions=instrs)
        result, _ = a.analyze()
        # skeleton ignores operand leaves, typed includes them -> different hashes
        assert result["sha256_mlil_skeleton"] != result["sha256_mlil_typed"]


class TestMinHasherMLILKinds:
    def _make_func(self, instrs):
        # MinHasher iterates basic_blocks then over each block
        block = MagicMock()
        block.__iter__ = lambda self_: iter(instrs)
        f = MagicMock()
        f.basic_blocks = [block]
        return f

    def test_mlil_skeleton_uses_medium_normalizer(self):
        from redb.extractors.decompiler.bninja.similarity.minhasher import (
            MinHasher, TokenKind,
        )
        instrs = [
            MockMLILInstruction(operation=i, operands=[]) for i in range(5)
        ]
        func = self._make_func(instrs)
        hasher = MinHasher(seed=42, il_function=func, kind=TokenKind.MLIL)
        result = hasher.calculateMinHash()
        # 5 instructions -> 3 trigrams -> non-empty signature
        assert result != []

    def test_typed_mlil_differs_from_skeleton(self):
        from redb.extractors.decompiler.bninja.similarity.minhasher import (
            MinHasher, TokenKind,
        )
        instrs = [
            MockMLILInstruction(operation=1, operands=[42]),
            MockMLILInstruction(operation=2, operands=["s"]),
            MockMLILInstruction(operation=3, operands=[True]),
            MockMLILInstruction(operation=4, operands=[1.5]),
        ]
        func = self._make_func(instrs)
        skel = MinHasher(seed=42, il_function=func, kind=TokenKind.MLIL).calculateMinHash()
        typed = MinHasher(seed=42, il_function=func, kind=TokenKind.TYPED_MLIL).calculateMinHash()
        # Same seed, same instructions, but typed has extra leaf tokens
        # -> hashes should generally differ
        assert skel != typed

    def test_mlil_too_few_instructions(self):
        from redb.extractors.decompiler.bninja.similarity.minhasher import (
            MinHasher, TokenKind,
        )
        instrs = [MockMLILInstruction(operation=1, operands=[])] * 2
        func = self._make_func(instrs)
        hasher = MinHasher(seed=42, il_function=func, kind=TokenKind.MLIL)
        assert hasher.calculateMinHash() == []

    def test_unsupported_kind_raises(self):
        from redb.extractors.decompiler.bninja.similarity.minhasher import MinHasher
        func = self._make_func([])
        hasher = MinHasher(seed=42, il_function=func, kind="bogus")
        with pytest.raises(ValueError):
            hasher.calculateMinHash()