Haibo Yu

89 papers A 1B 3C 12Misc 2Journal 49Unranked 22
YearRankTypeTitle / Venue / Authors
2026 J jnl
Eng. Appl. Artif. Intell.
Qinna Zhu, Haibo Yu, Li Kang, Jianchao Zeng
2026 J jnl
Neurocomputing
Taizhi Wang, Xin Gao, Xinping Diao, Yuan Li, Haibo Yu, Yukun Lin, Huiting Xu, Yinglan Liu
2025 J jnl
Eng. Appl. Artif. Intell.
Fan Deng, Tao Liu, Haibo Yu, Ruitao Yang
2025 J jnl
IEEE Robotics Autom. Lett.
Wenguang Yang, Zezheng Qiao, Zhizheng Gao, Haibo Yu
2025 J jnl
Expert Syst. Appl.
Zhihang Meng, Xin Gao, Huang Tan, Haibo Yu, Xinping Diao, Tianyang Chen, Qiangwei Li, Yu Hao
2025 conf
OFC
Yunkai Wang, Xinyi Liu, Xianhao Lin, Jifan Cai, Fujie Li, Zhilan Lu, Yiqi Huang, Haibo Yu, Jiabin Ye, Yingjun Zhou, Nan Chi
2025 J jnl
CoRR
Baojiao Xiong, Boheng Chen, Chengzhi Wang, Daxiong Luo, Dongsheng Xu, Dongyang Liu, Fan Yang, Fangyuan Li, Fei Teng, Feng Wang, Fukang Qin, Fuquan Peng, Guanxin Tan, Guozhi Wang, Haibo Yu, Haohao Gao, Heng Liu, Hongbo Yang, Hongjian Zou, Houzheng Shen, Hu Meng, Huan Li, Hui Tan, Jiali Chen, Jianzhao Chen, Jinliang Zhu, Kai Wang, Lei Wu, Liangbing Liu, Liuyang Bian, Liyan He, Long Liu, Peiwen Li, Penggang Shi, Qi Ding, Rui Hu, Shuai Cao, Shuai Ren, Shuang Peng, Teng Xie, Weiji Chen, Weilin Xiang, Weixin Wu, Xi Yin, Xiaoxin Chen, Xu Chen, Yafei Wen, Yan Hu, Yanzhou Yang, Yina Xie, Yinghao Chen, Yixuan Liao, Yu Geng, Yuanjiang Ouyang, Yuanzhuo Yang, Yuehua He, Yushuai Peng, Zhaoxiong Wang, Zheng Wang, Zhibo Zhou, Ziyang Wu
2025 J jnl
Autom. Softw. Eng.
Haibo Yu, Xiaohong Han, Simin Chen, Xiaoning Feng, Guangzhao Sun, Wei Yang
2025 J jnl
Neurocomputing
Haibo Yu, Zhangkai Zheng, Yun Xue, Yiping Song, Zhuoming Liang
2025 J jnl
Digit. Signal Process.
Fan Deng, Haibo Yu, Tao Liu, Ruitao Yang
2025 J jnl
Knowl. Based Syst.
Lingli Chen, Xin Gao, Haibo Yu, Yuan Li, Xinping Diao, Yukun Lin, Taizhi Wang, Le He
2025 J jnl
IEEE Geosci. Remote. Sens. Lett.
Cheng Wang, Haibo Yu, Ling Zhang, Gangsheng Li, Q. M. Jonathan Wu
2025 J jnl
IEEE Geosci. Remote. Sens. Lett.
Jiangnan Zhong, Haibo Yu, Ling Zhang, Gangsheng Li, Q. M. Jonathan Wu
2025 conf
Q-SE
Haibo Yu, Jianjun Zhao
2025 J jnl
J. Real Time Image Process.
Limei Song, Haibo Yu, Yangang Yang, Yu Tong, Siyuan Ren, Chenchao Ye
2024 conf
ICIRA (3)
Mingxi Tu, Jiawen Liang, Hongji Guo, Tianming Zhao, Haibo Yu
2024 J jnl
IEEE Access
Jiyuan Sun, Haibo Yu
2024 J jnl
Inf. Sci.
Qinna Zhu, Haibo Yu, Li Kang, Jianchao Zeng
2024 J jnl
Memetic Comput.
Yaxin Kang, Haibo Yu, Li Kang, Gangzhu Qiao, Dongpeng Guo, Jianchao Zeng
2024 J jnl
Swarm Evol. Comput.
Yiyun Gong, Haibo Yu, Li Kang, Gangzhu Qiao, Dongpeng Guo, Jianchao Zeng
2024 J jnl
Complex Intell. Syst.
Haibo Yu, Yiyun Gong, Li Kang, Chaoli Sun, Jianchao Zeng
2024 J jnl
Neural Comput. Appl.
Yiyun Gong, Haibo Yu, Li Kang, Chaoli Sun, Jianchao Zeng
2024 J jnl
IEEE Robotics Autom. Lett.
Wenguang Yang, Xiaowen Wang, Zhixing Ge, Haibo Yu
2024 conf
ICCT
Haibo Yu, Xuehong Sun, Liping Liu, Tong Yu
2024 J jnl
IEEE Trans. Biomed. Eng.
Quan Gan, Zhixing Ge, Xiaoduo Wang, Songchen Dai, Na Li, Jingang Wang, Lianqing Liu, Haibo Yu
2023 J jnl
Remote. Sens.
Yaning Wang, Haibo Yu, Ling Zhang, Gangsheng Li
2023 J jnl
Complex Intell. Syst.
Haibo Yu, Yaxin Kang, Li Kang, Jianchao Zeng
2023 J jnl
Neurocomputing
Guojun Lu, Haibo Yu, Zehao Yan, Yun Xue
2023 J jnl
CoRR
Hui Sun, Hao Luo, Feifei Wang, Qingjiu Chen, Meng Chen, Xiaoduo Wang, Haibo Yu, Guanglie Zhang, Lianqing Liu, Jianping Wang, Dapeng Wu, Wen Jung Li
2023 conf
QRS Companion
Vo Dai Trinh, Yuri Nishimura, Haibo Yu
2023 J jnl
J. Inf. Sci. Eng.
Haibo Yu, Tao Zhang, Wenhuan Zhu, Li Zhang, Jiali Jin
2023 J jnl
IEEE Access
Weiyi Wang, Dongsheng Yu, Haibo Yu, Minghan Yang, Chidong Xu, Xiaodong Fang
2022 J jnl
Complex Intell. Syst.
Hao Wang, Chaoli Sun, Haibo Yu, Xiaobo Li
2022 J jnl
BMC Medical Informatics Decis. Mak.
Yujie Li, Yingshan Shen, Xiaomao Fan, Xingxian Huang, Haibo Yu, Gansen Zhao, Wenjun Ma
2022 J jnl
Multim. Tools Appl.
Haibo Yu, Ran Ma, Min Su, Ping An, Kai Li
2022 J jnl
J. Vis. Commun. Image Represent.
Lu Liu, Shenghui Wang, Lili Wan, Haibo Yu
2022 J jnl
IET Circuits Devices Syst.
Wenhao Chai, Yaxun Yang, Haibo Yu, Fuli Yang, Zhikui Yang
2022 J jnl
Symmetry
Haibo Yu, Liang Chen
2021 conf
ISBRA
Yidan Dai, Yuanyuan Zhuo, Xingxian Huang, Haibo Yu, Xiaomao Fan
2021 conf
ICIRA (4)
Yu Shan, Yanzhi Zhao, Kaida Guo, Dongyang Xu, Wannan Zhao, Haibo Yu
2021 conf
EMBC
Danqi Hong, Xingxian Huang, Yingshan Shen, Haibo Yu, Xiaomao Fan, Gansen Zhao, Wenbin Lei, Haoyu Luo
2021 J jnl
Signal Process. Image Commun.
Lu Liu, Haibo Yu, Shenghui Wang, Lili Wan, Shanshan Han
2021 J jnl
IEEE Access
Xiang Yin, Yanni Han, Hongyu Sun, Zhen Xu, Haibo Yu, Xiaoyu Duan
2021 conf
WI/IAT (Workshop/Special Session)
Guojun Lu, Haibo Yu, Yun Xue, Zhixun Qiu, Weiyu Zhong
2021 conf
ROBIO
Wenxiu Zhao, Haibo Yu, Yangdong Wen, Xiaoduo Wang, Lianqing Liu, Wen Jung Li
2021 conf
ICVISP
Haibo Yu, Hui Xu
2020 C conf
ISCC
Xiang Yin, Yanni Han, Hongyu Sun, Zhen Xu, Haibo Yu, Xiaoyu Duan
2020 J jnl
IEEE Signal Process. Lett.
Yunzhao Yang, Haibo Yu, Xianfeng Zhao, Xiaowei Yi
2020 conf
EMBC
Han Cui, Weizheng Zhong, Mingxing Zhu, Naifu Jiang, Xingxian Huang, Kai Lan, Liyu Hu, Shixiong Chen, Zhuoxin Yang, Haibo Yu, Guanglin Li
2020 B conf
TrustCom
Xiaonan Song, Aimin Yu, Haibo Yu, Shirun Liu, Xin Bai, Lijun Cai, Dan Meng
2020 J jnl
Appl. Soft Comput.
Haibo Yu, Li Kang, Ying Tan, Chaoli Sun, Jianchao Zeng
2019 B conf
CEC
Hao Wang, Chaoli Sun, Yaochu Jin, Shufen Qin, Haibo Yu
2019 J jnl
J. Medical Imaging Health Informatics
Xinhua Wang, Jihong Ouyang, Yungang Zhu, Haibo Yu, Helong Li
2019 J jnl
Soft Comput.
Haibo Yu, Ying Tan, Chaoli Sun, Jianchao Zeng
2019 J jnl
Knowl. Based Syst.
Haibo Yu, Ying Tan, Chaoli Sun, Jianchao Zeng
2019 J jnl
IEEE Trans. Inf. Forensics Secur.
Xiaowei Yi, Kun Yang, Xianfeng Zhao, Yuntao Wang, Haibo Yu
2019 C conf
ICICS
Meng Li, Lijun Cai, Aimin Yu, Haibo Yu, Dan Meng
2019 C conf
CIS
Helong Li, Haibo Yu, Jinshuai Yuan
2018 C conf
IWDW
Shiyang Zhang, Hong Zhang, Xianfeng Zhao, Haibo Yu
2018 J jnl
Inf. Sci.
Haibo Yu, Ying Tan, Jianchao Zeng, Chaoli Sun, Yaochu Jin
2018 J jnl
Multim. Tools Appl.
Zengzhen Zhao, Qingxiao Guan, Xianfeng Zhao, Haibo Yu, Changjun Liu
2017 conf
ICSAI
Sitao Li, Haibo Yu, Helong Li, Jinquan Zhao, Jianzhi Liu, Zhibin Zheng, Jing Zhang, Lixuan Jia
2017 C conf
HIS
Yingying Wang, Jianfeng Liu, Haibo Yu, Yunpeng Cai
2017 B conf
CEC
Haibo Yu, Ying Tan, Chao-Li Sun, Jianchao Zeng
2017 conf
SSCI
Jie Tian, Chaoli Sun, Jianchao Zeng, Haibo Yu, Ying Tan, Yaochu Jin
2017 J jnl
Appl. Math. Lett.
Haibo Yu, Hao Xu, Mingxuan Zhu
2017 conf
ICNC-FSKD
Xiaojun Xing, Zhen Ma, Haibo Yu, Tuo Li, Zhuoya Wang, Yaohong Qu
2016 C conf
IWDW
Xin He, Qingxiao Guan, Yanfei Tong, Xianfeng Zhao, Haibo Yu
2016 conf
SSCI
Haibo Yu, Yin Tan, Chao-Li Sun, Jianchao Zeng, Yaochu Jin
2016 C conf
IWDW
Hong Zhang, Yun Cao, Xianfeng Zhao, Haibo Yu, Changjun Liu
2016 J jnl
IEEE Robotics Autom. Lett.
Haibo Yu, Liao Wu, Keyu Wu, Hongliang Ren
2016 C conf
IWDW
Zengzhen Zhao, Qingxiao Guan, Xianfeng Zhao, Haibo Yu, Changjun Liu
2016 J jnl
Int. J. Digit. Crime Forensics
Xianfeng Zhao, Jie Zhu, Haibo Yu
2016 conf
CITS
Haibo Yu, Guoqiang Bai
2015 Misc conf
TASE
Huikang Hao, Zhoujun Li, Haibo Yu
2015 A conf
ICME
Peipei Wang, Yun Cao, Xianfeng Zhao, Haibo Yu
2015 C conf
ISCAS
Haibo Yu, Guoqiang Bai
2015 J jnl
IEEE Commun. Lett.
Yun Cao, Hong Zhang, Xianfeng Zhao, Haibo Yu
2015 Misc conf
FAW
Haibo Yu, Guoqiang Bai, Huikang Hao
2015 conf
NEMS
Wenguang Yang, Haibo Yu, Yuechao Wang, Lianqing Liu
2015 C conf
HIS
Furu Xiang, Wenxuan Guan, Xingxian Huang, Xiaomao Fan, Yunpeng Cai, Haibo Yu
2015 J jnl
Micromachines
Wenguang Yang, Haibo Yu, Wenfeng Liang, Yuechao Wang, Lianqing Liu
2015 C conf
ICICS
Haibo Yu, Guoqiang Bai
2015 C conf
IH&MMSec
Yun Cao, Hong Zhang, Xianfeng Zhao, Haibo Yu
2014 conf
ChinaSIP
Xianfeng Zhao, Haibo Yu, Jie Zhu, Yong Deng
2013 conf
BIBM
Yujie Yang, Yunpeng Cai, Wenshu Luo, Zhifeng Li, Zhenghui Ma, Xiaolu Yu, Haibo Yu
2012 conf
SSD
Sergei Loschek, Sascha Hermann, Haibo Yu, Stefan E. Schulz, Thomas Gessner
2009 conf
NEMS
Haibo Yu, Niandong Jiao, Zaili Dong, Yanli Qu, Wen Jung Li, Yuechao Wang
2006 J jnl
Int. J. Electron. Bus.
Jiangyu Li, Haibo Yu
tests/unit/test_cfg_features.py
← Index tests/unit/test_cfg_features.py python
"""
Unit tests for cfg_features.py — all new CFG feature computations.

These tests use plain Python data structures (index-based adjacency lists)
and require no Binary Ninja dependency.
"""
import pytest

