Ramakant Nevatia

147 papers A* 34A 11B 7Misc 3Journal 53Unranked 36
YearRankTypeTitle / Venue / Authors
2018 A conf
WACV
KangGeon Kim, Zhenheng Yang, Iacopo Masi, Ramakant Nevatia, Gérard G. Medioni
2018 A* conf
AAAI
Zhenheng Yang, Peng Wang, Wei Xu, Liang Zhao, Ramakant Nevatia
2017 B conf
FG
KangGeon Kim, Feng-Ju Chang, Jongmoo Choi, Louis-Philippe Morency, Ramakant Nevatia, Gérard G. Medioni
2017 J jnl
CoRR
Zhenheng Yang, Peng Wang, Wei Xu, Liang Zhao, Ramakant Nevatia
2016 B conf
ICPR
Zhenheng Yang, Ramakant Nevatia
2015 B conf
AVSS
Rémi Trichet, Ramakant Nevatia, J. Brian Burns
2014 A* conf
CVPR
Chen Sun, Ramakant Nevatia
2014 ch.
Fusion in Computer Vision
Gregory K. Myers, Cees G. M. Snoek, Ramakant Nevatia, Ramesh Nallapati, Julien van Hout, Stephanie Pancoast, Chen Sun, Amirhossein Habibian, Dennis C. Koelma, Koen E. A. van de Sande, Arnold W. M. Smeulders
2014 J jnl
Mach. Vis. Appl.
Gregory K. Myers, Ramesh Nallapati, Julien van Hout, Stephanie Pancoast, Ramakant Nevatia, Chen Sun, Amirhossein Habibian, Dennis C. Koelma, Koen E. A. van de Sande, Arnold W. M. Smeulders, Cees G. M. Snoek
2014 J jnl
J. Inf. Process. Syst.
Cyrus Shahabi, Seon Ho Kim, Luciano Nocera, Giorgos Constantinou, Ying Lu, Yinghao Cai, Gérard G. Medioni, Ramakant Nevatia, Farnoush Banaei Kashani
2014 Misc conf
ICASSP
Julien van Hout, Eric Yeh, Dennis C. Koelma, Cees G. M. Snoek, Chen Sun, Ramakant Nevatia, Julie Wong, Gregory K. Myers
2014 A conf
WACV
Pramod Sharma, Ramakant Nevatia
2014 J jnl
Int. J. Comput. Vis.
Bo Yang, Ramakant Nevatia
2014 conf
ECCV (2)
Prithviraj Banerjee, Ramakant Nevatia
2014 B conf
ICPR
Rémi Trichet, Ramakant Nevatia
2014 A conf
WACV
Rémi Trichet, Ramakant Nevatia
2013 J jnl
Comput. Vis. Image Underst.
Pradeep Natarajan, Ramakant Nevatia
2013 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Chang Huang, Yuan Li, Ramakant Nevatia
2013 B conf
AVSS
Rémi Trichet, Ramakant Nevatia
2012 B conf
ICPR
Prithviraj Banerjee, Ramakant Nevatia
2012 conf
ACCV (3)
Weijun Wang, Ramakant Nevatia
2011 ed.
IICAI
Bhanu Prasad, Pawan Lingras, Ramakant Nevatia
2011 conf
CVPR Workshops
Sung Chun Lee, Ramakant Nevatia
2010 ch.
Machine Learning for Human Motion Analysis
Pradeep Natarajan, Ramakant Nevatia
2010 A* conf
CVPR
Chang Huang, Ramakant Nevatia
2009 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Mun Wai Lee, Ramakant Nevatia
2009 A conf
WACV
Cheng-Hao Kuo, Ramakant Nevatia
2008 conf
CVPR Workshops
Li Zhang, Ramakant Nevatia
2008 A* conf
CVPR
Li Zhang, Yuan Li, Ramakant Nevatia
2008 conf
AMDO
Chi-Wei Chu, Ramakant Nevatia
2008 conf
ECCV (2)
Chang Huang, Bo Wu, Ramakant Nevatia
2008 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Tao Zhao, Ramakant Nevatia, Bo Wu
2007 conf
CLEAR
Bo Wu, Vivek Kumar Singh, Cheng-Hao Kuo, Li Zhang, Sung Chun Lee, Ramakant Nevatia
2007 A* conf
ICCV
Bo Wu, Ramakant Nevatia
2007 A* conf
ICCV
Li Zhang, Bo Wu, Ramakant Nevatia
2007 J jnl
Int. J. Comput. Vis.
Bo Wu, Ramakant Nevatia
2007 A* conf
IJCAI
Pradeep Natarajan, Ramakant Nevatia
2007 conf
IICAI
Ramakant Nevatia
2007 conf
ICCV-HCI
Chi-Wei Chu, Ramakant Nevatia
2007 A* conf
CVPR
Fengjun Lv, Ramakant Nevatia
2006 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Fengjun Lv, Tao Zhao, Ramakant Nevatia
2006 conf
ECCV (3)
Mun Wai Lee, Ramakant Nevatia
2006 conf
ECCV (4)
Fengjun Lv, Ramakant Nevatia
2006 conf
AIPR
Sung Chun Lee, Keith E. Price, Ramakant Nevatia, Tom Heinze, John M. Irvine
2005 conf
ICCV-HCI
Fengjun Lv, Ramakant Nevatia, Mun Wai Lee
2005 A* conf
ICCV
Xuefeng Song, Ramakant Nevatia
2005 A* conf
ICCV
Bo Wu, Ramakant Nevatia
2005 conf
WACV/MOTION
Mun Wai Lee, Ramakant Nevatia
2004 J jnl
Comput. Vis. Image Underst.
Zu Whan Kim, Ramakant Nevatia
2004 conf
ICPR (4)
Xuefeng Song, Ramakant Nevatia
2004 conf
CVPR (2)
Sung Chun Lee, Ramakant Nevatia
2004 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Tao Zhao, Ramakant Nevatia
2004 conf
CVPR (2)
Tao Zhao, Ramakant Nevatia
2004 J jnl
Comput. Vis. Image Underst.
Somboon Hongeng, Ramakant Nevatia, François Brémond
2003 conf
CVPR (2)
Tao Zhao, Ramakant Nevatia
2003 J jnl
Image Vis. Comput.
Tao Zhao, Ramakant Nevatia
2003 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Zu Whan Kim, Ramakant Nevatia
2003 J jnl
Mach. Learn.
Marcus A. Maloof, Pat Langley, Thomas O. Binford, Ramakant Nevatia, Stephanie Sage
2003 conf
HLK
Sung Chun Lee, Ramakant Nevatia
2003 A* conf
ICCV
Somboon Hongeng, Ramakant Nevatia
2002 conf
ICPR (1)
Tao Zhao, Ramakant Nevatia
2002 J jnl
Comput. Graph. Forum
Sung Chun Lee, Soon Ki Jung, Ramakant Nevatia
2002 A conf
WACV
Sung Chun Lee, Soon Ki Jung, Ramakant Nevatia
2002 conf
ICIP (3)
Ramakant Nevatia, Keith E. Price
2002 conf
ICPR (4)
Sung Chun Lee, Soon Ki Jung, Ramakant Nevatia
2002 conf
ICPR (1)
Fengjun Lv, Tao Zhao, Ramakant Nevatia
2001 conf
CVPR (2)
Zu Whan Kim, Andres Huertas, Ramakant Nevatia
2001 A* conf
ICCV
Tao Zhao, Ramakant Nevatia
2001 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Sanjay Noronha, Ramakant Nevatia
2001 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Gérard G. Medioni, Isaac Cohen, François Brémond, Somboon Hongeng, Ramakant Nevatia
2001 A* conf
ICCV
Somboon Hongeng, Ramakant Nevatia
2001 conf
CVPR (2)
Tao Zhao, Ramakant Nevatia, Fengjun Lv
2000 A conf
WACV
Zu Whan Kim, Andres Huertas, Ramakant Nevatia
2000 B conf
ICPR
Somboon Hongeng, François Brémond, Ramakant Nevatia
2000 J jnl
Image Vis. Comput.
Andres Huertas, Ramakant Nevatia
2000 A* conf
ICML
Zu Whan Kim, Ramakant Nevatia
2000 A conf
WACV
Sung Chun Lee, Andres Huertas, Ramakant Nevatia
2000 A* conf
CVPR
Andres Huertas, Zu Whan Kim, Ramakant Nevatia
2000 A* conf
CVPR
Somboon Hongeng, François Brémond, Ramakant Nevatia
1999 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Mourad Zerroug, Ramakant Nevatia
1999 J jnl
Auton. Robots
Dongsung Kim, Ramakant Nevatia
1999 J jnl
Comput. Vis. Image Underst.
Zu Whan Kim, Ramakant Nevatia
1999 A* conf
CVPR
J. Li, Ramakant Nevatia, Sanjay Noronha
1998 J jnl
Comput. Vis. Image Underst.
Armin Grün, Ramakant Nevatia
1998 J jnl
Comput. Vis. Image Underst.
Chungan Lin, Ramakant Nevatia
1998 A* conf
ICCV
Andres Huertas, Ramakant Nevatia
1998 A conf
WACV
Marcus A. Maloof, Pat Langley, Thomas O. Binford, Ramakant Nevatia
1998 conf
ACCV (2)
Sanjay Noronha, Ramakant Nevatia
1998 J jnl
Image Vis. Comput.
Dongsung Kim, Ramakant Nevatia
1997 A* conf
CVPR
Sanjay Noronha, Ramakant Nevatia
1996 J jnl
Int. J. Comput. Vis.
Ramakant Nevatia, Gérard G. Medioni
1996 conf
Object Representation in Computer Vision
Mourad Zerroug, Ramakant Nevatia
1996 Misc conf
HiPC
Yongwha Chung, Jongwook Woo, Ramakant Nevatia, Viktor K. Prasanna
1996 J jnl
Int. J. Comput. Vis.
Ronald Chung, Ramakant Nevatia
1996 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Mourad Zerroug, Ramakant Nevatia
1996 J jnl
Int. J. Comput. Vis.
Mourad Zerroug, Ramakant Nevatia
1995 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Fatih Ulupinar, Ramakant Nevatia
1995 J jnl
Comput. Vis. Image Underst.
Ronald Chung, Ramakant Nevatia
1994 A conf
WACV
Dongsung Kim, Ramakant Nevatia
1994 J jnl
Syst. Comput. Jpn.
Shoji Kurakake, Ramakant Nevatia
1994 A* conf
CVPR
Chungan Lin, Andres Huertas, Ramakant Nevatia
1994 conf
ICPR (1)
Mourad Zerroug, Ramakant Nevatia
1994 A conf
WACV
Mathias Bejanin, Andres Huertas, Gérard G. Medioni, Ramakant Nevatia
1994 J jnl
Artif. Intell.
Fatih Ulupinar, Ramakant Nevatia
1994 A* conf
CVPR
Dongsung Kim, Ramakant Nevatia
1994 conf
ICPR (1)
Mourad Zerroug, Ramakant Nevatia
1994 conf
ECCV (1)
Mourad Zerroug, Ramakant Nevatia
1993 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Fatih Ulupinar, Ramakant Nevatia
1993 A* conf
CVPR
Mourad Zerroug, Ramakant Nevatia
1993 conf
Applications of Invariance in Computer Vision
Mourad Zerroug, Ramakant Nevatia
1992 conf
ICPR (1)
Shoji Kurakake, Ramakant Nevatia
1992 J jnl
IEEE Expert
Bir Bhanu, Ramakant Nevatia, Edward M. Riseman
1992 J jnl
IEEE Expert
Bir Bhanu, Ramakant Nevatia, Edward M. Riseman
1992 conf
ICPR (4)
Craig C. Reinhart, Ramakant Nevatia
1992 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Rakesh Mohan, Ramakant Nevatia
1992 A* conf
CVPR
Ronald C.-K. Chung, Ramakant Nevatia
1992 A conf
WACV
Ronald Chi-kit Chung, Ramakant Nevatia
1992 A* conf
CVPR
Fatih Ulupinar, Ramakant Nevatia
1991 J jnl
CVGIP Image Underst.
Fatih Ulupinar, Ramakant Nevatia
1991 A* conf
CVPR
Fatih Ulupinar, Ramakant Nevatia
1991 A* conf
CVPR
Ronald Chung, Ramakant Nevatia
1990 J jnl
Comput. Vis. Graph. Image Process.
Andres Huertas, William Cole, Ramakant Nevatia
1990 conf
ICPR (1)
Fatih Ulupinar, Ramakant Nevatia
1990 conf
ICPR (1)
Kashipati Rao, Ramakant Nevatia
1990 A* conf
ICCV
Fatih Ulupinar, Ramakant Nevatia
1989 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Ting-Jun Fan, Gérard G. Medioni, Ramakant Nevatia
1989 A* conf
CVPR
Rakesh Mohan, Ramakant Nevatia
1989 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Rakesh Mohan, Gérard G. Medioni, Ramakant Nevatia
1989 A* conf
IJCAI
Andres Huertas, William Cole, Ramakant Nevatia
1989 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Rakesh Mohan, Ramakant Nevatia
1988 Misc conf
MVA
Sadaaki Yokoi, Gérard G. Medioni, Ramakant Nevatia
1988 J jnl
Int. J. Comput. Vis.
Kashipati Rao, Ramakant Nevatia
1988 J jnl
Comput. Vis. Graph. Image Process.
Andres Huertas, Ramakant Nevatia
1988 A* conf
ICRA
Ting-Jun Fan, Gérard G. Medioni, Ramakant Nevatia
1988 A* conf
ICCV
Fatih Ulupinar, Ramakant Nevatia
1987 A* conf
AAAI
Andres Huertas, William Cole, Ramakant Nevatia
1987 J jnl
IEEE J. Robotics Autom.
Ting-Jun Fan, Gérard G. Medioni, Ramakant Nevatia
1986 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Felicia M. Vilnrotter, Ramakant Nevatia, Keith E. Price
1985 J jnl
Comput. Vis. Graph. Image Process.
Gérard G. Medioni, Ramakant Nevatia
1984 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Gérard G. Medioni, Ramakant Nevatia
1984 J jnl
Pattern Recognit.
Peter Kaufmann, Gérard G. Medioni, Ramakant Nevatia
1983 A* conf
IJCAI
Andres Huertas, Ramakant Nevatia
1982 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Ramakant Nevatia, Keith E. Price
1977 J jnl
Artif. Intell.
Ramakant Nevatia, Thomas O. Binford
1976 J jnl
IEEE Trans. Computers
Ramakant Nevatia
1975 conf
ACM Annual Conference
Ramakant Nevatia
1973 A* conf
IJCAI
Ramakant Nevatia, Thomas O. Binford
tests/unit/test_apk_process_method.py
← Index tests/unit/test_apk_process_method.py python
"""Mock-based tests for APKCodeAnalyzer._process_method and androguard integration.

Tests the boundary between our code and androguard objects, covering:
- xref extraction (get_xref_from / get_xref_to) for both 4.x and 3.x APIs
- external method skipping
- library method filtering
- smali lookup and androguard fallback disassembly
- deduplication of content hashes
- full output population (all result dict keys)
- minimum instruction filtering
"""
from dataclasses import dataclass
from typing import List
from unittest.mock import MagicMock, Mock, patch, PropertyMock

