Oleg A. Prokopyev

127 papers A* 1B 1C 1Misc 1Journal 119Unranked 1
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Sergey S. Ketkov, Oleg A. Prokopyev
2025 J jnl
CoRR
Sergey S. Ketkov, Oleg A. Prokopyev
2025 J jnl
Networks
Yehor Blokhin, Sergiy Butenko, Mykyta Makovenko, Petar Momcilovic, Oleg A. Prokopyev
2025 J jnl
Eur. J. Oper. Res.
Tomás Lagos, Junyeong Choi, Brittany Segundo, Jianbang Gan, Lewis Ntaimo, Oleg A. Prokopyev
2025 J jnl
INFORMS J. Comput.
Haonan Zhong, Foad Mahdavi Pajouh, Sergiy Butenko, Oleg A. Prokopyev
2025 A* conf
ICML
Jourdain B. Lamperski, Haeseong Yang, Oleg A. Prokopyev
2025 J jnl
Oper. Res. Lett.
Sina Nadi, Taewoo Lee, Oleg A. Prokopyev
2025 J jnl
Comput. Optim. Appl.
Alexander Veremyev, Vladimir Boginski, Eduardo L. Pasiliao, Oleg A. Prokopyev
2024 J jnl
J. Glob. Optim.
Tomás Lagos, Oleg A. Prokopyev, Alexander Veremyev
2024 J jnl
CoRR
Sergey S. Ketkov, Oleg A. Prokopyev
2023 J jnl
Optim. Lett.
Jing Yang, Xueyu Shi, Oleg A. Prokopyev
2023 J jnl
Eur. J. Oper. Res.
Dmytro Matsypura, Alexander Veremyev, Eduardo L. Pasiliao, Oleg A. Prokopyev
2023 J jnl
SIAM J. Optim.
Jourdain B. Lamperski, Oleg A. Prokopyev, Luca G. Wrabetz
2023 J jnl
Math. Program. Comput.
Xueyu Shi, Oleg A. Prokopyev, Ted K. Ralphs
2023 J jnl
Oper. Res. Lett.
Tomás Lagos, Oleg A. Prokopyev
2023 J jnl
Eur. J. Oper. Res.
Haonan Zhong, Foad Mahdavi Pajouh, Oleg A. Prokopyev
2023 J jnl
IEEE Trans. Reliab.
Qingan Qiu, Lisa M. Maillart, Oleg A. Prokopyev, Lirong Cui
2023 J jnl
Ann. Oper. Res.
Sergey S. Ketkov, Oleg A. Prokopyev, Lisa M. Maillart
2022 J jnl
J. Glob. Optim.
Shaoning Han, Andrés Gómez, Oleg A. Prokopyev
2022 J jnl
INFORMS J. Optim.
Juan Sebastian Borrero, Oleg A. Prokopyev, Denis Sauré
2022 J jnl
Eur. J. Oper. Res.
Alexander Veremyev, Vladimir Boginski, Eduardo L. Pasiliao, Oleg A. Prokopyev
2022 J jnl
Networks
Yehor Blokhin, Sergiy Butenko, Petar Momcilovic, Oleg A. Prokopyev
2022 J jnl
Math. Program.
Xueyu Shi, Oleg A. Prokopyev, Bo Zeng
2021 J jnl
INFORMS J. Comput.
Andrés Gómez, Oleg A. Prokopyev
2021 J jnl
IISE Trans.
Shadi Sanoubar, Lisa M. Maillart, Oleg A. Prokopyev
2021 J jnl
Comput. Oper. Res.
Sergey S. Ketkov, Oleg A. Prokopyev, Evgenii P. Burashnikov
2021 J jnl
IISE Trans.
Erfan Mehmanchi, Hoda Bidkhori, Oleg A. Prokopyev
2021 J jnl
CoRR
Sergey S. Ketkov, Andrei S. Shilov, Oleg A. Prokopyev
2021 J jnl
INFORMS J. Comput.
Colin P. Gillen, Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2021 J jnl
Ann. Oper. Res.
Gabriel L. Zenarosa, Oleg A. Prokopyev, Eduardo L. Pasiliao
2021 J jnl
IEEE Trans. Reliab.
Shadi Sanoubar, Kai He, Lisa M. Maillart, Oleg A. Prokopyev
2021 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2021 J jnl
Decis. Anal.
Jing Yang, Juan Sebastian Borrero, Oleg A. Prokopyev, Denis Sauré
2021 J jnl
Ann. Oper. Res.
Erfan Mehmanchi, Andrés Gómez, Oleg A. Prokopyev
2020 J jnl
Decis. Anal.
M. Hosein Zare, Oleg A. Prokopyev, Denis Sauré
2020 J jnl
INFORMS J. Optim.
Erfan Mehmanchi, Colin P. Gillen, Andrés Gómez, Oleg A. Prokopyev
2020 J jnl
Eur. J. Oper. Res.
Jongeun Kim, Alexander Veremyev, Vladimir Boginski, Oleg A. Prokopyev
2020 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2020 B conf
IPCO
Xueyu Shi, Oleg A. Prokopyev, Bo Zeng
2020 J jnl
Networks
Steffen Rebennack, Oleg A. Prokopyev, Bismark Singh
2019 J jnl
Ann. Oper. Res.
M. Hosein Zare, Juan Sebastian Borrero, Bo Zeng, Oleg A. Prokopyev
2019 J jnl
CoRR
Sergey S. Ketkov, Oleg A. Prokopyev, Evgenii P. Burashnikov
2019 J jnl
INFORMS J. Comput.
Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2019 J jnl
J. Glob. Optim.
Erfan Mehmanchi, Andrés Gómez, Oleg A. Prokopyev
2019 J jnl
Networks
Xueyu Shi, Bo Zeng, Oleg A. Prokopyev
2019 J jnl
Eur. J. Oper. Res.
Dmytro Matsypura, Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2019 J jnl
Ann. Oper. Res.
Kai He, Lisa M. Maillart, Oleg A. Prokopyev
2019 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2019 J jnl
IEEE Trans. Netw. Sci. Eng.
Juan Sebastian Borrero, Oleg A. Prokopyev, Pavlo A. Krokhmal
2019 J jnl
Oper. Res.
Juan Sebastian Borrero, Oleg A. Prokopyev, Denis Sauré
2019 J jnl
Oper. Res.
Onur Tavaslioglu, Oleg A. Prokopyev, Andrew J. Schaefer
2018 J jnl
Networks
Colin P. Gillen, Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2018 J jnl
Manag. Sci.
Anahita Khojandi, Lisa M. Maillart, Oleg A. Prokopyev, Mark S. Roberts, Samir F. Saba
2018 J jnl
CoRR
Sergey S. Ketkov, Oleg A. Prokopyev
2018 J jnl
J. Glob. Optim.
M. Hosein Zare, Osman Y. Özaltin, Oleg A. Prokopyev
2018 J jnl
Networks
Grigory Pastukhov, Alexander Veremyev, Vladimir Boginski, Oleg A. Prokopyev
2018 J jnl
INFORMS J. Comput.
Osman Y. Özaltin, Oleg A. Prokopyev, Andrew J. Schaefer
2018 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2018 ch.
Handbook of Heuristics
Oleg V. Shylo, Oleg A. Prokopyev
2018 J jnl
Eur. J. Oper. Res.
Dmytro Matsypura, Oleg A. Prokopyev, Aizat Zahar
2017 J jnl
Ann. Oper. Res.
Vladimir Stozhkov, Vladimir Boginski, Oleg A. Prokopyev, Eduardo L. Pasiliao
2017 J jnl
Optim. Methods Softw.
Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2017 J jnl
J. Glob. Optim.
Juan Sebastian Borrero, Colin P. Gillen, Oleg A. Prokopyev
2017 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2017 J jnl
Optim. Methods Softw.
Colin P. Gillen, Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2016 J jnl
Oper. Res. Lett.
Juan Sebastian Borrero, Colin P. Gillen, Oleg A. Prokopyev
2016 J jnl
Comput. Optim. Appl.
Alexander Veremyev, Oleg A. Prokopyev, Sergiy Butenko, Eduardo L. Pasiliao
2016 J jnl
Comput. Optim. Appl.
Behdad Beheshti, Oleg A. Prokopyev, Eduardo L. Pasiliao
2016 J jnl
J. Glob. Optim.
Serdar Karademir, Oleg A. Prokopyev, Robert J. Mailloux
2016 J jnl
Networks
Sera Kahruman-Anderoglu, Austin Buchanan, Sergiy Butenko, Oleg A. Prokopyev
2016 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2016 J jnl
Decis. Anal.
Juan Sebastian Borrero, Oleg A. Prokopyev, Denis Sauré
2016 J jnl
INFORMS J. Comput.
Sepehr Nemati, Oleg V. Shylo, Oleg A. Prokopyev, Andrew J. Schaefer
2015 J jnl
Discret. Optim.
Andrew C. Trapp, Oleg A. Prokopyev
2015 J jnl
Networks
Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2015 J jnl
J. Glob. Optim.
Behdad Beheshti, Osman Y. Özaltin, M. Hosein Zare, Oleg A. Prokopyev
2015 J jnl
Oper. Res. Lett.
Serdar Karademir, Oleg A. Prokopyev
2015 J jnl
IEEE Trans. Reliab.
Kai He, Lisa M. Maillart, Oleg A. Prokopyev
2014 J jnl
J. Comb. Optim.
Alexander Veremyev, Oleg A. Prokopyev, Eduardo L. Pasiliao
2014 J jnl
Eur. J. Oper. Res.
Alexander Veremyev, Oleg A. Prokopyev, Vladimir Boginski, Eduardo L. Pasiliao
2014 J jnl
ACM Trans. Sens. Networks
Guvenc Degirmenci, Jeffrey P. Kharoufeh, Oleg A. Prokopyev
2014 J jnl
Optim. Methods Softw.
Serdar Karademir, Nan Kong, Oleg A. Prokopyev
2014 J jnl
INFORMS J. Comput.
Anahita Khojandi, Lisa M. Maillart, Oleg A. Prokopyev, Mark S. Roberts, Timothy Brown, William W. Barrington
2014 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2014 J jnl
J. Glob. Optim.
Oleg A. Prokopyev, Nikolaos V. Sahinidis
2013 J jnl
J. Glob. Optim.
Oleksii Ursulenko, Sergiy Butenko, Oleg A. Prokopyev
2013 J jnl
Optim. Lett.
Pavlo A. Krokhmal, Oleg A. Prokopyev
2013 J jnl
INFORMS J. Comput.
Oleksii Mostovyi, Oleg A. Prokopyev, Oleg V. Shylo
2013 J jnl
Oper. Res.
Andrew C. Trapp, Oleg A. Prokopyev, Andrew J. Schaefer
2013 J jnl
J. Heuristics
Foad Mahdavi Pajouh, Balabhaskar Balasundaram, Oleg A. Prokopyev
2013 J jnl
INFORMS J. Comput.
Oleg V. Shylo, Oleg A. Prokopyev, Andrew J. Schaefer
2012 J jnl
Math. Program.
Osman Y. Özaltin, Oleg A. Prokopyev, Andrew J. Schaefer
2011 J jnl
Comput. Optim. Appl.
Mustafa Baz, Brady Hunsaker, Oleg A. Prokopyev
2011 J jnl
Eur. J. Oper. Res.
Jayant Rajgopal, Zhouyan Wang, Andrew J. Schaefer, Oleg A. Prokopyev
2011 Misc conf
WSC
Oleg V. Shylo, K. Louis Luangkesorn, Oleg A. Prokopyev, Jayant Rajgopal, Andrew J. Schaefer
2011 J jnl
Oper. Res. Lett.
Oleg V. Shylo, Oleg A. Prokopyev, Jayant Rajgopal
2011 J jnl
Oper. Res.
Osman Y. Özaltin, Oleg A. Prokopyev, Andrew J. Schaefer, Mark S. Roberts
2010 J jnl
J. Comb. Optim.
Andrew C. Trapp, Oleg A. Prokopyev, Stanislav Busygin
2010 J jnl
Bioinform.
N. A. Temiz, Andrew C. Trapp, Oleg A. Prokopyev, Carlos J. Camacho
2010 J jnl
INFORMS J. Comput.
Andrew C. Trapp, Oleg A. Prokopyev
2010 J jnl
Oper. Res. Lett.
Osman Y. Özaltin, Oleg A. Prokopyev, Andrew J. Schaefer
2009 conf
AAMAS (2)
Prasanna Velagapudi, Oleg A. Prokopyev, Katia P. Sycara, Paul Scerri
2009 J jnl
Eur. J. Oper. Res.
Oleg A. Prokopyev, Sergiy Butenko, Andrew C. Trapp
2009 ch.
Encyclopedia of Optimization
Oleg A. Prokopyev
2009 J jnl
Comput. Optim. Appl.
Oleg A. Prokopyev
2009 J jnl
J. Comb. Optim.
Onur Seref, O. Erhun Kundakcioglu, Oleg A. Prokopyev, Panos M. Pardalos
2009 J jnl
Eur. J. Oper. Res.
Oleg A. Prokopyev, Nan Kong, Dayna L. Martinez-Torres
2008 J jnl
Comput. Oper. Res.
Stanislav Busygin, Oleg A. Prokopyev, Panos M. Pardalos
2008 ch.
Wiley Encyclopedia of Computer Science and Engineering
Carlos A. S. Oliveira, Panos M. Pardalos, Oleg A. Prokopyev
2008 J jnl
Optim. Methods Softw.
Panos M. Pardalos, Oleg A. Prokopyev, Oleg V. Shylo, Vladimir Shylo
2008 J jnl
Oper. Res. Lett.
Oleg V. Shylo, Oleg A. Prokopyev, Vladimir Shylo
2007 J jnl
Optim. Methods Softw.
Stanislav Busygin, Oleg A. Prokopyev, Panos M. Pardalos
2007 C conf
FUSION
Prasanna Velagapudi, Oleg A. Prokopyev, Katia P. Sycara, Paul Scerri
2007 J jnl
J. Glob. Optim.
Athanasia Karakitsiou, Oleg A. Prokopyev
2007 J jnl
Optim. Methods Softw.
Christodoulos A. Floudas, Oleg A. Prokopyev
2007 J jnl
Int. J. Comput. Sci. Eng.
Carlos A. S. Oliveira, Panos M. Pardalos, Oleg A. Prokopyev, Mauricio G. C. Resende
2006 J jnl
Optim. Methods Softw.
Oleg A. Prokopyev
2006 J jnl
Ann. Oper. Res.
Wanpracha Art Chaovalitwongse, Oleg A. Prokopyev, Panos M. Pardalos
2006 J jnl
Comput. Optim. Appl.
Hong-Xuan Huang, Panos M. Pardalos, Oleg A. Prokopyev
2006 J jnl
J. Comb. Optim.
Manki Min, Oleg A. Prokopyev, Panos M. Pardalos
2005 J jnl
J. Comb. Optim.
Ho Ki Fung, S. Rao, Christodoulos A. Floudas, Oleg A. Prokopyev, Panos M. Pardalos, Franz Rendl
2005 J jnl
J. Comb. Optim.
Stanislav Busygin, Oleg A. Prokopyev, Panos M. Pardalos
2005 J jnl
Oper. Res. Lett.
Oleg A. Prokopyev, Hong-Xuan Huang, Panos M. Pardalos
2004 J jnl
Oper. Res. Lett.
Wanpracha Art Chaovalitwongse, Panos M. Pardalos, Oleg A. Prokopyev
2004 J jnl
J. Comb. Optim.
Oleg A. Prokopyev, Panos M. Pardalos
2004 J jnl
J. Comb. Optim.
Oleg A. Prokopyev, Panos M. Pardalos
2004 J jnl
Math. Program.
Panos M. Pardalos, Wanpracha Art Chaovalitwongse, Leonidas D. Iasemidis, J. Chris Sackellares, Deng-Shan Shiau, Paul R. Carney, Oleg A. Prokopyev, Vitaliy A. Yatsenko
redb/extractors/decompiler/apk/analyzer.py
← Index redb/extractors/decompiler/apk/analyzer.py python
"""APK Code Analyzer — orchestrates androguard + JADX + apktool analysis.

This is the APK equivalent of BinaryNinjaDecompiler in
redb/extractors/decompiler/bninja/decompiler.py.
"""

