• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2025 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Runs R8's TraceReferences tool to ensure DEX files are valid."""
6
7import argparse
8import json
9import logging
10import os
11import pathlib
12import re
13import sys
14
15from util import build_utils
16from util import server_utils
17import action_helpers  # build_utils adds //build to sys.path.
18
19_DUMP_DIR_NAME = 'r8inputs_tracerefs'
20
21_SUPPRESSION_PATTERN = '|'.join([
22    # Summary contains warning count, which our filtering makes wrong.
23    r'Warning: Tracereferences found',
24    r'dalvik\.system',
25    r'libcore\.io',
26    r'sun\.misc\.Unsafe',
27
28    # Explicictly guarded by try (NoClassDefFoundError) in Flogger's
29    # PlatformProvider.
30    r'com\.google\.common\.flogger\.backend\.google\.GooglePlatform',
31    r'com\.google\.common\.flogger\.backend\.system\.DefaultPlatform',
32
33    # TODO(agrieve): Exclude these only when use_jacoco_coverage=true.
34    r'java\.lang\.instrument\.ClassFileTransformer',
35    r'java\.lang\.instrument\.IllegalClassFormatException',
36    r'java\.lang\.instrument\.Instrumentation',
37    r'java\.lang\.management\.ManagementFactory',
38    r'javax\.management\.MBeanServer',
39    r'javax\.management\.ObjectInstance',
40    r'javax\.management\.ObjectName',
41    r'javax\.management\.StandardMBean',
42
43    # Explicitly guarded by try (NoClassDefFoundError) in Firebase's
44    # KotlinDetector: com.google.firebase.platforminfo.KotlinDetector.
45    r'kotlin\.KotlinVersion',
46
47    # Not sure why these two are missing, but they do not seem important.
48    r'ResultIgnorabilityUnspecified',
49    r'kotlin\.DeprecationLevel',
50
51    # Assume missing android.* / java.* references are OS APIs that are not in
52    # android.jar. Not in the above list so as to not match parameter types.
53    # E.g. Missing method void android.media.MediaRouter2$RouteCallback
54    # E.g. Missing class android.util.StatsEvent$Builder
55    r'Missing method \S+ android\.',
56    r'Missing class android\.',
57
58    # The follow classes are from Android XR system libraries and used on
59    # immersive environment.
60    r'Missing class com.google.ar.imp.core\.',
61])
62
63
64def _RunTraceReferences(error_title, r8jar, libs, dex_files, options):
65  cmd = build_utils.JavaCmd(xmx='2G')
66
67  if options.dump_inputs:
68    cmd += [f'-Dcom.android.tools.r8.dumpinputtodirectory={_DUMP_DIR_NAME}']
69
70  cmd += [
71      '-cp', r8jar, 'com.android.tools.r8.tracereferences.TraceReferences',
72      '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
73      '--check'
74  ]
75
76  for path in libs:
77    cmd += ['--lib', path]
78  for path in dex_files:
79    cmd += ['--source', path]
80
81  failed_holder = [False]
82
83  def stderr_filter(stderr):
84
85    had_unfiltered_items = '  ' in stderr
86    stderr = build_utils.FilterLines(stderr, _SUPPRESSION_PATTERN)
87    if stderr:
88      if 'Missing' in stderr:
89        failed_holder[0] = True
90        stderr = 'TraceReferences failed: ' + error_title + """
91Tip: Build with:
92        is_java_debug=false
93        treat_warnings_as_errors=false
94        enable_proguard_obfuscation=false
95     and then use dexdump to see which class(s) reference them.
96
97     E.g.:
98       third_party/android_sdk/public/build-tools/*/dexdump -d \
99out/Release/apks/YourApk.apk > dex.txt
100""" + stderr
101      elif had_unfiltered_items:
102        # Left only with empty headings. All indented items filtered out.
103        stderr = ''
104    return stderr
105
106  try:
107    if options.verbose:
108      stderr_filter = None
109    build_utils.CheckOutput(cmd,
110                            print_stdout=True,
111                            stderr_filter=stderr_filter,
112                            fail_on_output=options.warnings_as_errors)
113  except build_utils.CalledProcessError as e:
114    # Do not output command line because it is massive and makes the actual
115    # error message hard to find.
116    sys.stderr.write(e.output)
117    sys.exit(1)
118  return failed_holder[0]
119
120
121def main():
122  build_utils.InitLogging('TRACEREFS_DEBUG')
123
124  parser = argparse.ArgumentParser()
125  parser.add_argument('--tracerefs-json')
126  parser.add_argument('--use-build-server',
127                      action='store_true',
128                      help='Always use the build server.')
129  parser.add_argument('--stamp')
130  parser.add_argument('--depfile')
131  parser.add_argument('--warnings-as-errors',
132                      action='store_true',
133                      help='Treat all warnings as errors.')
134  parser.add_argument('--dump-inputs',
135                      action='store_true',
136                      help='Use when filing R8 bugs to capture inputs.'
137                      ' Stores inputs to r8inputs.zip')
138  parser.add_argument('--verbose',
139                      action='store_true',
140                      help='Do not filter output')
141  args = parser.parse_args()
142
143  with open(args.tracerefs_json) as f:
144    spec = json.load(f)
145  r8jar = spec['r8jar']
146  libs = spec['libs']
147
148  # No need for r8jar here because GN knows about it already.
149  depfile_deps = []
150  depfile_deps += libs
151  for job in spec['jobs']:
152    depfile_deps += job['jars']
153
154  action_helpers.write_depfile(args.depfile, args.stamp, depfile_deps)
155
156  if server_utils.MaybeRunCommand(name=args.stamp,
157                                  argv=sys.argv,
158                                  stamp_file=args.stamp,
159                                  use_build_server=args.use_build_server):
160    return
161
162  if args.dump_inputs:
163    # Dumping inputs causes output to be emitted, avoid failing due to stdout.
164    args.warnings_as_errors = False
165
166    # Use dumpinputtodirectory instead of dumpinputtofile to avoid failing the
167    # build and keep running tracereferences.
168    dump_dir_name = _DUMP_DIR_NAME
169    dump_dir_path = pathlib.Path(dump_dir_name)
170    if dump_dir_path.exists():
171      shutil.rmtree(dump_dir_path)
172    # The directory needs to exist before r8 adds the zip files in it.
173    dump_dir_path.mkdir()
174
175  logging.debug('Running TraceReferences')
176  error_title = 'DEX contains references to non-existent symbols after R8.'
177  for job in spec['jobs']:
178    name = job['name']
179    dex_files = job['jars']
180    if _RunTraceReferences(error_title, r8jar, libs, dex_files, args):
181      # Failed but didn't raise due to warnings_as_errors=False
182      break
183    error_title = (f'DEX within module "{name}" contains reference(s) to '
184                   'symbols within child splits')
185
186  logging.info('Checks completed.')
187  server_utils.MaybeTouch(args.stamp)
188
189
190if __name__ == '__main__':
191  main()
192