from redb.extractors.decompiler.bninja.analysis.cfg_features import (
    bfs_order,
    bfs_max_depth,
    count_back_edges,
    compute_topology_hash,
    compute_md_index_topdown,
    compute_md_index_bottomup,
    compute_prime_product,
    build_block_features,
    compute_cfg_feature_tlsh,
    compute_wl_minhash,
    pack_adjacency,
    LLIL_OP_CATEGORIES,
    CAT_ARITHMETIC,
    CAT_LOGIC,
    CAT_CALL,
    CAT_MEMORY,
    NUM_WL_MINHASH_PERMS,
)


# ===================================================================
# Helper: common graph topologies
# ===================================================================

def _linear_chain(n):
    """0 -> 1 -> 2 -> ... -> (n-1)"""
    return [[i + 1] if i < n - 1 else [] for i in range(n)]


def _diamond():
    """
    0 -> 1, 0 -> 2, 1 -> 3, 2 -> 3
    (classic if/else diamond)
    """
    return [[1, 2], [3], [3], []]


def _predecessors_from_successors(successors, n):
    preds = [[] for _ in range(n)]
    for src, targets in enumerate(successors):
        for tgt in targets:
            preds[tgt].append(src)
    return preds


# ===================================================================
# TestBfsOrder
# ===================================================================