import math
import os
import tempfile
from collections import Counter
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Any, Dict, List, Optional, Set

from redb.extractors.decompiler.apk.apktool_wrapper import ApktoolDisassembler
from redb.extractors.decompiler.apk.jadx_wrapper import JADXDecompiler
from redb.extractors.decompiler.apk.library_filter import LibraryFilter
from redb.extractors.decompiler.apk.method_extractor import (
    compute_minhash,
    compute_prime_product_smali,
    compute_sha256,
    compute_ssdeep,
    compute_tlsh,
    count_call_instructions,
    dalvik_to_java_class,
    dalvik_to_java_prototype,
    detect_obfuscation_indicators,
)
from redb.extractors.decompiler.apk.smali_normalization import normalize_method_body
from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
from redb.extractors.decompiler.apk.smali_parser import SmaliParser


class APKCodeAnalyzer:
    """Orchestrates APK code analysis combining three tools.

    Combines androguard (call graphs, xrefs, method enumeration),
    JADX (Java decompilation), and apktool (smali disassembly).
    """

    def __init__(
        self,
        filepath: str,
        timeout: int = 600,
        log=None,
        decompile_modules: Set[str] = None,
    ):
        self.filepath = filepath
        self.timeout = timeout
        self.log = log
        self.decompile_modules = decompile_modules or {"all"}
        self.min_instructions = int(
            os.getenv("APK_MIN_METHOD_INSTRUCTIONS", "5")
        )
        self.library_filter = LibraryFilter()
        self.jadx = JADXDecompiler(log=log)
        self.apktool = ApktoolDisassembler(log=log)

        self._temp_dirs = []

    def extract(self) -> Dict[str, Any]:
        """Run full APK code analysis.

        Returns dict with keys:
            decompiled_content, decompiled_refs,
            smali_content, smali_refs,
            similarity_metrics, strings, analysis_errors
        """
        results = {
            "decompiled_content": [],
            "decompiled_refs": [],
            "smali_content": [],
            "smali_refs": [],
            "similarity_metrics": [],
            "cfg": [],
            "strings": [],
            "analysis_errors": [],
        }

        # Create temp directories
        jadx_dir = tempfile.mkdtemp(prefix="redb_jadx_")
        apktool_dir = tempfile.mkdtemp(prefix="redb_apktool_")
        self._temp_dirs.extend([jadx_dir, apktool_dir])

        # Step 1-3: Run JADX, apktool, and androguard in parallel
        jadx_success = False
        apktool_success = False
        androguard_result = None

        with ThreadPoolExecutor(max_workers=3) as executor:
            futures = {}
            futures[executor.submit(self.jadx.decompile, self.filepath, jadx_dir)] = "jadx"
            futures[executor.submit(self.apktool.disassemble, self.filepath, apktool_dir)] = "apktool"
            futures[executor.submit(self._run_androguard)] = "androguard"

            for future in as_completed(futures):
                tool = futures[future]
                try:
                    result = future.result()
                    if tool == "jadx":
                        jadx_success = result
                    elif tool == "apktool":
                        apktool_success = result
                    elif tool == "androguard":
                        androguard_result = result
                except Exception as e:
                    if self.log:
                        self.log.error(f"{tool} failed: {e}")
                    results["analysis_errors"].append({
                        "class_name": None,
                        "method_name": None,
                        "error_location": tool,
                        "error_message": str(e),
                        "error_type": type(e).__name__,
                    })

        if androguard_result is None:
            if self.log:
                self.log.error("Androguard analysis failed — cannot proceed")
            return results

        apk_obj, dexs, analysis = androguard_result

        # Step 4b: Extract strings from DEX files
        try:
            results["strings"] = self._extract_strings(dexs)
        except Exception as e:
            if self.log:
                self.log.error(f"String extraction failed: {e}")
            results["analysis_errors"].append({
                "class_name": None,
                "method_name": None,
                "error_location": "strings",
                "error_message": f"String extraction failed: {e}",
                "error_type": type(e).__name__,
            })

        # Step 5: Parse smali output
        smali_methods = {}
        if apktool_success:
            try:
                smali_dirs = self.apktool.get_smali_directories(apktool_dir)
                for sdir in smali_dirs:
                    smali_methods.update(SmaliParser.parse_smali_directory(sdir))
            except Exception as e:
                if self.log:
                    self.log.error(f"Smali parsing failed: {e}")
                results["analysis_errors"].append({
                    "class_name": None,
                    "method_name": None,
                    "error_location": "apktool",
                    "error_message": f"Smali parsing failed: {e}",
                    "error_type": type(e).__name__,
                })

        # Step 6: Parse Java output
        java_methods = {}
        if jadx_success:
            try:
                java_methods = self.jadx.parse_java_methods(jadx_dir)
            except Exception as e:
                if self.log:
                    self.log.error(f"Java parsing failed: {e}")
                results["analysis_errors"].append({
                    "class_name": None,
                    "method_name": None,
                    "error_location": "jadx",
                    "error_message": f"Java parsing failed: {e}",
                    "error_type": type(e).__name__,
                })

        if not apktool_success and self.log:
            self.log.info(
                "apktool failed — falling back to androguard disassembly"
            )

        # Step 7-8: Process each method from androguard
        seen_decompiled_hashes = set()
        seen_smali_hashes = set()

        try:
            for method in analysis.get_methods():
                try:
                    self._process_method(
                        method,
                        smali_methods,
                        java_methods,
                        results,
                        seen_decompiled_hashes,
                        seen_smali_hashes,
                    )
                except Exception as e:
                    method_name = "unknown"
                    class_name = "unknown"
                    try:
                        if not method.is_external():
                            enc = method.get_method()
                            class_name = enc.get_class_name()
                            method_name = enc.get_name()
                    except Exception:
                        pass
                    results["analysis_errors"].append({
                        "class_name": class_name,
                        "method_name": method_name,
                        "error_location": "analysis",
                        "error_message": str(e),
                        "error_type": type(e).__name__,
                    })
        except Exception as e:
            if self.log:
                self.log.error(f"Method enumeration failed: {e}")
            results["analysis_errors"].append({
                "class_name": None,
                "method_name": None,
                "error_location": "androguard",
                "error_message": f"Method enumeration failed: {e}",
                "error_type": type(e).__name__,
            })

        if self.log:
            stats = self.library_filter.get_filter_stats()
            self.log.info(
                f"APK analysis complete: {stats['user']} user methods, "
                f"{stats['library']} library methods filtered, "
                f"{len(results['decompiled_content'])} decompiled, "
                f"{len(results['smali_content'])} smali, "
                f"{len(results['strings'])} strings"
            )

        return results

    def _run_androguard(self):
        """Run androguard analysis on the APK."""
        from androguard.misc import AnalyzeAPK
        return AnalyzeAPK(self.filepath)

    def _process_method(
        self,
        method,
        smali_methods: Dict,
        java_methods: Dict,
        results: Dict,
        seen_decompiled: set,
        seen_smali: set,
    ):
        """Process a single method from androguard analysis."""
        # Skip external methods (no code body)
        if method.is_external():
            return

        encoded = method.get_method()
        class_name = encoded.get_class_name()
        method_name = encoded.get_name()
        descriptor = encoded.get_descriptor()

        # Build method key for cross-tool matching
        method_key = SmaliParser.make_method_key(class_name, method_name, descriptor)

        # Check if library
        is_lib = self.library_filter.is_library(class_name)
        method_type = "LIBRARY" if is_lib else "USER"

        # Skip library methods for content tables (but they're still in xrefs)
        if is_lib:
            return

        # Get xrefs
        # In androguard 4.x, xref tuples are (ClassAnalysis, MethodAnalysis, offset).
        # The MethodAnalysis wrapper doesn't expose get_class_name()/get_name()
        # directly — we need to unwrap via .get_method() first.
        callers = []
        callees = []
        try:
            for ref_class, ref_method, offset in method.get_xref_from():
                try:
                    enc = ref_method.get_method()
                    caller_key = f"{enc.get_class_name()}->{enc.get_name()}"
                except AttributeError:
                    # Fallback for older androguard where ref_method is EncodedMethod
                    caller_key = f"{ref_method.get_class_name()}->{ref_method.get_name()}"
                callers.append(caller_key)
        except Exception:
            pass

        try:
            for ref_class, ref_method, offset in method.get_xref_to():
                try:
                    enc = ref_method.get_method()
                    callee_key = f"{enc.get_class_name()}->{enc.get_name()}"
                except AttributeError:
                    callee_key = f"{ref_method.get_class_name()}->{ref_method.get_name()}"
                callees.append(callee_key)
        except Exception:
            pass

        # Look up smali body
        smali_method = smali_methods.get(method_key)
        smali_body = None
        smali_normalized = None
        smali_hash = None
        instruction_count = 0
        register_count = 0

        if smali_method:
            smali_body = smali_method.body
            instruction_count = smali_method.instruction_count
            register_count = smali_method.register_count
        else:
            # Fallback: use androguard's own disassembler
            smali_body, instruction_count, register_count = (
                self._disassemble_with_androguard(encoded)
            )

        # Apply minimum instruction filter
        if instruction_count < self.min_instructions:
            return

        if smali_body:
            smali_normalized = SmaliParser.normalize_smali_body(smali_body)
            if smali_normalized:
                smali_hash = compute_sha256(smali_normalized)

        if not smali_hash:
            return

        # Compute obfuscation indicators from smali
        obfuscation = detect_obfuscation_indicators(
            method_name, class_name, smali_body, instruction_count
        )

        # Compute CFG metrics early — needed for both content tables and cfg table
        cfg_metrics = None
        if smali_body:
            try:
                cfg_metrics = compute_cfg_metrics(smali_body)
            except Exception:
                pass

        # Smali content (deduplicated)
        if smali_hash and smali_hash not in seen_smali:
            seen_smali.add(smali_hash)
            results["smali_content"].append({
                "smali_method_hash": smali_hash,
                "smali_method": smali_body,
                "smali_method_type": method_type,
                "smali_instructions_count": instruction_count,
                "smali_register_count": register_count,
                "smali_has_string_encryption": obfuscation.get("has_string_encryption", False),
                "smali_has_reflection_calls": obfuscation.get("has_reflection_calls", False),
                "smali_excessive_goto_count": obfuscation.get("excessive_goto_count", False),
                "smali_flattened_score": cfg_metrics.flattened_score if cfg_metrics else 0.0,
                "smali_mba_score": cfg_metrics.mba_score if cfg_metrics else 0.0,
            })

        # Smali reference (per-binary)
        ssdeep_val = compute_ssdeep(smali_normalized) if smali_normalized else None
        tlsh_val = compute_tlsh(smali_normalized) if smali_normalized else None

        # Semantically normalized ssdeep/TLSH (analogous to Binja's ssdeep_llil/tlsh_llil)
        ssdeep_normalized_val = None
        tlsh_normalized_val = None
        if smali_normalized:
            normalized_lines = normalize_method_body(smali_normalized, level="opcode_api")
            if normalized_lines:
                normalized_text = "\n".join(normalized_lines)
                ssdeep_normalized_val = compute_ssdeep(normalized_text)
                tlsh_normalized_val = compute_tlsh(normalized_text)

        # Look up Java source
        java_class = dalvik_to_java_class(class_name)
        java_key_candidates = [
            f"{java_class}.{method_name}",
        ]
        java_source = None
        decompiled_hash = None

        for jk in java_key_candidates:
            for key, source in java_methods.items():
                if key.startswith(jk):
                    java_source = source
                    break
            if java_source:
                break

        if java_source:
            # Normalize and hash decompiled content
            normalized_java = _normalize_java(java_source)
            decompiled_hash = compute_sha256(normalized_java)

            # Decompiled content (deduplicated)
            if decompiled_hash not in seen_decompiled:
                seen_decompiled.add(decompiled_hash)
                results["decompiled_content"].append({
                    "decompiled_method_hash": decompiled_hash,
                    "decompiled_method": java_source,
                    "decompiled_method_type": method_type,
                    "decompiled_has_string_encryption": obfuscation.get("has_string_encryption", False),
                    "decompiled_has_reflection_calls": obfuscation.get("has_reflection_calls", False),
                    "decompiled_excessive_goto_count": obfuscation.get("excessive_goto_count", False),
                })

            # Decompiled reference
            method_prototype = dalvik_to_java_prototype(
                method_name, descriptor, class_name
            )
            results["decompiled_refs"].append({
                "decompiled_method_hash": decompiled_hash,
                "smali_method_hash": smali_hash,
                "decompiled_class_name": dalvik_to_java_class(class_name),
                "decompiled_method_name": method_name,
                "decompiled_method_signature": descriptor,
                "decompiled_method_prototype": method_prototype,
                "functions_caller": callers,
                "functions_call": callees,
            })

        # Smali reference
        if smali_hash:
            results["smali_refs"].append({
                "smali_method_hash": smali_hash,
                "decompiled_method_hash": decompiled_hash,
                "smali_class_name": dalvik_to_java_class(class_name),
                "smali_method_name": method_name,
                "smali_method_signature": descriptor,
                "ssdeep_smali": ssdeep_val,
                "tlsh_smali": tlsh_val,
            })

        # Similarity metrics (content-based fuzzy matching only)
        if smali_hash and smali_normalized:
            minhash_sig = compute_minhash(smali_normalized)
            sim_entry = {
                "smali_method_hash": smali_hash,
                "cyclomatic_complexity": cfg_metrics.cyclomatic_complexity if cfg_metrics else None,
                "ssdeep_smali": ssdeep_val,
                "tlsh_smali": tlsh_val,
                "ssdeep_smali_normalized": ssdeep_normalized_val,
                "tlsh_smali_normalized": tlsh_normalized_val,
                "minhash": minhash_sig or [],
            }
            results["similarity_metrics"].append(sim_entry)

        # CFG entry (structural/topological features)
        if smali_hash and cfg_metrics and smali_body:
            prime_product = compute_prime_product_smali(smali_body)
            call_count = count_call_instructions(smali_body)
            cfg_entry = {
                "smali_method_hash": smali_hash,
                "cfg_topology_hash": cfg_metrics.cfg_topology_hash,
                "block_count": cfg_metrics.block_count,
                "edge_count": cfg_metrics.edge_count,
                "cfg_instructions_count": instruction_count,
                "call_count": call_count,
                "cyclomatic_complexity": cfg_metrics.cyclomatic_complexity,
                "loop_count": cfg_metrics.loop_count,
                "max_depth": cfg_metrics.max_depth,
                "max_fan_out": cfg_metrics.max_fan_out,
                "md_index_topdown": cfg_metrics.md_index_topdown,
                "md_index_bottomup": cfg_metrics.md_index_bottomup,
                "prime_product_smali": prime_product,
                "cfg_feature_tlsh": cfg_metrics.cfg_feature_tlsh,
                "wl_minhash": cfg_metrics.wl_minhash,
                "bb_features": cfg_metrics.block_features,
                "cfg_adjacency": cfg_metrics.cfg_adjacency,
            }
            results["cfg"].append(cfg_entry)

    @staticmethod
    def _string_entropy(s: str) -> float:
        """Compute Shannon entropy of a string."""
        if not s:
            return 0.0
        freq = Counter(s)
        length = len(s)
        return -sum(
            (count / length) * math.log2(count / length)
            for count in freq.values()
        )

    def _extract_strings(self, dexs) -> List[Dict[str, Any]]:
        """Extract deduplicated strings from all DEX objects.

        Uses androguard's get_strings() on each DEX, deduplicates by value,
        and computes entropy — matching the Binja StringAnalysis output format
        so IOCExtractorFromResults can consume them identically.

        Returns list of dicts with keys:
            string, string_encoding, string_offset, string_length, string_entropy
        """
        seen = set()
        strings = []
        offset_counter = 0

        for dex in dexs:
            try:
                dex_strings = dex.get_strings()
            except Exception:
                continue

            if not dex_strings:
                continue

            for s in dex_strings:
                if not s or s in seen:
                    continue
                # DEX MUTF-8 strings may contain unpaired surrogates (e.g. \ud800)
                # that are invalid UTF-8 and will fail ClickHouse insert
                s = s.encode('utf-8', errors='replace').decode('utf-8')
                if not s or s in seen:
                    continue
                seen.add(s)

                strings.append({
                    "string": s,
                    "string_encoding": "UTF8",
                    "string_offset": offset_counter,
                    "string_length": len(s),
                    "string_entropy": self._string_entropy(s),
                })
                offset_counter += 1

        return strings

    @staticmethod
    def _disassemble_with_androguard(encoded):
        """Fallback disassembly using androguard when apktool fails.

        Returns (smali_body, instruction_count, register_count).
        """
        code = encoded.get_code()
        if not code:
            return None, 0, 0

        register_count = code.get_registers_size()
        lines = []
        instruction_count = 0

        try:
            bytecode = code.get_bc()
            if not bytecode:
                return None, 0, register_count

            for instruction in bytecode.get_instructions():
                op_name = instruction.get_name()
                output = instruction.get_output()
                if output:
                    output = _normalize_androguard_operands(op_name, output)
                    lines.append(f"    {op_name} {output}")
                else:
                    lines.append(f"    {op_name}")
                instruction_count += 1
        except Exception:
            return None, 0, register_count

        if not lines:
            return None, 0, register_count

        smali_body = "\n".join(lines)
        return smali_body, instruction_count, register_count

    def cleanup(self):
        """Remove temporary directories."""
        import shutil
        for d in self._temp_dirs:
            try:
                if os.path.isdir(d):
                    shutil.rmtree(d)
            except Exception:
                pass
        self._temp_dirs.clear()