import pytest

pytestmark = [pytest.mark.unit, pytest.mark.apk, pytest.mark.decompile]


# ---------------------------------------------------------------------------
# Helpers: build mock androguard objects
# ---------------------------------------------------------------------------

SAMPLE_SMALI_BODY = """\
.locals 2
const/4 v0, 0x0
invoke-virtual {p0, v0}, Lcom/example/Foo;->bar(I)V
iget-object v1, p0, Lcom/example/Foo;->name:Ljava/lang/String;
add-int v0, v0, v1
return-void
"""

SAMPLE_JAVA_SOURCE = """\
public void doStuff() {
    int x = 0;
    bar(x);
    String n = this.name;
    x = x + n;
}
"""


def _make_encoded_method(
    class_name="Lcom/example/Foo;",
    method_name="doStuff",
    descriptor="()V",
    code=None,
):
    """Create a mock EncodedMethod."""
    enc = MagicMock()
    enc.get_class_name.return_value = class_name
    enc.get_name.return_value = method_name
    enc.get_descriptor.return_value = descriptor
    enc.get_code.return_value = code
    return enc


def _make_xref_entry_4x(class_name, method_name, offset=0):
    """Create a mock xref tuple in androguard 4.x format.

    4.x: (ClassAnalysis, MethodAnalysis, offset)
    MethodAnalysis has .get_method() returning the EncodedMethod.
    """
    ref_class = MagicMock()
    ref_method = MagicMock()
    inner_enc = MagicMock()
    inner_enc.get_class_name.return_value = class_name
    inner_enc.get_name.return_value = method_name
    ref_method.get_method.return_value = inner_enc
    # Remove direct get_class_name/get_name to simulate 4.x behavior
    del ref_method.get_class_name
    del ref_method.get_name
    return (ref_class, ref_method, offset)