class TestBfsOrder:
    def test_empty_graph(self):
        assert bfs_order([], 0) == []

    def test_single_node(self):
        assert bfs_order([[]], 1) == [0]

    def test_linear_chain(self):
        succs = _linear_chain(4)
        assert bfs_order(succs, 4) == [0, 1, 2, 3]

    def test_diamond(self):
        succs = _diamond()
        order = bfs_order(succs, 4)
        assert order[0] == 0
        assert order[-1] == 3
        assert set(order) == {0, 1, 2, 3}

    def test_unreachable_nodes(self):
        # 0 -> 1, node 2 is unreachable
        succs = [[1], [], []]
        order = bfs_order(succs, 3)
        assert order[:2] == [0, 1]
        assert 2 in order  # unreachable appended

    def test_all_nodes_visited(self):
        succs = _diamond()
        order = bfs_order(succs, 4)
        assert len(order) == 4


# ===================================================================
# TestBfsMaxDepth
# ===================================================================

class TestBfsMaxDepth:
    def test_empty_graph(self):
        assert bfs_max_depth([], 0) == 0

    def test_single_block(self):
        assert bfs_max_depth([[]], 1) == 0

    def test_linear_chain(self):
        succs = _linear_chain(5)
        assert bfs_max_depth(succs, 5) == 4

    def test_diamond(self):
        succs = _diamond()
        assert bfs_max_depth(succs, 4) == 2

    def test_wide_graph(self):
        # 0 -> 1, 0 -> 2, 0 -> 3 (all at depth 1)
        succs = [[1, 2, 3], [], [], []]
        assert bfs_max_depth(succs, 4) == 1


