File: tracereferences.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (190 lines) | stat: -rwxr-xr-x 6,538 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs R8's TraceReferences tool to ensure DEX files are valid."""

import argparse
import json
import logging
import pathlib
import shutil
import sys

from util import build_utils
from util import server_utils
import action_helpers  # build_utils adds //build to sys.path.

_DUMP_DIR_NAME = 'r8inputs_tracerefs'

_SUPPRESSION_PATTERN = '|'.join([
    # Summary contains warning count, which our filtering makes wrong.
    r'Warning: Tracereferences found',
    r'dalvik\.system',
    r'libcore\.io',
    r'sun\.misc\.Unsafe',

    # Explicictly guarded by try (NoClassDefFoundError) in Flogger's
    # PlatformProvider.
    r'com\.google\.common\.flogger\.backend\.google\.GooglePlatform',
    r'com\.google\.common\.flogger\.backend\.system\.DefaultPlatform',

    # TODO(agrieve): Exclude these only when use_jacoco_coverage=true.
    r'java\.lang\.instrument\.ClassFileTransformer',
    r'java\.lang\.instrument\.IllegalClassFormatException',
    r'java\.lang\.instrument\.Instrumentation',
    r'java\.lang\.management\.ManagementFactory',
    r'javax\.management\.MBeanServer',
    r'javax\.management\.ObjectInstance',
    r'javax\.management\.ObjectName',
    r'javax\.management\.StandardMBean',

    # Explicitly guarded by try (NoClassDefFoundError) in Firebase's
    # KotlinDetector: com.google.firebase.platforminfo.KotlinDetector.
    r'kotlin\.KotlinVersion',

    # Not sure why these two are missing, but they do not seem important.
    r'ResultIgnorabilityUnspecified',
    r'kotlin\.DeprecationLevel',

    # Assume missing android.* / java.* references are OS APIs that are not in
    # android.jar. Not in the above list so as to not match parameter types.
    # E.g. Missing method void android.media.MediaRouter2$RouteCallback
    # E.g. Missing class android.util.StatsEvent$Builder
    r'Missing method \S+ android\.',
    r'Missing class android\.',

    # The follow classes are from Android XR system libraries and used on
    # immersive environment.
    r'Missing class com.google.ar.imp.core\.',
])


def _RunTraceReferences(error_title, r8jar, libs, dex_files, options):
  cmd = build_utils.JavaCmd(xmx='2G')

  if options.dump_inputs:
    cmd += [f'-Dcom.android.tools.r8.dumpinputtodirectory={_DUMP_DIR_NAME}']

  cmd += [
      '-cp', r8jar, 'com.android.tools.r8.tracereferences.TraceReferences',
      '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
      '--check'
  ]

  for path in libs:
    cmd += ['--lib', path]
  for path in dex_files:
    cmd += ['--source', path]

  failed_holder = [False]

  def stderr_filter(stderr):

    had_unfiltered_items = '  ' in stderr
    stderr = build_utils.FilterLines(stderr, _SUPPRESSION_PATTERN)
    if stderr:
      if 'Missing' in stderr:
        failed_holder[0] = True
        stderr = 'TraceReferences failed: ' + error_title + """
Tip: Build with:
        is_java_debug=false
        treat_warnings_as_errors=false
        enable_proguard_obfuscation=false
     and then use dexdump to see which class(s) reference them.

     E.g.:
       third_party/android_sdk/public/build-tools/*/dexdump -d \
out/Release/apks/YourApk.apk > dex.txt
""" + stderr
      elif had_unfiltered_items:
        # Left only with empty headings. All indented items filtered out.
        stderr = ''
    return stderr

  try:
    if options.verbose:
      stderr_filter = None
    build_utils.CheckOutput(cmd,
                            print_stdout=True,
                            stderr_filter=stderr_filter,
                            fail_on_output=options.warnings_as_errors)
  except build_utils.CalledProcessError as e:
    # Do not output command line because it is massive and makes the actual
    # error message hard to find.
    sys.stderr.write(e.output)
    sys.exit(1)
  return failed_holder[0]


def main():
  build_utils.InitLogging('TRACEREFS_DEBUG')

  parser = argparse.ArgumentParser()
  parser.add_argument('--tracerefs-json')
  parser.add_argument('--use-build-server',
                      action='store_true',
                      help='Always use the build server.')
  parser.add_argument('--stamp')
  parser.add_argument('--depfile')
  parser.add_argument('--warnings-as-errors',
                      action='store_true',
                      help='Treat all warnings as errors.')
  parser.add_argument('--dump-inputs',
                      action='store_true',
                      help='Use when filing R8 bugs to capture inputs.'
                      ' Stores inputs to r8inputs.zip')
  parser.add_argument('--verbose',
                      action='store_true',
                      help='Do not filter output')
  args = parser.parse_args()

  with open(args.tracerefs_json, encoding='utf-8') as f:
    spec = json.load(f)
  r8jar = spec['r8jar']
  libs = spec['libs']

  # No need for r8jar here because GN knows about it already.
  depfile_deps = []
  depfile_deps += libs
  for job in spec['jobs']:
    depfile_deps += job['jars']

  action_helpers.write_depfile(args.depfile, args.stamp, depfile_deps)

  if server_utils.MaybeRunCommand(name=args.stamp,
                                  argv=sys.argv,
                                  stamp_file=args.stamp,
                                  use_build_server=args.use_build_server):
    return

  if args.dump_inputs:
    # Dumping inputs causes output to be emitted, avoid failing due to stdout.
    args.warnings_as_errors = False

    # Use dumpinputtodirectory instead of dumpinputtofile to avoid failing the
    # build and keep running tracereferences.
    dump_dir_name = _DUMP_DIR_NAME
    dump_dir_path = pathlib.Path(dump_dir_name)
    if dump_dir_path.exists():
      shutil.rmtree(dump_dir_path)
    # The directory needs to exist before r8 adds the zip files in it.
    dump_dir_path.mkdir()

  logging.debug('Running TraceReferences')
  error_title = 'DEX contains references to non-existent symbols after R8.'
  for job in spec['jobs']:
    name = job['name']
    dex_files = job['jars']
    if _RunTraceReferences(error_title, r8jar, libs, dex_files, args):
      # Failed but didn't raise due to warnings_as_errors=False
      break
    error_title = (f'DEX within module "{name}" contains reference(s) to '
                   'symbols within child splits')

  logging.info('Checks completed.')
  server_utils.MaybeTouch(args.stamp)


if __name__ == '__main__':
  main()