xref: /aosp_15_r20/build/soong/scripts/check_boot_jars/check_boot_jars.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1#!/usr/bin/env python
2"""Check boot jars.
3
4Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> \
5<jar2> ...
6"""
7import logging
8import re
9import subprocess
10import sys
11import xml.etree.ElementTree
12
13# The compiled allow list RE.
14allow_list_re = None
15
16
17def LoadAllowList(filename):
18    """ Load and compile allow list regular expressions from filename."""
19    lines = []
20    with open(filename, 'r') as f:
21        for line in f:
22            line = line.strip()
23            if not line or line.startswith('#'):
24                continue
25            lines.append(line)
26    combined_re = r'^(%s)$' % '|'.join(lines)
27    global allow_list_re #pylint: disable=global-statement
28    try:
29        allow_list_re = re.compile(combined_re)
30    except re.error:
31        logging.exception(
32            'Cannot compile package allow list regular expression: %r',
33            combined_re)
34        allow_list_re = None
35        return False
36    return True
37
38def CheckDexJar(dexdump_path, allow_list_path, jar):
39    """Check a dex jar file."""
40    # Use dexdump to generate the XML representation of the dex jar file.
41    p = subprocess.Popen(
42        args='%s -l xml %s' % (dexdump_path, jar),
43        stdout=subprocess.PIPE,
44        shell=True)
45    stdout, _ = p.communicate()
46    if p.returncode != 0:
47        return False
48
49    packages = 0
50    try:
51        # TODO(b/172063475) - improve performance
52        root = xml.etree.ElementTree.fromstring(stdout)
53    except xml.etree.ElementTree.ParseError as e:
54        print('Error processing jar %s - %s' % (jar, e), file=sys.stderr)
55        print(stdout, file=sys.stderr)
56        return False
57    for package_elt in root.iterfind('package'):
58        packages += 1
59        package_name = package_elt.get('name')
60        if not package_name or not allow_list_re.match(package_name):
61            # Report the name of a class in the package as it is easier to
62            # navigate to the source of a concrete class than to a package
63            # which is often required to investigate this failure.
64            class_name = package_elt[0].get('name')
65            if package_name:
66                class_name = package_name + '.' + class_name
67            print((
68                'Error: %s contains class file %s, whose package name "%s" is '
69                'empty or not in the allow list %s of packages allowed on the '
70                'bootclasspath.'
71                % (jar, class_name, package_name, allow_list_path)),
72                  file=sys.stderr)
73            return False
74    if packages == 0:
75        print(('Error: %s does not contain any packages.' % jar),
76              file=sys.stderr)
77        return False
78    return True
79
80def main(argv):
81    if len(argv) < 3:
82        print(__doc__)
83        return 1
84    dexdump_path = argv[0]
85    allow_list_path = argv[1]
86
87    if not LoadAllowList(allow_list_path):
88        return 1
89
90    passed = True
91    for jar in argv[2:]:
92        if not CheckDexJar(dexdump_path, allow_list_path, jar):
93            passed = False
94    if not passed:
95        return 1
96
97    return 0
98
99
100if __name__ == '__main__':
101    sys.exit(main(sys.argv[1:]))
102