| #!/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. |
| |
| import argparse |
| import logging |
| import os |
| import pathlib |
| import re |
| import shlex |
| import shutil |
| import subprocess |
| import sys |
| |
| _SRC_ROOT = pathlib.Path(__file__).parents[3] |
| sys.path.insert(1, str(_SRC_ROOT / 'build/android/gyp')) |
| |
| from util import build_utils |
| import action_helpers |
| |
| _ANNOTATOR_JAR = ('../NullAwayAnnotator/annotator-core/build/libs/' |
| 'annotator-core-1.3.16-SNAPSHOT.jar') |
| _CHROME_JAVA_TURBINE_JAR = 'obj/chrome/android/chrome_java.turbine.jar' |
| |
| |
| def _read_file(path): |
| return pathlib.Path(path).read_text() |
| |
| |
| def _write_file(path, data): |
| return pathlib.Path(path).write_text(data) |
| |
| |
| def _find_unmarked(java_files): |
| ret = set() |
| for path in java_files: |
| data = _read_file(path) |
| if '@NullUnmarked' in data or '@SuppressWarnings("NullAway' in data: |
| ret.add(path) |
| return sorted(ret) |
| |
| |
| def _read_build_config_value(path, key): |
| value = build_utils.ExpandFileArgs([f'@FileArg({path}:{key})'])[0] |
| return action_helpers.parse_gn_list(value) |
| |
| |
| def main(): |
| logging.basicConfig(format='%(message)s', level=logging.INFO) |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--loud', |
| action='store_true', |
| help='Print compiler while annotating output') |
| args = parser.parse_args() |
| |
| if not os.path.exists('args.gn'): |
| parser.error('Must be run from within output directory.') |
| if not os.path.exists(_ANNOTATOR_JAR): |
| parser.error('Annotator .jar not found. Follow steps to build it.') |
| if not os.path.exists(_CHROME_JAVA_TURBINE_JAR): |
| parser.error('Run "autoninja chrome/android:chrome_java" first.') |
| |
| # Compile only files that are @NullMarked (to speed things up). |
| java_files = _read_file( |
| 'gen/chrome/android/chrome_java.sources').splitlines() |
| java_files = [p for p in java_files if '@NullMarked' in _read_file(p)] |
| sources_path = 'null-away-chrome-java-sources.txt' |
| _write_file(sources_path, '\n'.join(java_files)) |
| logging.info( |
| 'Running annotator over %d @NullMarked files within chrome_java', |
| len(java_files)) |
| logging.info('This will probably take 3-5 minutes 🐢🐢🐢') |
| |
| classpath = [ |
| 'obj/third_party/android_sdk/android_sdk_java.ijar.jar', |
| _CHROME_JAVA_TURBINE_JAR, |
| ] |
| classpath += _read_build_config_value( |
| 'gen/chrome/android/chrome_java.build_config.json', |
| 'javac_full_interface_classpath') |
| |
| processor_path = _read_build_config_value( |
| 'gen/tools/android/errorprone_plugin/errorprone_plugin.build_config.json', |
| 'classpath') |
| processor_path.append(_ANNOTATOR_JAR) |
| |
| contract_annotations = [ |
| 'org.chromium.build.annotations.Contract', |
| 'org.chromium.support_lib_boundary.util.Contract', |
| ] |
| init_methods = [ |
| 'android.app.Application.onCreate', |
| 'android.app.Activity.onCreate', |
| 'android.app.Service.onCreate', |
| 'android.app.backup.BackupAgent.onCreate', |
| 'android.content.ContentProvider.attachInfo', |
| 'android.content.ContentProvider.onCreate', |
| 'android.content.ContextWrapper.attachBaseContext', |
| 'androidx.preference.PreferenceFragmentCompat.onCreatePreferences', |
| ] |
| errorprone_args = [ |
| '-Xplugin:ErrorProne', |
| '-XepDisableAllChecks', |
| '-Xep:NullAway:ERROR', |
| '-Xep:AnnotatorScanner:ERROR', |
| '-XepOpt:NullAway:OnlyNullMarked', |
| '-XepOpt:NullAway:CustomContractAnnotations=' + |
| ','.join(contract_annotations), |
| '-XepOpt:NullAway:CastToNonNullMethod=org.chromium.build.NullUtil.assumeNonNull', |
| '-XepOpt:NullAway:AssertsEnabled=true', |
| '-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true', |
| '-XepOpt:Nullaway:AcknowledgeAndroidRecent=true', |
| '-XepOpt:NullAway:JSpecifyMode=true', |
| '-XepOpt:NullAway:KnownInitializers=' + ','.join(init_methods), |
| '-XepOpt:AnnotatorScanner:ConfigPath=../nullaway_scanner.xml', |
| '-XepOpt:NullAway:SerializeFixMetadata=true', |
| '-XepOpt:NullAway:FixSerializationConfigPath=../nullaway_config.xml', |
| ] |
| javac_cmd = [ |
| '../../third_party/jdk/current/bin/javac', '-g', '-parameters', |
| '--release', '17', '-encoding', 'UTF-8', '-sourcepath', ':', |
| '-Xlint:-dep-ann', '-Xlint:-removal', '-J-XX:+PerfDisableSharedMem', |
| '-Xmaxerrs', '100000', '-Xmaxwarns', '100000', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', |
| '-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', |
| '-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', |
| '-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', |
| ' '.join(errorprone_args), '-XDcompilePolicy=simple', |
| '-XDshould-stop.ifError=FLOW', '-XDshould-stop.ifNoError=FLOW', |
| '-processorpath', ':'.join(processor_path), '-d', |
| 'nullaway-annotator-output', '-classpath', ':'.join(classpath), |
| f'@{sources_path}' |
| ] |
| |
| outdir = os.path.abspath('annotator-out') |
| if os.path.exists(outdir): |
| shutil.rmtree(outdir) |
| |
| compile_script = f'nullaway-annotator-compile.sh' |
| compile_logs = f'nullaway-annotator-compile.log' |
| if os.path.exists(compile_logs): |
| os.unlink(compile_logs) |
| |
| _write_file( |
| compile_script, f"""\ |
| #!/bin/bash |
| set -e |
| echo -e "\n\n============= START OF COMPILE =============" >> {compile_logs} |
| {shlex.join(javac_cmd)} 2>&1 | tee -a {compile_logs} |
| """) |
| os.chmod(compile_script, 0o744) |
| |
| cmd = build_utils.JavaCmd() + [ |
| '-jar', _ANNOTATOR_JAR, '-bc', f'./{compile_script}', '-n', |
| 'org.chromium.build.annotations.Nullable', '-d', outdir, '-cp', |
| '../nullaway_config.tsv', '-cn', 'NULLAWAY', '-sre', |
| 'org.chromium.build.annotations.NullUnmarked', '-i', |
| 'org.chromium.build.annotations.Initializer' |
| ] |
| if args.loud: |
| cmd += ['--redirect-build-output-stderr'] |
| |
| result = subprocess.run(cmd).returncode |
| logging.warning('🪵 Error Prone output (find warnings here):\n%s', |
| os.path.abspath(compile_logs)) |
| |
| if result: |
| print('😰 Command failed.') |
| sys.exit(result) |
| |
| unmarked_files = _find_unmarked(java_files) |
| |
| if unmarked_files: |
| for path in unmarked_files: |
| data = _read_file(path) |
| # Leave a whitespace change as a hint it was nullunmarked. |
| data = data.replace('@NullUnmarked ', '\t') |
| data = data.replace('@SuppressWarnings("NullAway.Init")', '\t') |
| _write_file(path, data) |
| |
| print(""" |
| The following files have unresolved warnings, which have been marked by \\t \ |
| characters. |
| |
| Files:""") |
| print('\n'.join(unmarked_files)) |
| else: |
| print('No suppressions remained after auto-annotating.') |
| |
| |
| if __name__ == '__main__': |
| main() |