def _make_xref_entry_3x(class_name, method_name, offset=0):
    """Create a mock xref tuple in androguard 3.x format.

    3.x: (ClassAnalysis, EncodedMethod, offset)
    EncodedMethod has .get_class_name() and .get_name() directly.
    """
    ref_class = MagicMock()
    ref_method = MagicMock(spec=[
        "get_class_name", "get_name",
    ])
    ref_method.get_class_name.return_value = class_name
    ref_method.get_name.return_value = method_name
    return (ref_class, ref_method, offset)


def _make_method_analysis(
    encoded=None,
    is_external=False,
    xref_from=None,
    xref_to=None,
):
    """Create a mock MethodAnalysis."""
    method = MagicMock()
    method.is_external.return_value = is_external
    method.get_method.return_value = encoded or _make_encoded_method()
    method.get_xref_from.return_value = xref_from or []
    method.get_xref_to.return_value = xref_to or []
    return method


def _make_analyzer(min_instructions=1):
    """Create an APKCodeAnalyzer with mocked dependencies."""
    with patch(
        "redb.extractors.decompiler.apk.analyzer.JADXDecompiler"
    ), patch(
        "redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler"
    ):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        analyzer = APKCodeAnalyzer("/fake/path.apk")
        analyzer.min_instructions = min_instructions
        analyzer.library_filter = MagicMock()
        analyzer.library_filter.is_library.return_value = False
        return analyzer


