diff options
Diffstat (limited to 'third_party/libwebrtc/build/android/pylib/utils/dexdump.py')
-rw-r--r-- | third_party/libwebrtc/build/android/pylib/utils/dexdump.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/pylib/utils/dexdump.py b/third_party/libwebrtc/build/android/pylib/utils/dexdump.py new file mode 100644 index 0000000000..f81ac603d4 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/utils/dexdump.py @@ -0,0 +1,136 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import re +import shutil +import sys +import tempfile +from xml.etree import ElementTree + +from devil.utils import cmd_helper +from pylib import constants + +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'gyp')) +from util import build_utils + +DEXDUMP_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'dexdump') + + +def Dump(apk_path): + """Dumps class and method information from a APK into a dict via dexdump. + + Args: + apk_path: An absolute path to an APK file to dump. + Returns: + A dict in the following format: + { + <package_name>: { + 'classes': { + <class_name>: { + 'methods': [<method_1>, <method_2>] + } + } + } + } + """ + try: + dexfile_dir = tempfile.mkdtemp() + parsed_dex_files = [] + for dex_file in build_utils.ExtractAll(apk_path, + dexfile_dir, + pattern='*classes*.dex'): + output_xml = cmd_helper.GetCmdOutput( + [DEXDUMP_PATH, '-l', 'xml', dex_file]) + # Dexdump doesn't escape its XML output very well; decode it as utf-8 with + # invalid sequences replaced, then remove forbidden characters and + # re-encode it (as etree expects a byte string as input so it can figure + # out the encoding itself from the XML declaration) + BAD_XML_CHARS = re.compile( + u'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\x84\x86-\x9f' + + u'\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') + if sys.version_info[0] < 3: + decoded_xml = output_xml.decode('utf-8', 'replace') + clean_xml = BAD_XML_CHARS.sub(u'\ufffd', decoded_xml) + else: + # Line duplicated to avoid pylint redefined-variable-type error. + clean_xml = BAD_XML_CHARS.sub(u'\ufffd', output_xml) + parsed_dex_files.append( + _ParseRootNode(ElementTree.fromstring(clean_xml.encode('utf-8')))) + return parsed_dex_files + finally: + shutil.rmtree(dexfile_dir) + + +def _ParseRootNode(root): + """Parses the XML output of dexdump. This output is in the following format. + + This is a subset of the information contained within dexdump output. + + <api> + <package name="foo.bar"> + <class name="Class" extends="foo.bar.SuperClass"> + <field name="Field"> + </field> + <constructor name="Method"> + <parameter name="Param" type="int"> + </parameter> + </constructor> + <method name="Method"> + <parameter name="Param" type="int"> + </parameter> + </method> + </class> + </package> + </api> + """ + results = {} + for child in root: + if child.tag == 'package': + package_name = child.attrib['name'] + parsed_node = _ParsePackageNode(child) + if package_name in results: + results[package_name]['classes'].update(parsed_node['classes']) + else: + results[package_name] = parsed_node + return results + + +def _ParsePackageNode(package_node): + """Parses a <package> node from the dexdump xml output. + + Returns: + A dict in the format: + { + 'classes': { + <class_1>: { + 'methods': [<method_1>, <method_2>] + }, + <class_2>: { + 'methods': [<method_1>, <method_2>] + }, + } + } + """ + classes = {} + for child in package_node: + if child.tag == 'class': + classes[child.attrib['name']] = _ParseClassNode(child) + return {'classes': classes} + + +def _ParseClassNode(class_node): + """Parses a <class> node from the dexdump xml output. + + Returns: + A dict in the format: + { + 'methods': [<method_1>, <method_2>] + } + """ + methods = [] + for child in class_node: + if child.tag == 'method': + methods.append(child.attrib['name']) + return {'methods': methods, 'superclass': class_node.attrib['extends']} |