# ===================================================================
# TestCountBackEdges
# ===================================================================

class TestCountBackEdges:
    def test_empty_graph(self):
        assert count_back_edges([], 0) == 0

    def test_no_loops(self):
        succs = _linear_chain(3)
        assert count_back_edges(succs, 3) == 0

    def test_single_loop(self):
        # 0 -> 1 -> 2 -> 0 (one back edge: 2->0)
        succs = [[1], [2], [0]]
        assert count_back_edges(succs, 3) == 1

    def test_nested_loops(self):
        # 0 -> 1 -> 2 -> 1 (inner), 2 -> 3 -> 0 (outer)
        succs = [[1], [2], [1, 3], [0]]
        assert count_back_edges(succs, 4) == 2

    def test_self_loop(self):
        # 0 -> 0 (self-loop)
        succs = [[0]]
        assert count_back_edges(succs, 1) == 1

    def test_diamond_no_loops(self):
        succs = _diamond()
        assert count_back_edges(succs, 4) == 0

    def test_single_node_no_loop(self):
        succs = [[]]
        assert count_back_edges(succs, 1) == 0


# ===================================================================
# TestTopologyHash
# ===================================================================

class TestTopologyHash:
    def test_same_graph_same_hash(self):
        succs = _diamond()
        bfs = bfs_order(succs, 4)
        h1 = compute_topology_hash(succs, bfs, 4)
        h2 = compute_topology_hash(succs, bfs, 4)
        assert h1 == h2

    def test_different_graphs_different_hash(self):
        succs1 = _linear_chain(3)
        bfs1 = bfs_order(succs1, 3)
        h1 = compute_topology_hash(succs1, bfs1, 3)

        succs2 = _diamond()
        bfs2 = bfs_order(succs2, 4)
        h2 = compute_topology_hash(succs2, bfs2, 4)

        assert h1 != h2

    def test_returns_16_bytes(self):
        succs = _diamond()
        bfs = bfs_order(succs, 4)
        h = compute_topology_hash(succs, bfs, 4)
        assert isinstance(h, bytes)
        assert len(h) == 16

    def test_isomorphic_graphs_same_hash(self):
        # Graph A: 0->1, 0->2, 1->3, 2->3 (diamond with successors [1,2])
        succs_a = [[1, 2], [3], [3], []]
        # Graph B: same structure but successors listed as [2,1]
        # BFS from 0 will visit them in different order, but after remapping
        # the canonical form should be identical for isomorphic graphs
        succs_b = [[2, 1], [3], [3], []]

        bfs_a = bfs_order(succs_a, 4)
        bfs_b = bfs_order(succs_b, 4)

        h_a = compute_topology_hash(succs_a, bfs_a, 4)
        h_b = compute_topology_hash(succs_b, bfs_b, 4)
        assert h_a == h_b

    def test_empty_graph(self):
        h = compute_topology_hash([], [], 0)
        assert h == b'\x00' * 16

    def test_single_node(self):
        succs = [[]]
        bfs = bfs_order(succs, 1)
        h = compute_topology_hash(succs, bfs, 1)
        assert isinstance(h, bytes)
        assert len(h) == 16