def _empty_results():
    """Create a fresh empty results dict."""
    return {
        "decompiled_content": [],
        "decompiled_refs": [],
        "smali_content": [],
        "smali_refs": [],
        "similarity_metrics": [],
        "cfg": [],
        "analysis_errors": [],
    }


def _make_smali_method(body=SAMPLE_SMALI_BODY, instruction_count=5):
    """Create a mock SmaliMethod."""
    m = MagicMock()
    m.body = body
    m.instruction_count = instruction_count
    m.register_count = 2
    return m


# ---------------------------------------------------------------------------
# Tests: xref extraction
# ---------------------------------------------------------------------------

class TestXrefExtraction:
    """Tests that xrefs are correctly extracted from androguard objects."""

    def test_xref_from_androguard_4x(self):
        """4.x: ref_method.get_method().get_class_name() path."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        xref_from = [
            _make_xref_entry_4x("Lcom/example/Caller;", "init", 42),
            _make_xref_entry_4x("Lcom/example/Other;", "run", 100),
        ]
        method = _make_method_analysis(xref_from=xref_from)

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        assert len(results["decompiled_refs"]) == 1
        ref = results["decompiled_refs"][0]
        assert ref["functions_caller"] == [
            "Lcom/example/Caller;->init",
            "Lcom/example/Other;->run",
        ]

    def test_xref_to_androguard_4x(self):
        """4.x: callees extracted via get_method() unwrap."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        xref_to = [
            _make_xref_entry_4x("Lcom/example/Dep;", "calculate", 10),
        ]
        method = _make_method_analysis(xref_to=xref_to)

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        ref = results["decompiled_refs"][0]
        assert ref["functions_call"] == ["Lcom/example/Dep;->calculate"]

    def test_xref_from_androguard_3x_fallback(self):
        """3.x: ref_method.get_class_name() direct access fallback."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        xref_from = [
            _make_xref_entry_3x("Lcom/example/OldCaller;", "legacy"),
        ]
        method = _make_method_analysis(xref_from=xref_from)

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        ref = results["decompiled_refs"][0]
        assert ref["functions_caller"] == ["Lcom/example/OldCaller;->legacy"]

    def test_xref_to_androguard_3x_fallback(self):
        """3.x: callees via direct access fallback."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        xref_to = [
            _make_xref_entry_3x("Lcom/example/OldDep;", "compute"),
        ]
        method = _make_method_analysis(xref_to=xref_to)

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        ref = results["decompiled_refs"][0]
        assert ref["functions_call"] == ["Lcom/example/OldDep;->compute"]

    def test_empty_xrefs(self):
        """No xrefs produces empty lists, not None."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        method = _make_method_analysis()
        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        ref = results["decompiled_refs"][0]
        assert ref["functions_caller"] == []
        assert ref["functions_call"] == []

    def test_xref_exception_does_not_crash(self):
        """If get_xref_from() itself throws, method still processes."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        method = _make_method_analysis()
        method.get_xref_from.side_effect = RuntimeError("xref table corrupt")
        method.get_xref_to.side_effect = RuntimeError("xref table corrupt")

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        # Method should still be processed with empty xrefs
        assert len(results["smali_refs"]) == 1
        ref = results["decompiled_refs"][0]
        assert ref["functions_caller"] == []
        assert ref["functions_call"] == []

    def test_multiple_xrefs(self):
        """Multiple callers and callees are all captured."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        xref_from = [
            _make_xref_entry_4x("Lcom/A;", "a1"),
            _make_xref_entry_4x("Lcom/B;", "b1"),
            _make_xref_entry_4x("Lcom/C;", "c1"),
        ]
        xref_to = [
            _make_xref_entry_4x("Lcom/D;", "d1"),
            _make_xref_entry_4x("Lcom/E;", "e1"),
        ]
        method = _make_method_analysis(xref_from=xref_from, xref_to=xref_to)

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )

        ref = results["decompiled_refs"][0]
        assert len(ref["functions_caller"]) == 3
        assert len(ref["functions_call"]) == 2


# ---------------------------------------------------------------------------
# Tests: method filtering and skipping
# ---------------------------------------------------------------------------

class TestMethodFiltering:
    """Tests for external/library/min-instruction filtering."""

    def test_external_method_skipped(self):
        """External methods (no code body) produce no output."""
        analyzer = _make_analyzer()
        results = _empty_results()

        method = _make_method_analysis(is_external=True)

        analyzer._process_method(
            method, {}, {}, results, set(), set(),
        )

        assert all(len(v) == 0 for v in results.values())

    def test_library_method_skipped(self):
        """Library methods produce no output."""
        analyzer = _make_analyzer()
        analyzer.library_filter.is_library.return_value = True
        results = _empty_results()

        method = _make_method_analysis()

        analyzer._process_method(
            method, {}, {}, results, set(), set(),
        )

        assert all(len(v) == 0 for v in results.values())

    def test_min_instruction_filter(self):
        """Methods below min_instructions threshold are skipped."""
        analyzer = _make_analyzer(min_instructions=10)
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        method = _make_method_analysis()
        # Only 3 instructions — below threshold of 10
        smali_methods = {method_key: _make_smali_method(instruction_count=3)}

        analyzer._process_method(
            method, smali_methods, {}, results, set(), set(),
        )

        assert all(len(v) == 0 for v in results.values())

    def test_method_above_min_instructions_processed(self):
        """Methods at or above threshold are processed."""
        analyzer = _make_analyzer(min_instructions=5)
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        method = _make_method_analysis()
        smali_methods = {method_key: _make_smali_method(instruction_count=5)}

        analyzer._process_method(
            method, smali_methods, {}, results, set(), set(),
        )

        assert len(results["smali_content"]) == 1
        assert len(results["smali_refs"]) == 1


# ---------------------------------------------------------------------------
# Tests: smali body lookup and androguard fallback
# ---------------------------------------------------------------------------

class TestSmaliLookup:
    """Tests for smali body lookup from apktool and androguard fallback."""

    def test_apktool_smali_used_when_available(self):
        """When apktool smali is available, it's used."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        method = _make_method_analysis()
        smali_methods = {method_key: _make_smali_method()}

        analyzer._process_method(
            method, smali_methods, {}, results, set(), set(),
        )

        assert len(results["smali_content"]) == 1
        assert results["smali_content"][0]["smali_method"] == SAMPLE_SMALI_BODY

    def test_androguard_fallback_when_no_apktool(self):
        """When apktool smali is missing, _disassemble_with_androguard is used."""
        analyzer = _make_analyzer()
        results = _empty_results()

        # Create encoded method with code
        instruction1 = MagicMock()
        instruction1.get_name.return_value = "const/4"
        instruction1.get_output.return_value = "v0, 0x0"
        instruction2 = MagicMock()
        instruction2.get_name.return_value = "invoke-virtual"
        instruction2.get_output.return_value = "v0, Lcom/Foo;->bar()V"
        instruction3 = MagicMock()
        instruction3.get_name.return_value = "add-int"
        instruction3.get_output.return_value = "v0, v1, v2"
        instruction4 = MagicMock()
        instruction4.get_name.return_value = "sub-int"
        instruction4.get_output.return_value = "v3, v0, v1"
        instruction5 = MagicMock()
        instruction5.get_name.return_value = "return-void"
        instruction5.get_output.return_value = ""

        code = MagicMock()
        code.get_registers_size.return_value = 4
        bytecode = MagicMock()
        bytecode.get_instructions.return_value = [
            instruction1, instruction2, instruction3, instruction4, instruction5,
        ]
        code.get_bc.return_value = bytecode

        encoded = _make_encoded_method(code=code)
        method = _make_method_analysis(encoded=encoded)

        # Empty smali_methods — forces androguard fallback
        analyzer._process_method(
            method, {}, {}, results, set(), set(),
        )

        assert len(results["smali_content"]) == 1
        body = results["smali_content"][0]["smali_method"]
        assert "const/4" in body
        assert "return-void" in body

    def test_androguard_fallback_no_code(self):
        """When encoded method has no code, method is skipped."""
        analyzer = _make_analyzer()
        results = _empty_results()

        encoded = _make_encoded_method(code=None)
        method = _make_method_analysis(encoded=encoded)

        analyzer._process_method(
            method, {}, {}, results, set(), set(),
        )

        assert all(len(v) == 0 for v in results.values())

    def test_androguard_fallback_bytecode_exception(self):
        """When bytecode iteration throws, method is skipped gracefully."""
        analyzer = _make_analyzer()
        results = _empty_results()

        code = MagicMock()
        code.get_registers_size.return_value = 2
        bytecode = MagicMock()
        bytecode.get_instructions.side_effect = RuntimeError("bad bytecode")
        code.get_bc.return_value = bytecode

        encoded = _make_encoded_method(code=code)
        method = _make_method_analysis(encoded=encoded)

        analyzer._process_method(
            method, {}, {}, results, set(), set(),
        )

        # Should not crash, but no content produced
        assert len(results["smali_content"]) == 0