def _normalize_androguard_operands(op_name: str, output: str) -> str:
    """Normalize androguard instruction output to match apktool smali format.

    Androguard's get_output() differs from apktool in two key ways:
    1. invoke-* operands lack {braces} around registers:
       androguard: 'v0, v1, Lcom/Foo;->bar()V'
       apktool:    '{v0, v1}, Lcom/Foo;->bar()V'
    2. Field instructions use a space instead of colon between name and type:
       androguard: 'v0, v1, LA;->field Ljava/lang/String;'
       apktool:    'v0, v1, LA;->field:Ljava/lang/String;'
    """
    # invoke-* instructions: wrap register args in {braces}
    if op_name.startswith("invoke-"):
        # Find the class/method reference (starts with L or [)
        # Split on ', ' and find where the reference begins
        parts = output.split(", ")
        reg_parts = []
        ref_idx = None
        for i, part in enumerate(parts):
            stripped = part.strip()
            if stripped.startswith("L") or stripped.startswith("["):
                ref_idx = i
                break
            reg_parts.append(part)

        if ref_idx is not None and reg_parts:
            regs = ", ".join(reg_parts)
            ref = ", ".join(parts[ref_idx:])
            return "{" + regs + "}, " + ref
        elif reg_parts:
            # No reference found — just wrap all as registers
            return "{" + ", ".join(reg_parts) + "}"

    # Field instructions: fix 'field Ltype;' → 'field:Ltype;'
    if op_name.startswith(("iget", "iput", "sget", "sput")):
        # Pattern: '... ClassName;->fieldName Ltype;' or '... ClassName;->fieldName [Ltype;'
        # The space between fieldName and the type descriptor should be a colon
        arrow_idx = output.find("->")
        if arrow_idx != -1:
            after_arrow = output[arrow_idx + 2:]
            # Find the space before the type descriptor
            space_idx = after_arrow.find(" ")
            if space_idx != -1:
                remaining = after_arrow[space_idx + 1:]
                # Check that what follows is a type descriptor
                if remaining.startswith(("L", "[", "Z", "B", "S", "C",
                                         "I", "J", "F", "D")):
                    after_arrow = after_arrow[:space_idx] + ":" + remaining
                    output = output[:arrow_idx + 2] + after_arrow

    return output


def _normalize_java(source: str) -> str:
    """Normalize Java source for consistent hashing.

    Strip leading/trailing whitespace, normalize indentation.
    """
    lines = []
    for line in source.split("\n"):
        stripped = line.strip()
        if stripped:
            lines.append(stripped)
    return "\n".join(lines)