# ===================================================================
# TestMdIndex
# ===================================================================

class TestMdIndex:
    def test_single_block_topdown(self):
        succs = [[]]
        preds = [[]]
        bfs = [0]
        result = compute_md_index_topdown(succs, preds, bfs)
        assert isinstance(result, int)
        assert result > 0

    def test_single_block_bottomup(self):
        succs = [[]]
        preds = [[]]
        result = compute_md_index_bottomup(succs, preds, 1)
        assert isinstance(result, int)
        assert result > 0

    def test_linear_chain_topdown_vs_bottomup(self):
        succs = _linear_chain(4)
        preds = _predecessors_from_successors(succs, 4)
        bfs = bfs_order(succs, 4)
        td = compute_md_index_topdown(succs, preds, bfs)
        bu = compute_md_index_bottomup(succs, preds, 4)
        # Top-down and bottom-up should be different for a linear chain
        # (entry has in_deg=0, exit has out_deg=0, so the sequences differ)
        assert td != bu

    def test_deterministic(self):
        succs = _diamond()
        preds = _predecessors_from_successors(succs, 4)
        bfs = bfs_order(succs, 4)
        td1 = compute_md_index_topdown(succs, preds, bfs)
        td2 = compute_md_index_topdown(succs, preds, bfs)
        assert td1 == td2

    def test_different_graphs_different_index(self):
        succs1 = _linear_chain(3)
        preds1 = _predecessors_from_successors(succs1, 3)
        bfs1 = bfs_order(succs1, 3)
        td1 = compute_md_index_topdown(succs1, preds1, bfs1)

        succs2 = _diamond()
        preds2 = _predecessors_from_successors(succs2, 4)
        bfs2 = bfs_order(succs2, 4)
        td2 = compute_md_index_topdown(succs2, preds2, bfs2)

        assert td1 != td2

    def test_topdown_empty(self):
        assert compute_md_index_topdown([], [], []) == 0

    def test_bottomup_empty(self):
        assert compute_md_index_bottomup([], [], 0) == 0


