Ioannis Xenarios

62 papers B 2Journal 59Unranked 1
YearRankTypeTitle / Venue / Authors
2022 J jnl
PLoS Comput. Biol.
Nastassia Gobet, Maxime Jan, Paul Franken, Ioannis Xenarios
2022 J jnl
Bioinform.
Rick Wertenbroek, Simone Rubinacci, Ioannis Xenarios, Yann Thoma, Olivier Delaneau
2021 J jnl
Briefings Bioinform.
Anna Niarakis, Martin Kuiper, Marek Ostaszewski, Rahuman S. Malik-Sheriff, Cristina Casals-Casas, Denis Thieffry, Tom C. Freeman, Paul D. Thomas, Vasundra Touré, Vincent Noël, Gautier Stoll, Julio Saez-Rodriguez, Aurélien Naldi, Eugenia Oshurko, Ioannis Xenarios, Sylvain Soliman, Claudine Chaouiya, Tomás Helikar, Laurence Calzone
2020 J jnl
Bioinform.
Anne Morgat, Thierry Lombardot, Elisabeth Coudert, Kristian B. Axelsen, Teresa Batista Neto, Sebastien Gehant, Parit Bansal, Jerven T. Bolleman, Elisabeth Gasteiger, Edouard De Castro, Delphine Baratin, Monica Pozzato, Ioannis Xenarios, Sylvain Poux, Nicole Redaschi, Alan J. Bridge, UniProt Consortium
2020 J jnl
Bioinform.
Simon Dellicour, Philippe Lemey, Jean Artois, Tommy T. Lam, Alice Fusaro, Isabella Monne, Giovanni Cattoli, Dmitry Kuznetsov, Ioannis Xenarios, Gwenaelle Dauphin, Wantanee Kalpravidh, Sophie von Dobschütz, Filip Claes, Scott H. Newman, Marc A. Suchard, Guy Baele, Marius Gilbert
2020 J jnl
Bioinform.
Amel Bekkar, Anita Nasrallah, Nicolas Guex, Lluis Fajas, Ioannis Xenarios, Isabel C. Lopez-Mejia
2019 J jnl
Database J. Biol. Databases Curation
Maria Livia Famiglietti, Anne Estreicher, Lionel Breuza, Sylvain Poux, Nicole Redaschi, Ioannis Xenarios, Alan J. Bridge
2019 J jnl
Nucleic Acids Res.
Thierry Lombardot, Anne Morgat, Kristian B. Axelsen, Lucila Aimo, Nevila Hyka-Nouspikel, Anne Niknejad, Alexandr Ignatchenko, Ioannis Xenarios, Elisabeth Coudert, Nicole Redaschi, Alan J. Bridge
2018 J jnl
Database J. Biol. Databases Curation
Amel Bekkar, Anne Estreicher, Anne Niknejad, Cristina Casals-Casas, Alan J. Bridge, Ioannis Xenarios, Julien Dorier, Isaac Crespo
2018 J jnl
PLoS Comput. Biol.
Kyubum Lee, Maria Livia Famiglietti, Aoife McMahon, Chih-Hsuan Wei, Jacqueline A. L. MacArthur, Sylvain Poux, Lionel Breuza, Alan J. Bridge, Fiona Cunningham, Ioannis Xenarios, Zhiyong Lu
2018 B conf
FPL
Simone Casale Brunet, Thierry Schüpbach, Nicolas Guex, Christian Iseli, Alan J. Bridge, Dmitry Kuznetsov, Christian J. A. Sigrist, Phillippe Lemercier, Ioannis Xenarios, Endri Bezati
2017 conf
DH
Sara Schulthess, Anastasia Chasapi, Ioannis Xenarios, Martial Sankar, Claire Clivaz
2017 J jnl
Nucleic Acids Res.
Robert D. Finn, Teresa K. Attwood, Patricia C. Babbitt, Alex Bateman, Peer Bork, Alan J. Bridge, Hsin-Yu Chang, Zsuzsanna Dosztányi, Sara El-Gebali, Matthew Fraser, Julian Gough, David Haft, Gemma L. Holliday, Hongzhan Huang, Xiaosong Huang, Ivica Letunic, Rodrigo Lopez, Shennan Lu, Aron Marchler-Bauer, Huaiyu Mi, Jaina Mistry, Darren A. Natale, Marco Necci, Gift Nuka, Christine A. Orengo, Young Mi Park, Sebastien Pesseat, Damiano Piovesan, Simon C. Potter, Neil D. Rawlings, Nicole Redaschi, Lorna J. Richardson, Catherine Rivoire, Amaia Sangrador-Vegas, Christian J. A. Sigrist, Ian Sillitoe, Ben Smithers, Silvano Squizzato, Granger G. Sutton, Narmada Thanki, Paul D. Thomas, Silvio C. E. Tosatto, Cathy H. Wu, Ioannis Xenarios, Lai-Su L. Yeh, Siew-Yit Yong, Alex L. Mitchell
2017 J jnl
Nucleic Acids Res.
Anne Morgat, Thierry Lombardot, Kristian B. Axelsen, Lucila Aimo, Anne Niknejad, Nevila Hyka-Nouspikel, Elisabeth Coudert, Monica Pozzato, Marco Pagni, Sébastien Moretti, Steven Rosanoff, Joseph Onwubiko, Lydie Bougueleret, Ioannis Xenarios, Nicole Redaschi, Alan J. Bridge
2016 J jnl
BMC Bioinform.
Julien Dorier, Isaac Crespo, Anne Niknejad, Robin Liechti, Martin Ebeling, Ioannis Xenarios
2016 J jnl
Database J. Biol. Databases Curation
Megan Druce, Chantal Hulo, Patrick Masson, Paula Sommer, Ioannis Xenarios, Philippe Le Mercier, Tulio de Oliveira
2016 J jnl
Database J. Biol. Databases Curation
Ayush Singhal, Robert Leaman, Natalie L. Catlett, Thomas Lemberger, Johanna R. McEntyre, Shawn W. Polson, Ioannis Xenarios, Cecilia N. Arighi, Zhiyong Lu
2015 J jnl
PLoS Comput. Biol.
Nicolas Guex, Isaac Crespo, Sylvian Bron, Assia Ifticene-Treboux, Eveline Faes-van't Hull, Solange Kharoubi, Robin Liechti, Patricia Werffeli, Mark Ibberson, Francois Majo, Michäel Nicolas, Julien Laurent, Abhishek Garg, Khalil Zaman, Hans-Anton Lehr, Brian J. Stevenson, Curzio Rüegg, George Coukos, Jean-François Delaloye, Ioannis Xenarios, Marie-Agnes Doucey
2015 J jnl
Bioinform.
Aurélien Naldi, Pedro T. Monteiro, Christoph Müssel, Hans A. Kestler, Denis Thieffry, Ioannis Xenarios, Julio Saez-Rodriguez, Tomás Helikar, Claudine Chaouiya
2015 J jnl
Nucleic Acids Res.
Ivo Pedruzzi, Catherine Rivoire, Andrea H. Auchincloss, Elisabeth Coudert, Guillaume Keller, Edouard De Castro, Delphine Baratin, Béatrice A. Cuche, Lydie Bougueleret, Sylvain Poux, Nicole Redaschi, Ioannis Xenarios, Alan J. Bridge
2015 J jnl
Bioinform.
Elke Schaper, Alexander Korsunsky, Julija Pecerska, Antonio Messina, Riccardo Murri, Heinz Stockinger, Stefan Zoller, Ioannis Xenarios, Maria Anisimova
2015 J jnl
Nucleic Acids Res.
Alex L. Mitchell, Hsin-Yu Chang, Louise C. Daugherty, Matthew Fraser, Sarah Hunter, Rodrigo Lopez, Craig McAnulla, Conor McMenamin, Gift Nuka, Sebastien Pesseat, Amaia Sangrador-Vegas, Maxim Scheremetjew, Claudia Rato, Siew-Yit Yong, Alex Bateman, Marco Punta, Teresa K. Attwood, Christian J. A. Sigrist, Nicole Redaschi, Catherine Rivoire, Ioannis Xenarios, Daniel Kahn, Dominique Guyot, Peer Bork, Ivica Letunic, Julian Gough, Matt E. Oates, Daniel H. Haft, Hongzhan Huang, Darren A. Natale, Cathy H. Wu, Christine A. Orengo, Ian Sillitoe, Huaiyu Mi, Paul D. Thomas, Robert D. Finn
2015 J jnl
Bioinform.
Lucila Aimo, Robin Liechti, Nevila Hyka-Nouspikel, Anne Niknejad, Anne Gleizes, Lou Götz, Dmitry Kuznetsov, Fabrice P. A. David, F. Gisou van der Goot, Howard Riezman, Lydie Bougueleret, Ioannis Xenarios, Alan J. Bridge
2015 J jnl
Nucleic Acids Res.
Anne Morgat, Kristian B. Axelsen, Thierry Lombardot, Rafael Alcántara, Lucila Aimo, Mohamed Zerara, Anne Niknejad, Eugeni Belda, Nevila Hyka-Nouspikel, Elisabeth Coudert, Nicole Redaschi, Lydie Bougueleret, Christoph Steinbeck, Ioannis Xenarios, Alan J. Bridge
2014 J jnl
PLoS Comput. Biol.
Antonio Rausell, Pejman Mohammadi, Paul J. McLaren, István Bartha, Ioannis Xenarios, Jacques Fellay, Amalio Telenti
2014 J jnl
Nucleic Acids Res.
Heinz Stockinger, Adrian M. Altenhoff, Konstantin Arnold, Amos Bairoch, Frederic B. Bastian, Sven Bergmann, Lydie Bougueleret, Philipp Bucher, Mauro Delorenzi, Lydie Lane, Philippe Le Mercier, Frédérique Lisacek, Olivier Michielin, Patricia M. Palagi, Jacques Rougemont, Torsten Schwede, Christian von Mering, Erik van Nimwegen, Daniel Walther, Ioannis Xenarios, Mihaela Zavolan, Evgeny M. Zdobnov, Vincent Zoete, Ron D. Appel
2014 J jnl
Briefings Bioinform.
Thomas Bernard, Alan J. Bridge, Anne Morgat, Sébastien Moretti, Ioannis Xenarios, Marco Pagni
2014 J jnl
Database J. Biol. Databases Curation
Filip Claes, Dmitry Kuznetsov, Robin Liechti, Sophie von Dobschütz, Bao Dinh Truong, Anne Gleizes, Daniele Conversa, Alessandro Colonna, Ettore Demaio, Sabina Ramazzotto, Fairouz Larfaoui, Julio Pinto, Philippe Le Mercier, Ioannis Xenarios, Gwenaelle Dauphin
2013 J jnl
BMC Bioinform.
Anne-Lise Veuthey, Alan J. Bridge, Julien Gobeill, Patrick Ruch, Johanna R. McEntyre, Lydie Bougueleret, Ioannis Xenarios
2013 J jnl
Bioinform.
Marco Pagni, Hélène Niculita-Hirzel, Loïc Pellissier, Anne Dubuis, Ioannis Xenarios, Antoine Guisan, Ian R. Sanders, Jérôme Goudet, Nicolas Guex
2013 J jnl
Nucleic Acids Res.
Ivo Pedruzzi, Catherine Rivoire, Andrea H. Auchincloss, Elisabeth Coudert, Guillaume Keller, Edouard De Castro, Delphine Baratin, Béatrice A. Cuche, Lydie Bougueleret, Sylvain Poux, Nicole Redaschi, Ioannis Xenarios, Alan J. Bridge
2013 J jnl
Bioinform.
Nicola Bonzanni, Abhishek Garg, K. Anton Feenstra, Judith Schütte, Sarah Kinston, Diego Miranda-Saavedra, Jaap Heringa, Ioannis Xenarios, Berthold Göttgens
2013 J jnl
Nucleic Acids Res.
Christian J. A. Sigrist, Edouard De Castro, Lorenzo Cerutti, Béatrice A. Cuche, Nicolas Hulo, Alan J. Bridge, Lydie Bougueleret, Ioannis Xenarios
2013 J jnl
BMC Syst. Biol.
Claudine Chaouiya, Duncan Bérenguier, Sarah M. Keating, Aurélien Naldi, Martijn P. van Iersel, Nicolas Rodriguez, Andreas Dräger, Finja Büchel, Thomas Cokelaer, Bryan M. Kowal, Benjamin Wicks, Emanuel J. V. Gonçalves, Julien Dorier, Michel Page, Pedro T. Monteiro, Axel von Kamp, Ioannis Xenarios, Hidde de Jong, Michael Hucka, Steffen Klamt, Denis Thieffry, Nicolas Le Novère, Julio Saez-Rodriguez, Tomás Helikar
2013 J jnl
Nucleic Acids Res.
Patrick Masson, Chantal Hulo, Edouard De Castro, Hans Bitter, Lore Gruenbaum, Laurent Essioux, Lydie Bougueleret, Ioannis Xenarios, Philippe Le Mercier
2013 J jnl
Bioinform.
Thierry Schüpbach, Marco Pagni, Alan J. Bridge, Lydie Bougueleret, Ioannis Xenarios, Lorenzo Cerutti
2012 J jnl
Database J. Biol. Databases Curation
Sarah W. Burge, Teresa K. Attwood, Alex Bateman, Tanya Z. Berardini, J. Michael Cherry, Claire O'Donovan, Ioannis Xenarios, Pascale Gaudet
2012 J jnl
Nucleic Acids Res.
Panu Artimo, Manohar Jonnalagedda, Konstantin Arnold, Delphine Baratin, Gábor Csárdi, Edouard De Castro, Severine Duvaud, Volker Flegel, Arnaud Fortier, Elisabeth Gasteiger, Aurélien Grosdidier, Céline Hernandez, Vassilios Ioannidis, Dmitri Kuznetsov, Robin Liechti, Sébastien Moretti, Khaled Mostaguir, Nicole Redaschi, Grégoire Rossier, Ioannis Xenarios, Heinz Stockinger
2012 J jnl
Nucleic Acids Res.
Rafael Alcántara, Kristian B. Axelsen, Anne Morgat, Eugeni Belda, Elisabeth Coudert, Alan J. Bridge, Hong Cao, Paula de Matos, Marcus Ennis, Steve Turner, Gareth I. Owen, Lydie Bougueleret, Ioannis Xenarios, Christoph Steinbeck
2012 J jnl
Nucleic Acids Res.
Emily C. Dimmer, Rachael P. Huntley, Yasmin Alam-Faruque, Tony Sawford, Claire O'Donovan, Maria Jesus Martin, Benoit Bely, Paul Browne, Wei Mun Chan, Ruth Y. Eberhardt, Michael Gardner, Kati Laiho, Duncan Legge, Michele Magrane, Klemens Pichler, Diego Poggioli, Harminder Sehra, Andrea H. Auchincloss, Kristian B. Axelsen, Marie-Claude Blatter, Emmanuel Boutet, Silvia Braconi-Quintaje, Lionel Breuza, Alan J. Bridge, Elisabeth Coudert, Anne Estreicher, Maria Livia Famiglietti, Serenella Ferro-Rojas, Marc Feuermann, Arnaud Gos, Nadine Gruaz-Gumowski, Ursula Hinz, Chantal Hulo, Janet James, Silvia Jimenez, Florence Jungo, Guillaume Keller, Phillippe Lemercier, Damien Lieberherr, Patrick Masson, Madelaine Moinat, Ivo Pedruzzi, Sylvain Poux, Catherine Rivoire, Bernd Roechert, Michel Schneider, Andre Stutz, Shyamala Sundaram, Michael Tognolli, Lydie Bougueleret, Ghislaine Argoud-Puy, Isabelle Cusin, Paula Duek Roggli, Ioannis Xenarios, Rolf Apweiler
2012 J jnl
Nucleic Acids Res.
Anne Morgat, Eric Coissac, Elisabeth Coudert, Kristian B. Axelsen, Guillaume Keller, Amos Bairoch, Alan J. Bridge, Lydie Bougueleret, Ioannis Xenarios, Alain Viari
2011 J jnl
Bioinform.
Martial Sankar, Karen S. Osmont, Jakub Rolcik, Bojan Gujas, Danuse Tarkowska, Miroslav Strnad, Ioannis Xenarios, Christian S. Hardtke
2011 J jnl
Briefings Bioinform.
Brigitte Boeckmann, Marc Robinson-Rechavi, Ioannis Xenarios, Christophe Dessimoz
2011 J jnl
Nucleic Acids Res.
Paolo Di Tommaso, Sébastien Moretti, Ioannis Xenarios, Miquel Orobitg, Alberto Montanyola, Jia-Ming Chang, Jean-François Taly, Cédric Notredame
2011 J jnl
Nucleic Acids Res.
Chantal Hulo, Edouard De Castro, Patrick Masson, Lydie Bougueleret, Amos Bairoch, Ioannis Xenarios, Philippe Le Mercier
2011 J jnl
Bioinform.
Oksana Riba-Grognuz, Laurent Keller, Laurent Falquet, Ioannis Xenarios, Yannick Wurm
2010 J jnl
Database J. Biol. Databases Curation
Robin Liechti, Gábor Csárdi, Sven Bergmann, Frédéric Schütz, Thierry Sengstag, Sylvia F. Boj, Joan-Marc Servitja, Jorge Ferrer, Leentje Van Lommel, Frans Schuit, Sonia Klinger, Bernard Thorens, Najib Naamane, Decio L. Eizirik, Lorella Marselli, Marco Bugliani, Piero Marchetti, Stephanie Lucas, Cecilia Holm, C. Victor Jongeneel, Ioannis Xenarios
2010 J jnl
Bioinform.
Thierry Schüpbach, Ioannis Xenarios, Sven Bergmann, Karen Kapur
2010 J jnl
Database J. Biol. Databases Curation
Robin Liechti, Anne Gleizes, Dmitry Kuznetsov, Lydie Bougueleret, Philippe Le Mercier, Amos Bairoch, Ioannis Xenarios
2009 J jnl
Bioinform.
Olivier Martin, Armand Valsesia, Amalio Telenti, Ioannis Xenarios, Brian J. Stevenson
2009 J jnl
BMC Bioinform.
Alexandre Gattiker, Leandro Hermida, Robin Liechti, Ioannis Xenarios, Olivier Collin, Jacques Rougemont, Michael Primig
2009 J jnl
Bioinform.
Abhishek Garg, Kartik Mohanram, Alessandro Di Cara, Giovanni De Micheli, Ioannis Xenarios
2009 J jnl
Nucleic Acids Res.
Alexandre Gattiker, Christophe Dessimoz, Adrian Schneider, Ioannis Xenarios, Marco Pagni, Jacques Rougemont
2008 J jnl
BMC Bioinform.
Jacques Rougemont, Arnaud Amzallag, Christian Iseli, Laurent Farinelli, Ioannis Xenarios, Felix Naef
2008 J jnl
Nucleic Acids Res.
Sébastien Moretti, Andreas Wilm, Desmond G. Higgins, Ioannis Xenarios, Cédric Notredame
2008 J jnl
Bioinform.
Abhishek Garg, Alessandro Di Cara, Ioannis Xenarios, Luis Mendoza, Giovanni De Micheli
2007 B conf
RECOMB
Abhishek Garg, Ioannis Xenarios, Luis Mendoza, Giovanni De Micheli
2007 J jnl
BMC Bioinform.
Alessandro Di Cara, Abhishek Garg, Giovanni De Micheli, Ioannis Xenarios, Luis Mendoza
2002 J jnl
Nucleic Acids Res.
Ioannis Xenarios, Lukasz Salwínski, Xiaoqun Joyce Duan, Patrick Higney, Sul-Min Kim, David S. Eisenberg
2001 J jnl
Nucleic Acids Res.
Ioannis Xenarios, Esteban Fernandez, Lukasz Salwínski, Xiaoqun Joyce Duan, Michael J. Thompson, Edward M. Marcotte, David Eisenberg
2001 J jnl
Bioinform.
Edward M. Marcotte, Ioannis Xenarios, David Eisenberg
2000 J jnl
Nucleic Acids Res.
Ioannis Xenarios, Danny W. Rice, Lukasz Salwínski, Marisa K. Baron, Edward M. Marcotte, David Eisenberg
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)