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