# ===================================================================
# TestPrimeProduct
# ===================================================================

class TestPrimeProduct:
    def test_empty(self):
        assert compute_prime_product([]) == 0

    def test_known_sequence(self):
        # Use actual LLIL enum values from conftest_binja_stubs:
        # LLIL_NOP=0 -> prime 1, LLIL_LOAD=4 -> prime 5
        from redb.extractors.decompiler.bninja.analysis.cfg_features import LLIL_OP_PRIMES
        nop_val = 0   # LLIL_NOP
        load_val = 4  # LLIL_LOAD
        expected = LLIL_OP_PRIMES.get(nop_val, 1) * LLIL_OP_PRIMES.get(load_val, 1)
        result = compute_prime_product([nop_val, load_val])
        assert result == expected

    def test_order_independence(self):
        # LLIL_LOAD=4, LLIL_STORE=5, LLIL_ADD=13
        ops_a = [4, 5, 13]
        ops_b = [13, 4, 5]
        assert compute_prime_product(ops_a) == compute_prime_product(ops_b)

    def test_unknown_ops_map_to_1(self):
        # Unknown ops get prime 1, so they don't change the product
        result_known = compute_prime_product([4])  # LLIL_LOAD -> 5
        result_with_unknown = compute_prime_product([4, 9999])  # LOAD * unknown(1)
        assert result_known == result_with_unknown

    def test_mod_2_64(self):
        # Product should be mod 2^64
        result = compute_prime_product([4] * 1000)  # LLIL_LOAD
        assert 0 <= result < 2**64

    def test_single_op(self):
        # LLIL_STORE=5 -> prime 7
        assert compute_prime_product([5]) == 7