# ---------------------------------------------------------------------------
# Tests: output population and deduplication
# ---------------------------------------------------------------------------

class TestOutputPopulation:
    """Tests that all result dict keys are correctly populated."""

    def _process_standard_method(self, with_java=True):
        """Helper: process a standard method and return results."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"

        xref_from = [_make_xref_entry_4x("Lcom/Caller;", "call")]
        xref_to = [_make_xref_entry_4x("Lcom/Dep;", "dep")]
        method = _make_method_analysis(
            xref_from=xref_from, xref_to=xref_to
        )

        smali_methods = {method_key: _make_smali_method()}
        java_methods = {}
        if with_java:
            java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, set(), set(),
        )
        return results

    def test_smali_content_populated(self):
        results = self._process_standard_method()
        assert len(results["smali_content"]) == 1
        entry = results["smali_content"][0]
        assert "smali_method_hash" in entry
        assert entry["smali_method"] == SAMPLE_SMALI_BODY
        assert entry["smali_method_type"] == "USER"
        assert entry["smali_instructions_count"] == 5
        assert isinstance(entry["smali_has_string_encryption"], bool)
        assert isinstance(entry["smali_has_reflection_calls"], bool)
        assert isinstance(entry["smali_excessive_goto_count"], bool)

    def test_smali_refs_populated(self):
        results = self._process_standard_method()
        assert len(results["smali_refs"]) == 1
        ref = results["smali_refs"][0]
        assert "smali_method_hash" in ref
        assert ref["smali_class_name"] == "com.example.Foo"
        assert ref["smali_method_name"] == "doStuff"
        assert ref["smali_method_signature"] == "()V"

    def test_decompiled_content_populated_with_java(self):
        results = self._process_standard_method(with_java=True)
        assert len(results["decompiled_content"]) == 1
        entry = results["decompiled_content"][0]
        assert "decompiled_method_hash" in entry
        assert entry["decompiled_method"] == SAMPLE_JAVA_SOURCE

    def test_decompiled_refs_populated_with_java(self):
        results = self._process_standard_method(with_java=True)
        assert len(results["decompiled_refs"]) == 1
        ref = results["decompiled_refs"][0]
        assert "decompiled_method_hash" in ref
        assert "smali_method_hash" in ref
        assert ref["decompiled_class_name"] == "com.example.Foo"
        assert ref["decompiled_method_name"] == "doStuff"
        assert "decompiled_method_prototype" in ref
        assert ref["functions_caller"] == ["Lcom/Caller;->call"]
        assert ref["functions_call"] == ["Lcom/Dep;->dep"]

    def test_no_decompiled_without_java(self):
        results = self._process_standard_method(with_java=False)
        assert len(results["decompiled_content"]) == 0
        assert len(results["decompiled_refs"]) == 0
        # Smali should still be there
        assert len(results["smali_content"]) == 1

    def test_similarity_metrics_populated(self):
        results = self._process_standard_method()
        assert len(results["similarity_metrics"]) == 1
        sim = results["similarity_metrics"][0]
        assert "smali_method_hash" in sim
        assert "cyclomatic_complexity" in sim
        assert "minhash" in sim
        assert isinstance(sim["minhash"], list)
        # block_count moved to cfg table
        assert "block_count" not in sim

    def test_cfg_entry_populated(self):
        results = self._process_standard_method()
        assert len(results["cfg"]) == 1
        cfg = results["cfg"][0]
        assert "smali_method_hash" in cfg
        assert "block_count" in cfg
        assert "cfg_topology_hash" in cfg
        assert "prime_product_smali" in cfg
        assert "wl_minhash" in cfg
        assert "bb_features" in cfg
        assert isinstance(cfg["bb_features"], list)
        for feat in cfg["bb_features"]:
            assert len(feat) == 8

    def test_smali_deduplication(self):
        """Same smali body processed twice produces one content entry."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"
        seen_smali = set()

        method = _make_method_analysis()
        smali_methods = {method_key: _make_smali_method()}

        # Process same method twice
        analyzer._process_method(
            method, smali_methods, {}, results, set(), seen_smali,
        )
        analyzer._process_method(
            method, smali_methods, {}, results, set(), seen_smali,
        )

        # Content deduplicated, but refs are per-call
        assert len(results["smali_content"]) == 1
        assert len(results["smali_refs"]) == 2

    def test_decompiled_deduplication(self):
        """Same Java source processed twice produces one content entry."""
        analyzer = _make_analyzer()
        results = _empty_results()
        method_key = "Lcom/example/Foo;->doStuff()V"
        seen_decompiled = set()
        seen_smali = set()

        method = _make_method_analysis()
        smali_methods = {method_key: _make_smali_method()}
        java_methods = {"com.example.Foo.doStuff": SAMPLE_JAVA_SOURCE}

        analyzer._process_method(
            method, smali_methods, java_methods,
            results, seen_decompiled, seen_smali,
        )
        analyzer._process_method(
            method, smali_methods, java_methods,
            results, seen_decompiled, seen_smali,
        )

        assert len(results["decompiled_content"]) == 1
        assert len(results["decompiled_refs"]) == 2