# ===================================================================
# TestBuildBlockFeatures
# ===================================================================

class TestBuildBlockFeatures:
    def test_empty_llil(self):
        succs = [[1], []]
        features = build_block_features([[], []], succs, 2)
        assert len(features) == 2
        # All zeros except successor_count
        assert features[0] == [0, 0, 0, 0, 0, 0, 0, 1]  # 1 successor
        assert features[1] == [0, 0, 0, 0, 0, 0, 0, 0]  # 0 successors

    def test_correct_categorization(self):
        # Set up categories for testing
        import redb.extractors.decompiler.bninja.analysis.cfg_features as cf
        old_cats = cf.LLIL_OP_CATEGORIES.copy()
        cf.LLIL_OP_CATEGORIES.update({
            100: CAT_ARITHMETIC,
            101: CAT_ARITHMETIC,
            200: CAT_LOGIC,
            300: CAT_CALL,
            400: CAT_MEMORY,
        })
        try:
            block_ops = [[100, 101, 200, 300, 400]]
            succs = [[]]
            features = build_block_features(block_ops, succs, 1)
            assert features[0][0] == 5   # instr_count
            assert features[0][1] == 2   # arithmetic
            assert features[0][2] == 1   # logic
            assert features[0][4] == 1   # call
            assert features[0][6] == 1   # memory
        finally:
            cf.LLIL_OP_CATEGORIES.clear()
            cf.LLIL_OP_CATEGORIES.update(old_cats)

    def test_cap_at_65535(self):
        # More than 65535 ops in one block
        huge_ops = [0] * 70000  # NOP x 70000
        succs = [[]]
        features = build_block_features([huge_ops], succs, 1)
        assert features[0][0] == 65535  # capped

    def test_missing_block_ops(self):
        # block_llil_ops shorter than n
        succs = [[1], [2], []]
        features = build_block_features([[1, 2]], succs, 3)
        assert len(features) == 3
        # Block 1 and 2 get empty ops since block_llil_ops only has 1 entry
        assert features[1] == [0, 0, 0, 0, 0, 0, 0, 1]
        assert features[2] == [0, 0, 0, 0, 0, 0, 0, 0]


# ===================================================================
# TestCfgFeatureTlsh
# ===================================================================

class TestCfgFeatureTlsh:
    def test_too_few_blocks_returns_none(self):
        # 5 blocks = 5 * 9 bytes = 45 < 50
        bb_features = [[10, 1, 0, 2, 0, 1, 1, 2]] * 5
        bfs = list(range(5))
        result = compute_cfg_feature_tlsh(bb_features, bfs)
        assert result is None

    def test_uniform_data_returns_none(self):
        # 7 identical blocks — TLSH returns TNULL for low-entropy input
        bb_features = [[10, 1, 0, 2, 0, 1, 1, 2]] * 7
        bfs = list(range(7))
        result = compute_cfg_feature_tlsh(bb_features, bfs)
        assert result is None

    def test_varied_data_returns_string(self):
        # 20 blocks with varied features — enough entropy for TLSH
        bb_features = [
            [i * 7 + 3, (i * 13) % 50, (i * 17) % 30, (i * 23) % 40,
             (i * 11) % 20, (i * 7) % 25, (i * 19) % 35, (i * 3) % 10]
            for i in range(20)
        ]
        bfs = list(range(20))
        result = compute_cfg_feature_tlsh(bb_features, bfs)
        assert isinstance(result, str)
        assert len(result) > 0
        assert result.startswith("T1")


# ===================================================================
# TestWlMinhash
# ===================================================================

class TestWlMinhash:
    def test_empty_function(self):
        result = compute_wl_minhash([], [], [], 0)
        assert result == [255] * NUM_WL_MINHASH_PERMS

    def test_returns_128_elements(self):
        succs = _diamond()
        preds = _predecessors_from_successors(succs, 4)
        bb_feats = [[5, 1, 0, 2, 0, 1, 1, 2]] * 4
        result = compute_wl_minhash(succs, preds, bb_feats, 4)
        assert len(result) == 128

    def test_all_uint8(self):
        succs = _linear_chain(3)
        preds = _predecessors_from_successors(succs, 3)
        bb_feats = [[3, 1, 0, 1, 0, 0, 1, 1]] * 3
        result = compute_wl_minhash(succs, preds, bb_feats, 3)
        assert all(0 <= v <= 255 for v in result)

    def test_identical_graphs_same_signature(self):
        succs = _diamond()
        preds = _predecessors_from_successors(succs, 4)
        bb_feats = [[5, 1, 0, 2, 0, 1, 1, 2]] * 4
        sig1 = compute_wl_minhash(succs, preds, bb_feats, 4)
        sig2 = compute_wl_minhash(succs, preds, bb_feats, 4)
        assert sig1 == sig2

    def test_different_graphs_different_signatures(self):
        # Graph 1: linear chain
        succs1 = _linear_chain(4)
        preds1 = _predecessors_from_successors(succs1, 4)
        bb_feats1 = [[5, 1, 0, 2, 0, 1, 1, i] for i in range(4)]
        sig1 = compute_wl_minhash(succs1, preds1, bb_feats1, 4)

        # Graph 2: diamond
        succs2 = _diamond()
        preds2 = _predecessors_from_successors(succs2, 4)
        bb_feats2 = [[10, 3, 2, 1, 0, 0, 0, i] for i in range(4)]
        sig2 = compute_wl_minhash(succs2, preds2, bb_feats2, 4)

        assert sig1 != sig2

    def test_single_node(self):
        succs = [[]]
        preds = [[]]
        bb_feats = [[1, 0, 0, 0, 0, 0, 0, 0]]
        result = compute_wl_minhash(succs, preds, bb_feats, 1)
        assert len(result) == 128


# ===================================================================
# TestPackAdjacency
# ===================================================================

class TestPackAdjacency:
    def test_empty(self):
        assert pack_adjacency([]) == []

    def test_single_edge(self):
        succs = [[1], []]
        edges = pack_adjacency(succs)
        assert len(edges) == 1
        assert edges[0] == (0 << 16) | 1

    def test_correct_packing(self):
        succs = _diamond()
        edges = pack_adjacency(succs)
        assert len(edges) == 4
        # 0->1, 0->2, 1->3, 2->3
        expected = {
            (0 << 16) | 1,
            (0 << 16) | 2,
            (1 << 16) | 3,
            (2 << 16) | 3,
        }
        assert set(edges) == expected

    def test_roundtrip(self):
        """Unpack edges and verify source/target pairs."""
        succs = [[1, 2], [3], [3], []]
        edges = pack_adjacency(succs)
        unpacked = [(e >> 16, e & 0xFFFF) for e in edges]
        expected = [(0, 1), (0, 2), (1, 3), (2, 3)]
        assert sorted(unpacked) == sorted(expected)

    def test_large_index_filtered(self):
        # Create a successor list where index >= 65536
        succs = [[] for _ in range(65537)]
        succs[0] = [65536]  # target is exactly 65536 — should be filtered
        edges = pack_adjacency(succs)
        assert len(edges) == 0

    def test_max_valid_index(self):
        # Index 65535 is the maximum valid
        succs = [[] for _ in range(65536)]
        succs[0] = [65535]
        edges = pack_adjacency(succs)
        assert len(edges) == 1
        assert edges[0] == (0 << 16) | 65535