# ---------------------------------------------------------------------------
# Tests: _disassemble_with_androguard
# ---------------------------------------------------------------------------

class TestDisassembleWithAndroguard:
    """Tests for the androguard bytecode disassembly fallback."""

    def test_no_code_returns_none(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        encoded = _make_encoded_method(code=None)
        body, count, regs = APKCodeAnalyzer._disassemble_with_androguard(encoded)
        assert body is None
        assert count == 0
        assert regs == 0

    def test_no_bytecode_returns_none(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        code = MagicMock()
        code.get_registers_size.return_value = 3
        code.get_bc.return_value = None
        encoded = _make_encoded_method(code=code)

        body, count, regs = APKCodeAnalyzer._disassemble_with_androguard(encoded)
        assert body is None
        assert count == 0
        assert regs == 3

    def test_basic_disassembly(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        instructions = []
        for name, output in [
            ("const/4", "v0, 0x0"),
            ("invoke-virtual", "v0, Lcom/Foo;->bar()V"),
            ("return-void", ""),
        ]:
            instr = MagicMock()
            instr.get_name.return_value = name
            instr.get_output.return_value = output
            instructions.append(instr)

        code = MagicMock()
        code.get_registers_size.return_value = 2
        bytecode = MagicMock()
        bytecode.get_instructions.return_value = instructions
        code.get_bc.return_value = bytecode

        encoded = _make_encoded_method(code=code)
        body, count, regs = APKCodeAnalyzer._disassemble_with_androguard(encoded)

        assert body is not None
        assert count == 3
        assert regs == 2
        assert "const/4" in body
        assert "return-void" in body

    def test_invoke_operands_normalized(self):
        """Androguard invoke output gets braces added around registers."""
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        instr = MagicMock()
        instr.get_name.return_value = "invoke-virtual"
        # Androguard format: no braces
        instr.get_output.return_value = "v0, v1, Lcom/Foo;->bar(I)V"

        code = MagicMock()
        code.get_registers_size.return_value = 2
        bytecode = MagicMock()
        bytecode.get_instructions.return_value = [instr]
        code.get_bc.return_value = bytecode

        encoded = _make_encoded_method(code=code)
        body, count, regs = APKCodeAnalyzer._disassemble_with_androguard(encoded)

        # Should be normalized to apktool format with braces
        assert "{v0, v1}" in body

    def test_bytecode_exception_returns_none(self):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        code = MagicMock()
        code.get_registers_size.return_value = 2
        bytecode = MagicMock()
        bytecode.get_instructions.side_effect = RuntimeError("corrupt")
        code.get_bc.return_value = bytecode

        encoded = _make_encoded_method(code=code)
        body, count, regs = APKCodeAnalyzer._disassemble_with_androguard(encoded)

        assert body is None
        assert regs == 2