Merge commit '871f85e8' into dev-release
diff --git a/.gitignore b/.gitignore index 6e4c0e3..bbe6eea 100644 --- a/.gitignore +++ b/.gitignore
@@ -138,10 +138,43 @@ third_party/opensource-apps/friendlyeats.tar.gz third_party/opensource-apps/iosched third_party/opensource-apps/iosched.tar.gz +third_party/opensource-apps/kiss +third_party/opensource-apps/kiss.tar.gz +third_party/opensource-apps/materialistic +third_party/opensource-apps/materialistic.tar.gz +third_party/opensource-apps/minimal-todo +third_party/opensource-apps/minimal-todo.tar.gz +third_party/opensource-apps/muzei +third_party/opensource-apps/muzei.tar.gz +third_party/opensource-apps/newpipe +third_party/opensource-apps/newpipe.tar.gz +third_party/opensource-apps/rover-android +third_party/opensource-apps/rover-android.tar.gz +third_party/opensource-apps/santa-tracker +third_party/opensource-apps/santa-tracker.tar.gz +third_party/opensource-apps/signal-android +third_party/opensource-apps/signal-android.tar.gz +third_party/opensource-apps/simple-calendar +third_party/opensource-apps/simple-calendar.tar.gz +third_party/opensource-apps/simple-camera +third_party/opensource-apps/simple-camera.tar.gz +third_party/opensource-apps/simple-file-manager +third_party/opensource-apps/simple-file-manager.tar.gz +third_party/opensource-apps/simple-gallery +third_party/opensource-apps/simple-gallery.tar.gz +third_party/opensource-apps/sqldelight +third_party/opensource-apps/sqldelight.tar.gz third_party/opensource-apps/sunflower third_party/opensource-apps/sunflower.tar.gz +third_party/opensource-apps/tachiyomi +third_party/opensource-apps/tachiyomi.tar.gz +third_party/opensource-apps/tivi +third_party/opensource-apps/tivi.tar.gz +third_party/opensource-apps/tusky +third_party/opensource-apps/tusky.tar.gz third_party/opensource-apps/wikipedia third_party/opensource-apps/wikipedia.tar.gz +third_party/opensource-apps/android/compose-samples/* third_party/opensource_apps third_party/opensource_apps.tar.gz third_party/proguard/*
diff --git a/build.gradle b/build.gradle index 558919e..981dcfd 100644 --- a/build.gradle +++ b/build.gradle
@@ -1875,10 +1875,11 @@ } def printStackTrace(TestResult result) { + filterStackTraces(result) if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) { def out = new StringBuffer() def err = new StringBuffer() - def command = "python tools/retrace.py" + def command = "python tools/retrace.py --quiet" def header = "RETRACED STACKTRACE"; if (System.getenv('BUILDBOT_BUILDERNAME') != null && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) { @@ -1893,19 +1894,49 @@ result.exception.printStackTrace(processIn) processIn.flush() processIn.close() - if (process.waitFor() != 0) { + def errorDuringRetracing = process.waitFor() != 0 + if (errorDuringRetracing) { out.append("ERROR DURING RETRACING\n") out.append(err.toString()) } - out.append("\n\n--------------------------------------\n") - out.append("OBFUSCATED STACKTRACE\n") - out.append("--------------------------------------\n") - result.exceptions.add(0, new Exception(out.toString())) + if (project.hasProperty('print_obfuscated_stacktraces') || errorDuringRetracing) { + out.append("\n\n--------------------------------------\n") + out.append("OBFUSCATED STACKTRACE\n") + out.append("--------------------------------------\n") + } else { + result.exceptions.clear() + } + def exception = new Exception(out.toString()) + exception.setStackTrace([] as StackTraceElement[]) + result.exceptions.add(0, exception) } else { result.exception.printStackTrace() } } +def filterStackTraces(TestResult result) { + for (Throwable throwable : result.getExceptions()) { + filterStackTrace(throwable) + } +} + +def filterStackTrace(Throwable exception) { + if (!project.hasProperty('print_full_stacktraces')) { + def elements = [] + def skipped = [] + for (StackTraceElement element : exception.getStackTrace()) { + if (element.toString().contains("com.android.tools.r8")) { + elements.addAll(skipped) + elements.add(element) + skipped.clear() + } else { + skipped.add(element) + } + } + exception.setStackTrace(elements as StackTraceElement[]) + } +} + test { if (project.hasProperty('generate_golden_files_to')) { systemProperty 'generate_golden_files_to', project.property('generate_golden_files_to')
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg index c0222e8..3ebf03f 100644 --- a/infra/config/global/cr-buildbucket.cfg +++ b/infra/config/global/cr-buildbucket.cfg
@@ -167,15 +167,6 @@ } } builders { - name: "linux-jdk8_9" - mixins: "linux" - mixins: "normal" - priority: 26 - recipe { - properties_j: "test_options:[\"--runtimes=jdk8:jdk9\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]" - } - } - builders { name: "linux_release" mixins: "normal" mixins: "linux" @@ -346,24 +337,6 @@ } } builders { - name: "linux-run-on-as-app" - mixins: "linux" - mixins: "normal" - recipe { - properties: "run_on_apps:True" - properties: "recompilation:False" - } - } - builders { - name: "linux-run-on-as-app-recompilation" - mixins: "linux" - mixins: "normal" - recipe { - properties: "run_on_apps:True" - properties: "recompilation:True" - } - } - builders { name: "linux-run-on-app-dump" mixins: "linux" mixins: "normal" @@ -373,15 +346,6 @@ } } builders { - name: "linux-run-on-as-app_release" - mixins: "linux" - mixins: "normal" - execution_timeout_secs: 25200 # 7h - recipe { - properties: "run_on_apps:True" - } - } - builders { name: "linux-run-on-app-dump_release" mixins: "linux" mixins: "normal" @@ -448,5 +412,15 @@ properties: "tool:r8" } } + builders { + name: "kotlin-builder" + mixins: "linux" + mixins: "normal" + recipe { + properties_j: "test_options:[\"--not_used\"]" + properties: "test_wrapper:google-scripts/build.py" + properties: "kotlin_repo:True" + } + } } }
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg index e76751b..1c2c87f 100644 --- a/infra/config/global/luci-milo.cfg +++ b/infra/config/global/luci-milo.cfg
@@ -76,16 +76,6 @@ short_name: "internal" } builders { - name: "buildbucket/luci.r8.ci/linux-run-on-as-app" - category: "R8" - short_name: "apps" - } - builders { - name: "buildbucket/luci.r8.ci/linux-run-on-as-app-recompilation" - category: "R8" - short_name: "apps-rec" - } - builders { name: "buildbucket/luci.r8.ci/linux-run-on-app-dump" category: "R8" short_name: "apps-dump" @@ -166,11 +156,6 @@ short_name: "internal" } builders { - name: "buildbucket/luci.r8.ci/linux-run-on-as-app_release" - category: "R8 release" - short_name: "apps" - } - builders { name: "buildbucket/luci.r8.ci/linux-run-on-app-dump_release" category: "R8 release" short_name: "apps-dump" @@ -190,5 +175,9 @@ category: "win release" short_name: "win" } - + builders { + name: "buildbucket/luci.r8.ci/kotlin-builder" + category: "kotlin" + short_name: "kotlin_builder" + } }
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg index 1e48f29..92b76f3 100644 --- a/infra/config/global/luci-notify.cfg +++ b/infra/config/global/luci-notify.cfg
@@ -39,11 +39,6 @@ repository: "https://r8.googlesource.com/r8" } builders { - name: "linux-jdk8_9" - bucket: "ci" - repository: "https://r8.googlesource.com/r8" - } - builders { name: "linux_release" bucket: "ci" repository: "https://r8.googlesource.com/r8" @@ -139,26 +134,11 @@ repository: "https://r8.googlesource.com/r8" } builders { - name: "linux-run-on-as-app" - bucket: "ci" - repository: "https://r8.googlesource.com/r8" - } - builders { - name: "linux-run-on-as-app-recompilation" - bucket: "ci" - repository: "https://r8.googlesource.com/r8" - } - builders { name: "linux-run-on-app-dump" bucket: "ci" repository: "https://r8.googlesource.com/r8" } builders { - name: "linux-run-on-as-app_release" - bucket: "ci" - repository: "https://r8.googlesource.com/r8" - } - builders { name: "linux-run-on-app-dump_release" bucket: "ci" repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg index a0b3fa3..a18a124 100644 --- a/infra/config/global/luci-scheduler.cfg +++ b/infra/config/global/luci-scheduler.cfg
@@ -29,7 +29,6 @@ triggers: "linux" triggers: "linux-jdk8" triggers: "linux-jdk9" - triggers: "linux-jdk8_9" triggers: "linux-android-4.0.4" triggers: "linux-android-4.4.4" triggers: "linux-android-5.1.1" @@ -38,8 +37,6 @@ triggers: "linux-android-8.1.0" triggers: "linux-android-9.0.0" triggers: "linux-android-10.0.0" - triggers: "linux-run-on-as-app" - triggers: "linux-run-on-as-app-recompilation" triggers: "linux-run-on-app-dump" triggers: "linux-internal" triggers: "linux-jctf" @@ -58,6 +55,16 @@ } trigger { + id: "kotlin_trigger" + acl_sets: "default" + gitiles: { + repo: "https://github.googlesource.com/google/kotlin" + refs: "refs/heads/google-ir" + } + triggers: "kotlin-builder" +} + +trigger { id: "branch-gitiles-trigger" acl_sets: "default" gitiles: { @@ -77,7 +84,6 @@ triggers: "linux-android-10.0.0_release" triggers: "linux-internal_release" triggers: "linux-jctf_release" - triggers: "linux-run-on-as-app_release" triggers: "linux-run-on-app-dump_release" triggers: "linux_release" triggers: "r8cf-linux-jctf_release" @@ -128,6 +134,19 @@ } job { + id: "kotlin-builder" + acl_sets: "default" + triggering_policy: { + max_concurrent_invocations: 1 + } + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "luci.r8.ci" + builder: "kotlin-builder" + } +} + +job { id: "linux" acl_sets: "default" triggering_policy: { @@ -170,20 +189,6 @@ } job { - id: "linux-jdk8_9" - acl_sets: "default" - triggering_policy: { - kind: GREEDY_BATCHING - max_concurrent_invocations: 2 - } - buildbucket { - server: "cr-buildbucket.appspot.com" - bucket: "luci.r8.ci" - builder: "linux-jdk8_9" - } -} - -job { id: "linux-android-4.0.4" acl_sets: "default" triggering_policy: { @@ -429,35 +434,6 @@ } job { - id: "linux-run-on-as-app" - acl_sets: "default" - triggering_policy: { - kind: GREEDY_BATCHING - max_concurrent_invocations: 3 - } - buildbucket { - server: "cr-buildbucket.appspot.com" - bucket: "luci.r8.ci" - builder: "linux-run-on-as-app" - } -} - -job { - id: "linux-run-on-as-app-recompilation" - acl_sets: "default" - triggering_policy: { - kind: GREEDY_BATCHING - max_concurrent_invocations: 3 - } - buildbucket { - server: "cr-buildbucket.appspot.com" - bucket: "luci.r8.ci" - builder: "linux-run-on-as-app-recompilation" - } -} - - -job { id: "linux-run-on-app-dump" acl_sets: "default" triggering_policy: { @@ -472,20 +448,6 @@ } job { - id: "linux-run-on-as-app_release" - acl_sets: "default" - triggering_policy: { - max_batch_size: 1 - max_concurrent_invocations: 3 - } - buildbucket { - server: "cr-buildbucket.appspot.com" - bucket: "luci.r8.ci" - builder: "linux-run-on-as-app_release" - } -} - -job { id: "linux-run-on-app-dump_release" acl_sets: "default" triggering_policy: {
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json index 98ceb80..ed216b0 100644 --- a/src/library_desugar/desugar_jdk_libs.json +++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@ "configuration_format_version": 3, "group_id" : "com.tools.android", "artifact_id" : "desugar_jdk_libs", - "version": "1.1.0", + "version": "1.1.1", "required_compilation_api_level": 26, "synthesized_library_classes_package_prefix": "j$.", "support_all_callbacks_from_library": true, @@ -243,7 +243,7 @@ "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }", "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }", "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }", - "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }", + "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }", "-keeppackagenames j$", "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }", "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
diff --git a/src/library_desugar/desugar_jdk_libs_alternative_3.json b/src/library_desugar/desugar_jdk_libs_alternative_3.json index 628fc53..012d6ce 100644 --- a/src/library_desugar/desugar_jdk_libs_alternative_3.json +++ b/src/library_desugar/desugar_jdk_libs_alternative_3.json
@@ -2,7 +2,7 @@ "configuration_format_version": 3, "group_id" : "com.tools.android", "artifact_id" : "desugar_jdk_libs_alternative_3", - "version": "1.1.0", + "version": "1.1.1", "required_compilation_api_level": 26, "synthesized_library_classes_package_prefix": "j$.", "support_all_callbacks_from_library": false, @@ -246,7 +246,7 @@ "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }", "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }", "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }", - "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }", + "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }", "-keeppackagenames j$", "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }", "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java index 036dd57..893bc64 100644 --- a/src/main/java/com/android/tools/r8/BaseCommand.java +++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -275,14 +275,16 @@ * @see #addMainDexListFiles(Path...) */ public B addMainDexListFiles(Collection<Path> files) { - guard(() -> { - try { - app.addMainDexListFiles(files); - } catch (NoSuchFileException e) { - reporter.error(new StringDiagnostic( - "Main-dex-ist file does not exist", new PathOrigin(Paths.get(e.getFile())))); - } - }); + guard( + () -> { + try { + app.addMainDexListFiles(files); + } catch (NoSuchFileException e) { + reporter.error( + new StringDiagnostic( + "Main-dex-list file does not exist", new PathOrigin(Paths.get(e.getFile())))); + } + }); return self(); }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java index 80dcf13..b12b467 100644 --- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java +++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -109,6 +109,15 @@ return minApiLevel; } + void dumpBaseCommandOptions(DumpOptions.Builder builder) { + builder + .setCompilationMode(getMode()) + .setMinApi(getMinApiLevel()) + .setOptimizeMultidexForLinearAlloc(isOptimizeMultidexForLinearAlloc()) + .setThreadCount(getThreadCount()) + .setDesugarState(getDesugarState()); + } + /** * Get the program consumer that will receive the compilation output. *
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index 3372bc9..501d4ae 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING; import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; +import com.android.tools.r8.dex.Marker.Tool; import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.inspector.Inspector; @@ -449,7 +450,17 @@ internal.dumpInputToDirectory = null; internal.dumpInputToFile = null; } + internal.dumpOptions = dumpOptions(); return internal; } + + private DumpOptions dumpOptions() { + DumpOptions.Builder builder = DumpOptions.builder(Tool.D8); + dumpBaseCommandOptions(builder); + return builder + .setIntermediate(intermediate) + .setDesugaredLibraryConfiguration(libraryConfiguration) + .build(); + } }
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java new file mode 100644 index 0000000..cd2ac79 --- /dev/null +++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -0,0 +1,263 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8; + +import com.android.tools.r8.dex.Marker.Tool; +import com.android.tools.r8.features.FeatureSplitConfiguration; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; +import com.android.tools.r8.shaking.ProguardConfiguration; +import com.android.tools.r8.utils.InternalOptions.DesugarState; +import com.android.tools.r8.utils.ThreadUtils; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DumpOptions { + + // The following keys and values should not be changed to keep the dump utility backward + // compatible with previous versions. They are also used by the python script compileDump and + // the corresponding CompileDumpCompatR8 java class. + private static final String TOOL_KEY = "tool"; + private static final String MODE_KEY = "mode"; + private static final String DEBUG_MODE_VALUE = "debug"; + private static final String RELEASE_MODE_VALUE = "release"; + private static final String MIN_API_KEY = "min-api"; + private static final String OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY = + "optimize-multidex-for-linear-alloc"; + private static final String THREAD_COUNT_KEY = "thread-count"; + private static final String DESUGAR_STATE_KEY = "desugar-state"; + private static final String INTERMEDIATE_KEY = "intermediate"; + private static final String INCLUDE_DATA_RESOURCES_KEY = "include-data-resources"; + private static final String TREE_SHAKING_KEY = "tree-shaking"; + private static final String MINIFICATION_KEY = "minification"; + private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility"; + + private final Tool tool; + private final CompilationMode compilationMode; + private final int minApi; + private final boolean optimizeMultidexForLinearAlloc; + private final int threadCount; + private final DesugarState desugarState; + private final Optional<Boolean> intermediate; + private final Optional<Boolean> includeDataResources; + private final Optional<Boolean> treeShaking; + private final Optional<Boolean> minification; + private final Optional<Boolean> forceProguardCompatibility; + + // Dump if present. + private final DesugaredLibraryConfiguration desugaredLibraryConfiguration; + private final FeatureSplitConfiguration featureSplitConfiguration; + private final ProguardConfiguration proguardConfiguration; + + // Reporting only. + private final boolean dumpInputToFile; + + private DumpOptions( + Tool tool, + CompilationMode compilationMode, + int minAPI, + DesugaredLibraryConfiguration desugaredLibraryConfiguration, + boolean optimizeMultidexForLinearAlloc, + int threadCount, + DesugarState desugarState, + Optional<Boolean> intermediate, + Optional<Boolean> includeDataResources, + Optional<Boolean> treeShaking, + Optional<Boolean> minification, + Optional<Boolean> forceProguardCompatibility, + FeatureSplitConfiguration featureSplitConfiguration, + ProguardConfiguration proguardConfiguration, + boolean dumpInputToFile) { + this.tool = tool; + this.compilationMode = compilationMode; + this.minApi = minAPI; + this.desugaredLibraryConfiguration = desugaredLibraryConfiguration; + this.optimizeMultidexForLinearAlloc = optimizeMultidexForLinearAlloc; + this.threadCount = threadCount; + this.desugarState = desugarState; + this.intermediate = intermediate; + this.includeDataResources = includeDataResources; + this.treeShaking = treeShaking; + this.minification = minification; + this.forceProguardCompatibility = forceProguardCompatibility; + this.featureSplitConfiguration = featureSplitConfiguration; + this.proguardConfiguration = proguardConfiguration; + this.dumpInputToFile = dumpInputToFile; + } + + public String dumpOptions() { + StringBuilder builder = new StringBuilder(); + addDumpEntry(builder, TOOL_KEY, tool.name()); + // We keep the following values for backward compatibility. + addDumpEntry( + builder, + MODE_KEY, + compilationMode == CompilationMode.DEBUG ? DEBUG_MODE_VALUE : RELEASE_MODE_VALUE); + addDumpEntry(builder, MIN_API_KEY, minApi); + addDumpEntry(builder, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc); + if (threadCount != ThreadUtils.NOT_SPECIFIED) { + addDumpEntry(builder, THREAD_COUNT_KEY, threadCount); + } + addDumpEntry(builder, DESUGAR_STATE_KEY, desugarState); + addOptionalDumpEntry(builder, INTERMEDIATE_KEY, intermediate); + addOptionalDumpEntry(builder, INCLUDE_DATA_RESOURCES_KEY, includeDataResources); + addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking); + addOptionalDumpEntry(builder, MINIFICATION_KEY, minification); + addOptionalDumpEntry(builder, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility); + return builder.toString(); + } + + private void addOptionalDumpEntry(StringBuilder builder, String key, Optional<?> optionalValue) { + optionalValue.ifPresent(bool -> addDumpEntry(builder, key, bool)); + } + + private void addDumpEntry(StringBuilder builder, String key, Object value) { + builder.append(key).append("=").append(value).append("\n"); + } + + private boolean hasDesugaredLibraryConfiguration() { + return desugaredLibraryConfiguration != null + && desugaredLibraryConfiguration + != DesugaredLibraryConfiguration.EMPTY_DESUGARED_LIBRARY_CONFIGURATION; + } + + public String getDesugaredLibraryJsonSource() { + if (hasDesugaredLibraryConfiguration()) { + return desugaredLibraryConfiguration.getJsonSource(); + } + return null; + } + + public FeatureSplitConfiguration getFeatureSplitConfiguration() { + return featureSplitConfiguration; + } + + public String getParsedProguardConfiguration() { + return proguardConfiguration == null ? null : proguardConfiguration.getParsedConfiguration(); + } + + public boolean dumpInputToFile() { + return dumpInputToFile; + } + + public static Builder builder(Tool tool) { + return new Builder(tool); + } + + public static class Builder { + private final Tool tool; + private CompilationMode compilationMode; + private int minApi; + private boolean optimizeMultidexForLinearAlloc; + private int threadCount; + private DesugarState desugarState; + private Optional<Boolean> intermediate = Optional.empty(); + private Optional<Boolean> includeDataResources = Optional.empty(); + private Optional<Boolean> treeShaking = Optional.empty(); + private Optional<Boolean> minification = Optional.empty(); + private Optional<Boolean> forceProguardCompatibility = Optional.empty(); + // Dump if present. + private DesugaredLibraryConfiguration desugaredLibraryConfiguration; + private FeatureSplitConfiguration featureSplitConfiguration; + private ProguardConfiguration proguardConfiguration; + + // Reporting only. + private boolean dumpInputToFile; + + public Builder(Tool tool) { + this.tool = tool; + } + + public Builder setCompilationMode(CompilationMode compilationMode) { + this.compilationMode = compilationMode; + return this; + } + + public Builder setMinApi(int minAPI) { + this.minApi = minAPI; + return this; + } + + public Builder setDesugaredLibraryConfiguration( + DesugaredLibraryConfiguration desugaredLibraryConfiguration) { + this.desugaredLibraryConfiguration = desugaredLibraryConfiguration; + return this; + } + + public Builder setOptimizeMultidexForLinearAlloc(boolean optimizeMultidexForLinearAlloc) { + this.optimizeMultidexForLinearAlloc = optimizeMultidexForLinearAlloc; + return this; + } + + public Builder setThreadCount(int threadCount) { + this.threadCount = threadCount; + return this; + } + + public Builder setDesugarState(DesugarState desugarState) { + this.desugarState = desugarState; + return this; + } + + public Builder setIntermediate(boolean intermediate) { + this.intermediate = Optional.of(intermediate); + return this; + } + + public Builder setIncludeDataResources(Optional<Boolean> includeDataResources) { + this.includeDataResources = includeDataResources; + return this; + } + + public Builder setForceProguardCompatibility(boolean forceProguardCompatibility) { + this.forceProguardCompatibility = Optional.of(forceProguardCompatibility); + return this; + } + + public Builder setMinification(boolean minification) { + this.minification = Optional.of(minification); + return this; + } + + public Builder setTreeShaking(boolean treeShaking) { + this.treeShaking = Optional.of(treeShaking); + return this; + } + + public Builder setDumpInputToFile(boolean dumpInputToFile) { + this.dumpInputToFile = dumpInputToFile; + return this; + } + + public Builder setFeatureSplitConfiguration( + FeatureSplitConfiguration featureSplitConfiguration) { + this.featureSplitConfiguration = featureSplitConfiguration; + return this; + } + + public Builder setProguardConfiguration(ProguardConfiguration proguardConfiguration) { + this.proguardConfiguration = proguardConfiguration; + return this; + } + + public DumpOptions build() { + return new DumpOptions( + tool, + compilationMode, + minApi, + desugaredLibraryConfiguration, + optimizeMultidexForLinearAlloc, + threadCount, + desugarState, + intermediate, + includeDataResources, + treeShaking, + minification, + forceProguardCompatibility, + featureSplitConfiguration, + proguardConfiguration, + dumpInputToFile); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java index 8e4bb2a..dce2b21 100644 --- a/src/main/java/com/android/tools/r8/L8Command.java +++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.ProgramResource.Kind; +import com.android.tools.r8.dex.Marker.Tool; import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.inspector.Inspector; @@ -196,6 +197,7 @@ assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; internal.threadCount = getThreadCount(); } + internal.dumpOptions = dumpOptions(); return internal; } @@ -383,4 +385,10 @@ @Override public void finished(DiagnosticsHandler handler) {} } + + private DumpOptions dumpOptions() { + DumpOptions.Builder builder = DumpOptions.builder(Tool.L8); + dumpBaseCommandOptions(builder); + return builder.setDesugaredLibraryConfiguration(libraryConfiguration).build(); + } }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index b97eb7f..cfb3540 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -40,8 +40,11 @@ import com.android.tools.r8.graph.SubtypingInfo; import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis; import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis; +import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses; +import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger; import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens; +import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter; @@ -523,7 +526,11 @@ NestedGraphLens lens = staticClassMerger.run(); appView.rewriteWithLens(lens); timing.end(); + } else { + appView.setStaticallyMergedClasses(StaticallyMergedClasses.empty()); } + assert appView.staticallyMergedClasses() != null; + if (options.enableVerticalClassMerging) { timing.begin("VerticalClassMerger"); VerticalClassMerger verticalClassMerger = @@ -535,12 +542,14 @@ mainDexTracingResult); VerticalClassMergerGraphLens lens = verticalClassMerger.run(); if (lens != null) { - appView.setVerticallyMergedClasses(lens.getMergedClasses()); appView.rewriteWithLens(lens); runtimeTypeCheckInfo = runtimeTypeCheckInfo.rewriteWithLens(lens); } timing.end(); + } else { + appView.setVerticallyMergedClasses(VerticallyMergedClasses.empty()); } + assert appView.verticallyMergedClasses() != null; if (options.enableArgumentRemoval) { SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo(); @@ -585,8 +594,9 @@ runtimeTypeCheckInfo = null; } timing.end(); + } else { + appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty()); } - } // None of the optimizations above should lead to the creation of type lattice elements.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 4a68964..40be618 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation; import com.android.tools.r8.ProgramResource.Kind; +import com.android.tools.r8.dex.Marker.Tool; import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.experimental.graphinfo.GraphConsumer; import com.android.tools.r8.features.FeatureSplitConfiguration; @@ -42,6 +43,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Consumer; @@ -98,6 +100,7 @@ private boolean disableMinification = false; private boolean disableVerticalClassMerging = false; private boolean forceProguardCompatibility = false; + private Optional<Boolean> includeDataResources = Optional.empty(); private StringConsumer proguardMapConsumer = null; private StringConsumer proguardUsageConsumer = null; private StringConsumer proguardSeedsConsumer = null; @@ -368,6 +371,7 @@ */ @Override public Builder setOutput(Path outputPath, OutputMode outputMode, boolean includeDataResources) { + this.includeDataResources = Optional.of(includeDataResources); return super.setOutput(outputPath, outputMode, includeDataResources); } @@ -578,6 +582,7 @@ configuration.isObfuscating(), disableVerticalClassMerging, forceProguardCompatibility, + includeDataResources, proguardMapConsumer, proguardUsageConsumer, proguardSeedsConsumer, @@ -667,6 +672,7 @@ private final boolean enableMinification; private final boolean disableVerticalClassMerging; private final boolean forceProguardCompatibility; + private final Optional<Boolean> includeDataResources; private final StringConsumer proguardMapConsumer; private final StringConsumer proguardUsageConsumer; private final StringConsumer proguardSeedsConsumer; @@ -741,6 +747,7 @@ boolean enableMinification, boolean disableVerticalClassMerging, boolean forceProguardCompatibility, + Optional<Boolean> includeDataResources, StringConsumer proguardMapConsumer, StringConsumer proguardUsageConsumer, StringConsumer proguardSeedsConsumer, @@ -781,6 +788,7 @@ this.enableMinification = enableMinification; this.disableVerticalClassMerging = disableVerticalClassMerging; this.forceProguardCompatibility = forceProguardCompatibility; + this.includeDataResources = includeDataResources; this.proguardMapConsumer = proguardMapConsumer; this.proguardUsageConsumer = proguardUsageConsumer; this.proguardSeedsConsumer = proguardSeedsConsumer; @@ -803,6 +811,7 @@ enableMinification = false; disableVerticalClassMerging = false; forceProguardCompatibility = false; + includeDataResources = null; proguardMapConsumer = null; proguardUsageConsumer = null; proguardSeedsConsumer = null; @@ -974,6 +983,7 @@ internal.dumpInputToDirectory = null; internal.dumpInputToFile = null; } + internal.dumpOptions = dumpOptions(); return internal; } @@ -1002,4 +1012,18 @@ System.out.print(string); } } + + private DumpOptions dumpOptions() { + DumpOptions.Builder builder = DumpOptions.builder(Tool.R8); + dumpBaseCommandOptions(builder); + return builder + .setIncludeDataResources(includeDataResources) + .setTreeShaking(getEnableTreeShaking()) + .setMinification(getEnableMinification()) + .setForceProguardCompatibility(forceProguardCompatibility) + .setFeatureSplitConfiguration(featureSplitConfiguration) + .setProguardConfiguration(proguardConfiguration) + .setDesugaredLibraryConfiguration(libraryConfiguration) + .build(); + } }
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java index cd3fe0a..85fa3eb 100644 --- a/src/main/java/com/android/tools/r8/bisect/BisectState.java +++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -323,7 +323,7 @@ private static List<DexProgramClass> getSortedClasses(DexApplication app) { List<DexProgramClass> classes = new ArrayList<>(app.classes()); - classes.sort((a, b) -> a.type.slowCompareTo(b.type, NamingLens.getIdentityLens())); + classes.sort((a, b) -> a.type.compareToWithNamingLens(b.type, NamingLens.getIdentityLens())); return classes; }
diff --git a/src/main/java/com/android/tools/r8/cf/CfVersion.java b/src/main/java/com/android/tools/r8/cf/CfVersion.java index 84228b9..d90696c 100644 --- a/src/main/java/com/android/tools/r8/cf/CfVersion.java +++ b/src/main/java/com/android/tools/r8/cf/CfVersion.java
@@ -3,10 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.cf; -import java.util.Comparator; +import com.android.tools.r8.utils.structural.DefaultCompareToVisitor; +import com.android.tools.r8.utils.structural.Equatable; +import com.android.tools.r8.utils.structural.HashCodeVisitor; +import com.android.tools.r8.utils.structural.Ordered; +import com.android.tools.r8.utils.structural.StructuralSpecification; import org.objectweb.asm.Opcodes; -public final class CfVersion implements Comparable<CfVersion> { +public final class CfVersion implements Ordered<CfVersion> { public static final CfVersion V1_1 = new CfVersion(Opcodes.V1_1); public static final CfVersion V1_2 = new CfVersion(Opcodes.V1_2); @@ -43,62 +47,23 @@ return version; } - public static CfVersion maxAllowNull(CfVersion v1, CfVersion v2) { - assert v1 != null || v2 != null; - if (v1 == null) { - return v2; - } - if (v2 == null) { - return v1; - } - return v1.max(v2); - } - - public CfVersion max(CfVersion other) { - return isLessThan(other) ? other : this; - } - - public boolean isEqual(CfVersion other) { - return version == other.version; - } - - public boolean isLessThan(CfVersion other) { - return compareTo(other) < 0; - } - - public boolean isLessThanOrEqual(CfVersion other) { - return compareTo(other) <= 0; - } - - public boolean isGreaterThan(CfVersion other) { - return compareTo(other) > 0; - } - - public boolean isGreaterThanOrEqual(CfVersion other) { - return compareTo(other) >= 0; - } - - @Override - public int compareTo(CfVersion o) { - return Comparator.comparingInt(CfVersion::major) - .thenComparingInt(CfVersion::minor) - .compare(this, o); + private static void accept(StructuralSpecification<CfVersion, ?> spec) { + spec.withInt(CfVersion::major).withInt(CfVersion::minor); } @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof CfVersion)) { - return false; - } - return isEqual((CfVersion) o); + return Equatable.equalsImpl(this, o); } @Override public int hashCode() { - return version; + return HashCodeVisitor.run(this, CfVersion::accept); + } + + @Override + public int compareTo(CfVersion other) { + return DefaultCompareToVisitor.run(this, other, CfVersion::accept); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java index 7af534e..a391dbb 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -44,7 +44,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return type.slowCompareTo(((CfCheckCast) other).type); + return type.compareTo(((CfCheckCast) other).type); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java index 9d4ffa0..ced7984 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -40,7 +40,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return type.slowCompareTo(((CfConstClass) other).type); + return type.compareTo(((CfConstClass) other).type); } public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java index 37d0ff4..9c3b478 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -44,7 +44,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return handle.slowCompareTo(((CfConstMethodHandle) other).handle); + return handle.compareTo(((CfConstMethodHandle) other).handle); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java index b6a560f..c285c80 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -44,7 +44,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return type.slowCompareTo(((CfConstMethodType) other).type); + return type.compareTo(((CfConstMethodType) other).type); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java index ee042dc..7ac89da 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -36,7 +36,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return string.slowCompareTo(other.asConstString().string); + return string.compareTo(other.asConstString().string); } public DexString getString() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java index 9b463fd..0915f0d 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -56,8 +56,8 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return Comparator.comparing(CfFieldInstruction::getField, DexField::slowCompareTo) - .thenComparing(field -> field.declaringField, DexField::slowCompareTo) + return Comparator.comparing(CfFieldInstruction::getField) + .thenComparing(field -> field.declaringField) .compare(this, (CfFieldInstruction) other); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java index 2c81fce..53437ec 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -49,7 +49,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return clazz.slowCompareTo(((CfInitClass) other).clazz); + return clazz.compareTo(((CfInitClass) other).clazz); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java index 899ef31..1bbb32a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -43,7 +43,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return type.slowCompareTo(other.asInstanceOf().type); + return type.compareTo(other.asInstanceOf().type); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java index 4f5159b..3e07e17 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -62,7 +62,7 @@ public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { CfInvoke otherInvoke = other.asInvoke(); int itfDiff = Boolean.compare(itf, otherInvoke.itf); - return itfDiff != 0 ? itfDiff : method.slowCompareTo(otherInvoke.method); + return itfDiff != 0 ? itfDiff : method.compareTo(otherInvoke.method); } public DexMethod getMethod() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java index d52e429..a504f8c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -52,7 +52,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { return Comparator.comparingInt(CfMultiANewArray::getDimensions) - .thenComparing(CfMultiANewArray::getType, DexType::slowCompareTo) + .thenComparing(CfMultiANewArray::getType) .compare(this, ((CfMultiANewArray) other)); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java index a4be181..41f468c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -44,7 +44,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return type.slowCompareTo(((CfNew) other).type); + return type.compareTo(((CfNew) other).type); } @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java index 794227e..414bd58 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -47,7 +47,7 @@ @Override public int internalCompareTo(CfInstruction other, CfCompareHelper helper) { - return type.slowCompareTo(((CfNewArray) other).type); + return type.compareTo(((CfNewArray) other).type); } private int getPrimitiveTypeCode() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java index 01ff28f..a93f320 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -52,7 +52,7 @@ public int compareTo(CfTryCatch other, CfCompareHelper helper) { return Comparator.comparing((CfTryCatch c) -> c.start, helper::compareLabels) .thenComparing(c -> c.end, helper::compareLabels) - .thenComparing(c -> c.guards, ComparatorUtils.listComparator(DexType::slowCompareTo)) + .thenComparing(c -> c.guards, ComparatorUtils.listComparator()) .thenComparing(c -> c.targets, ComparatorUtils.listComparator(helper::compareLabels)) .compare(this, other); }
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java index 06edf90..32df17b 100644 --- a/src/main/java/com/android/tools/r8/code/CheckCast.java +++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -45,7 +45,7 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexType) other.BBBB); + return BBBB.compareTo((DexType) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java index 5562a69..ce1c26b 100644 --- a/src/main/java/com/android/tools/r8/code/ConstClass.java +++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -30,7 +30,7 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexType) other.BBBB); + return BBBB.compareTo((DexType) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java index d69329a..0409f06 100644 --- a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java +++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -52,7 +52,7 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexMethodHandle) other.BBBB); + return BBBB.compareTo((DexMethodHandle) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java index ba6e89f..8f54c24 100644 --- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java +++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -51,7 +51,7 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexProto) other.BBBB); + return BBBB.compareTo((DexProto) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstString.java b/src/main/java/com/android/tools/r8/code/ConstString.java index 0b44f8a..e9e910d 100644 --- a/src/main/java/com/android/tools/r8/code/ConstString.java +++ b/src/main/java/com/android/tools/r8/code/ConstString.java
@@ -35,7 +35,7 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexString) other.BBBB); + return BBBB.compareTo((DexString) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/DexInitClass.java b/src/main/java/com/android/tools/r8/code/DexInitClass.java index 2d9cda9..ab39c88 100644 --- a/src/main/java/com/android/tools/r8/code/DexInitClass.java +++ b/src/main/java/com/android/tools/r8/code/DexInitClass.java
@@ -129,7 +129,7 @@ @Override final int internalCompareTo(Instruction other) { return Comparator.comparingInt((DexInitClass i) -> i.dest) - .thenComparing(i -> i.clazz, DexType::slowCompareTo) + .thenComparing(i -> i.clazz) .compare(this, (DexInitClass) other); }
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArray.java b/src/main/java/com/android/tools/r8/code/FilledNewArray.java index b432eec..a1ee0ba 100644 --- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java +++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -44,7 +44,7 @@ @Override int internalCompareBBBB(Format35c<?> other) { - return BBBB.slowCompareTo((DexType) other.BBBB); + return BBBB.compareTo((DexType) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java index 13dc9a5..03594c2 100644 --- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java +++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -44,7 +44,7 @@ @Override int internalCompareBBBB(Format3rc<?> other) { - return BBBB.slowCompareTo((DexType) other.BBBB); + return BBBB.compareTo((DexType) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format31c.java b/src/main/java/com/android/tools/r8/code/Format31c.java index a1ae51e..672057b 100644 --- a/src/main/java/com/android/tools/r8/code/Format31c.java +++ b/src/main/java/com/android/tools/r8/code/Format31c.java
@@ -54,7 +54,7 @@ final int internalCompareTo(Instruction other) { Format31c o = (Format31c) other; int diff = Short.compare(AA, o.AA); - return diff != 0 ? diff : BBBBBBBB.slowCompareTo(o.BBBBBBBB); + return diff != 0 ? diff : BBBBBBBB.compareTo(o.BBBBBBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format45cc.java b/src/main/java/com/android/tools/r8/code/Format45cc.java index aa62780..bb0e1c7 100644 --- a/src/main/java/com/android/tools/r8/code/Format45cc.java +++ b/src/main/java/com/android/tools/r8/code/Format45cc.java
@@ -90,8 +90,8 @@ if (diff != 0) { return diff; } - int bDiff = BBBB.slowCompareTo(o.BBBB); - return bDiff != 0 ? bDiff : HHHH.slowCompareTo(o.HHHH); + int bDiff = BBBB.compareTo(o.BBBB); + return bDiff != 0 ? bDiff : HHHH.compareTo(o.HHHH); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format4rcc.java b/src/main/java/com/android/tools/r8/code/Format4rcc.java index 690b230..f29357d 100644 --- a/src/main/java/com/android/tools/r8/code/Format4rcc.java +++ b/src/main/java/com/android/tools/r8/code/Format4rcc.java
@@ -73,8 +73,8 @@ final int internalCompareTo(Instruction other) { return Comparator.comparingInt((Format4rcc i) -> i.AA) .thenComparingInt(i -> i.CCCC) - .thenComparing(i -> i.BBBB, DexMethod::slowCompareTo) - .thenComparing(i -> i.HHHH, DexProto::slowCompareTo) + .thenComparing(i -> i.BBBB) + .thenComparing(i -> i.HHHH) .compare(this, (Format4rcc) other); }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/code/InvokeMethod.java index de46296..ec8e6c4 100644 --- a/src/main/java/com/android/tools/r8/code/InvokeMethod.java +++ b/src/main/java/com/android/tools/r8/code/InvokeMethod.java
@@ -43,7 +43,7 @@ @Override int internalCompareBBBB(Format35c<?> other) { - return BBBB.slowCompareTo((DexMethod) other.BBBB); + return BBBB.compareTo((DexMethod) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java b/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java index 102d793..19c2bf1 100644 --- a/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java +++ b/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
@@ -43,7 +43,7 @@ @Override int internalCompareBBBB(Format3rc<?> other) { - return BBBB.slowCompareTo((DexMethod) other.BBBB); + return BBBB.compareTo((DexMethod) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/NewInstance.java b/src/main/java/com/android/tools/r8/code/NewInstance.java index d3d25c6..0b90d1a 100644 --- a/src/main/java/com/android/tools/r8/code/NewInstance.java +++ b/src/main/java/com/android/tools/r8/code/NewInstance.java
@@ -45,7 +45,7 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexType) other.BBBB); + return BBBB.compareTo((DexType) other.BBBB); } @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetOrSput.java b/src/main/java/com/android/tools/r8/code/SgetOrSput.java index 48112fc..4524f8e 100644 --- a/src/main/java/com/android/tools/r8/code/SgetOrSput.java +++ b/src/main/java/com/android/tools/r8/code/SgetOrSput.java
@@ -50,6 +50,6 @@ @Override int internalCompareBBBB(Format21c<?> other) { - return BBBB.slowCompareTo((DexField) other.BBBB); + return BBBB.compareTo((DexField) other.BBBB); } }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java index 2f3c56e..b46cd9b 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -184,7 +184,7 @@ } if (dumpOutput != null) { timing.begin("ApplicationReader.dump"); - dumpInputToFile(inputApp, dumpOutput, options); + inputApp.dump(dumpOutput, options.dumpOptions, options.reporter, options.dexItemFactory()); if (cleanDump) { Files.delete(dumpOutput); } @@ -230,10 +230,6 @@ } } - private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) { - app.dump(output, options); - } - private static boolean verifyMainDexOptionsCompatible( AndroidApp inputApp, InternalOptions options) { if (!options.isGeneratingDex()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 77a7d70..2ad4409 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -301,8 +301,7 @@ } } ObjectToOffsetMapping objectMapping = - virtualFile.computeMapping( - appView.appInfo(), graphLens, namingLens, initClassLens); + virtualFile.computeMapping(appView, graphLens, namingLens, initClassLens); MethodToCodeObjectMapping codeMapping = rewriteCodeWithJumboStrings( objectMapping, virtualFile.classes(), appView.appInfo().app()); @@ -614,8 +613,8 @@ return MethodToCodeObjectMapping.fromMethodBacking(); } // If the globally highest sorting string is not a jumbo string this is also a no-op. - if (application.highestSortingString != null && - application.highestSortingString.slowCompareTo(mapping.getFirstJumboString()) < 0) { + if (application.highestSortingString != null + && application.highestSortingString.compareTo(mapping.getFirstJumboString()) < 0) { return MethodToCodeObjectMapping.fromMethodBacking(); } } @@ -672,7 +671,7 @@ StringBuilder builder = new StringBuilder(); List<DexType> list = new ArrayList<>(mainDexClasses.size()); mainDexClasses.forEach(list::add); - list.sort(DexType::slowCompareTo); + list.sort(DexType::compareTo); list.forEach( type -> builder.append(mapMainDexListName(type, namingLens)).append('\n')); return builder.toString();
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java index 24d3a36..48fded1 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -609,7 +609,7 @@ // compareTo instead of slowCompareTo. That would require us to assign indices during // reading. Those indices should be cleared after reading to make sure that we resort // everything correctly at the end. - while (index < annotations.length && annotations[index].item.slowCompareTo(item) < 0) { + while (index < annotations.length && annotations[index].item.compareTo(item) < 0) { index++; } if (index >= annotations.length || !annotations[index].item.equals(item)) {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java index 74201ff..c07e0f5 100644 --- a/src/main/java/com/android/tools/r8/dex/FileWriter.java +++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -133,7 +133,7 @@ Log.verbose(FileWriter.class, "Writing encoded annotation @ %08x", dest.position()); } List<DexAnnotationElement> elements = new ArrayList<>(Arrays.asList(annotation.elements)); - elements.sort((a, b) -> a.name.slowCompareTo(b.name, mapping.getNamingLens())); + elements.sort((a, b) -> a.name.compareToWithNamingLens(b.name, mapping.getNamingLens())); dest.putUleb128(mapping.getOffsetFor(annotation.type)); dest.putUleb128(elements.size()); for (DexAnnotationElement element : elements) { @@ -579,7 +579,8 @@ Log.verbose(getClass(), "Writing AnnotationSet @ 0x%08x.", dest.position()); } List<DexAnnotation> annotations = new ArrayList<>(Arrays.asList(set.annotations)); - annotations.sort((a, b) -> a.annotation.type.slowCompareTo(b.annotation.type, namingLens)); + annotations.sort( + (a, b) -> a.annotation.type.compareToWithNamingLens(b.annotation.type, namingLens)); dest.putInt(annotations.size()); for (DexAnnotation annotation : annotations) { dest.putInt(mixedSectionOffsets.getOffsetFor(annotation)); @@ -629,7 +630,7 @@ private void writeEncodedFields(List<DexEncodedField> unsortedFields) { List<DexEncodedField> fields = new ArrayList<>(unsortedFields); - fields.sort((a, b) -> a.field.slowCompareTo(b.field, namingLens)); + fields.sort((a, b) -> a.field.compareToWithNamingLens(b.field, namingLens)); int currentOffset = 0; for (DexEncodedField field : fields) { assert field.validateDexValue(application.dexItemFactory); @@ -645,7 +646,7 @@ private void writeEncodedMethods( Iterable<DexEncodedMethod> unsortedMethods, boolean isSharedSynthetic) { List<DexEncodedMethod> methods = IterableUtils.toNewArrayList(unsortedMethods); - methods.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens)); + methods.sort((a, b) -> a.method.compareToWithNamingLens(b.method, namingLens)); int currentOffset = 0; for (DexEncodedMethod method : methods) { int nextOffset = mapping.getOffsetFor(method.method);
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java index cad079b..8e8750a 100644 --- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java +++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -45,7 +45,7 @@ public class InheritanceClassInDexDistributor { private static final Comparator<DexProgramClass> DEX_PROGRAM_CLASS_COMPARATOR = - (a, b) -> a.type.descriptor.slowCompareTo(b.type.descriptor); + (a, b) -> a.type.descriptor.compareTo(b.type.descriptor); private static final int DEX_FULL_ENOUGH_THRESHOLD = VirtualFile.MAX_ENTRIES - 100; private final ExecutorService executorService;
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java index d1f8749..e402ce5 100644 --- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java +++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -281,7 +281,7 @@ instruction.setOffset(orignalOffset + offsetDelta); if (instruction instanceof ConstString) { ConstString string = (ConstString) instruction; - if (string.getString().slowCompareTo(firstJumboString) >= 0) { + if (string.getString().compareTo(firstJumboString) >= 0) { ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString()); jumboString.setOffset(string.getOffset()); offsetDelta++;
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java index b808097..108df1d 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -9,7 +9,6 @@ import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.errors.InternalCompilerError; import com.android.tools.r8.features.ClassToFeatureSplitMap; -import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexCallSite; import com.android.tools.r8.graph.DexField; @@ -210,10 +209,10 @@ } public ObjectToOffsetMapping computeMapping( - AppInfo appInfo, GraphLens graphLens, NamingLens namingLens, InitClassLens initClassLens) { + AppView<?> appView, GraphLens graphLens, NamingLens namingLens, InitClassLens initClassLens) { assert transaction.isEmpty(); return new ObjectToOffsetMapping( - appInfo, + appView, graphLens, namingLens, initClassLens, @@ -742,7 +741,7 @@ this.graphLens = graphLens; this.initClassLens = initClassLens; this.namingLens = namingLens; - this.rewriter = new LensCodeRewriterUtils(appView, graphLens); + this.rewriter = new LensCodeRewriterUtils(appView); } private <T extends DexItem> boolean maybeInsert(T item, Set<T> set, Set<T> baseSet) {
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java index 519bd27..b6588df 100644 --- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java +++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.graph.ProgramDefinition; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Reporter; import com.google.common.collect.Sets; import java.util.IdentityHashMap; import java.util.Map; @@ -39,8 +40,14 @@ public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap( InternalOptions options) { - DexItemFactory dexItemFactory = options.dexItemFactory(); - FeatureSplitConfiguration featureSplitConfiguration = options.featureSplitConfiguration; + return createInitialClassToFeatureSplitMap( + options.dexItemFactory(), options.featureSplitConfiguration, options.reporter); + } + + public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap( + DexItemFactory dexItemFactory, + FeatureSplitConfiguration featureSplitConfiguration, + Reporter reporter) { ClassToFeatureSplitMap result = new ClassToFeatureSplitMap(); if (featureSplitConfiguration == null) { @@ -58,7 +65,7 @@ } } } catch (ResourceException e) { - throw options.reporter.fatalError(e.getMessage()); + throw reporter.fatalError(e.getMessage()); } } }
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java index 4615f1f..8767555 100644 --- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java +++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.IdentityHashMap; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; @@ -86,6 +87,8 @@ return UnknownAccessContexts.getInstance(); } + public abstract AbstractAccessContexts join(AbstractAccessContexts contexts); + public static class EmptyAccessContexts extends AbstractAccessContexts { public static EmptyAccessContexts INSTANCE = new EmptyAccessContexts(); @@ -140,6 +143,11 @@ AbstractAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { return this; } + + @Override + public AbstractAccessContexts join(AbstractAccessContexts contexts) { + return contexts; + } } public static class ConcreteAccessContexts extends AbstractAccessContexts { @@ -305,6 +313,28 @@ }); return new ConcreteAccessContexts(newAccessesWithContexts); } + + @Override + public AbstractAccessContexts join(AbstractAccessContexts contexts) { + if (contexts.isEmpty()) { + return this; + } + if (contexts.isTop()) { + return contexts; + } + Map<DexField, ProgramMethodSet> newAccessesWithContexts = new IdentityHashMap<>(); + accessesWithContexts.forEach( + (field, methodSet) -> + newAccessesWithContexts.put(field, ProgramMethodSet.create(methodSet))); + + BiConsumer<DexField, ProgramMethodSet> addAllMethods = + (field, methodSet) -> + newAccessesWithContexts + .computeIfAbsent(field, ignore -> ProgramMethodSet.create()) + .addAll(methodSet); + contexts.asConcrete().accessesWithContexts.forEach(addAllMethods); + return new ConcreteAccessContexts(newAccessesWithContexts); + } } public static class UnknownAccessContexts extends AbstractAccessContexts { @@ -362,5 +392,10 @@ AbstractAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { return this; } + + @Override + public AbstractAccessContexts join(AbstractAccessContexts contexts) { + return this; + } } }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index ef9b742..70db016 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses; import com.android.tools.r8.graph.classmerging.MergedClasses; import com.android.tools.r8.graph.classmerging.MergedClassesCollection; +import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses; import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker; @@ -85,6 +86,7 @@ private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods; private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses; private HorizontallyMergedClasses horizontallyMergedClasses; + private StaticallyMergedClasses staticallyMergedClasses; private VerticallyMergedClasses verticallyMergedClasses; private EnumValueInfoMapCollection unboxedEnums = EnumValueInfoMapCollection.empty(); // TODO(b/169115389): Remove @@ -437,6 +439,9 @@ public MergedClassesCollection allMergedClasses() { MergedClassesCollection collection = new MergedClassesCollection(); + if (horizontallyMergedClasses != null) { + collection.add(horizontallyMergedClasses); + } if (horizontallyMergedLambdaClasses != null) { collection.add(horizontallyMergedLambdaClasses); } @@ -470,8 +475,8 @@ } /** - * Get the result of horizontal class merging. Returns null if horizontal lambda class merging has - * not been run. + * Get the result of horizontal class merging. Returns null if horizontal class merging has not + * been run. */ public HorizontallyMergedClasses horizontallyMergedClasses() { return horizontallyMergedClasses; @@ -484,6 +489,19 @@ } /** + * Get the result of static class merging. Returns null if static class merging has not been run. + */ + public StaticallyMergedClasses staticallyMergedClasses() { + return staticallyMergedClasses; + } + + public void setStaticallyMergedClasses(StaticallyMergedClasses staticallyMergedClasses) { + assert this.staticallyMergedClasses == null; + this.staticallyMergedClasses = staticallyMergedClasses; + testing().staticallyMergedClassesConsumer.accept(dexItemFactory(), staticallyMergedClasses); + } + + /** * Get the result of vertical class merging. Returns null if vertical class merging has not been * run. */
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java index bae65e8..887f10d 100644 --- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -102,7 +102,7 @@ @Override public Iterable<DexType> getOriginalTypes(DexType type) { - Set<DexType> originalTypes = renamedTypeNames.getKeys(type); + Set<DexType> originalTypes = renamedTypeNames.getKeysOrNull(type); if (originalTypes == null) { return ImmutableList.of(type); }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java index d6ae59f..c17ff09 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -312,7 +312,7 @@ for (CfInstruction instruction : instructions) { if (instruction instanceof CfFrame && (classFileVersion.isLessThan(CfVersion.V1_6) - || (classFileVersion.isEqual(CfVersion.V1_6) + || (classFileVersion.isEqualTo(CfVersion.V1_6) && !options.shouldKeepStackMapTable()))) { continue; }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java index b43a42c..7998171 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -109,7 +109,7 @@ public boolean areValid(CfVersion version, boolean isPackageInfo) { if (isInterface()) { // We ignore the super flags prior to JDK 9, as so did the VM. - if (version.isGreaterThanOrEqual(CfVersion.V9) && isSuper()) { + if (version.isGreaterThanOrEqualTo(CfVersion.V9) && isSuper()) { return false; } // When not coming from DEX input we require interfaces to be abstract - except for
diff --git a/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java b/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java index 3bb9931..333b8a7 100644 --- a/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java +++ b/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
@@ -33,9 +33,9 @@ @Override public int compareTo(DebugLocalInfo other) { - return Comparator.comparing((DebugLocalInfo info) -> info.name, DexString::slowCompareTo) - .thenComparing(info -> info.type, DexType::slowCompareTo) - .thenComparing(info -> info.signature, Comparator.nullsFirst(DexString::slowCompareTo)) + return Comparator.comparing((DebugLocalInfo info) -> info.name) + .thenComparing(info -> info.type) + .thenComparing(info -> info.signature, Comparator.nullsFirst(DexString::compareTo)) .compare(this, other); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java index 04f59d7..2ee613c 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -46,17 +46,17 @@ } public List<DexEncodedMethod> sortMethodAnnotations(NamingLens namingLens) { - methodAnnotations.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens)); + methodAnnotations.sort((a, b) -> a.method.compareToWithNamingLens(b.method, namingLens)); return methodAnnotations; } public List<DexEncodedMethod> sortParameterAnnotations(NamingLens namingLens) { - parameterAnnotations.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens)); + parameterAnnotations.sort((a, b) -> a.method.compareToWithNamingLens(b.method, namingLens)); return parameterAnnotations; } public List<DexEncodedField> sortFieldAnnotations(NamingLens namingLens) { - fieldAnnotations.sort((a, b) -> a.field.slowCompareTo(b.field, namingLens)); + fieldAnnotations.sort((a, b) -> a.field.compareToWithNamingLens(b.field, namingLens)); return fieldAnnotations; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java index e13faf8..ba71338 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static com.android.tools.r8.utils.PredicateUtils.not; + import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.naming.NamingLens; @@ -99,7 +101,8 @@ return; } Arrays.sort( - annotations, (a, b) -> a.annotation.type.slowCompareTo(b.annotation.type, namingLens)); + annotations, + (a, b) -> a.annotation.type.compareToWithNamingLens(b.annotation.type, namingLens)); for (DexAnnotation annotation : annotations) { annotation.annotation.sort(); } @@ -157,7 +160,11 @@ } public DexAnnotationSet keepIf(Predicate<DexAnnotation> filter) { - return rewrite(anno -> filter.test(anno) ? anno : null); + return removeIf(not(filter)); + } + + public DexAnnotationSet removeIf(Predicate<DexAnnotation> filter) { + return rewrite(annotation -> filter.test(annotation) ? null : annotation); } public DexAnnotationSet rewrite(Function<DexAnnotation, DexAnnotation> rewriter) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java index abb91ed..6e38ed4 100644 --- a/src/main/java/com/android/tools/r8/graph/DexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -109,7 +109,7 @@ // that. if (options.testing.deterministicSortingBasedOnDexType) { // To keep the order deterministic, we sort the classes by their type, which is a unique key. - classes.sort((a, b) -> a.type.slowCompareTo(b.type)); + classes.sort((a, b) -> a.type.compareTo(b.type)); } return classes; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java index bcb8873..b38e016 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java +++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -158,7 +158,7 @@ @Override public int compareTo(DexCallSite other) { assert method != null && other.method != null; - int methodCompare = method.slowCompareTo(other.method); + int methodCompare = method.compareTo(other.method); if (methodCompare != 0) { return methodCompare; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 45130dc..5215ada 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -3,13 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import static com.google.common.base.Predicates.alwaysFalse; + import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.GenericSignature.ClassSignature; import com.android.tools.r8.kotlin.KotlinClassLevelInfo; import com.android.tools.r8.origin.Origin; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.OptionalBool; @@ -673,57 +674,28 @@ } public boolean classInitializationMayHaveSideEffects(AppView<?> appView) { - return classInitializationMayHaveSideEffects( - appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); - } - - public boolean classInitializationMayHaveSideEffectsInContext( - AppView<AppInfoWithLiveness> appView, ProgramDefinition context) { - return classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of the current context are guaranteed to be initialized - // already. - type -> appView.appInfo().isSubtype(context.getContextType(), type), - Sets.newIdentityHashSet()); + return classInitializationMayHaveSideEffects(appView, alwaysFalse()); } public boolean classInitializationMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { - if (!seen.add(type)) { - return false; - } - if (ignore.test(type)) { - return false; - } - if (isLibraryClass()) { - if (isInterface()) { - return appView.options().libraryInterfacesMayHaveStaticInitialization; - } - if (appView.dexItemFactory().libraryClassesWithoutStaticInitialization.contains(type)) { - return false; - } - } - if (hasClassInitializerThatCannotBePostponed()) { - return true; - } - if (defaultValuesForStaticFieldsMayTriggerAllocation()) { - return true; - } - return initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen); + AppView<?> appView, Predicate<DexType> ignore) { + return internalClassOrInterfaceMayHaveInitializationSideEffects( + appView, this, ignore, Sets.newIdentityHashSet()); } - private boolean hasClassInitializerThatCannotBePostponed() { - if (isLibraryClass()) { - // We don't know for library classes in general but assume that java.lang.Object is safe. - return superType != null; - } - DexEncodedMethod clinit = getClassInitializer(); - if (clinit == null || clinit.getCode() == null) { - return false; - } - return !clinit.getOptimizationInfo().classInitializerMayBePostponed(); + public final boolean classInitializationMayHaveSideEffectsInContext( + AppView<?> appView, ProgramDefinition context) { + // Types that are a super type of the current context are guaranteed to be initialized already. + return classInitializationMayHaveSideEffects( + appView, type -> appView.isSubtype(context.getContextType(), type).isTrue()); } + abstract boolean internalClassOrInterfaceMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen); + public void forEachImmediateSupertype(Consumer<DexType> fn) { if (superType != null) { fn.accept(superType); @@ -742,32 +714,14 @@ return () -> iterator; } - public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) { - return initializationOfParentTypesMayHaveSideEffects( - appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); - } - - public boolean initializationOfParentTypesMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { - for (DexType iface : interfaces.values) { - if (iface.classInitializationMayHaveSideEffects(appView, ignore, seen)) { - return true; - } - } - if (superType != null - && superType.classInitializationMayHaveSideEffects(appView, ignore, seen)) { - return true; - } - return false; - } - public boolean definesFinalizer(DexItemFactory factory) { return lookupVirtualMethod(factory.objectMembers.finalize) != null; } public boolean defaultValuesForStaticFieldsMayTriggerAllocation() { return staticFields().stream() - .anyMatch(field -> field.getStaticValue().mayHaveSideEffects()); + .anyMatch( + field -> field.hasExplicitStaticValue() && field.getStaticValue().mayHaveSideEffects()); } public List<InnerClassAttribute> getInnerClasses() { @@ -904,6 +858,10 @@ /** Returns kotlin class info if the class is synthesized by kotlin compiler. */ public abstract KotlinClassLevelInfo getKotlinInfo(); + public boolean hasStaticFields() { + return staticFields.length > 0; + } + public boolean hasInstanceFields() { return instanceFields.length > 0; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java index 410f985..c97a1a4 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -11,6 +11,8 @@ import com.android.tools.r8.kotlin.KotlinClassLevelInfo; import com.android.tools.r8.origin.Origin; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; import java.util.function.Supplier; public class DexClasspathClass extends DexClass implements Supplier<DexClasspathClass> { @@ -90,4 +92,16 @@ public DexClasspathClass get() { return this; } + + @Override + boolean internalClassOrInterfaceMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { + if (!seen.add(getType()) || ignore.test(getType())) { + return false; + } + return !isInterface() || appView.options().classpathInterfacesMayHaveStaticInitialization; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index 7a9c724..ab974b3 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -430,7 +430,7 @@ private void updateHighestSortingString(DexString candidate) { assert candidate != null; - if (highestSortingString == null || highestSortingString.slowCompareTo(candidate) < 0) { + if (highestSortingString == null || highestSortingString.compareTo(candidate) < 0) { highestSortingString = candidate; } } @@ -621,7 +621,7 @@ return 0; } return Comparator.comparingInt((TypeAddrPair p) -> p.addr) - .thenComparing(p -> p.type, DexType::slowCompareTo) + .thenComparing(p -> p.type) .compare(this, other); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java index 903686d..16842b0 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -300,9 +300,9 @@ @Override int internalCompareTo(DexDebugEvent other) { return Comparator.comparingInt((StartLocal e) -> e.registerNum) - .thenComparing(e -> e.name, DexString::slowCompareTo) - .thenComparing(e -> e.type, DexType::slowCompareTo) - .thenComparing(e -> e.signature, Comparator.nullsFirst(DexString::slowCompareTo)) + .thenComparing(e -> e.name) + .thenComparing(e -> e.type) + .thenComparing(e -> e.signature, Comparator.nullsFirst(DexString::compareTo)) .compare(this, (StartLocal) other); } } @@ -431,7 +431,7 @@ @Override int internalCompareTo(DexDebugEvent other) { - return fileName.slowCompareTo(((SetFile) other).fileName); + return fileName.compareTo(((SetFile) other).fileName); } } @@ -473,7 +473,7 @@ @Override int internalCompareTo(DexDebugEvent other) { - return Comparator.comparing((SetInlineFrame e) -> e.callee, DexMethod::slowCompareTo) + return Comparator.comparing((SetInlineFrame e) -> e.callee, DexMethod::compareTo) .thenComparing(e -> e.caller, Comparator.nullsFirst(Position::compareTo)) .compare(this, (SetInlineFrame) other); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java index 5e44a57..5bfbd8d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -54,7 +54,7 @@ return Comparator.comparingInt((DexDebugInfo i) -> i.startLine) .thenComparing( i -> i.parameters, - ComparatorUtils.arrayComparator(Comparator.nullsFirst(DexString::slowCompareTo))) + ComparatorUtils.arrayComparator(Comparator.nullsFirst(DexString::compareTo))) .thenComparing(i -> i.events, ComparatorUtils.arrayComparator()) .compare(this, other); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java index 7259345..0153cd2 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java +++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -40,6 +40,10 @@ this.annotations = annotations; } + public void removeAnnotations(Predicate<DexAnnotation> predicate) { + setAnnotations(annotations().removeIf(predicate)); + } + public boolean isDexClass() { return false; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java index cb4eb4e..051dd92 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -78,7 +78,7 @@ assert sorted == sortedHashCode(); return; } - Arrays.sort(elements, (a, b) -> a.name.slowCompareTo(b.name)); + Arrays.sort(elements, (a, b) -> a.name.compareTo(b.name)); for (DexAnnotationElement element : elements) { element.value.sort(); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java index 159c27b..16879f0 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -20,7 +20,6 @@ import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo; import com.android.tools.r8.kotlin.KotlinFieldLevelInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.Sets; public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField> { public static final DexEncodedField[] EMPTY_ARRAY = {}; @@ -267,32 +266,6 @@ return null; } - public boolean mayTriggerClassInitializationSideEffects( - AppView<AppInfoWithLiveness> appView, ProgramMethod context) { - // Only static field matters when it comes to class initialization side effects. - if (!isStatic()) { - return false; - } - DexClass clazz = appView.definitionFor(field.holder); - if (clazz == null) { - return true; - } - if (clazz.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of the current context are guaranteed to be initialized - // already. - type -> appView.appInfo().isSubtype(context.getHolderType(), type), - Sets.newIdentityHashSet())) { - // Ignore class initialization side-effects for dead proto extension fields to ensure that - // we force replace these field reads by null. - boolean ignore = - appView.withGeneratedExtensionRegistryShrinker( - shrinker -> shrinker.isDeadProtoExtensionField(field), false); - return !ignore; - } - return false; - } - public DexEncodedField toTypeSubstitutedField(DexField field) { if (this.field == field) { return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index c353e8a..e84e3e8 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -75,6 +75,11 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.structural.CompareToVisitorWithTypeEquivalence; +import com.android.tools.r8.utils.structural.HashingVisitorWithTypeEquivalence; +import com.android.tools.r8.utils.structural.Ordered; +import com.android.tools.r8.utils.structural.RepresentativeMap; +import com.android.tools.r8.utils.structural.StructuralSpecification; import com.google.common.collect.ImmutableList; import com.google.common.hash.Hasher; import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; @@ -82,7 +87,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -318,14 +322,50 @@ return deprecated; } - public void hashSyntheticContent(Hasher hasher) { - // Method holder does not contribute to the synthetic hash (it is freely chosen). - // Method name does not contribute to the synthetic hash (it is freely chosen). - method.proto.hashSyntheticContent(hasher); - hasher.putInt(accessFlags.getAsCfAccessFlags()); - assert annotations().isEmpty(); - assert parameterAnnotationsList.isEmpty(); - assert code != null; + // Visitor specifying the structure of the method with respect to its "synthetic" content. + // TODO(b/171867022): Generalize this so that it determines any method in full. + private static void syntheticSpecify(StructuralSpecification<DexEncodedMethod, ?> spec) { + spec.withAssert(m1 -> m1.annotations().isEmpty()) + .withAssert(m1 -> m1.parameterAnnotationsList.isEmpty()) + .withAssert(m1 -> m1.code != null) + .withItem(DexEncodedMethod::getHolderType) + .withItem(DexEncodedMethod::getName) + .withInt(m -> m.getAccessFlags().getAsCfAccessFlags()) + .withItem(DexEncodedMethod::proto) + .withCustomItem( + DexEncodedMethod::getCode, + (c1, c2, v) -> v.visit(c1, c2, DexEncodedMethod::compareCodeObject), + (c, h) -> h.visit(c, DexEncodedMethod::hashCodeObject)); + } + + public void hashSyntheticContent(Hasher hasher, RepresentativeMap map) { + HashingVisitorWithTypeEquivalence.run(this, hasher, map, DexEncodedMethod::syntheticSpecify); + } + + public boolean isSyntheticContentEqual(DexEncodedMethod other) { + return syntheticCompareTo(other) == 0; + } + + public int syntheticCompareTo(DexEncodedMethod other) { + // Consider the holder types to be equivalent, using the holder of this method as the + // representative. + RepresentativeMap map = t -> t == other.getHolderType() ? getHolderType() : t; + return CompareToVisitorWithTypeEquivalence.run( + this, other, map, DexEncodedMethod::syntheticSpecify); + } + + private static int compareCodeObject(Code code1, Code code2) { + if (code1.isCfCode() && code2.isCfCode()) { + return code1.asCfCode().compareTo(code2.asCfCode()); + } + if (code1.isDexCode() && code2.isDexCode()) { + return code1.asDexCode().compareTo(code2.asDexCode()); + } + throw new Unreachable( + "Unexpected attempt to compare incompatible synthetic objects: " + code1 + " and " + code2); + } + + private static void hashCodeObject(Code code, Hasher hasher) { // TODO(b/158159959): Implement a more precise hashing on code objects. if (code.isCfCode()) { CfCode cfCode = code.asCfCode(); @@ -339,30 +379,6 @@ } } - public boolean isSyntheticContentEqual(DexEncodedMethod other) { - return syntheticCompareTo(other) == 0; - } - - public int syntheticCompareTo(DexEncodedMethod other) { - assert annotations().isEmpty(); - assert parameterAnnotationsList.isEmpty(); - Comparator<DexEncodedMethod> comparator = - Comparator.comparing(DexEncodedMethod::proto, DexProto::slowCompareTo) - .thenComparingInt(m -> m.accessFlags.getAsCfAccessFlags()); - if (code.isCfCode() && other.getCode().isCfCode()) { - comparator = comparator.thenComparing(m -> m.getCode().asCfCode()); - } else if (code.isDexCode() && other.getCode().isDexCode()) { - comparator = comparator.thenComparing(m -> m.getCode().asDexCode()); - } else { - throw new Unreachable( - "Unexpected attempt to compare incompatible synthetic objects: " - + code - + " and " - + other.getCode()); - } - return comparator.compare(this, other); - } - public DexType getHolderType() { return getReference().holder; } @@ -832,7 +848,7 @@ public void upgradeClassFileVersion(CfVersion version) { checkIfObsolete(); assert version != null; - classFileVersion = CfVersion.maxAllowNull(classFileVersion, version); + classFileVersion = Ordered.maxIgnoreNull(classFileVersion, version); } public String qualifiedName() { @@ -1216,7 +1232,7 @@ public DexEncodedMethod toRenamedHolderMethod(DexType newHolderType, DexItemFactory factory) { DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this); - builder.setMethod(factory.createMethod(newHolderType, method.proto, method.name)); + builder.setMethod(method.withHolder(newHolderType, factory)); return builder.build(); } @@ -1282,8 +1298,7 @@ // and if different forwarding methods are created in different subclasses the first could be // final. accessFlags.demoteFromFinal(); - DexMethod newMethod = - definitions.dexItemFactory().createMethod(holder.type, method.proto, method.name); + DexMethod newMethod = method.withHolder(holder.type, definitions.dexItemFactory()); Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER; Builder builder = syntheticBuilder(this); builder.setMethod(newMethod); @@ -1417,7 +1432,7 @@ } public static int slowCompare(DexEncodedMethod m1, DexEncodedMethod m2) { - return m1.method.slowCompareTo(m2.method); + return m1.method.compareTo(m2.method); } public MethodOptimizationInfo getOptimizationInfo() { @@ -1548,6 +1563,11 @@ return this; } + public Builder setIsLibraryMethodOverrideIfKnown(OptionalBool isLibraryMethodOverride) { + return setIsLibraryMethodOverrideIf( + !isLibraryMethodOverride.isUnknown(), isLibraryMethodOverride); + } + public Builder setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) { this.parameterAnnotations = parameterAnnotations; return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java index 87adbf7..5f95369 100644 --- a/src/main/java/com/android/tools/r8/graph/DexField.java +++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -5,9 +5,11 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.errors.CompilationError; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.references.FieldReference; import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.structural.CompareToVisitor; +import com.android.tools.r8.utils.structural.StructuralAccept; +import com.android.tools.r8.utils.structural.StructuralSpecification; import java.util.Collections; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -26,10 +28,28 @@ } } + private static void accept(StructuralSpecification<DexField, ?> spec) { + spec.withItem(DexField::getHolderType).withItem(DexField::getName).withItem(DexField::getType); + } + + @Override + public DexField self() { + return this; + } + + @Override + public StructuralAccept<DexField> getStructuralAccept() { + return DexField::accept; + } + public DexType getHolderType() { return holder; } + public DexString getName() { + return name; + } + public DexType getType() { return type; } @@ -122,29 +142,8 @@ } @Override - public int slowCompareTo(DexField other) { - int result = holder.slowCompareTo(other.holder); - if (result != 0) { - return result; - } - result = name.slowCompareTo(other.name); - if (result != 0) { - return result; - } - return type.slowCompareTo(other.type); - } - - @Override - public int slowCompareTo(DexField other, NamingLens namingLens) { - int result = holder.slowCompareTo(other.holder, namingLens); - if (result != 0) { - return result; - } - result = namingLens.lookupName(this).slowCompareTo(namingLens.lookupName(other)); - if (result != 0) { - return result; - } - return type.slowCompareTo(other.type, namingLens); + public void acceptCompareTo(DexField other, CompareToVisitor visitor) { + visitor.visitDexField(this, other); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 6edd1e1..eee5f4b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1784,6 +1784,7 @@ Function<DexString, Optional<T>> tryString, String baseName, DexType holder) { int index = 0; while (true) { + assert index < 1000; DexString name = createString(createMemberString(baseName, holder, index++)); Optional<T> result = tryString.apply(name); if (result.isPresent()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java index dcb4ed3..e5a8097 100644 --- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -12,6 +12,8 @@ import com.android.tools.r8.origin.Origin; import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; import java.util.function.Supplier; public class DexLibraryClass extends DexClass implements Supplier<DexLibraryClass> { @@ -122,4 +124,18 @@ public DexLibraryClass get() { return this; } + + @Override + boolean internalClassOrInterfaceMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { + if (!seen.add(getType()) || ignore.test(getType())) { + return false; + } + return isInterface() + ? appView.options().libraryInterfacesMayHaveStaticInitialization + : !appView.dexItemFactory().libraryClassesWithoutStaticInitialization.contains(type); + } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java index 3041e01..84a1953 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -6,7 +6,7 @@ import com.google.common.collect.Iterables; public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> - extends DexReference implements PresortedComparable<R> { + extends DexReference implements NamingLensComparable<R> { public final DexType holder; public final DexString name;
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java index 6c288c7..249e77e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -5,10 +5,12 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.errors.CompilationError; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; import com.android.tools.r8.references.TypeReference; +import com.android.tools.r8.utils.structural.CompareToVisitor; +import com.android.tools.r8.utils.structural.StructuralAccept; +import com.android.tools.r8.utils.structural.StructuralSpecification; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; @@ -29,6 +31,25 @@ } } + private static void accept(StructuralSpecification<DexMethod, ?> spec) { + spec.withItem(DexMethod::getHolderType).withItem(DexMethod::getName).withItem(m -> m.proto); + } + + @Override + public StructuralAccept<DexMethod> getStructuralAccept() { + return DexMethod::accept; + } + + @Override + public DexMethod self() { + return this; + } + + @Override + public void acceptCompareTo(DexMethod other, CompareToVisitor visitor) { + visitor.visitDexMethod(this, other); + } + public DexType getHolderType() { return holder; } @@ -181,32 +202,6 @@ } @Override - public int slowCompareTo(DexMethod other) { - int result = holder.slowCompareTo(other.holder); - if (result != 0) { - return result; - } - result = name.slowCompareTo(other.name); - if (result != 0) { - return result; - } - return proto.slowCompareTo(other.proto); - } - - @Override - public int slowCompareTo(DexMethod other, NamingLens namingLens) { - int result = holder.slowCompareTo(other.holder, namingLens); - if (result != 0) { - return result; - } - result = namingLens.lookupName(this).slowCompareTo(namingLens.lookupName(other)); - if (result != 0) { - return result; - } - return proto.slowCompareTo(other.proto, namingLens); - } - - @Override public boolean match(DexMethod method) { return method.name == name && method.proto == proto; } @@ -270,8 +265,12 @@ holder, dexItemFactory.prependTypeToProto(type, proto), name); } - public DexMethod withHolder(DexType holder, DexItemFactory dexItemFactory) { - return dexItemFactory.createMethod(holder, proto, name); + public DexMethod withHolder(DexDefinition definition, DexItemFactory dexItemFactory) { + return withHolder(definition.getReference(), dexItemFactory); + } + + public DexMethod withHolder(DexReference reference, DexItemFactory dexItemFactory) { + return dexItemFactory.createMethod(reference.getContextType(), proto, name); } public DexMethod withName(DexString name, DexItemFactory dexItemFactory) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java index 80a3cb9..5ee9c8f 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -8,12 +8,14 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.utils.structural.StructuralAccept; +import com.android.tools.r8.utils.structural.StructuralSpecification; import java.util.Objects; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; -public class DexMethodHandle extends IndexedDexItem implements - PresortedComparable<DexMethodHandle> { +public class DexMethodHandle extends IndexedDexItem + implements NamingLensComparable<DexMethodHandle> { public enum MethodHandleType { STATIC_PUT((short) 0x00), @@ -313,31 +315,21 @@ } @Override - public int slowCompareTo(DexMethodHandle other) { - int result = type.getValue() - other.type.getValue(); - if (result == 0) { - if (isFieldHandle()) { - result = asField().slowCompareTo(other.asField()); - } else { - assert isMethodHandle(); - result = asMethod().slowCompareTo(other.asMethod()); - } - } - return result; + public DexMethodHandle self() { + return this; } @Override - public int slowCompareTo(DexMethodHandle other, NamingLens namingLens) { - int result = type.getValue() - other.type.getValue(); - if (result == 0) { - if (isFieldHandle()) { - result = asField().slowCompareTo(other.asField(), namingLens); - } else { - assert isMethodHandle(); - result = asMethod().slowCompareTo(other.asMethod(), namingLens); - } - } - return result; + public StructuralAccept<DexMethodHandle> getStructuralAccept() { + return DexMethodHandle::specify; + } + + private static void specify(StructuralSpecification<DexMethodHandle, ?> spec) { + spec.withInt(m -> m.type.getValue()) + .withConditionalItem(DexMethodHandle::isFieldHandle, DexMethodHandle::asField) + .withConditionalItem(DexMethodHandle::isMethodHandle, DexMethodHandle::asMethod) + .withBool(m -> m.isInterface) + .withItem(m -> m.rewrittenTarget); } public Handle toAsmHandle(NamingLens lens) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java index 6474140..ee32f8a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -29,6 +29,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -402,6 +403,102 @@ this.kotlinInfo = kotlinInfo; } + @Override + boolean internalClassOrInterfaceMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { + if (!seen.add(getType()) || ignore.test(getType())) { + return false; + } + return isInterface() + ? internalInterfaceMayHaveInitializationSideEffects( + appView, initialAccessHolder, ignore, seen) + : internalClassMayHaveInitializationSideEffects(appView, initialAccessHolder, ignore, seen); + } + + private boolean internalClassMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { + assert !isInterface(); + assert seen.contains(getType()); + assert !ignore.test(getType()); + if (hasClassInitializer() + && !getClassInitializer().getOptimizationInfo().classInitializerMayBePostponed()) { + return true; + } + return defaultValuesForStaticFieldsMayTriggerAllocation() + || initializationOfParentTypesMayHaveSideEffects( + appView, initialAccessHolder, ignore, seen); + } + + /** + * Interface initialization is described the JVM Specification, section 5.5 Initialization (Java + * SE 11 Edition). + * + * <p>A class or interface C may be initialized only as a result of: + * + * <ul> + * <li>The execution of any one of the Java Virtual Machine instructions new, getstatic, + * putstatic, or invokestatic that references C. + * <li>... + * <li>If C is an interface that declares a non-abstract, non-static method, the initialization + * of a class that implements C directly or indirectly. + * </ul> + */ + private boolean internalInterfaceMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { + assert isInterface(); + assert seen.contains(getType()); + assert !ignore.test(getType()); + + // If there is a direct access to the interface, then this has side effects if its clinit has + // side effects. Parent types are not initialized and thus don't need to be considered. + if (this == initialAccessHolder) { + if (hasClassInitializer() + && !getClassInitializer().getOptimizationInfo().classInitializerMayBePostponed()) { + return true; + } + return defaultValuesForStaticFieldsMayTriggerAllocation(); + } + + // Otherwise, this interface has side effects if its clinit has side effects and it has at least + // one default interface method, or if one of its parent types have observable side effects. + if (hasClassInitializer() + && !getClassInitializer().getOptimizationInfo().classInitializerMayBePostponed() + && getMethodCollection().hasVirtualMethods(DexEncodedMethod::isDefaultMethod)) { + return true; + } + + return initializationOfParentTypesMayHaveSideEffects( + appView, initialAccessHolder, ignore, seen); + } + + private boolean initializationOfParentTypesMayHaveSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { + if (superType != null + && superType.internalClassOrInterfaceMayHaveInitializationSideEffects( + appView, initialAccessHolder, ignore, seen)) { + return true; + } + for (DexType iface : interfaces) { + if (iface.internalClassOrInterfaceMayHaveInitializationSideEffects( + appView, initialAccessHolder, ignore, seen)) { + return true; + } + } + return false; + } + public boolean hasFields() { return instanceFields.length + staticFields.length > 0; } @@ -454,7 +551,7 @@ return null; } DexEncodedField[] fields = staticFields; - Arrays.sort(fields, (a, b) -> a.field.slowCompareTo(b.field, namingLens)); + Arrays.sort(fields, (a, b) -> a.field.compareToWithNamingLens(b.field, namingLens)); int length = 0; List<DexValue> values = new ArrayList<>(fields.length); for (int i = 0; i < fields.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java index 333be20..3855408 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProto.java +++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -5,12 +5,13 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.utils.structural.StructuralAccept; +import com.android.tools.r8.utils.structural.StructuralSpecification; import com.google.common.collect.Iterables; -import com.google.common.hash.Hasher; import java.util.Collections; import java.util.function.Consumer; -public class DexProto extends IndexedDexItem implements PresortedComparable<DexProto> { +public class DexProto extends IndexedDexItem implements NamingLensComparable<DexProto> { public static final DexProto SENTINEL = new DexProto(null, null, null); @@ -24,6 +25,43 @@ this.parameters = parameters; } + private static void accept(StructuralSpecification<DexProto, ?> spec) { + spec.withItem(DexProto::getReturnType) + .withItem(p -> p.parameters) + // TODO(b/172206529): Consider removing shorty. + .withItem(p1 -> p1.shorty); + } + + @Override + public StructuralAccept<DexProto> getStructuralAccept() { + return DexProto::accept; + } + + @Override + public DexProto self() { + return this; + } + + @Override + public boolean computeEquals(Object other) { + if (other instanceof DexProto) { + DexProto o = (DexProto) other; + return shorty.equals(o.shorty) + && returnType.equals(o.returnType) + && parameters.equals(o.parameters); + } + return false; + } + + @Override + public int computeHashCode() { + return shorty.hashCode() + returnType.hashCode() * 7 + parameters.hashCode() * 31; + } + + public DexType getReturnType() { + return returnType; + } + public Iterable<DexType> getParameterBaseTypes(DexItemFactory dexItemFactory) { return Iterables.transform(parameters, type -> type.toBaseType(dexItemFactory)); } @@ -50,24 +88,6 @@ } @Override - public int computeHashCode() { - return shorty.hashCode() - + returnType.hashCode() * 7 - + parameters.hashCode() * 31; - } - - @Override - public boolean computeEquals(Object other) { - if (other instanceof DexProto) { - DexProto o = (DexProto) other; - return shorty.equals(o.shorty) - && returnType.equals(o.returnType) - && parameters.equals(o.parameters); - } - return false; - } - - @Override public String toString() { return "Proto " + shorty + " " + returnType + " " + parameters; } @@ -86,24 +106,6 @@ } @Override - public int slowCompareTo(DexProto other) { - int result = returnType.slowCompareTo(other.returnType); - if (result == 0) { - result = parameters.slowCompareTo(other.parameters); - } - return result; - } - - @Override - public int slowCompareTo(DexProto other, NamingLens namingLens) { - int result = returnType.slowCompareTo(other.returnType, namingLens); - if (result == 0) { - result = parameters.slowCompareTo(other.parameters, namingLens); - } - return result; - } - - @Override public String toSmaliString() { return toDescriptorString(); } @@ -122,11 +124,4 @@ builder.append(lens.lookupDescriptor(returnType)); return builder.toString(); } - - public void hashSyntheticContent(Hasher hasher) { - hasher.putInt(returnType.hashCode()); - for (DexType param : parameters.values) { - hasher.putInt(param.hashCode()); - } - } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java index 3263b6d..d32c7ba 100644 --- a/src/main/java/com/android/tools/r8/graph/DexReference.java +++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -80,12 +80,12 @@ return typeDiff; } if (isDexType()) { - return asDexType().slowCompareTo(o.asDexType()); + return asDexType().compareTo(o.asDexType()); } if (isDexField()) { - return asDexField().slowCompareTo(o.asDexField()); + return asDexField().compareTo(o.asDexField()); } assert isDexMethod(); - return asDexMethod().slowCompareTo(o.asDexMethod()); + return asDexMethod().compareTo(o.asDexMethod()); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java index b5d50f0..7a50fe1 100644 --- a/src/main/java/com/android/tools/r8/graph/DexString.java +++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -5,16 +5,19 @@ import com.android.tools.r8.dex.Constants; import com.android.tools.r8.dex.IndexedItemCollection; -import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.IdentifierUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ThrowingCharIterator; +import com.android.tools.r8.utils.structural.CompareToVisitor; +import com.android.tools.r8.utils.structural.HashingVisitor; +import com.android.tools.r8.utils.structural.StructuralAccept; import java.io.UTFDataFormatException; import java.util.Arrays; import java.util.NoSuchElementException; -public class DexString extends IndexedDexItem implements PresortedComparable<DexString> { +public class DexString extends IndexedDexItem implements NamingLensComparable<DexString> { public static final DexString[] EMPTY_ARRAY = {}; private static final int ARRAY_CHARACTER = '['; @@ -32,6 +35,27 @@ this.content = encodeToMutf8(string); } + @Override + public DexString self() { + return this; + } + + @Override + public StructuralAccept<DexString> getStructuralAccept() { + // Structural accept is never accessed as all accept methods are defined directly. + throw new Unreachable(); + } + + @Override + public void acceptCompareTo(DexString other, CompareToVisitor visitor) { + visitor.visitDexString(this, other, DexString::internalCompareTo); + } + + @Override + public void acceptHashing(HashingVisitor visitor) { + visitor.visitDexString(this); + } + public ThrowingCharIterator<UTFDataFormatException> iterator() { return new ThrowingCharIterator<UTFDataFormatException>() { @@ -103,14 +127,6 @@ } } - public int numberOfLeadingSquareBrackets() { - int result = 0; - while (content.length > result && content[result] == ((byte) '[')) { - result++; - } - return result; - } - private String decode() throws UTFDataFormatException { char[] out = new char[size]; int decodedLength = decodePrefix(out); @@ -248,8 +264,7 @@ return mapping.getOffsetFor(this); } - @Override - public int slowCompareTo(DexString other) { + private int internalCompareTo(DexString other) { // Compare the bytes, as comparing UTF-8 encoded strings as strings of unsigned bytes gives // the same result as comparing the corresponding Unicode strings lexicographically by // codepoint. The only complication is the MUTF-8 encoding have the two byte encoding c0 80 of @@ -281,12 +296,6 @@ } } - @Override - public int slowCompareTo(DexString other, NamingLens lens) { - // The naming lens cannot affect strings. - return slowCompareTo(other); - } - private static boolean isValidClassDescriptor(String string) { if (string.length() < 3 || string.charAt(0) != 'L'
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java index 72e632d..39aea8c 100644 --- a/src/main/java/com/android/tools/r8/graph/DexType.java +++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -20,14 +20,14 @@ import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring; import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter; import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions.OutlineOptions; -import com.google.common.base.Predicates; +import com.android.tools.r8.utils.structural.CompareToVisitor; +import com.android.tools.r8.utils.structural.HashingVisitor; +import com.android.tools.r8.utils.structural.StructuralAccept; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; import java.util.Arrays; import java.util.List; import java.util.Set; @@ -36,7 +36,7 @@ import java.util.function.Function; import java.util.function.Predicate; -public class DexType extends DexReference implements PresortedComparable<DexType> { +public class DexType extends DexReference implements NamingLensComparable<DexType> { public static final DexType[] EMPTY_ARRAY = {}; // Bundletool is merging classes that may originate from a build with an old version of R8. @@ -53,6 +53,29 @@ } @Override + public DexType self() { + return this; + } + + @Override + public StructuralAccept<DexType> getStructuralAccept() { + // Structural accept is never accessed as all accept methods are defined directly. + throw new Unreachable(); + } + + // DexType overrides accept to ensure the visitors always gets a visitDexType callback. + @Override + public void acceptCompareTo(DexType other, CompareToVisitor visitor) { + visitor.visitDexType(this, other); + } + + // DexType overrides accept to ensure the visitors always gets a visitDexType callback. + @Override + public void acceptHashing(HashingVisitor visitor) { + visitor.visitDexType(this); + } + + @Override public DexType getContextType() { return this; } @@ -74,27 +97,21 @@ return false; } - public boolean classInitializationMayHaveSideEffects(AppView<?> appView) { - return classInitializationMayHaveSideEffects( - appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); - } - - public boolean classInitializationMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { + public boolean classInitializationMayHaveSideEffectsInContext( + AppView<?> appView, ProgramDefinition context) { DexClass clazz = appView.definitionFor(this); - return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore, seen); + return clazz == null || clazz.classInitializationMayHaveSideEffectsInContext(appView, context); } - public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) { - return initializationOfParentTypesMayHaveSideEffects( - appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet()); - } - - public boolean initializationOfParentTypesMayHaveSideEffects( - AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) { + final boolean internalClassOrInterfaceMayHaveInitializationSideEffects( + AppView<?> appView, + DexClass initialAccessHolder, + Predicate<DexType> ignore, + Set<DexType> seen) { DexClass clazz = appView.definitionFor(this); return clazz == null - || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen); + || clazz.internalClassOrInterfaceMayHaveInitializationSideEffects( + appView, initialAccessHolder, ignore, seen); } public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) { @@ -209,18 +226,6 @@ return this; } - @Override - public int slowCompareTo(DexType other) { - return descriptor.slowCompareTo(other.descriptor); - } - - @Override - public int slowCompareTo(DexType other, NamingLens namingLens) { - DexString thisDescriptor = namingLens.lookupDescriptor(this); - DexString otherDescriptor = namingLens.lookupDescriptor(other); - return thisDescriptor.slowCompareTo(otherDescriptor, namingLens); - } - public boolean isPrimitiveType() { return DescriptorUtils.isPrimitiveType((char) descriptor.content[0]); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java index 7d6ed51..a641039 100644 --- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java +++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -6,15 +6,18 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.structural.CompareToVisitor; +import com.android.tools.r8.utils.structural.HashingVisitor; +import com.android.tools.r8.utils.structural.StructuralAccept; +import com.android.tools.r8.utils.structural.StructuralItem; import com.google.common.collect.Iterators; import java.util.Arrays; import java.util.Iterator; import java.util.function.Consumer; import java.util.stream.Stream; -public class DexTypeList extends DexItem implements Iterable<DexType> { +public class DexTypeList extends DexItem implements Iterable<DexType>, StructuralItem<DexTypeList> { private static final DexTypeList theEmptyTypeList = new DexTypeList(); @@ -33,6 +36,27 @@ this.values = values; } + @Override + public StructuralAccept<DexTypeList> getStructuralAccept() { + // Structural accept is never accessed as all accept methods are defined directly. + throw new Unreachable(); + } + + @Override + public DexTypeList self() { + return this; + } + + @Override + public void acceptCompareTo(DexTypeList other, CompareToVisitor visitor) { + visitor.visitDexTypeList(this, other); + } + + @Override + public void acceptHashing(HashingVisitor visitor) { + visitor.visitDexTypeList(this); + } + public boolean contains(DexType type) { return ArrayUtils.contains(values, type); } @@ -93,38 +117,6 @@ return builder.toString(); } - public int slowCompareTo(DexTypeList other) { - for (int i = 0; i <= Math.min(values.length, other.values.length); i++) { - if (i == values.length) { - return i == other.values.length ? 0 : -1; - } else if (i == other.values.length) { - return 1; - } else { - int result = values[i].slowCompareTo(other.values[i]); - if (result != 0) { - return result; - } - } - } - throw new Unreachable(); - } - - public int slowCompareTo(DexTypeList other, NamingLens namingLens) { - for (int i = 0; i <= Math.min(values.length, other.values.length); i++) { - if (i == values.length) { - return i == other.values.length ? 0 : -1; - } else if (i == other.values.length) { - return 1; - } else { - int result = values[i].slowCompareTo(other.values[i], namingLens); - if (result != 0) { - return result; - } - } - } - throw new Unreachable(); - } - @Override public Iterator<DexType> iterator() { return Iterators.forArray(values); @@ -136,7 +128,7 @@ } DexType[] newValues = values.clone(); - Arrays.sort(newValues, DexType::slowCompareTo); + Arrays.sort(newValues); return new DexTypeList(newValues); } }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java index 88869df..439ef05 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.graph; +import com.android.tools.r8.utils.ObjectUtils; import com.android.tools.r8.utils.SetUtils; import java.util.IdentityHashMap; import java.util.Map; @@ -75,10 +76,16 @@ public FieldAccessInfoCollectionImpl rewrittenWithLens( DexDefinitionSupplier definitions, GraphLens lens) { FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl(); - infos.forEach( - (field, info) -> - collection.infos.put( - lens.lookupField(field), info.rewrittenWithLens(definitions, lens))); + Consumer<FieldAccessInfoImpl> rewriteAndMergeFieldInfo = + info -> { + FieldAccessInfoImpl rewrittenInfo = info.rewrittenWithLens(definitions, lens); + DexField newField = rewrittenInfo.getField(); + collection.infos.compute( + newField, + (ignore, oldInfo) -> + ObjectUtils.mapNotNullOrDefault(oldInfo, rewrittenInfo, rewrittenInfo::join)); + }; + infos.values().forEach(rewriteAndMergeFieldInfo); return collection; }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java index 9dae761..641ef08 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -281,4 +281,12 @@ rewritten.writesWithContexts = writesWithContexts.rewrittenWithLens(definitions, lens); return rewritten; } + + public FieldAccessInfoImpl join(FieldAccessInfoImpl impl) { + FieldAccessInfoImpl merged = new FieldAccessInfoImpl(field); + merged.flags = flags | impl.flags; + merged.readsWithContexts = readsWithContexts.join(impl.readsWithContexts); + merged.writesWithContexts = writesWithContexts.join(impl.writesWithContexts); + return merged; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java index e9b45f1..fd77b73 100644 --- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java +++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -10,6 +10,7 @@ import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_SIGNATURES; import static com.android.tools.r8.graph.GenericSignature.FieldTypeSignature.noSignature; import static com.android.tools.r8.graph.GenericSignature.StarFieldTypeSignature.STAR_FIELD_TYPE_SIGNATURE; +import static com.google.common.base.Predicates.alwaysFalse; import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature; import com.android.tools.r8.graph.GenericSignature.ClassSignature; @@ -19,22 +20,40 @@ import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; import com.android.tools.r8.graph.GenericSignature.ReturnType; import com.android.tools.r8.graph.GenericSignature.TypeSignature; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; public class GenericSignatureTypeRewriter { - private final AppView<?> appView; + private final DexItemFactory factory; + private final Predicate<DexType> wasPruned; + private final Function<DexType, DexType> lookupType; private final DexProgramClass context; private final FieldTypeSignature objectTypeSignature; public GenericSignatureTypeRewriter(AppView<?> appView, DexProgramClass context) { - this.appView = appView; + this( + appView.dexItemFactory(), + appView.appInfo().hasLiveness() + ? appView.appInfo().withLiveness()::wasPruned + : alwaysFalse(), + appView.graphLens()::lookupType, + context); + } + + public GenericSignatureTypeRewriter( + DexItemFactory factory, + Predicate<DexType> wasPruned, + Function<DexType, DexType> lookupType, + DexProgramClass context) { + this.factory = factory; + this.wasPruned = wasPruned; + this.lookupType = lookupType; this.context = context; - objectTypeSignature = - new ClassTypeSignature(appView.dexItemFactory().objectType, EMPTY_TYPE_ARGUMENTS); + objectTypeSignature = new ClassTypeSignature(factory.objectType, EMPTY_TYPE_ARGUMENTS); } public ClassSignature rewrite(ClassSignature classSignature) { @@ -48,7 +67,8 @@ if (fieldTypeSignature.hasNoSignature()) { return fieldTypeSignature; } - return new TypeSignatureRewriter().run(fieldTypeSignature); + FieldTypeSignature rewrittenSignature = new TypeSignatureRewriter().run(fieldTypeSignature); + return rewrittenSignature == null ? FieldTypeSignature.noSignature() : rewrittenSignature; } public MethodTypeSignature rewrite(MethodTypeSignature methodTypeSignature) { @@ -80,8 +100,7 @@ public void visitSuperClass(ClassTypeSignature classTypeSignature) { rewrittenSuperClass = new ClassTypeSignatureRewriter(true).run(classTypeSignature); if (rewrittenSuperClass == null) { - rewrittenSuperClass = - new ClassTypeSignature(appView.dexItemFactory().objectType, EMPTY_TYPE_ARGUMENTS); + rewrittenSuperClass = new ClassTypeSignature(factory.objectType, EMPTY_TYPE_ARGUMENTS); } } @@ -99,7 +118,7 @@ if (rewrittenTypeParameters.isEmpty() && rewrittenSuperInterfaces.isEmpty() && rewrittenSuperClass.isNoSignature() - && rewrittenSuperClass.type == appView.dexItemFactory().objectType) { + && rewrittenSuperClass.type == factory.objectType) { return ClassSignature.noSignature(); } return new ClassSignature( @@ -245,7 +264,6 @@ private class ClassTypeSignatureRewriter implements GenericSignatureVisitor { - private final AppInfoWithLiveness appInfoWithLiveness; private final boolean isSuperClassOrInterface; // These fields are updated when iterating the modeled structure. @@ -259,8 +277,6 @@ private ClassTypeSignature parentClassSignature; private ClassTypeSignatureRewriter(boolean isSuperClassOrInterface) { - appInfoWithLiveness = - appView.appInfo().hasLiveness() ? appView.appInfo().withLiveness() : null; this.isSuperClassOrInterface = isSuperClassOrInterface; } @@ -313,8 +329,8 @@ } private DexType getTarget(DexType type) { - DexType rewrittenType = appView.graphLens().lookupType(type); - if (appInfoWithLiveness != null && appInfoWithLiveness.wasPruned(rewrittenType)) { + DexType rewrittenType = lookupType.apply(type); + if (wasPruned.test(rewrittenType)) { return null; } if (isSuperClassOrInterface && context.type == rewrittenType) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java index 87cea9c..398ed87 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -19,7 +19,6 @@ import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; @@ -29,6 +28,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -104,22 +104,43 @@ */ public static class FieldLookupResult extends MemberLookupResult<DexField> { - private FieldLookupResult(DexField reference, DexField reboundReference) { + private final DexType castType; + + private FieldLookupResult(DexField reference, DexField reboundReference, DexType castType) { super(reference, reboundReference); + this.castType = castType; } public static Builder builder(GraphLens lens) { return new Builder(lens); } + public boolean hasCastType() { + return castType != null; + } + + public DexType getCastType() { + return castType; + } + + public DexType getRewrittenCastType(Function<DexType, DexType> fn) { + return hasCastType() ? fn.apply(castType) : null; + } + public static class Builder extends MemberLookupResult.Builder<DexField, Builder> { + private DexType castType; private GraphLens lens; private Builder(GraphLens lens) { this.lens = lens; } + public Builder setCastType(DexType castType) { + this.castType = castType; + return this; + } + @Override public Builder self() { return this; @@ -127,7 +148,7 @@ public FieldLookupResult build() { // TODO(b/168282032): All non-identity graph lenses should set the rebound reference. - return new FieldLookupResult(reference, reboundReference); + return new FieldLookupResult(reference, reboundReference, castType); } } } @@ -544,15 +565,6 @@ return builder.build(); } - public ImmutableSortedSet<DexMethod> rewriteMethodsSorted(Set<DexMethod> methods) { - ImmutableSortedSet.Builder<DexMethod> builder = - new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare); - for (DexMethod method : methods) { - builder.add(getRenamedMethodSignature(method)); - } - return builder.build(); - } - public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) { ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder(); map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value)); @@ -560,8 +572,7 @@ } public ImmutableSet<DexType> rewriteTypes(Set<DexType> types) { - ImmutableSortedSet.Builder<DexType> builder = - new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare); + ImmutableSet.Builder<DexType> builder = new ImmutableSet.Builder<>(); for (DexType type : types) { builder.add(lookupType(type)); } @@ -1056,12 +1067,16 @@ return FieldLookupResult.builder(this) .setReboundReference(rewrittenReboundReference) .setReference(rewrittenNonReboundReference) + .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType)) .build(); } else { // TODO(b/168282032): We should always have the rebound reference, so this should become // unreachable. DexField rewrittenReference = previous.getRewrittenReference(fieldMap); - return FieldLookupResult.builder(this).setReference(rewrittenReference).build(); + return FieldLookupResult.builder(this) + .setReference(rewrittenReference) + .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType)) + .build(); } }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java index 8aa36c0..7ba6207 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -11,7 +11,6 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -46,11 +45,6 @@ return new IdentityBuilder(); } - // TODO(b/132593519): We should not need sorted maps with the new member rebinding analysis. - public static SortedBuilder sortedBuilder() { - return new SortedBuilder(); - } - public Modifier modifier() { return new Modifier( directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes); @@ -103,7 +97,7 @@ Map<DexMethod, ProgramMethodSet> invokes, DexDefinitionSupplier definitions, GraphLens lens) { return MapUtils.map( invokes, - capacity -> new TreeMap<>(DexMethod::slowCompareTo), + IdentityHashMap::new, lens::getRenamedMethodSignature, methods -> methods.rewrittenWithLens(definitions, lens), (methods, other) -> { @@ -225,13 +219,6 @@ } } - public static class SortedBuilder extends Builder<TreeMap<DexMethod, ProgramMethodSet>> { - - private SortedBuilder() { - super(() -> new TreeMap<>(DexMethod::slowCompareTo)); - } - } - public static class Modifier extends Builder<Map<DexMethod, ProgramMethodSet>> { private Modifier(
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java index d483a59..3200163 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -137,7 +137,7 @@ public List<DexEncodedMethod> allMethodsSorted() { List<DexEncodedMethod> sorted = new ArrayList<>(size()); forEachMethod(sorted::add); - sorted.sort((a, b) -> a.method.slowCompareTo(b.method)); + sorted.sort((a, b) -> a.method.compareTo(b.method)); return sorted; }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java index 2874e03..c6f7a5f 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -34,7 +34,7 @@ } public static MethodMapBacking createSorted() { - Comparator<Wrapper<DexMethod>> comparator = (x, y) -> x.get().slowCompareTo(y.get()); + Comparator<Wrapper<DexMethod>> comparator = (x, y) -> x.get().compareTo(y.get()); return new MethodMapBacking(new Object2ReferenceRBTreeMap<>(comparator)); }
diff --git a/src/main/java/com/android/tools/r8/graph/NamingLensComparable.java b/src/main/java/com/android/tools/r8/graph/NamingLensComparable.java new file mode 100644 index 0000000..e6323eb --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/NamingLensComparable.java
@@ -0,0 +1,15 @@ +// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.graph; + +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.utils.structural.CompareToVisitorWithNamingLens; +import com.android.tools.r8.utils.structural.StructuralItem; + +public interface NamingLensComparable<T extends NamingLensComparable<T>> extends StructuralItem<T> { + + default int compareToWithNamingLens(T other, NamingLens lens) { + return CompareToVisitorWithNamingLens.run(self(), other, lens, StructuralItem::acceptCompareTo); + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java index b4797a9..b5ce56a 100644 --- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java +++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -42,7 +42,7 @@ private DexString firstJumboString; public ObjectToOffsetMapping( - AppInfo appInfo, + AppView<?> appView, GraphLens graphLens, NamingLens namingLens, InitClassLens initClassLens, @@ -54,7 +54,7 @@ Collection<DexString> strings, Collection<DexCallSite> callSites, Collection<DexMethodHandle> methodHandles) { - assert appInfo != null; + assert appView != null; assert graphLens != null; assert classes != null; assert protos != null; @@ -68,8 +68,8 @@ this.graphLens = graphLens; this.namingLens = namingLens; this.initClassLens = initClassLens; - this.lensCodeRewriter = new LensCodeRewriterUtils(appInfo, graphLens); - this.classes = sortClasses(appInfo, classes, namingLens); + this.lensCodeRewriter = new LensCodeRewriterUtils(appView); + this.classes = sortClasses(appView.appInfo(), classes, namingLens); this.protos = createSortedMap(protos, compare(namingLens), this::failOnOverflow); this.types = createSortedMap(types, compare(namingLens), this::failOnOverflow); this.methods = createSortedMap(methods, compare(namingLens), this::failOnOverflow); @@ -79,8 +79,8 @@ this.methodHandles = createSortedMap(methodHandles, compare(namingLens), this::failOnOverflow); } - private static <T extends PresortedComparable<T>> Comparator<T> compare(NamingLens namingLens) { - return (a, b) -> a.slowCompareTo(b, namingLens); + private static <T extends NamingLensComparable<T>> Comparator<T> compare(NamingLens namingLens) { + return (a, b) -> a.compareToWithNamingLens(b, namingLens); } private void setFirstJumboString(DexString string) { @@ -168,7 +168,7 @@ (x, y) -> { int dx = classDepths.getDepth(x); int dy = classDepths.getDepth(y); - return dx != dy ? dx - dy : x.type.slowCompareTo(y.type, namingLens); + return dx != dy ? dx - dy : x.type.compareToWithNamingLens(y.type, namingLens); }) .collect(Collectors.toList()); return sortedClasses.toArray(DexProgramClass.EMPTY_ARRAY);
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java deleted file mode 100644 index e4eb4b7..0000000 --- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java +++ /dev/null
@@ -1,16 +0,0 @@ -// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.graph; - -import com.android.tools.r8.naming.NamingLens; - -public interface PresortedComparable<T> { - - int slowCompareTo(T other); - int slowCompareTo(T other, NamingLens namingLens); - - static <T extends PresortedComparable<T>> int slowCompare(T a, T b) { - return a.slowCompareTo(b); - } -}
diff --git a/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java b/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java index 97afd09..08be3b6 100644 --- a/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java +++ b/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java
@@ -9,6 +9,6 @@ public class SortedProgramPackage extends ProgramPackage { public SortedProgramPackage(String packageDescriptor) { - super(packageDescriptor, () -> new TreeSet<>((a, b) -> a.getType().slowCompareTo(b.getType()))); + super(packageDescriptor, () -> new TreeSet<>((a, b) -> a.getType().compareTo(b.getType()))); } }
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java index b73d2bc..d9b17f2 100644 --- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java +++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -280,7 +280,7 @@ private void ensureDirectSubTypeSet() { if (directSubtypes == NO_DIRECT_SUBTYPE) { - directSubtypes = new ConcurrentSkipListSet<>(DexType::slowCompareTo); + directSubtypes = new ConcurrentSkipListSet<>(DexType::compareTo); } }
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java index 285e397..d0948bf 100644 --- a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java +++ b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
@@ -6,25 +6,40 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.optimize.lambda.LambdaGroup; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.ImmutableSet; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; +import java.util.Collections; +import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; public class HorizontallyMergedLambdaClasses implements MergedClasses { - private final Set<DexType> sources; + private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses; - public HorizontallyMergedLambdaClasses(Set<DexType> sources) { - this.sources = sources; + public HorizontallyMergedLambdaClasses(Map<DexType, LambdaGroup> lambdas) { + this.mergedClasses = new BidirectionalManyToOneMap<>(); + lambdas.forEach((lambda, group) -> mergedClasses.put(lambda, group.getGroupClassType())); } public static HorizontallyMergedLambdaClasses empty() { - return new HorizontallyMergedLambdaClasses(ImmutableSet.of()); + return new HorizontallyMergedLambdaClasses(Collections.emptyMap()); + } + + @Override + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + mergedClasses.forEach(consumer); + } + + @Override + public boolean hasBeenMerged(DexType type) { + return mergedClasses.containsKey(type); } @Override public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { - for (DexType source : sources) { + for (DexType source : mergedClasses.keySet()) { assert appView.appInfo().wasPruned(source) : "Expected horizontally merged lambda class `" + source.toSourceString() @@ -32,9 +47,4 @@ } return true; } - - @Override - public boolean hasBeenMerged(DexType type) { - return sources.contains(type); - } }
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java index 3f25895..8fa6995 100644 --- a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java +++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
@@ -8,13 +8,17 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import java.util.Set; +import java.util.function.BiConsumer; public interface MergedClasses { - boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView); + void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer); boolean hasBeenMerged(DexType type); + boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView); + /** * Determine if the class has been merged by the merged classes object. If the merged classes is * null then return false.
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java index 55c34b5..1225127 100644 --- a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java +++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
@@ -9,6 +9,8 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; public class MergedClassesCollection implements MergedClasses { @@ -19,11 +21,10 @@ } @Override - public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { for (MergedClasses mergedClasses : collection) { - assert mergedClasses.verifyAllSourcesPruned(appView); + mergedClasses.forEachMergeGroup(consumer); } - return true; } @Override @@ -35,4 +36,12 @@ } return false; } + + @Override + public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { + for (MergedClasses mergedClasses : collection) { + assert mergedClasses.verifyAllSourcesPruned(appView); + } + return true; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java new file mode 100644 index 0000000..7046e80 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java
@@ -0,0 +1,64 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.graph.classmerging; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; +import java.util.Set; +import java.util.function.BiConsumer; + +public class StaticallyMergedClasses implements MergedClasses { + + private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses; + + public StaticallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) { + this.mergedClasses = mergedClasses; + } + + public static StaticallyMergedClasses empty() { + return new StaticallyMergedClasses(BidirectionalManyToOneMap.empty()); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + mergedClasses.forEach(consumer); + } + + @Override + public boolean hasBeenMerged(DexType type) { + return false; + } + + @Override + public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { + return true; + } + + public static class Builder { + + private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses = + new BidirectionalManyToOneMap<>(); + + private Builder() {} + + public void recordMerge(DexProgramClass source, DexProgramClass target) { + for (DexType previousSource : mergedClasses.removeValue(source.getType())) { + mergedClasses.put(previousSource, target.getType()); + } + mergedClasses.put(source.getType(), target.getType()); + } + + public StaticallyMergedClasses build() { + return new StaticallyMergedClasses(mergedClasses); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java index ed69ace..4b25311 100644 --- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -7,28 +7,35 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; import java.util.Collection; -import java.util.Collections; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; public class VerticallyMergedClasses implements MergedClasses { - private final Map<DexType, DexType> mergedClasses; - private final Map<DexType, Set<DexType>> mergedClassesInverse; + private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses; - public VerticallyMergedClasses( - Map<DexType, DexType> mergedClasses, Map<DexType, Set<DexType>> mergedClassesInverse) { + public VerticallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) { this.mergedClasses = mergedClasses; - this.mergedClassesInverse = mergedClassesInverse; + } + + public static VerticallyMergedClasses empty() { + return new VerticallyMergedClasses(BidirectionalManyToOneMap.empty()); + } + + @Override + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + mergedClasses.forEach(consumer); } public Map<DexType, DexType> getForwardMap() { - return mergedClasses; + return mergedClasses.getForwardMap(); } public Collection<DexType> getSourcesFor(DexType type) { - return mergedClassesInverse.getOrDefault(type, Collections.emptySet()); + return mergedClasses.getKeys(type); } public DexType getTargetFor(DexType type) { @@ -36,27 +43,33 @@ return mergedClasses.get(type); } + public DexType getTargetForOrDefault(DexType type, DexType defaultValue) { + return mergedClasses.getOrDefault(type, defaultValue); + } + public boolean hasBeenMergedIntoSubtype(DexType type) { return mergedClasses.containsKey(type); } + public boolean isEmpty() { + return mergedClasses.isEmpty(); + } + public boolean isTarget(DexType type) { return !getSourcesFor(type).isEmpty(); } @Override - public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { - for (Collection<DexType> sourcesForTarget : mergedClassesInverse.values()) { - for (DexType source : sourcesForTarget) { - assert appView.appInfo().wasPruned(source) - : "Expected vertically merged class `" + source.toSourceString() + "` to be absent"; - } - } - return true; + public boolean hasBeenMerged(DexType type) { + return hasBeenMergedIntoSubtype(type); } @Override - public boolean hasBeenMerged(DexType type) { - return hasBeenMergedIntoSubtype(type); + public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { + for (DexType source : mergedClasses.keySet()) { + assert appView.appInfo().wasPruned(source) + : "Expected vertically merged class `" + source.toSourceString() + "` to be absent"; + } + return true; } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java new file mode 100644 index 0000000..7cf3029 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -0,0 +1,71 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging; + +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder; +import com.android.tools.r8.utils.ListUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ClassInstanceFieldsMerger { + + // Map from target class field to all fields which should be merged into that field. + private final Map<DexEncodedField, List<DexEncodedField>> fieldMappings = new LinkedHashMap<>(); + private final Builder lensBuilder; + + public ClassInstanceFieldsMerger( + HorizontalClassMergerGraphLens.Builder lensBuilder, DexProgramClass target) { + this.lensBuilder = lensBuilder; + target + .instanceFields() + .forEach(field -> fieldMappings.computeIfAbsent(field, ignore -> new ArrayList<>())); + } + + public void addFields(DexProgramClass clazz) { + Map<DexType, List<DexEncodedField>> availableFields = new IdentityHashMap<>(); + for (DexEncodedField field : fieldMappings.keySet()) { + availableFields.computeIfAbsent(field.type(), ignore -> new LinkedList<>()).add(field); + } + + for (DexEncodedField oldField : clazz.instanceFields()) { + DexEncodedField newField = + ListUtils.removeFirstMatch( + availableFields.get(oldField.type()), + field -> field.getAccessFlags().isSameVisibility(oldField.getAccessFlags())) + .get(); + assert newField != null; + fieldMappings.get(newField).add(oldField); + } + } + + private void mergeField(DexEncodedField oldField, DexEncodedField newField) { + if (newField.isFinal() && !oldField.isFinal()) { + newField.getAccessFlags().demoteFromFinal(); + } + lensBuilder.moveField(oldField.field, newField.field); + } + + private void mergeFields(DexEncodedField newField, Collection<DexEncodedField> oldFields) { + DexField newFieldReference = newField.getReference(); + + lensBuilder.moveField(newFieldReference, newFieldReference); + lensBuilder.setRepresentativeField(newFieldReference, newFieldReference); + + oldFields.forEach(oldField -> mergeField(oldField, newField)); + } + + public void merge() { + fieldMappings.forEach(this::mergeFields); + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java index 79913ff..f75f969 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.horizontalclassmerging; +import static com.google.common.base.Predicates.not; + import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexAnnotationSet; @@ -22,7 +24,6 @@ import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier; import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.google.common.base.Equivalence.Wrapper; -import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; @@ -52,6 +53,7 @@ private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder(); private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>(); private final ClassStaticFieldsMerger classStaticFieldsMerger; + private final ClassInstanceFieldsMerger classInstanceFieldsMerger; private final Collection<VirtualMethodMerger> virtualMethodMergers; private final Collection<ConstructorMerger> constructorMergers; private final DexField classIdField; @@ -77,6 +79,7 @@ this.dexItemFactory = appView.dexItemFactory(); this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, target); + this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(lensBuilder, target); buildClassIdentifierMap(); } @@ -182,22 +185,26 @@ toMergeGroup.forEach(clazz -> clazz.setStaticFields(null)); } - void fixFinal() { - if (Iterables.any(toMergeGroup, Predicates.not(DexProgramClass::isFinal))) { - target.accessFlags.demoteFromFinal(); + void fixAccessFlags() { + if (Iterables.any(toMergeGroup, not(DexProgramClass::isAbstract))) { + target.getAccessFlags().demoteFromAbstract(); + } + if (Iterables.any(toMergeGroup, not(DexProgramClass::isFinal))) { + target.getAccessFlags().demoteFromFinal(); } } void mergeInstanceFields() { - // TODO: support instance field merging - assert Iterables.all(toMergeGroup, clazz -> !clazz.hasInstanceFields()); - - // The target should only have the class id field. - assert target.instanceFields().size() == 1; + toMergeGroup.forEach( + clazz -> { + classInstanceFieldsMerger.addFields(clazz); + clazz.setInstanceFields(null); + }); + classInstanceFieldsMerger.merge(); } public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) { - fixFinal(); + fixAccessFlags(); appendClassIdField(); mergeVirtualMethods();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java index d826b86..e287090 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
@@ -48,7 +48,7 @@ field = field.toTypeSubstitutedField(newFieldReference); targetFields.put(newFieldReference, field); - lensBuilder.mapField(oldFieldReference, newFieldReference); + lensBuilder.moveField(oldFieldReference, newFieldReference); } public void addFields(DexProgramClass toMerge) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java index d15ed7e..2e2020d 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier; import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.structural.Ordered; import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; @@ -156,7 +157,7 @@ for (DexEncodedMethod constructor : constructors) { if (constructor.hasClassFileVersion()) { classFileVersion = - CfVersion.maxAllowNull(classFileVersion, constructor.getClassFileVersion()); + Ordered.maxIgnoreNull(classFileVersion, constructor.getClassFileVersion()); } DexMethod movedConstructor = moveConstructor(classMethodsBuilder, constructor); lensBuilder.mapMethod(movedConstructor, movedConstructor);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java index 37222de..6cbffcc 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java
@@ -4,19 +4,59 @@ package com.android.tools.r8.horizontalclassmerging; +import com.android.tools.r8.graph.AccessFlags; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; public class FieldMultiset { - private final Multiset<DexType> fields = HashMultiset.create(); + private static class VisibilitySignature { + private int publicVisible = 0; + private int protectedVisible = 0; + private int privateVisible = 0; + private int packagePrivateVisible = 0; + + public void addAccessModifier(AccessFlags accessFlags) { + if (accessFlags.isPublic()) { + publicVisible++; + } else if (accessFlags.isPrivate()) { + privateVisible++; + } else if (accessFlags.isPackagePrivate()) { + packagePrivateVisible++; + } else if (accessFlags.isProtected()) { + protectedVisible++; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VisibilitySignature that = (VisibilitySignature) o; + return publicVisible == that.publicVisible + && protectedVisible == that.protectedVisible + && privateVisible == that.privateVisible + && packagePrivateVisible == that.packagePrivateVisible; + } + + @Override + public int hashCode() { + return Objects.hash(publicVisible, protectedVisible, privateVisible, packagePrivateVisible); + } + } + + // This *must* not be an IdentityHashMap, because hash equality does not work for the values. + private final Map<DexType, VisibilitySignature> fields = new HashMap<>(); public FieldMultiset(DexProgramClass clazz) { for (DexEncodedField field : clazz.instanceFields()) { - fields.add(field.type()); + fields + .computeIfAbsent(field.type(), ignore -> new VisibilitySignature()) + .addAccessModifier(field.getAccessFlags()); } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java index 2200267..8dfa67c 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -8,19 +8,19 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated; +import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses; import com.android.tools.r8.horizontalclassmerging.policies.ClassesHaveSameInterfaces; import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy; import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible; import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses; import com.android.tools.r8.horizontalclassmerging.policies.IgnoreSynthetics; -import com.android.tools.r8.horizontalclassmerging.policies.NoAbstractClasses; import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations; import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations; import com.android.tools.r8.horizontalclassmerging.policies.NoEnums; import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses; -import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFields; import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces; import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules; +import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinLambdas; import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata; import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods; import com.android.tools.r8.horizontalclassmerging.policies.NoRuntimeTypeChecks; @@ -34,6 +34,7 @@ import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation; import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries; import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit; +import com.android.tools.r8.horizontalclassmerging.policies.SameFields; import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost; import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -44,9 +45,8 @@ import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; public class HorizontalClassMerger { private final AppView<AppInfoWithLiveness> appView; @@ -61,17 +61,15 @@ DirectMappedDexApplication.Builder appBuilder, MainDexTracingResult mainDexTracingResult, RuntimeTypeCheckInfo runtimeTypeCheckInfo) { - Map<FieldMultiset, List<DexProgramClass>> classes = new LinkedHashMap<>(); + List<DexProgramClass> initialGroup = appView.appInfo().classesWithDeterministicOrder(); - // Group classes by same field signature using the hash map. - for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) { - classes.computeIfAbsent(new FieldMultiset(clazz), ignore -> new ArrayList<>()).add(clazz); - } - - // Run the policies on all collected classes to produce a final grouping. + // Run the policies on all program classes to produce a final grouping. Collection<List<DexProgramClass>> groups = new SimplePolicyExecutor() - .run(classes.values(), getPolicies(mainDexTracingResult, runtimeTypeCheckInfo)); + .run( + Collections.singletonList(initialGroup), + getPolicies(mainDexTracingResult, runtimeTypeCheckInfo)); + // If there are no groups, then end horizontal class merging. if (groups.isEmpty()) { appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty()); @@ -109,12 +107,12 @@ RuntimeTypeCheckInfo runtimeTypeCheckInfo) { return ImmutableList.of( new NotMatchedByNoHorizontalClassMerging(appView), - new NoInstanceFields(), + new SameFields(), new NoInterfaces(), new ClassesHaveSameInterfaces(), new NoAnnotations(), new NoEnums(appView), - new NoAbstractClasses(), + new CheckAbstractClasses(appView), new IgnoreSynthetics(appView), new NoClassesOrMembersWithAnnotations(), new NoInnerClasses(), @@ -122,6 +120,7 @@ new NoNativeMethods(), new NoKeepRules(appView), new NoKotlinMetadata(), + new NoKotlinLambdas(appView), new NoServiceLoaders(appView), new NotVerticallyMergedIntoSubtype(appView), new NoRuntimeTypeChecks(runtimeTypeCheckInfo),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java index 8dadc71..cc0528f 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.ir.conversion.ExtraParameter; import com.android.tools.r8.utils.IterableUtils; import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; @@ -25,8 +24,9 @@ public class HorizontalClassMergerGraphLens extends NestedGraphLens { private final AppView<?> appView; private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters; - private final Map<DexMethod, DexMethod> originalConstructorSignatures; + private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures; private final HorizontallyMergedClasses mergedClasses; + private final Map<DexField, DexField> extraOriginalFieldSignatures; private HorizontalClassMergerGraphLens( AppView<?> appView, @@ -36,7 +36,8 @@ Map<DexMethod, DexMethod> methodMap, BiMap<DexField, DexField> originalFieldSignatures, BiMap<DexMethod, DexMethod> originalMethodSignatures, - Map<DexMethod, DexMethod> originalConstructorSignatures, + Map<DexMethod, DexMethod> extraOriginalMethodSignatures, + Map<DexField, DexField> extraOriginalFieldSignatures, GraphLens previousLens) { super( mergedClasses.getForwardMap(), @@ -48,7 +49,8 @@ appView.dexItemFactory()); this.appView = appView; this.methodExtraParameters = methodExtraParameters; - this.originalConstructorSignatures = originalConstructorSignatures; + this.extraOriginalFieldSignatures = extraOriginalFieldSignatures; + this.extraOriginalMethodSignatures = extraOriginalMethodSignatures; this.mergedClasses = mergedClasses; } @@ -59,13 +61,22 @@ @Override public DexMethod getOriginalMethodSignature(DexMethod method) { - DexMethod originalConstructor = originalConstructorSignatures.get(method); + DexMethod originalConstructor = extraOriginalMethodSignatures.get(method); if (originalConstructor == null) { return super.getOriginalMethodSignature(method); } return getPrevious().getOriginalMethodSignature(originalConstructor); } + @Override + public DexField getOriginalFieldSignature(DexField field) { + DexField originalField = extraOriginalFieldSignatures.get(field); + if (originalField == null) { + return super.getOriginalFieldSignature(field); + } + return getPrevious().getOriginalFieldSignature(originalField); + } + /** * If an overloaded constructor is requested, add the constructor id as a parameter to the * constructor. Otherwise return the lookup on the underlying graph lens. @@ -86,10 +97,8 @@ } public static class Builder { - private final BiMap<DexField, DexField> fieldMap = HashBiMap.create(); - + private ManyToOneMap<DexField, DexField> fieldMap = new ManyToOneMap<>(); private ManyToOneMap<DexMethod, DexMethod> methodMap = new ManyToOneMap<>(); - private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters = new IdentityHashMap<>(); @@ -104,17 +113,24 @@ assert false; return group.iterator().next(); }); - BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse(); + ManyToOneInverseMap<DexField, DexField> inverseFieldMap = + fieldMap.inverse( + group -> { + // Every group should have a representative. Fail in debug mode. + assert false; + return group.iterator().next(); + }); return new HorizontalClassMergerGraphLens( appView, mergedClasses, methodExtraParameters, - fieldMap, + fieldMap.getForwardMap(), methodMap.getForwardMap(), - originalFieldSignatures, + inverseFieldMap.getBiMap(), inverseMethodMap.getBiMap(), inverseMethodMap.getExtraMap(), + inverseFieldMap.getExtraMap(), appView.graphLens()); } @@ -122,12 +138,18 @@ methodMap = methodMap.remap(remapMethods, Function.identity(), Function.identity()); } - public Builder mapField(DexField from, DexField to) { - DexField previousFrom = fieldMap.inverse().remove(from); - if (previousFrom != null) { - from = previousFrom; - } + public void remapFields(BiMap<DexField, DexField> remapFields) { + fieldMap = fieldMap.remap(remapFields, Function.identity(), Function.identity()); + } + + public Builder moveField(DexField from, DexField to) { fieldMap.put(from, to); + fieldMap.putInverse(from, to); + return this; + } + + public Builder setRepresentativeField(DexField from, DexField to) { + fieldMap.setRepresentative(from, to); return this; }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java index 080102a..5432ef1 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -13,6 +13,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; public class HorizontallyMergedClasses implements MergedClasses { @@ -26,6 +27,11 @@ return new HorizontallyMergedClasses(new BidirectionalManyToOneMap<>()); } + @Override + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + mergedClasses.forEach(consumer); + } + public DexType getMergeTargetOrDefault(DexType type) { return mergedClasses.getOrDefault(type, type); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java index 1a3a965..92130de 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -38,7 +38,16 @@ MultiClassPolicy policy, LinkedList<List<DexProgramClass>> groups) { // For each group apply the multi class policy and add all the new groups together. return groups.stream() - .flatMap(group -> policy.apply(group).stream()) + .flatMap( + group -> { + int previousNumberOfClasses = group.size(); + Collection<List<DexProgramClass>> newGroups = policy.apply(group); + policy.numberOfRemovedClasses += previousNumberOfClasses; + for (List<DexProgramClass> newGroup : newGroups) { + policy.numberOfRemovedClasses -= newGroup.size(); + } + return newGroups.stream(); + }) .collect(Collectors.toCollection(LinkedList::new)); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java index a7ce343..49399bf 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -46,6 +46,7 @@ private final AppView<AppInfoWithLiveness> appView; private final DexItemFactory dexItemFactory; private final BiMap<DexMethod, DexMethod> movedMethods = HashBiMap.create(); + private final BiMap<DexField, DexField> movedFields = HashBiMap.create(); private final SyntheticArgumentClass syntheticArgumentClass; private final BiMap<DexMethodSignature, DexMethodSignature> reservedInterfaceSignatures = HashBiMap.create(); @@ -129,6 +130,7 @@ } lensBuilder.remapMethods(movedMethods); + lensBuilder.remapFields(movedFields); HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses); fieldAccessChangesBuilder.build(this::fixupMethodReference).modify(appView); @@ -381,7 +383,7 @@ } if (newField != encodedField.field) { - lensBuilder.mapField(field, newField); + movedFields.put(field, newField); setter.setField(i, encodedField.toTypeSubstitutedField(newField)); } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java index 07a3172..258b1c7 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; @@ -20,17 +21,20 @@ import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier; +import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.OptionalBool; +import com.android.tools.r8.utils.structural.Ordered; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import java.util.ArrayList; -import java.util.Collection; +import java.util.List; public class VirtualMethodMerger { private final DexProgramClass target; private final DexItemFactory dexItemFactory; - private final Collection<ProgramMethod> methods; + private final List<ProgramMethod> methods; private final DexField classIdField; private final AppView<AppInfoWithLiveness> appView; private final DexMethod superMethod; @@ -38,7 +42,7 @@ public VirtualMethodMerger( AppView<AppInfoWithLiveness> appView, DexProgramClass target, - Collection<ProgramMethod> methods, + List<ProgramMethod> methods, DexField classIdField, DexMethod superMethod) { this.dexItemFactory = appView.dexItemFactory(); @@ -50,7 +54,7 @@ } public static class Builder { - private final Collection<ProgramMethod> methods = new ArrayList<>(); + private final List<ProgramMethod> methods = new ArrayList<>(); public Builder add(ProgramMethod constructor) { methods.add(constructor); @@ -122,63 +126,130 @@ private MethodAccessFlags getAccessFlags() { // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound - MethodAccessFlags flags = methods.iterator().next().getDefinition().getAccessFlags().copy(); + MethodAccessFlags flags = methods.iterator().next().getAccessFlags().copy(); + if (flags.isAbstract() + && Iterables.any(methods, method -> !method.getAccessFlags().isAbstract())) { + flags.unsetAbstract(); + } if (flags.isFinal() && Iterables.any(methods, method -> !method.getAccessFlags().isFinal())) { flags.unsetFinal(); } return flags; } + private DexMethod getNewMethodReference() { + return ListUtils.first(methods).getReference().withHolder(target, dexItemFactory); + } + + /** + * If there is a super method and all methods are abstract, then we can simply remove all abstract + * methods. + */ + private boolean isNop() { + return superMethod != null + && Iterables.all(methods, method -> method.getDefinition().isAbstract()); + } + + /** + * If the method is present on all classes in the merge group, and there is at most one + * non-abstract method, then we can simply move that method (or the first abstract method) to the + * target class. + */ + private boolean isTrivial() { + if (superMethod != null) { + return false; + } + if (methods.size() == 1) { + return true; + } + int numberOfNonAbstractMethods = + Iterables.size(Iterables.filter(methods, method -> !method.getDefinition().isAbstract())); + return numberOfNonAbstractMethods <= 1; + } + /** * If there is only a single method that does not override anything then it is safe to just move * it to the target type if it is not already in it. */ - public void mergeTrivial( + private void mergeTrivial( ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) { - DexEncodedMethod method = methods.iterator().next().getDefinition(); + DexMethod newMethodReference = getNewMethodReference(); - if (method.getHolderType() != target.type) { - // If the method is not in the target type, move it and record it in the lens. - DexMethod originalReference = method.getReference(); - method = method.toRenamedHolderMethod(target.type, dexItemFactory); - lensBuilder.moveMethod(originalReference, method.getReference()); + // Find the first non-abstract method. If all are abstract, then select the first method. + ProgramMethod representative = + Iterables.find(methods, method -> !method.getDefinition().isAbstract(), null); + if (representative == null) { + representative = ListUtils.first(methods); } - classMethodsBuilder.addVirtualMethod(method); + for (ProgramMethod method : methods) { + if (method.getReference() == representative.getReference()) { + lensBuilder.moveMethod(method.getReference(), newMethodReference); + } else { + lensBuilder.mapMethod(method.getReference(), newMethodReference); + } + } + + if (representative.getHolderType() == target.getType()) { + classMethodsBuilder.addVirtualMethod(representative.getDefinition()); + } else { + // If the method is not in the target type, move it. + OptionalBool isLibraryMethodOverride = + representative.getDefinition().isLibraryMethodOverride(); + classMethodsBuilder.addVirtualMethod( + representative + .getDefinition() + .toTypeSubstitutedMethod( + newMethodReference, + builder -> builder.setIsLibraryMethodOverrideIfKnown(isLibraryMethodOverride))); + } } public void merge( ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder, FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder, - Reference2IntMap classIdentifiers) { - + Reference2IntMap<DexType> classIdentifiers) { assert !methods.isEmpty(); + // Handle nop merges. + if (isNop()) { + return; + } + // Handle trivial merges. - if (superMethod == null && methods.size() == 1) { + if (isTrivial()) { mergeTrivial(classMethodsBuilder, lensBuilder); return; } + Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>(); CfVersion classFileVersion = null; + ProgramMethod representative = null; for (ProgramMethod method : methods) { + if (method.getDefinition().isAbstract()) { + continue; + } if (method.getDefinition().hasClassFileVersion()) { CfVersion methodVersion = method.getDefinition().getClassFileVersion(); - classFileVersion = CfVersion.maxAllowNull(classFileVersion, methodVersion); + classFileVersion = Ordered.maxIgnoreNull(classFileVersion, methodVersion); } DexMethod newMethod = moveMethod(classMethodsBuilder, method); lensBuilder.mapMethod(newMethod, newMethod); lensBuilder.mapMethodInverse(method.getReference(), newMethod); classIdToMethodMap.put(classIdentifiers.getInt(method.getHolderType()), newMethod); + if (representative == null) { + representative = method; + } } + assert representative != null; + // Use the first of the original methods as the original method for the merged constructor. - DexMethod templateReference = methods.iterator().next().getReference(); DexMethod originalMethodReference = - appView.graphLens().getOriginalMethodSignature(templateReference); + appView.graphLens().getOriginalMethodSignature(representative.getReference()); DexMethod bridgeMethodReference = dexItemFactory.createFreshMethodName( originalMethodReference.getName().toSourceString() + "$bridge", @@ -187,8 +258,7 @@ originalMethodReference.getHolderType(), classMethodsBuilder::isFresh); - DexMethod newMethodReference = - dexItemFactory.createMethod(target.type, templateReference.proto, templateReference.name); + DexMethod newMethodReference = getNewMethodReference(); AbstractSynthesizedCode synthesizedCode = new VirtualMethodEntryPointSynthesizedCode( classIdToMethodMap, @@ -206,10 +276,17 @@ synthesizedCode, true, classFileVersion); + if (!representative.getDefinition().isLibraryMethodOverride().isUnknown()) { + newMethod.setLibraryMethodOverride(representative.getDefinition().isLibraryMethodOverride()); + } - // Map each old method to the newly synthesized method in the graph lens. + // Map each old non-abstract method to the newly synthesized method in the graph lens. for (ProgramMethod oldMethod : methods) { - lensBuilder.moveMethod(oldMethod.getReference(), newMethodReference); + if (oldMethod.getDefinition().isAbstract()) { + lensBuilder.mapMethod(oldMethod.getReference(), newMethodReference); + } else { + lensBuilder.moveMethod(oldMethod.getReference(), newMethodReference); + } } lensBuilder.recordExtraOriginalSignature(bridgeMethodReference, newMethodReference);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java new file mode 100644 index 0000000..4839801 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
@@ -0,0 +1,50 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging.policies; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.InternalOptions; +import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class CheckAbstractClasses extends MultiClassPolicy { + + private final InternalOptions options; + + public CheckAbstractClasses(AppView<AppInfoWithLiveness> appView) { + this.options = appView.options(); + } + + @Override + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { + if (options.canUseAbstractMethodOnNonAbstractClass()) { + // We can just make the target class non-abstract if one of the classes in the group + // is non-abstract. + return Lists.<List<DexProgramClass>>newArrayList(group); + } + List<DexProgramClass> abstractClasses = new LinkedList<>(); + List<DexProgramClass> nonAbstractClasses = new LinkedList<>(); + for (DexProgramClass clazz : group) { + if (clazz.isAbstract()) { + abstractClasses.add(clazz); + } else { + nonAbstractClasses.add(clazz); + } + } + List<List<DexProgramClass>> newGroups = new LinkedList<>(); + if (abstractClasses.size() > 1) { + newGroups.add(abstractClasses); + } + if (nonAbstractClasses.size() > 1) { + newGroups.add(nonAbstractClasses); + } + return newGroups; + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAbstractClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAbstractClasses.java deleted file mode 100644 index fdc849d..0000000 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAbstractClasses.java +++ /dev/null
@@ -1,18 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.horizontalclassmerging.policies; - -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -import com.google.common.collect.Iterables; - -public class NoAbstractClasses extends SingleClassPolicy { - @Override - public boolean canMerge(DexProgramClass program) { - return !program.isAbstract() - && !Iterables.any(program.virtualMethods(), DexEncodedMethod::isAbstract); - } -}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFields.java deleted file mode 100644 index 326e94a..0000000 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFields.java +++ /dev/null
@@ -1,15 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.horizontalclassmerging.policies; - -import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; - -public class NoInstanceFields extends SingleClassPolicy { - @Override - public boolean canMerge(DexProgramClass program) { - return !program.hasInstanceFields(); - } -}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinLambdas.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinLambdas.java new file mode 100644 index 0000000..a137e88 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinLambdas.java
@@ -0,0 +1,33 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging.policies; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; +import com.android.tools.r8.shaking.AppInfoWithLiveness; + +public class NoKotlinLambdas extends SingleClassPolicy { + private final AppView<AppInfoWithLiveness> appView; + + public NoKotlinLambdas(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + } + + @Override + public boolean shouldSkipPolicy() { + return appView.options().enableHorizontalClassMergingOfKotlinLambdas; + } + + @Override + public boolean canMerge(DexProgramClass program) { + if (program.getKotlinInfo().isNoKotlinInformation() + || !program.getKotlinInfo().isSyntheticClass()) { + return true; + } + + return !program.getKotlinInfo().asSyntheticClass().isLambda(); + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java index 474fcea..245b391 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoOverlappingConstructors.java
@@ -7,7 +7,6 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; -import com.android.tools.r8.graph.DexType; import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy; import java.util.ArrayList; import java.util.Collection; @@ -35,8 +34,7 @@ private Set<DexProgramClass> sortedClassSet(Collection<DexProgramClass> classes) { Set<DexProgramClass> set = - new TreeSet<DexProgramClass>( - Comparator.comparing(DexProgramClass::getType, DexType::slowCompareTo)); + new TreeSet<DexProgramClass>(Comparator.comparing(DexProgramClass::getType)); set.addAll(classes); return set; }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java index 5b11ff5..bfe0f2a 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -15,19 +15,19 @@ public class NotMatchedByNoHorizontalClassMerging extends SingleClassPolicy { + private final AppView<AppInfoWithLiveness> appView; private final Set<DexType> deadEnumLiteMaps; - private final Set<DexType> neverMergeClassHorizontally; public NotMatchedByNoHorizontalClassMerging(AppView<AppInfoWithLiveness> appView) { - deadEnumLiteMaps = + this.appView = appView; + this.deadEnumLiteMaps = appView.withProtoEnumShrinker( EnumLiteProtoShrinker::getDeadEnumLiteMaps, Collections.emptySet()); - neverMergeClassHorizontally = appView.appInfo().getNoHorizontalClassMergingSet(); } @Override public boolean canMerge(DexProgramClass program) { return !deadEnumLiteMaps.contains(program.getType()) - && !neverMergeClassHorizontally.contains(program.getType()); + && !appView.appInfo().isNoHorizontalClassMergingOfType(program.getType()); } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java index f8f0a3f..783ede5 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java
@@ -6,48 +6,40 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy; +import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy; +import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex.MainDexClassification; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.MainDexClasses; import com.android.tools.r8.shaking.MainDexTracingResult; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -public class PreventMergeIntoMainDex extends MultiClassPolicy { +public class PreventMergeIntoMainDex extends MultiClassSameReferencePolicy<MainDexClassification> { private final MainDexClasses mainDexClasses; private final MainDexTracingResult mainDexTracingResult; + enum MainDexClassification { + MAIN_DEX_LIST, + MAIN_DEX_ROOT, + MAIN_DEX_DEPENDENCY, + NOT_IN_MAIN_DEX + } + public PreventMergeIntoMainDex( AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) { this.mainDexClasses = appView.appInfo().getMainDexClasses(); this.mainDexTracingResult = mainDexTracingResult; } - public boolean isMainDexClass(DexProgramClass clazz) { - return mainDexClasses.contains(clazz) || mainDexTracingResult.contains(clazz); - } - @Override - public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { - List<DexProgramClass> mainDexMembers = new LinkedList<>(); - Iterator<DexProgramClass> iterator = group.iterator(); - while (iterator.hasNext()) { - DexProgramClass clazz = iterator.next(); - if (isMainDexClass(clazz)) { - iterator.remove(); - mainDexMembers.add(clazz); - } + public MainDexClassification getMergeKey(DexProgramClass clazz) { + if (mainDexClasses.contains(clazz)) { + return MainDexClassification.MAIN_DEX_LIST; } - - Collection<List<DexProgramClass>> newGroups = new LinkedList<>(); - if (!isTrivial(mainDexMembers)) { - newGroups.add(mainDexMembers); + if (mainDexTracingResult.isRoot(clazz)) { + return MainDexClassification.MAIN_DEX_ROOT; } - if (!isTrivial(group)) { - newGroups.add(group); + if (mainDexTracingResult.isDependency(clazz)) { + return MainDexClassification.MAIN_DEX_DEPENDENCY; } - return newGroups; + return MainDexClassification.NOT_IN_MAIN_DEX; } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFields.java new file mode 100644 index 0000000..63134de --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFields.java
@@ -0,0 +1,17 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.horizontalclassmerging.policies; + +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.FieldMultiset; +import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy; + +public class SameFields extends MultiClassSameReferencePolicy<FieldMultiset> { + + @Override + public FieldMultiset getMergeKey(DexProgramClass clazz) { + return new FieldMultiset(clazz); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java index 29d38c6..6e46731 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -328,12 +328,7 @@ } if (state.hasTrackedValueEscaped()) { DexType holder = staticPut.getField().holder; - if (holder.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of the current context are guaranteed to be - // initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet())) { + if (holder.classInitializationMayHaveSideEffectsInContext(appView, context)) { return true; } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java index edd1b91..eeeea70 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -134,7 +134,7 @@ Set<DexType> interfaces = getInterfaces(); if (interfaces != null) { List<DexType> sortedInterfaces = new ArrayList<>(interfaces); - sortedInterfaces.sort(DexType::slowCompareTo); + sortedInterfaces.sort(DexType::compareTo); builder.append( sortedInterfaces.stream().map(DexType::toString).collect(Collectors.joining(", "))); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java index 17b49b5..3e0f741 100644 --- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java +++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -38,6 +38,10 @@ this.type = type; } + public static Builder builder() { + return new Builder(); + } + @Override public int opcode() { return Opcodes.CHECK_CAST; @@ -230,4 +234,30 @@ public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) { return false; } + + public static class Builder extends BuilderBase<Builder, CheckCast> { + + private DexType castType; + private Value object; + + public Builder setCastType(DexType castType) { + this.castType = castType; + return this; + } + + public Builder setObject(Value object) { + this.object = object; + return this; + } + + @Override + public CheckCast build() { + return amend(new CheckCast(outValue, object, castType)); + } + + @Override + public Builder self() { + return this; + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java index fde9019..7ec3198 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -20,7 +20,6 @@ import com.android.tools.r8.ir.analysis.value.SingleFieldValue; import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.Sets; import java.util.Collections; import java.util.List; @@ -109,11 +108,7 @@ } } // May trigger <clinit> that may have side effects. - if (field.holder.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet())) { + if (field.holder.classInitializationMayHaveSideEffectsInContext(appView, context)) { return true; } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java index c878d9a..aceeab3 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java +++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -22,7 +22,6 @@ import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.Sets; public class InitClass extends Instruction { @@ -111,11 +110,7 @@ .isPossiblyFalse()) { return true; } - if (clazz.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet())) { + if (clazz.classInitializationMayHaveSideEffectsInContext(appView, context)) { return true; } return false; @@ -132,11 +127,7 @@ if (appView.enableWholeProgramOptimizations()) { // In R8, check if the class initialization of `clazz` or any of its ancestor types may have // side effects. - return clazz.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet()); + return clazz.classInitializationMayHaveSideEffectsInContext(appView, context); } else { // In D8, this instruction may trigger class initialization if `clazz` is different from the // current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java index 9bae360..3139d20 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -28,7 +28,6 @@ import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; import java.util.Collections; import java.util.List; @@ -209,11 +208,11 @@ return true; } - DexEncodedMethod singleTarget = resolutionResult.getSingleTarget(); + DexClassAndMethod singleTarget = resolutionResult.getResolutionPair(); assert singleTarget != null; // Verify that the target method is static and accessible. - if (!singleTarget.isStatic() + if (!singleTarget.getDefinition().isStatic() || resolutionResult.isAccessibleFrom(context, appInfoWithLiveness).isPossiblyFalse()) { return true; } @@ -223,7 +222,7 @@ return false; } - if (singleTarget.getOptimizationInfo().mayHaveSideEffects()) { + if (singleTarget.getDefinition().getOptimizationInfo().mayHaveSideEffects()) { return true; } @@ -232,13 +231,8 @@ } return singleTarget - .holder() - .classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized - // already. - type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type), - Sets.newIdentityHashSet()); + .getHolder() + .classInitializationMayHaveSideEffectsInContext(appView, context); } public static class Builder extends BuilderBase<Builder, InvokeStatic> {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java index 704de30..39f2aee 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -26,7 +26,6 @@ import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.Sets; public class NewInstance extends Instruction { @@ -176,11 +175,7 @@ } // Verify that the new-instance instruction won't lead to class initialization. - if (definition.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appViewWithLiveness.appInfo().isSubtype(context.getHolderType(), type), - Sets.newIdentityHashSet())) { + if (definition.classInitializationMayHaveSideEffectsInContext(appViewWithLiveness, context)) { return true; } @@ -213,11 +208,7 @@ if (appView.enableWholeProgramOptimizations()) { // In R8, check if the class initialization of the holder or any of its ancestor types may // have side effects. - return clazz.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet()); + return clazz.classInitializationMayHaveSideEffectsInContext(appView, context); } else { // In D8, this instruction may trigger class initialization if the holder of the field is // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java index 6cc4f93..ab28d55 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Position.java +++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -134,9 +134,9 @@ return 0; } return Comparator.comparingInt((Position p) -> p.line) - .thenComparing(p -> p.file, Comparator.nullsFirst(DexString::slowCompareTo)) + .thenComparing(p -> p.file, Comparator.nullsFirst(DexString::compareTo)) .thenComparing(p -> p.synthetic) - .thenComparing(p -> p.method, DexMethod::slowCompareTo) + .thenComparing(p -> p.method) .thenComparing(p -> p.callerPosition, Comparator.nullsFirst(Position::compareTo)) .compare(this, o); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java index 8a3f867..70f0502 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -29,7 +29,6 @@ import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.Sets; import java.util.Set; public class StaticGet extends FieldInstruction implements StaticFieldInstruction { @@ -237,11 +236,7 @@ if (appView.enableWholeProgramOptimizations()) { // In R8, check if the class initialization of the holder or any of its ancestor types may // have side effects. - return holder.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet()); + return holder.classInitializationMayHaveSideEffectsInContext(appView, context); } else { // In D8, this instruction may trigger class initialization if the holder of the field is // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java index 9df14cc..82d67b0 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -29,7 +29,6 @@ import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.ProguardMemberRule; -import com.google.common.collect.Sets; public class StaticPut extends FieldInstruction implements StaticFieldInstruction { @@ -232,11 +231,7 @@ if (appView.enableWholeProgramOptimizations()) { // In R8, check if the class initialization of the holder or any of its ancestor types may // have side effects. - return holder.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized already. - type -> appView.isSubtype(context.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet()); + return holder.classInitializationMayHaveSideEffectsInContext(appView, context); } else { // In D8, this instruction may trigger class initialization if the holder of the field is // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java index 2084df9..66d7fa4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -203,9 +203,7 @@ @Override public int compareTo(Node other) { - return getProgramMethod() - .getReference() - .slowCompareTo(other.getProgramMethod().getReference()); + return getProgramMethod().getReference().compareTo(other.getProgramMethod().getReference()); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 8ba06e0..ec95001 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1206,7 +1206,7 @@ assert !method.isProcessed() || !isDebugMode; assert !method.isProcessed() || !appView.enableWholeProgramOptimizations() - || !appView.appInfo().withLiveness().neverReprocess.contains(method.method); + || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.method); if (lambdaMerger != null) { timing.begin("Merge lambdas"); @@ -1769,7 +1769,7 @@ DexString highestSortingReferencedString = method.getCode().asDexCode().highestSortingString; if (highestSortingReferencedString != null) { if (highestSortingString == null - || highestSortingReferencedString.slowCompareTo(highestSortingString) > 0) { + || highestSortingReferencedString.compareTo(highestSortingString) > 0) { highestSortingString = highestSortingReferencedString; } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index e1c9063..20cc2c6 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -379,21 +379,36 @@ { InstanceGet instanceGet = current.asInstanceGet(); DexField field = instanceGet.getField(); - DexField actualField = rewriteFieldReference(field, method, graphLens); + FieldLookupResult lookup = graphLens.lookupFieldResult(field); + DexField rewrittenField = rewriteFieldReference(lookup, method); DexMethod replacementMethod = - graphLens.lookupGetFieldForMethod(actualField, method.getReference()); + graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference()); + Value newOutValue = null; if (replacementMethod != null) { - Value newOutValue = makeOutValue(current, code); + newOutValue = makeOutValue(instanceGet, code); iterator.replaceCurrentInstruction( - new InvokeStatic(replacementMethod, newOutValue, current.inValues())); - if (newOutValue != null && newOutValue.getType() != current.getOutType()) { - affectedPhis.addAll(current.outValue().uniquePhiUsers()); - } - } else if (actualField != field) { - Value newOutValue = makeOutValue(instanceGet, code); + new InvokeStatic(replacementMethod, newOutValue, instanceGet.inValues())); + } else if (rewrittenField != field) { + newOutValue = makeOutValue(instanceGet, code); iterator.replaceCurrentInstruction( - new InstanceGet(newOutValue, instanceGet.object(), actualField)); - if (newOutValue != null && newOutValue.getType() != current.getOutType()) { + new InstanceGet(newOutValue, instanceGet.object(), rewrittenField)); + } + if (newOutValue != null) { + if (lookup.hasCastType() && newOutValue.hasNonDebugUsers()) { + TypeElement castType = + TypeElement.fromDexType( + lookup.getCastType(), newOutValue.getType().nullability(), appView); + CheckCast checkCast = + CheckCast.builder() + .setCastType(lookup.getCastType()) + .setFreshOutValue(code, castType) + .setObject(newOutValue) + .setPosition(instanceGet) + .build(); + iterator.add(checkCast); + newOutValue.replaceUsers(checkCast.outValue()); + affectedPhis.addAll(checkCast.outValue().uniquePhiUsers()); + } else if (newOutValue.getType() != instanceGet.getOutType()) { affectedPhis.addAll(newOutValue.uniquePhiUsers()); } } @@ -404,19 +419,20 @@ { InstancePut instancePut = current.asInstancePut(); DexField field = instancePut.getField(); - DexField actualField = rewriteFieldReference(field, method, graphLens); + FieldLookupResult lookup = graphLens.lookupFieldResult(field); + DexField rewrittenField = rewriteFieldReference(lookup, method); DexMethod replacementMethod = - graphLens.lookupPutFieldForMethod(actualField, method.getReference()); + graphLens.lookupPutFieldForMethod(rewrittenField, method.getReference()); if (replacementMethod != null) { iterator.replaceCurrentInstruction( - new InvokeStatic(replacementMethod, null, current.inValues())); - } else if (actualField != field) { + new InvokeStatic(replacementMethod, null, instancePut.inValues())); + } else if (rewrittenField != field) { Value rewrittenValue = rewriteValueIfDefault( - code, iterator, field.type, actualField.type, instancePut.value()); + code, iterator, field.type, rewrittenField.type, instancePut.value()); InstancePut newInstancePut = InstancePut.createPotentiallyInvalid( - actualField, instancePut.object(), rewrittenValue); + rewrittenField, instancePut.object(), rewrittenValue); iterator.replaceCurrentInstruction(newInstancePut); } } @@ -426,20 +442,35 @@ { StaticGet staticGet = current.asStaticGet(); DexField field = staticGet.getField(); - DexField actualField = rewriteFieldReference(field, method, graphLens); + FieldLookupResult lookup = graphLens.lookupFieldResult(field); + DexField rewrittenField = rewriteFieldReference(lookup, method); DexMethod replacementMethod = - graphLens.lookupGetFieldForMethod(actualField, method.getReference()); + graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference()); + Value newOutValue = null; if (replacementMethod != null) { - Value newOutValue = makeOutValue(current, code); + newOutValue = makeOutValue(staticGet, code); iterator.replaceCurrentInstruction( - new InvokeStatic(replacementMethod, newOutValue, current.inValues())); - if (newOutValue != null && newOutValue.getType() != current.getOutType()) { - affectedPhis.addAll(newOutValue.uniquePhiUsers()); - } - } else if (actualField != field) { - Value newOutValue = makeOutValue(staticGet, code); - iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField)); - if (newOutValue != null && newOutValue.getType() != current.getOutType()) { + new InvokeStatic(replacementMethod, newOutValue, staticGet.inValues())); + } else if (rewrittenField != field) { + newOutValue = makeOutValue(staticGet, code); + iterator.replaceCurrentInstruction(new StaticGet(newOutValue, rewrittenField)); + } + if (newOutValue != null) { + if (lookup.hasCastType() && newOutValue.hasNonDebugUsers()) { + TypeElement castType = + TypeElement.fromDexType( + lookup.getCastType(), newOutValue.getType().nullability(), appView); + CheckCast checkCast = + CheckCast.builder() + .setCastType(lookup.getCastType()) + .setFreshOutValue(code, castType) + .setObject(newOutValue) + .setPosition(staticGet) + .build(); + iterator.add(checkCast); + newOutValue.replaceUsers(checkCast.outValue()); + affectedPhis.addAll(checkCast.outValue().uniquePhiUsers()); + } else if (newOutValue.getType() != staticGet.getOutType()) { affectedPhis.addAll(newOutValue.uniquePhiUsers()); } } @@ -450,18 +481,19 @@ { StaticPut staticPut = current.asStaticPut(); DexField field = staticPut.getField(); - DexField actualField = rewriteFieldReference(field, method, graphLens); + FieldLookupResult lookup = graphLens.lookupFieldResult(field); + DexField actualField = rewriteFieldReference(lookup, method); DexMethod replacementMethod = graphLens.lookupPutFieldForMethod(actualField, method.getReference()); if (replacementMethod != null) { iterator.replaceCurrentInstruction( - new InvokeStatic(replacementMethod, current.outValue(), current.inValues())); + new InvokeStatic( + replacementMethod, staticPut.outValue(), staticPut.inValues())); } else if (actualField != field) { Value rewrittenValue = rewriteValueIfDefault( code, iterator, field.type, actualField.type, staticPut.value()); - StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField); - iterator.replaceCurrentInstruction(newStaticPut); + iterator.replaceCurrentInstruction(new StaticPut(rewrittenValue, actualField)); } } break; @@ -586,16 +618,15 @@ assert code.hasNoVerticallyMergedClasses(appView); } - private DexField rewriteFieldReference( - DexField reference, ProgramMethod context, GraphLens graphLens) { - FieldLookupResult lookup = graphLens.lookupFieldResult(reference); + private DexField rewriteFieldReference(FieldLookupResult lookup, ProgramMethod context) { if (lookup.hasReboundReference()) { DexClass holder = appView.definitionFor(lookup.getReboundReference().getHolderType()); DexEncodedField definition = lookup.getReboundReference().lookupOnClass(holder); if (definition != null) { DexClassAndField field = DexClassAndField.create(holder, definition); if (AccessControl.isMemberAccessible(field, holder, context, appView).isTrue()) { - return MemberRebindingAnalysis.validMemberRebindingTargetFor(appView, field, reference); + return MemberRebindingAnalysis.validMemberRebindingTargetFor( + appView, field, lookup.getReference()); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java index 4b44ada..ae5e3f9 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -33,20 +33,28 @@ public class LensCodeRewriterUtils { + private final AppView<?> appView; private final DexDefinitionSupplier definitions; private final GraphLens graphLens; private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>(); public LensCodeRewriterUtils(AppView<?> appView) { - this(appView, appView.graphLens()); + this.appView = appView; + this.definitions = appView; + this.graphLens = null; } public LensCodeRewriterUtils(DexDefinitionSupplier definitions, GraphLens graphLens) { + this.appView = null; this.definitions = definitions; this.graphLens = graphLens; } + private GraphLens graphLens() { + return appView != null ? appView.graphLens() : graphLens; + } + public DexCallSite rewriteCallSite(DexCallSite callSite, ProgramMethod context) { DexItemFactory dexItemFactory = definitions.dexItemFactory(); DexProto newMethodProto = rewriteProto(callSite.methodProto); @@ -74,7 +82,7 @@ DexMethod invokedMethod = methodHandle.asMethod(); MethodHandleType oldType = methodHandle.type; MethodLookupResult lensLookup = - graphLens.lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType()); + graphLens().lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType()); DexMethod rewrittenTarget = lensLookup.getReference(); DexMethod actualTarget; MethodHandleType newType; @@ -115,7 +123,7 @@ } } else { DexField field = methodHandle.asField(); - DexField actualField = graphLens.lookupField(field); + DexField actualField = graphLens().lookupField(field); if (actualField != field) { return new DexMethodHandle(methodHandle.type, actualField, methodHandle.isInterface); } @@ -158,7 +166,7 @@ return rewriteDexMethodType(value.asDexValueMethodType()); case TYPE: DexType oldType = value.asDexValueType().value; - DexType newType = graphLens.lookupType(oldType); + DexType newType = graphLens().lookupType(oldType); return newType != oldType ? new DexValueType(newType) : value; default: return value; @@ -168,7 +176,7 @@ public DexProto rewriteProto(DexProto proto) { return definitions .dexItemFactory() - .applyClassMappingToProto(proto, graphLens::lookupType, protoFixupCache); + .applyClassMappingToProto(proto, graphLens()::lookupType, protoFixupCache); } private DexValueMethodHandle rewriteDexValueMethodHandle(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java index 48aaf3a..3d86936 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.GraphLens; import com.android.tools.r8.graph.ProgramMethod; @@ -28,6 +29,7 @@ import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -122,20 +124,17 @@ PostMethodProcessor build( AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing) throws ExecutionException { - if (!appView.appInfo().reprocess.isEmpty()) { + Set<DexMethod> reprocessMethods = appView.appInfo().getReprocessMethods(); + if (!reprocessMethods.isEmpty()) { ProgramMethodSet set = ProgramMethodSet.create(); - appView - .appInfo() - .reprocess - .forEach( - reference -> { - DexProgramClass clazz = - asProgramClassOrNull(appView.definitionForHolder(reference)); - DexEncodedMethod definition = reference.lookupOnClass(clazz); - if (definition != null) { - set.createAndAdd(clazz, definition); - } - }); + reprocessMethods.forEach( + reference -> { + DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(reference)); + DexEncodedMethod definition = reference.lookupOnClass(clazz); + if (definition != null) { + set.createAndAdd(clazz, definition); + } + }); put(set); } if (methodsToReprocess.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java index 758830d..0884ab5 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -423,12 +423,32 @@ } // We need to introduce them in deterministic order for deterministic compilation. ArrayList<DexType> sortedEmulatedInterfaces = new ArrayList<>(emulatedInterfaces); - Collections.sort(sortedEmulatedInterfaces, DexType::slowCompareTo); + Collections.sort(sortedEmulatedInterfaces); List<GenericSignature.ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>(); for (DexType extraInterface : sortedEmulatedInterfaces) { extraInterfaceSignatures.add( new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(extraInterface))); } + // The emulated interface might already be implemented if the input class has gone through + // library desugaring already. + clazz + .getInterfaces() + .forEach( + iface -> { + for (int i = 0; i < extraInterfaceSignatures.size(); i++) { + if (extraInterfaceSignatures.get(i).type() == iface) { + if (!appView.options().desugarSpecificOptions().allowDesugaredInput) { + throw new CompilationError( + "Code has already been library desugared. Interface " + + iface.getDescriptor() + + " is already implemented by " + + clazz.getType().getDescriptor()); + } + extraInterfaceSignatures.remove(i); + break; + } + } + }); clazz.asProgramClass().addExtraInterfaces(extraInterfaceSignatures); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java index 904ca7e..214c17a 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -180,7 +180,7 @@ .computeIfAbsent( newClass, ignore -> - new TreeSet<>((x, y) -> x.getReference().slowCompareTo(y.getReference()))) + new TreeSet<>((x, y) -> x.getReference().compareTo(y.getReference()))) .add( new DexEncodedMethod( retargetMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java index d6f6264..ddcb3c7 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -588,7 +588,7 @@ Map<DexType, List<DexType>> emulatedInterfacesHierarchy = new IdentityHashMap<>(); Set<DexType> processed = Sets.newIdentityHashSet(); ArrayList<DexType> emulatedInterfacesSorted = new ArrayList<>(emulatedInterfaces.keySet()); - emulatedInterfacesSorted.sort(DexType::slowCompareTo); + emulatedInterfacesSorted.sort(DexType::compareTo); for (DexType interfaceType : emulatedInterfacesSorted) { processEmulatedInterfaceHierarchy(interfaceType, processed, emulatedInterfacesHierarchy); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java index fbeb14a..02388be 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -33,7 +33,7 @@ import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DexTypeList; -import com.android.tools.r8.graph.DexValue.DexValueNull; +import com.android.tools.r8.graph.DexValue.DexValueInt; import com.android.tools.r8.graph.FieldAccessFlags; import com.android.tools.r8.graph.GenericSignature.ClassSignature; import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; @@ -179,13 +179,13 @@ dexItemFactory.createField(iface.getType(), dexItemFactory.intType, "$desugar$clinit"); DexField clinitFieldReference = dexItemFactory.createFreshFieldName( - clinitFieldTemplateReference, candidate -> iface.lookupField(candidate) != null); + clinitFieldTemplateReference, candidate -> iface.lookupField(candidate) == null); return new DexEncodedField( clinitFieldReference, FieldAccessFlags.builder().setPackagePrivate().setStatic().setSynthetic().build(), FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), - DexValueNull.NULL); + DexValueInt.DEFAULT); } private DexEncodedMethod createCompanionClassInitializer(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java index 11cff1a..9688ae8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -111,7 +111,7 @@ throws ExecutionException { SortedProgramMethodSet nonDexAccessibilityBridges = SortedProgramMethodSet.create(); List<LambdaClass> sortedLambdaClasses = new ArrayList<>(lambdaClasses); - sortedLambdaClasses.sort((x, y) -> x.type.slowCompareTo(y.type)); + sortedLambdaClasses.sort((x, y) -> x.type.compareTo(y.type)); for (LambdaClass lambdaClass : sortedLambdaClasses) { // This call may cause originalMethodSignatures to be updated. ProgramMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index 0fc9b80..8a8d2d4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -380,7 +380,7 @@ // // For simplicity, we are conservative and consider all interfaces, not only the ones with // default methods. - if (!target.getHolder().classInitializationMayHaveSideEffects(appView)) { + if (!target.getHolder().classInitializationMayHaveSideEffectsInContext(appView, context)) { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index 945d9fe..3abbf9b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -707,7 +707,10 @@ assert !monitorEnterBlock.hasCatchHandlers(); InstructionListIterator monitorEnterBlockIterator = monitorEnterBlock.listIterator(code); - monitorEnterBlockIterator.setInsertionPosition(Position.syntheticNone()); + // MonitorEnter will only throw an NPE if the lock is null and that can only happen if the + // receiver was null. To preserve NPE's at call-sites for synchronized methods we therefore + // put in the invoke-position. + monitorEnterBlockIterator.setInsertionPosition(invoke.getPosition()); // If this is a static method, then the class object will act as the lock, so we load it // using a const-class instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java index ff7668a..1941743 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.ir.optimize.Inliner.Constraint; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.VerticalClassMerger.SingleTypeMapperGraphLens; import com.android.tools.r8.utils.TriFunction; // Computes the inlining constraint for a given instruction. @@ -62,7 +63,7 @@ } private boolean isVerticalClassMerging() { - return !graphLens.isIdentityLens(); + return graphLens instanceof SingleTypeMapperGraphLens; } public ConstraintWithTarget forAlwaysMaterializingUser() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java index 202caa1..91f7642 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -456,14 +456,7 @@ appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) { return; } - boolean classInitializationMayHaveSideEffects = - holder.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized - // already. - type -> appView.appInfo().isSubtype(context.getHolderType(), type), - Sets.newIdentityHashSet()); - if (!classInitializationMayHaveSideEffects) { + if (!holder.classInitializationMayHaveSideEffectsInContext(appView, context)) { iterator.removeOrReplaceByDebugLocalRead(); return; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java index 334238d..508e0f4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -337,7 +337,7 @@ return super.compareTo(other); } NewInstanceOutlineInstruction o = (NewInstanceOutlineInstruction) other; - return clazz.slowCompareTo(o.clazz); + return clazz.compareTo(o.clazz); } @Override @@ -435,7 +435,7 @@ return super.compareTo(other); } InvokeOutlineInstruction o = (InvokeOutlineInstruction) other; - int result = method.slowCompareTo(o.method); + int result = method.compareTo(o.method); if (result != 0) { return result; } @@ -448,7 +448,7 @@ return result; } if (proto != null) { - result = proto.slowCompareTo(o.proto); + result = proto.compareTo(o.proto); if (result != 0) { return result; } @@ -630,7 +630,7 @@ } // First compare the proto. int result; - result = buildProto().slowCompareTo(other.buildProto()); + result = buildProto().compareTo(other.buildProto()); if (result != 0) { assert !equals(other); return result; @@ -1262,8 +1262,9 @@ private boolean removeMethodFromOutlineList(Outline outline) { synchronized (outlineSites) { assert ListUtils.removeFirstMatch( - outlineSites.get(outline), - element -> element.getDefinition() == method.getDefinition()); + outlineSites.get(outline), + element -> element.getDefinition() == method.getDefinition()) + .isPresent(); } return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java index b2ab54f..868bcc5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; +import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.value.SingleValue; import com.android.tools.r8.ir.code.BasicBlock; @@ -282,12 +283,7 @@ killAllNonFinalActiveFields(); } else if (instruction.isNewInstance()) { NewInstance newInstance = instruction.asNewInstance(); - if (newInstance.clazz.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of `context` are guaranteed to be initialized - // already. - type -> appView.isSubtype(method.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet())) { + if (newInstance.clazz.classInitializationMayHaveSideEffectsInContext(appView, method)) { killAllNonFinalActiveFields(); } } else { @@ -345,8 +341,11 @@ private boolean verifyWasInstanceInitializer() { VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses(); + HorizontallyMergedClasses horizontallyMergedClasses = appView.horizontallyMergedClasses(); assert verticallyMergedClasses != null; - assert verticallyMergedClasses.isTarget(method.getHolderType()); + assert horizontallyMergedClasses != null; + assert verticallyMergedClasses.isTarget(method.getHolderType()) + || horizontallyMergedClasses.isMergeTarget(method.getHolderType()); assert appView .dexItemFactory() .isConstructor(appView.graphLens().getOriginalMethodSignature(method.getReference()));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java index 96e5e9c..c11c8bf 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -305,7 +305,7 @@ private RewrittenPrototypeDescription getPrototypeChanges( DexEncodedMethod encodedMethod, Strategy strategy) { if (ArgumentRemovalUtils.isPinned(encodedMethod, appView) - || appView.appInfo().keepConstantArguments.contains(encodedMethod.method)) { + || appView.appInfo().isKeepConstantArgumentsMethod(encodedMethod.method)) { return RewrittenPrototypeDescription.none(); } return RewrittenPrototypeDescription.createForUninstantiatedTypes(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java index 509daa1..6270034 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -284,7 +284,7 @@ private ArgumentInfoCollection collectUnusedArguments( DexEncodedMethod method, MemberPool<DexMethod> methodPool) { if (ArgumentRemovalUtils.isPinned(method, appView) - || appView.appInfo().keepUnusedArguments.contains(method.method)) { + || appView.appInfo().isKeepUnusedArgumentsMethod(method.method)) { return null; } // Only process classfile code objects.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index dab89c6..5d79d0e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -303,7 +303,7 @@ } // Check for static initializers in this class or any of interfaces it implements. - if (clazz.initializationOfParentTypesMayHaveSideEffects(appView)) { + if (clazz.classInitializationMayHaveSideEffects(appView)) { return EligibilityStatus.NOT_ELIGIBLE; } return EligibilityStatus.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 6b0b61d..550a0e1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult; import com.android.tools.r8.graph.LibraryMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.ResolutionResult; @@ -165,11 +166,10 @@ if (eligibleClass == null) { return EligibilityStatus.NOT_ELIGIBLE; } - if (eligibleClass.classInitializationMayHaveSideEffects( - appView, - // Types that are a super type of the current context are guaranteed to be initialized. - type -> appView.isSubtype(method.getHolderType(), type).isTrue(), - Sets.newIdentityHashSet())) { + if (method.getHolder() == eligibleClass) { + return EligibilityStatus.NOT_ELIGIBLE; + } + if (eligibleClass.classInitializationMayHaveSideEffectsInContext(appView, method)) { return EligibilityStatus.NOT_ELIGIBLE; } return EligibilityStatus.ELIGIBLE; @@ -178,10 +178,18 @@ assert root.isStaticGet(); StaticGet staticGet = root.asStaticGet(); + SuccessfulFieldResolutionResult fieldResolutionResult = + appView.appInfo().resolveField(staticGet.getField()).asSuccessfulResolution(); + if (fieldResolutionResult == null) { + return EligibilityStatus.NOT_ELIGIBLE; + } + if (method.getHolder() == fieldResolutionResult.getResolvedHolder()) { + return EligibilityStatus.NOT_ELIGIBLE; + } if (staticGet.instructionMayHaveSideEffects(appView, method)) { return EligibilityStatus.NOT_ELIGIBLE; } - DexEncodedField field = appView.appInfo().resolveField(staticGet.getField()).getResolvedField(); + DexEncodedField field = fieldResolutionResult.getResolvedField(); FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo(); ClassTypeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType(); if (dynamicLowerBoundType == null
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java index 27c125a..bfb520d 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -584,7 +584,7 @@ } // We make the order deterministic. for (List<T> value : encodedMembersMap.values()) { - value.sort((m1, m2) -> m1.getReference().slowCompareTo(m2.getReference())); + value.sort((m1, m2) -> m1.getReference().compareTo(m2.getReference())); } return encodedMembersMap; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index ebe815d..f1c2bdd 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -13,10 +13,10 @@ import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo; import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap; +import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; @@ -38,6 +38,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.SwitchMapCollector; +import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ArrayUtils; import com.google.common.collect.Sets; @@ -100,7 +101,8 @@ continue; } - AbstractValue abstractValue = definition.getOptimizationInfo().getAbstractValue(); + FieldOptimizationInfo optimizationInfo = definition.getOptimizationInfo(); + AbstractValue abstractValue = optimizationInfo.getAbstractValue(); if (!abstractValue.isSingleFieldValue()) { continue; } @@ -158,15 +160,18 @@ continue; } - EnumValueInfo valueInfo = appView.appInfo().withLiveness().getEnumValueInfo(field); - if (valueInfo == null) { + // Since the value is a single field value, the type should be exact. + assert abstractValue.isSingleFieldValue(); + ClassTypeElement enumFieldType = optimizationInfo.getExactClassType(appView); + if (enumFieldType == null) { + assert false : "Expected to have an exact dynamic type for enum instance"; continue; } DexEncodedMethod singleTarget = appView .appInfo() - .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type) + .resolveMethodOnClass(factory.objectMembers.toString, enumFieldType.getClassType()) .getSingleTarget(); if (singleTarget != null && singleTarget.method != factory.enumMembers.toString) { continue; @@ -180,22 +185,6 @@ nameValue.getDexString(), ThrowingInfo.defaultForConstString(appView.options()))); newValue.addAffectedValuesTo(affectedValues); - } else if (current.isArrayLength()) { - // Rewrites MyEnum.values().length to a constant int. - Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition; - if (arrayDefinition != null && arrayDefinition.isInvokeStatic()) { - DexMethod invokedMethod = arrayDefinition.asInvokeStatic().getInvokedMethod(); - DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder); - if (enumClass != null - && enumClass.isEnum() - && factory.enumMembers.isValuesMethod(invokedMethod, enumClass)) { - EnumValueInfoMap enumValueInfoMap = - appView.appInfo().withLiveness().getEnumValueInfoMap(invokedMethod.holder); - if (enumValueInfoMap != null) { - iterator.replaceCurrentInstructionWithConstInt(code, enumValueInfoMap.size()); - } - } - } } } if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java index c203772..a0ea10c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -143,7 +143,7 @@ for (DexProgramClass context : contexts) { if (deterministicContext == null) { deterministicContext = context.type; - } else if (context.type.slowCompareTo(deterministicContext) < 0) { + } else if (context.type.compareTo(deterministicContext) < 0) { deterministicContext = context.type; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java index 0365122..e70a91f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -4,9 +4,13 @@ package com.android.tools.r8.ir.optimize.info; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValue; +import com.android.tools.r8.shaking.AppInfoWithLiveness; public abstract class FieldOptimizationInfo { @@ -26,6 +30,24 @@ public abstract TypeElement getDynamicUpperBoundType(); + public ClassTypeElement getExactClassType(AppView<AppInfoWithLiveness> appView) { + ClassTypeElement dynamicLowerBoundType = getDynamicLowerBoundType(); + TypeElement dynamicUpperBoundType = getDynamicUpperBoundType(); + if (dynamicUpperBoundType == null || !dynamicUpperBoundType.isClassType()) { + return null; + } + DexType upperType = dynamicUpperBoundType.asClassType().getClassType(); + if (dynamicLowerBoundType != null && upperType == dynamicLowerBoundType.getClassType()) { + return dynamicLowerBoundType; + } + DexClass upperClass = appView.definitionFor(upperType); + if (upperClass != null && upperClass.isEffectivelyFinal(appView)) { + assert dynamicLowerBoundType == null; + return ClassTypeElement.create(upperType, dynamicUpperBoundType.nullability(), appView); + } + return null; + } + public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) { TypeElement dynamicUpperBoundType = getDynamicUpperBoundType(); return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java index 775d7e9..9fa412a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -43,8 +43,7 @@ public static class Builder { - TreeMap<DexField, InstanceFieldInitializationInfo> infos = - new TreeMap<>(DexField::slowCompareTo); + TreeMap<DexField, InstanceFieldInitializationInfo> infos = new TreeMap<>(DexField::compareTo); public void recordInitializationInfo( DexEncodedField field, InstanceFieldInitializationInfo info) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java index b3e72bf..81c6a35 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -19,7 +19,7 @@ public static WhyAreYouNotInliningReporter createFor( ProgramMethod callee, AppView<AppInfoWithLiveness> appView, ProgramMethod context) { - if (appView.appInfo().whyAreYouNotInlining.contains(callee.getReference())) { + if (appView.appInfo().isWhyAreYouNotInliningMethod(callee.getReference())) { return new WhyAreYouNotInliningReporterImpl( callee, context, appView.options().testing.whyAreYouNotInliningConsumer); } @@ -28,7 +28,7 @@ public static void handleInvokeWithUnknownTarget( InvokeMethod invoke, AppView<AppInfoWithLiveness> appView, ProgramMethod context) { - if (appView.appInfo().whyAreYouNotInlining.isEmpty()) { + if (appView.appInfo().hasNoWhyAreYouNotInliningMethods()) { return; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java index 8aacbde..89ffafd 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -62,6 +62,7 @@ import com.google.common.collect.Sets; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Comparator; import java.util.Deque; import java.util.IdentityHashMap; import java.util.Iterator; @@ -257,7 +258,7 @@ appView.testing().kotlinLambdaMergerFactoryForClass.apply(cls) != null && KotlinLambdaGroupIdFactory.hasValidAnnotations(kotlin, cls) && !appView.appInfo().getClassToFeatureSplitMap().isInFeature(cls)) - .sorted((a, b) -> a.type.slowCompareTo(b.type)) // Ensure stable ordering. + .sorted(Comparator.comparing(DexClass::getType)) // Ensure stable ordering. .forEachOrdered( lambda -> { try { @@ -268,14 +269,7 @@ group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { - if (error.reportable) { - reporter.info( - new StringDiagnostic( - "Unrecognized Kotlin lambda [" - + lambda.type.toSourceString() - + "]: " - + error.getMessage())); - } + // Intentionally empty. } }); @@ -395,8 +389,7 @@ rewriteLambdaReferences(converter, executorService, appliedGraphLens); this.mode = null; - appView.setHorizontallyMergedLambdaClasses( - new HorizontallyMergedLambdaClasses(lambdas.keySet())); + appView.setHorizontallyMergedLambdaClasses(new HorizontallyMergedLambdaClasses(lambdas)); } private void analyzeLambdaClassesStructure(ExecutorService service) throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java index 0179328..97ef2dc 100644 --- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -46,6 +46,7 @@ import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.PredicateUtils; +import com.android.tools.r8.utils.structural.Ordered; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.Sets; @@ -173,7 +174,7 @@ String sourceDebug = getSourceDebugExtension(clazz.annotations()); writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug); CfVersion version = getClassFileVersion(clazz); - if (version.isGreaterThanOrEqual(CfVersion.V1_8)) { + if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) { // JDK8 and after ignore ACC_SUPER so unset it. clazz.accessFlags.unsetSuper(); } else { @@ -182,7 +183,10 @@ clazz.accessFlags.setSuper(); } } - int access = clazz.accessFlags.getAsCfAccessFlags(); + int access = + options.testing.allowInvalidCfAccessFlags + ? clazz.accessFlags.materialize() + : clazz.accessFlags.getAsCfAccessFlags(); if (clazz.isDeprecated()) { access = AsmUtils.withDeprecated(access); } @@ -227,8 +231,7 @@ } if (options.desugarSpecificOptions().sortMethodsOnCfOutput) { SortedSet<ProgramMethod> programMethodSortedSet = - Sets.newTreeSet( - (a, b) -> a.getDefinition().method.slowCompareTo(b.getDefinition().method)); + Sets.newTreeSet((a, b) -> a.getDefinition().method.compareTo(b.getDefinition().method)); clazz.forEachProgramMethod(programMethodSortedSet::add); programMethodSortedSet.forEach( method -> writeMethod(method, version, rewriter, writer, defaults)); @@ -257,8 +260,9 @@ // In this case bridges have been introduced for the Cf back-end, // which do not have class file version. assert options.testing.enableForceNestBasedAccessDesugaringForTest - || options.isDesugaredLibraryCompilation() - || options.cfToCfDesugar; + || options.isDesugaredLibraryCompilation() + || options.cfToCfDesugar + : "Expected class file version for " + method.method.toSourceString(); // TODO(b/146424042): We may call static methods on interface classes so we have to go for // Java 8. assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(CfVersion.V1_8); @@ -273,10 +277,10 @@ ? clazz.getInitialClassFileVersion() : MIN_VERSION_FOR_COMPILER_GENERATED_CODE; for (DexEncodedMethod method : clazz.directMethods()) { - version = version.max(getClassFileVersion(method)); + version = Ordered.max(version, getClassFileVersion(method)); } for (DexEncodedMethod method : clazz.virtualMethods()) { - version = version.max(getClassFileVersion(method)); + version = Ordered.max(version, getClassFileVersion(method)); } return version; }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java index 5327873..189a6b0 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -58,6 +58,10 @@ appView.options().reporter, onlyProcessLambdas, method -> keepByteCodeFunctions.add(method.method))); + if (onlyProcessLambdas) { + clazz.removeAnnotations( + annotation -> annotation.getAnnotationType() == kotlinMetadataType); + } if (clazz.getEnclosingMethodAttribute() != null && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { localOrAnonymousClasses.add(clazz);
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java index b014c4f..55ed725 100644 --- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -301,7 +301,7 @@ } private Set<DexClass> buildSortedPartition(DexClass src) { - Set<DexClass> partition = new TreeSet<>((x, y) -> x.type.slowCompareTo(y.type)); + Set<DexClass> partition = new TreeSet<>((x, y) -> x.type.compareTo(y.type)); Deque<DexType> worklist = new ArrayDeque<>(); worklist.add(src.type);
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java index e6fcce6..86dac13 100644 --- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -264,7 +264,7 @@ // reservation, we have to prioritize that over the others, otherwise we just propose the // first ordered reserved name since we do not allow overwriting the name. List<DexEncodedMethod> sortedMethods = Lists.newArrayList(methodStates.keySet()); - sortedMethods.sort((x, y) -> x.getReference().slowCompareTo(y.getReference())); + sortedMethods.sort((x, y) -> x.getReference().compareTo(y.getReference())); DexString reservedName = null; for (DexEncodedMethod method : sortedMethods) { for (InterfaceReservationState state : methodStates.get(method)) {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java index b886f2b..9893fb7 100644 --- a/src/main/java/com/android/tools/r8/naming/Minifier.java +++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -47,7 +47,7 @@ assert appView.options().isMinifying(); SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo(); timing.begin("ComputeInterfaces"); - Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type)); + Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.compareTo(b.type)); interfaces.addAll(appView.appInfo().computeReachableInterfaces()); timing.end(); timing.begin("MinifyClasses");
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java index a81ebd7..6d8c889 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -73,7 +73,7 @@ private final SeedMapper seedMapper; private final BiMap<DexType, DexString> mappedNames = HashBiMap.create(); // To keep the order deterministic, we sort the classes by their type, which is a unique key. - private final Set<DexClass> mappedClasses = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type)); + private final Set<DexClass> mappedClasses = new TreeSet<>((a, b) -> a.type.compareTo(b.type)); private final Map<DexReference, MemberNaming> memberNames = Maps.newIdentityHashMap(); private final Map<DexType, DexString> syntheticCompanionClasses = Maps.newIdentityHashMap(); private final Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames = @@ -95,7 +95,7 @@ Set<DexReference> notMappedReferences = new HashSet<>(); timing.begin("MappingInterfaces"); - Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type)); + Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.compareTo(b.type)); Consumer<DexClass> consumer = dexClass -> { if (dexClass.isInterface()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java index 23a9a06..49b46cd 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -221,10 +221,6 @@ skipWhitespace(); // Workaround for proguard map files that contain entries for package-info.java files. assert IdentifierUtils.isDexIdentifierPart('-'); - if (before.endsWith("package-info")) { - skipLine(); - continue; - } if (before.endsWith("-") && acceptString(">")) { // With - as a legal identifier part the grammar is ambiguous, and we treat a->b as a -> b, // and not as a- > b (which would be a parse error).
diff --git a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java index 9b12469..d949ab2 100644 --- a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java +++ b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
@@ -43,7 +43,8 @@ @Override protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) { - assert previous.getReboundReference() == null; + assert !previous.hasCastType(); + assert !previous.hasReboundReference(); return FieldLookupResult.builder(this) .setReference(previous.getReference()) .setReboundReference(getReboundFieldReference(previous.getReference()))
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java index 77301cf..136595a 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -23,9 +23,15 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BiForEachable; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.TriConsumer; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -160,6 +166,14 @@ BiForEachable<DexMethod, ProgramMethodSet> methodsWithContexts, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) { + + Map<DexProgramClass, List<Pair<DexMethod, DexEncodedMethod>>> bridges = new IdentityHashMap<>(); + TriConsumer<DexProgramClass, DexMethod, DexEncodedMethod> addBridge = + (bridgeHolder, method, target) -> + bridges + .computeIfAbsent(bridgeHolder, k -> new ArrayList<>()) + .add(new Pair<>(method, target)); + methodsWithContexts.forEach( (method, contexts) -> { // We can safely ignore array types, as the corresponding methods are defined in a @@ -178,6 +192,7 @@ return; } DexClass targetClass = appView.definitionFor(target.holder()); + DexMethod targetMethod = target.method; if (originalClass.isProgramClass()) { // In Java bytecode, it is only possible to target interface methods that are in one of // the immediate super-interfaces via a super-invocation (see @@ -186,9 +201,9 @@ // bridge method when we are about to rebind to an interface method that is not the // original target. if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) { - target = + targetMethod = insertBridgeForInterfaceMethod( - method, target, originalClass.asProgramClass(), targetClass, lookupTarget); + method, target, originalClass.asProgramClass(), targetClass, addBridge); } // If the target class is not public but the targeted method is, we might run into @@ -196,13 +211,32 @@ final DexEncodedMethod finalTarget = target; if (contexts.stream() .anyMatch(context -> mayNeedBridgeForVisibility(context, finalTarget))) { - target = + targetMethod = insertBridgeForVisibilityIfNeeded( - method, target, originalClass, targetClass, lookupTarget); + method, target, originalClass, targetClass, addBridge); } } lensBuilder.map( - method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType); + method, lens.lookupMethod(validTargetFor(targetMethod, method)), invokeType); + }); + + bridges.forEach( + (bridgeHolder, targets) -> { + // Sorting the list of bridges within a class maintains a deterministic order of entries + // in the method collection. + targets.sort((p1, p2) -> p1.getFirst().compareTo(p2.getFirst())); + for (Pair<DexMethod, DexEncodedMethod> pair : targets) { + DexMethod method = pair.getFirst(); + DexEncodedMethod target = pair.getSecond(); + DexMethod bridgeMethod = + method.withHolder(bridgeHolder.getType(), appView.dexItemFactory()); + if (bridgeHolder.getMethodCollection().getMethod(bridgeMethod) == null) { + DexEncodedMethod bridgeMethodDefinition = + target.toForwardingMethod(bridgeHolder, appView); + bridgeHolder.addMethod(bridgeMethodDefinition); + } + assert lookupTarget.apply(method).method == bridgeMethod; + } }); } @@ -214,12 +248,12 @@ && targetClass.accessFlags.isInterface(); } - private DexEncodedMethod insertBridgeForInterfaceMethod( + private DexMethod insertBridgeForInterfaceMethod( DexMethod method, DexEncodedMethod target, DexProgramClass originalClass, DexClass targetClass, - Function<DexMethod, DexEncodedMethod> lookupTarget) { + TriConsumer<DexProgramClass, DexMethod, DexEncodedMethod> bridges) { // If `targetClass` is a class, then insert the bridge method on the upper-most super class that // implements the interface. Otherwise, if it is an interface, then insert the bridge method // directly on the interface (because that interface must be the immediate super type, assuming @@ -232,10 +266,8 @@ findHolderForInterfaceMethodBridge(originalClass, targetClass.type); assert bridgeHolder != null; assert bridgeHolder != targetClass; - DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appView); - bridgeHolder.addMethod(bridgeMethod); - assert lookupTarget.apply(method) == bridgeMethod; - return bridgeMethod; + bridges.accept(bridgeHolder, method, target); + return target.method.withHolder(bridgeHolder.getType(), appView.dexItemFactory()); } private DexProgramClass findHolderForInterfaceMethodBridge(DexProgramClass clazz, DexType iface) { @@ -267,12 +299,12 @@ && methodVisibility != ConstraintWithTarget.NEVER; } - private DexEncodedMethod insertBridgeForVisibilityIfNeeded( + private DexMethod insertBridgeForVisibilityIfNeeded( DexMethod method, DexEncodedMethod target, DexClass originalClass, DexClass targetClass, - Function<DexMethod, DexEncodedMethod> lookupTarget) { + TriConsumer<DexProgramClass, DexMethod, DexEncodedMethod> bridges) { // If the original class is public and this method is public, it might have been called // from anywhere, so we need a bridge. Likewise, if the original is in a different // package, we might need a bridge, too. @@ -283,12 +315,10 @@ DexProgramClass bridgeHolder = findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor); assert bridgeHolder != null; - DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appView); - bridgeHolder.addMethod(bridgeMethod); - assert lookupTarget.apply(method) == bridgeMethod; - return bridgeMethod; + bridges.accept(bridgeHolder, method, target); + return target.method.withHolder(bridgeHolder.getType(), appView.dexItemFactory()); } - return target; + return target.method; } private DexProgramClass findHolderForVisibilityBridge(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java index 02b56e3..3f77dbb 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -51,7 +51,8 @@ @Override protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) { - assert previous.getReboundReference() == null; + assert !previous.hasCastType(); + assert !previous.hasReboundReference(); return FieldLookupResult.builder(this) .setReference(previous.getReference()) .setReboundReference(getReboundFieldReference(previous.getReference()))
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java index 867ec0b..171dcbe 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -95,7 +95,8 @@ @Override protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) { - assert previous.getReboundReference() == null; + assert !previous.hasCastType(); + assert !previous.hasReboundReference(); return FieldLookupResult.builder(this) .setReference(previous.getReference()) .setReboundReference(getReboundFieldReference(previous.getReference()))
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java index 0d2c4e8..cea7fc2 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -113,7 +113,7 @@ private void processClass(DexProgramClass clazz, SubtypingInfo subtypingInfo) { Set<DexType> subtypes = subtypingInfo.allImmediateSubtypes(clazz.type); - Set<DexProgramClass> subclasses = new TreeSet<>((x, y) -> x.type.slowCompareTo(y.type)); + Set<DexProgramClass> subclasses = new TreeSet<>((x, y) -> x.type.compareTo(y.type)); for (DexType subtype : subtypes) { DexProgramClass subclass = asProgramClassOrNull(appView.definitionFor(subtype)); if (subclass == null) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java index 07af4a3..c6a2424 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
@@ -56,7 +56,7 @@ } appBuilder.replaceProgramClasses(new ArrayList<>(newProgramClasses.values())); RepackagingLens lens = lensBuilder.build(appView); - new AnnotationFixer(lens).run(appView.appInfo().classes()); + new AnnotationFixer(lens).run(newProgramClasses.values()); return lens; }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index fc6cb56..9706eab 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -261,7 +261,8 @@ return liveGetter ? original : null; } - private boolean enclosingMethodPinned(DexClass clazz) { + private static boolean enclosingMethodPinned( + AppView<AppInfoWithLiveness> appView, DexClass clazz) { return clazz.getEnclosingMethodAttribute() != null && clazz.getEnclosingMethodAttribute().getEnclosingClass() != null && appView.appInfo().isPinned(clazz.getEnclosingMethodAttribute().getEnclosingClass()); @@ -285,7 +286,7 @@ // is kept. boolean keptAnyway = appView.appInfo().isPinned(clazz.type) - || enclosingMethodPinned(clazz) + || enclosingMethodPinned(appView, clazz) || appView.options().forceProguardCompatibility; boolean keepForThisInnerClass = false; boolean keepForThisEnclosingClass = false; @@ -391,7 +392,7 @@ for (DexProgramClass clazz : appView.appInfo().classes()) { // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we // therefore need to keep the enclosing method and inner classes attributes, if requested. - if (appView.appInfo().isPinned(clazz.type)) { + if (appView.appInfo().isPinned(clazz) || enclosingMethodPinned(appView, clazz)) { for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) { DexType inner = innerClassAttribute.getInner(); if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java index 4d5a549..bdda13b 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -26,13 +26,11 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.graph.EnumValueInfoMapCollection; -import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo; import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap; import com.android.tools.r8.graph.FieldAccessInfo; import com.android.tools.r8.graph.FieldAccessInfoCollection; import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl; import com.android.tools.r8.graph.FieldResolutionResult; -import com.android.tools.r8.graph.GraphLens; import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; import com.android.tools.r8.graph.InstantiatedSubTypeInfo; import com.android.tools.r8.graph.LookupResult.LookupResultSuccess; @@ -40,7 +38,6 @@ import com.android.tools.r8.graph.MethodAccessInfoCollection; import com.android.tools.r8.graph.ObjectAllocationInfoCollection; import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl; -import com.android.tools.r8.graph.PresortedComparable; import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; @@ -60,6 +57,7 @@ import com.android.tools.r8.utils.Visibility; import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.android.tools.r8.utils.structural.Ordered; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; @@ -70,9 +68,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -89,11 +84,6 @@ */ private final Set<DexType> liveTypes; /** - * Set of service types (from META-INF/services/) that may have been instantiated reflectively via - * ServiceLoader.load() or ServiceLoader.loadInstalled(). - */ - public final Set<DexType> instantiatedAppServices; - /** * Set of methods that are the immediate target of an invoke. They might not actually be live but * are required so that invokes can find the method. If such a method is not live (i.e. not * contained in {@link #liveMethods}, it may be marked as abstract and its implementation may be @@ -148,29 +138,29 @@ /** All methods that *must* never be inlined due to a configuration directive (testing only). */ private final Set<DexMethod> neverInline; /** Items for which to print inlining decisions for (testing only). */ - public final Set<DexMethod> whyAreYouNotInlining; + private final Set<DexMethod> whyAreYouNotInlining; /** All methods that may not have any parameters with a constant value removed. */ - public final Set<DexMethod> keepConstantArguments; + private final Set<DexMethod> keepConstantArguments; /** All methods that may not have any unused arguments removed. */ - public final Set<DexMethod> keepUnusedArguments; + private final Set<DexMethod> keepUnusedArguments; /** All methods that must be reprocessed (testing only). */ - public final Set<DexMethod> reprocess; + private final Set<DexMethod> reprocess; /** All methods that must not be reprocessed (testing only). */ - public final Set<DexMethod> neverReprocess; + private final Set<DexMethod> neverReprocess; /** All types that should be inlined if possible due to a configuration directive. */ public final PredicateSet<DexType> alwaysClassInline; /** All types that *must* never be inlined due to a configuration directive (testing only). */ - public final Set<DexType> neverClassInline; + private final Set<DexType> neverClassInline; - private final Set<DexType> noUnusedInterfaceRemoval; - private final Set<DexType> noVerticalClassMerging; + private final Set<DexType> noClassMerging; private final Set<DexType> noHorizontalClassMerging; + private final Set<DexType> noVerticalClassMerging; private final Set<DexType> noStaticClassMerging; /** * Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction). */ - public final Set<DexType> lockCandidates; + private final Set<DexType> lockCandidates; /** * A map from seen init-class references to the minimum required visibility of the corresponding * static field. @@ -205,7 +195,6 @@ Set<DexType> deadProtoTypes, Set<DexType> missingTypes, Set<DexType> liveTypes, - Set<DexType> instantiatedAppServices, Set<DexMethod> targetedMethods, Set<DexMethod> failedResolutionTargets, Set<DexMethod> bootstrapMethods, @@ -230,7 +219,7 @@ Set<DexMethod> neverReprocess, PredicateSet<DexType> alwaysClassInline, Set<DexType> neverClassInline, - Set<DexType> noUnusedInterfaceRemoval, + Set<DexType> noClassMerging, Set<DexType> noVerticalClassMerging, Set<DexType> noHorizontalClassMerging, Set<DexType> noStaticClassMerging, @@ -245,7 +234,6 @@ this.deadProtoTypes = deadProtoTypes; this.missingTypes = missingTypes; this.liveTypes = liveTypes; - this.instantiatedAppServices = instantiatedAppServices; this.targetedMethods = targetedMethods; this.failedResolutionTargets = failedResolutionTargets; this.bootstrapMethods = bootstrapMethods; @@ -270,93 +258,7 @@ this.neverReprocess = neverReprocess; this.alwaysClassInline = alwaysClassInline; this.neverClassInline = neverClassInline; - this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval; - this.noVerticalClassMerging = noVerticalClassMerging; - this.noHorizontalClassMerging = noHorizontalClassMerging; - this.noStaticClassMerging = noStaticClassMerging; - this.neverPropagateValue = neverPropagateValue; - this.identifierNameStrings = identifierNameStrings; - this.prunedTypes = prunedTypes; - this.switchMaps = switchMaps; - this.enumValueInfoMaps = enumValueInfoMaps; - this.lockCandidates = lockCandidates; - this.initClassReferences = initClassReferences; - } - - public AppInfoWithLiveness( - AppInfoWithClassHierarchy appInfoWithClassHierarchy, - Set<DexType> deadProtoTypes, - Set<DexType> missingTypes, - Set<DexType> liveTypes, - Set<DexType> instantiatedAppServices, - Set<DexMethod> targetedMethods, - Set<DexMethod> failedResolutionTargets, - Set<DexMethod> bootstrapMethods, - Set<DexMethod> methodsTargetedByInvokeDynamic, - SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect, - SortedSet<DexMethod> liveMethods, - FieldAccessInfoCollectionImpl fieldAccessInfoCollection, - MethodAccessInfoCollection methodAccessInfoCollection, - ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection, - Map<DexCallSite, ProgramMethodSet> callSites, - KeepInfoCollection keepInfo, - Map<DexReference, ProguardMemberRule> mayHaveSideEffects, - Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects, - Map<DexMember<?, ?>, ProguardMemberRule> assumedValues, - Set<DexMethod> alwaysInline, - Set<DexMethod> forceInline, - Set<DexMethod> neverInline, - Set<DexMethod> whyAreYouNotInlining, - Set<DexMethod> keepConstantArguments, - Set<DexMethod> keepUnusedArguments, - Set<DexMethod> reprocess, - Set<DexMethod> neverReprocess, - PredicateSet<DexType> alwaysClassInline, - Set<DexType> neverClassInline, - Set<DexType> noUnusedInterfaceRemoval, - Set<DexType> noVerticalClassMerging, - Set<DexType> noHorizontalClassMerging, - Set<DexType> noStaticClassMerging, - Set<DexReference> neverPropagateValue, - Object2BooleanMap<DexReference> identifierNameStrings, - Set<DexType> prunedTypes, - Map<DexField, Int2ReferenceMap<DexField>> switchMaps, - EnumValueInfoMapCollection enumValueInfoMaps, - Set<DexType> lockCandidates, - Map<DexType, Visibility> initClassReferences) { - super( - appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()), - appInfoWithClassHierarchy.getClassToFeatureSplitMap(), - appInfoWithClassHierarchy.getMainDexClasses()); - this.deadProtoTypes = deadProtoTypes; - this.missingTypes = missingTypes; - this.liveTypes = liveTypes; - this.instantiatedAppServices = instantiatedAppServices; - this.targetedMethods = targetedMethods; - this.failedResolutionTargets = failedResolutionTargets; - this.bootstrapMethods = bootstrapMethods; - this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic; - this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect; - this.liveMethods = liveMethods; - this.fieldAccessInfoCollection = fieldAccessInfoCollection; - this.methodAccessInfoCollection = methodAccessInfoCollection; - this.objectAllocationInfoCollection = objectAllocationInfoCollection; - this.keepInfo = keepInfo; - this.mayHaveSideEffects = mayHaveSideEffects; - this.noSideEffects = noSideEffects; - this.assumedValues = assumedValues; - this.callSites = callSites; - this.alwaysInline = alwaysInline; - this.forceInline = forceInline; - this.neverInline = neverInline; - this.whyAreYouNotInlining = whyAreYouNotInlining; - this.keepConstantArguments = keepConstantArguments; - this.keepUnusedArguments = keepUnusedArguments; - this.reprocess = reprocess; - this.neverReprocess = neverReprocess; - this.alwaysClassInline = alwaysClassInline; - this.neverClassInline = neverClassInline; - this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval; + this.noClassMerging = noClassMerging; this.noVerticalClassMerging = noVerticalClassMerging; this.noHorizontalClassMerging = noHorizontalClassMerging; this.noStaticClassMerging = noStaticClassMerging; @@ -379,7 +281,6 @@ previous.missingTypes, CollectionUtils.mergeSets( Sets.difference(previous.liveTypes, removedTypes), committedItems.getCommittedTypes()), - previous.instantiatedAppServices, previous.targetedMethods, previous.failedResolutionTargets, previous.bootstrapMethods, @@ -404,7 +305,7 @@ previous.neverReprocess, previous.alwaysClassInline, previous.neverClassInline, - previous.noUnusedInterfaceRemoval, + previous.noClassMerging, previous.noVerticalClassMerging, previous.noHorizontalClassMerging, previous.noStaticClassMerging, @@ -431,7 +332,6 @@ removedClasses == null ? previous.liveTypes : Sets.difference(previous.liveTypes, removedClasses), - previous.instantiatedAppServices, previous.targetedMethods, previous.failedResolutionTargets, previous.bootstrapMethods, @@ -456,7 +356,7 @@ previous.neverReprocess, previous.alwaysClassInline, previous.neverClassInline, - previous.noUnusedInterfaceRemoval, + previous.noClassMerging, previous.noVerticalClassMerging, previous.noHorizontalClassMerging, previous.noStaticClassMerging, @@ -520,7 +420,6 @@ this.deadProtoTypes = previous.deadProtoTypes; this.missingTypes = previous.missingTypes; this.liveTypes = previous.liveTypes; - this.instantiatedAppServices = previous.instantiatedAppServices; this.targetedMethods = previous.targetedMethods; this.failedResolutionTargets = previous.failedResolutionTargets; this.bootstrapMethods = previous.bootstrapMethods; @@ -545,7 +444,7 @@ this.neverReprocess = previous.neverReprocess; this.alwaysClassInline = previous.alwaysClassInline; this.neverClassInline = previous.neverClassInline; - this.noUnusedInterfaceRemoval = previous.noUnusedInterfaceRemoval; + this.noClassMerging = previous.noClassMerging; this.noVerticalClassMerging = previous.noVerticalClassMerging; this.noHorizontalClassMerging = previous.noHorizontalClassMerging; this.noStaticClassMerging = previous.noStaticClassMerging; @@ -601,7 +500,7 @@ // Skip synthetic classes which may not have a specified version. if (clazz.hasClassFileVersion()) { largestInputCfVersion = - CfVersion.maxAllowNull(largestInputCfVersion, clazz.getInitialClassFileVersion()); + Ordered.maxIgnoreNull(largestInputCfVersion, clazz.getInitialClassFileVersion()); } } assert largestInputCfVersion != null; @@ -675,6 +574,30 @@ return neverInline.contains(method); } + public boolean isWhyAreYouNotInliningMethod(DexMethod method) { + return whyAreYouNotInlining.contains(method); + } + + public boolean hasNoWhyAreYouNotInliningMethods() { + return whyAreYouNotInlining.isEmpty(); + } + + public boolean isKeepConstantArgumentsMethod(DexMethod method) { + return keepConstantArguments.contains(method); + } + + public boolean isKeepUnusedArgumentsMethod(DexMethod method) { + return keepUnusedArguments.contains(method); + } + + public boolean isNeverReprocessMethod(DexMethod method) { + return neverReprocess.contains(method); + } + + public Set<DexMethod> getReprocessMethods() { + return reprocess; + } + public Collection<DexClass> computeReachableInterfaces() { Set<DexClass> interfaces = Sets.newIdentityHashSet(); WorkList<DexType> worklist = WorkList.newIdentityWorkList(); @@ -780,12 +703,6 @@ return enumValueInfoMaps.getEnumValueInfoMap(enumType); } - public EnumValueInfo getEnumValueInfo(DexField field) { - assert checkIfObsolete(); - EnumValueInfoMap map = enumValueInfoMaps.getEnumValueInfoMap(field.type); - return map != null ? map.getEnumValueInfo(field) : null; - } - public Int2ReferenceMap<DexField> getSwitchMap(DexField field) { assert checkIfObsolete(); return switchMaps.get(field); @@ -936,18 +853,6 @@ return holder == null || holder.isLibraryClass() || holder.isClasspathClass(); } - private static SortedMap<DexMethod, ProgramMethodSet> rewriteInvokesWithContexts( - Map<DexMethod, ProgramMethodSet> invokes, GraphLens lens) { - SortedMap<DexMethod, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare); - invokes.forEach( - (method, contexts) -> - result - .computeIfAbsent( - lens.getRenamedMethodSignature(method), ignore -> ProgramMethodSet.create()) - .addAll(contexts)); - return Collections.unmodifiableSortedMap(result); - } - public boolean isInstantiatedInterface(DexProgramClass clazz) { assert checkIfObsolete(); return objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz); @@ -1067,7 +972,6 @@ deadProtoTypes, missingTypes, lens.rewriteTypes(liveTypes), - lens.rewriteTypes(instantiatedAppServices), lens.rewriteMethods(targetedMethods), lens.rewriteMethods(failedResolutionTargets), lens.rewriteMethods(bootstrapMethods), @@ -1085,14 +989,14 @@ lens.rewriteMethods(alwaysInline), lens.rewriteMethods(forceInline), lens.rewriteMethods(neverInline), - lens.rewriteMethodsSorted(whyAreYouNotInlining), - lens.rewriteMethodsSorted(keepConstantArguments), - lens.rewriteMethodsSorted(keepUnusedArguments), - lens.rewriteMethodsSorted(reprocess), - lens.rewriteMethodsSorted(neverReprocess), + lens.rewriteMethods(whyAreYouNotInlining), + lens.rewriteMethods(keepConstantArguments), + lens.rewriteMethods(keepUnusedArguments), + lens.rewriteMethods(reprocess), + lens.rewriteMethods(neverReprocess), alwaysClassInline.rewriteItems(lens::lookupType), lens.rewriteTypes(neverClassInline), - lens.rewriteTypes(noUnusedInterfaceRemoval), + lens.rewriteTypes(noClassMerging), lens.rewriteTypes(noVerticalClassMerging), lens.rewriteTypes(noHorizontalClassMerging), lens.rewriteTypes(noStaticClassMerging), @@ -1463,25 +1367,14 @@ .shouldBreak(); } - /** All unused interface types that *must* never be pruned. */ - public Set<DexType> getNoUnusedInterfaceRemovalSet() { - return noUnusedInterfaceRemoval; + /** Predicate on types that *must* never be merged horizontally. */ + public boolean isNoHorizontalClassMergingOfType(DexType type) { + return noClassMerging.contains(type) || noHorizontalClassMerging.contains(type); } - /** - * All types that *must* never be merged vertically due to a configuration directive (testing - * only). - */ - public Set<DexType> getNoVerticalClassMergingSet() { - return noVerticalClassMerging; - } - - /** - * All types that *must* never be merged horizontally due to a configuration directive (testing - * only). - */ - public Set<DexType> getNoHorizontalClassMergingSet() { - return noHorizontalClassMerging; + /** Predicate on types that *must* never be merged vertically. */ + public boolean isNoVerticalClassMergingOfType(DexType type) { + return noClassMerging.contains(type) || noVerticalClassMerging.contains(type); } /**
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index e032ea2..ed31df5 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -208,8 +208,8 @@ private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection = new FieldAccessInfoCollectionImpl(); - private final MethodAccessInfoCollection.SortedBuilder methodAccessInfoCollection = - MethodAccessInfoCollection.sortedBuilder(); + private final MethodAccessInfoCollection.IdentityBuilder methodAccessInfoCollection = + MethodAccessInfoCollection.identityBuilder(); private final ObjectAllocationInfoCollectionImpl.Builder objectAllocationInfoCollection; private final Map<DexCallSite, ProgramMethodSet> callSites = new IdentityHashMap<>(); @@ -271,6 +271,8 @@ /** Set of types that was pruned during the first round of tree shaking. */ private Set<DexType> initialPrunedTypes; + private final Set<DexType> noClassMerging = Sets.newIdentityHashSet(); + /** Mapping from each unused interface to the set of live types that implements the interface. */ private final Map<DexProgramClass, Set<DexProgramClass>> unusedInterfaceTypes = new IdentityHashMap<>(); @@ -308,12 +310,6 @@ */ private final LiveFieldsSet liveFields; - /** - * Set of service types (from META-INF/services/) that may have been instantiated reflectively via - * ServiceLoader.load() or ServiceLoader.loadInstalled(). - */ - private final Set<DexType> instantiatedAppServices = Sets.newIdentityHashSet(); - /** A queue of items that need processing. Different items trigger different actions. */ private final EnqueuerWorklist workList; @@ -1360,6 +1356,7 @@ FieldResolutionResult resolutionResult = resolveField(fieldReference); if (resolutionResult.isFailedOrUnknownResolution()) { + noClassMerging.add(fieldReference.getHolderType()); return; } @@ -1410,6 +1407,7 @@ FieldResolutionResult resolutionResult = resolveField(fieldReference); if (resolutionResult.isFailedOrUnknownResolution()) { + noClassMerging.add(fieldReference.getHolderType()); return; } @@ -1460,6 +1458,7 @@ if (resolutionResult.isFailedOrUnknownResolution()) { // Must mark the field as targeted even if it does not exist. markFieldAsTargeted(fieldReference, currentMethod); + noClassMerging.add(fieldReference.getHolderType()); return; } @@ -1518,6 +1517,7 @@ if (resolutionResult.isFailedOrUnknownResolution()) { // Must mark the field as targeted even if it does not exist. markFieldAsTargeted(fieldReference, currentMethod); + noClassMerging.add(fieldReference.getHolderType()); return; } @@ -3242,7 +3242,6 @@ ? Sets.union(initialMissingTypes, missingTypes) : missingTypes, SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType), - Collections.unmodifiableSet(instantiatedAppServices), Enqueuer.toDescriptorSet(targetedMethods.getItems()), Collections.unmodifiableSet(failedResolutionTargets), Collections.unmodifiableSet(bootstrapMethods), @@ -3268,7 +3267,7 @@ rootSet.neverReprocess, rootSet.alwaysClassInline, rootSet.neverClassInline, - rootSet.noUnusedInterfaceRemoval, + noClassMerging, rootSet.noVerticalClassMerging, rootSet.noHorizontalClassMerging, rootSet.noStaticClassMerging, @@ -3925,6 +3924,11 @@ } else if (identifierTypeLookupResult.isTypeInitializedFromUse()) { markDirectAndIndirectClassInitializersAsLive(clazz); } + // To ensure we are not moving the class because we cannot prune it when there is a reflective + // use of it. + if (!keepInfo.getClassInfo(clazz).isPinned()) { + keepInfo.pinClass(clazz); + } } else if (referencedItem.isDexField()) { DexField field = referencedItem.asDexField(); DexProgramClass clazz = getProgramClassOrNull(field.holder); @@ -4200,8 +4204,6 @@ } private void handleServiceInstantiation(DexType serviceType, KeepReason reason) { - instantiatedAppServices.add(serviceType); - List<DexType> serviceImplementationTypes = appView.appServices().serviceImplementationsFor(serviceType); for (DexType serviceImplementationType : serviceImplementationTypes) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java index 7d75f05..36f5cdd 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -84,8 +84,9 @@ public boolean isAllowSignatureAttributeRemovalAllowed( GlobalKeepInfoConfiguration configuration) { - return !configuration.isKeepAttributesSignatureEnabled() - || !(isPinned() || configuration.isForceProguardCompatibilityEnabled()); + // TODO(b/172999267): For full mode we should be able to remove for not pinned items if + // java reflect will not throw up. + return !configuration.isKeepAttributesSignatureEnabled(); } public abstract boolean isTop();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java index 4a19c6f..aac168d 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -105,6 +105,10 @@ AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo, Iterable<DexProgramClass> defaultValue) { + List<DexType> specificTypes = getClassNames().asSpecificDexTypes(); + if (specificTypes != null) { + return DexProgramClass.asProgramClasses(specificTypes, appView); + } if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) { DexType type = getInheritanceClassName().getSpecificType(); if (appView.verticallyMergedClasses() != null
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java index 7267bac..1d476bf 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -745,7 +745,7 @@ DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method)); DexEncodedMethod definition = method.lookupOnClass(holder); if (definition == null) { - assert false; + assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod); return; } out.print(method.holder.toSourceString() + ": "); @@ -1705,7 +1705,7 @@ assert method.getHolderType() == options.dexItemFactory().objectType; OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition()); assumeNoSideEffectsWarnings - .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::slowCompareTo)) + .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo)) .add(method.getReference()); }
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java index 2a72863..97e980c 100644 --- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.GraphLens.NestedGraphLens; +import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses; import com.android.tools.r8.logging.Log; import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector; import com.android.tools.r8.utils.FieldSignatureEquivalence; @@ -188,6 +189,8 @@ private final AppView<AppInfoWithLiveness> appView; private final MainDexTracingResult mainDexClasses; + private final StaticallyMergedClasses.Builder mergedClassesBuilder = + StaticallyMergedClasses.builder(); /** The equivalence that should be used for the member buckets in {@link Representative}. */ private final Equivalence<DexField> fieldEquivalence; @@ -224,6 +227,7 @@ numberOfMergedClasses, fieldMapping.size() + methodMapping.size()); } + appView.setStaticallyMergedClasses(mergedClassesBuilder.build()); return buildGraphLens(); } @@ -337,7 +341,7 @@ // We are not allowed to merge synchronized classes with synchronized methods. return; } - if (appView.appInfo().lockCandidates.contains(clazz.type)) { + if (appView.appInfo().isLockCandidate(clazz.type)) { // Since the type is const-class referenced (and the static merger does not create a lens // to map the merged type) the class will likely remain and there is no gain from merging. return; @@ -456,6 +460,7 @@ assert getClassToFeatureSplitMap().isInSameFeatureOrBothInBase(sourceClass, targetClass); numberOfMergedClasses++; + mergedClassesBuilder.recordMerge(sourceClass, targetClass); // Move members from source to target. targetClass.addDirectMethods(
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java b/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java index 1d57c73..5b89abf 100644 --- a/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java +++ b/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java
@@ -24,8 +24,8 @@ } void sort() { - fields.sort((a, b) -> a.getReference().slowCompareTo(b.getReference())); - methods.sort((a, b) -> a.getReference().slowCompareTo(b.getReference())); + fields.sort((a, b) -> a.getReference().compareTo(b.getReference())); + methods.sort((a, b) -> a.getReference().compareTo(b.getReference())); } } @@ -73,7 +73,7 @@ } public void finished() { - classes.sort((a, b) -> a.getFirst().slowCompareTo(b.getFirst())); + classes.sort((a, b) -> a.getFirst().compareTo(b.getFirst())); for (Pair<DexType, Members> entry : classes) { DexType type = entry.getFirst(); Members members = entry.getSecond();
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index a294f1d..c98e345 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -57,6 +58,7 @@ import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TraversalContinuation; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.Iterables; @@ -204,16 +206,14 @@ private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>(); // Map from source class to target class. - private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>(); - - // Map from target class to the super classes that have been merged into the target class. - private final Map<DexType, Set<DexType>> mergedClassesInverse = new IdentityHashMap<>(); + private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses = + new BidirectionalManyToOneMap<>(); // Set of types that must not be merged into their subtype. private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet(); // The resulting graph lens that should be used after class merging. - private final VerticalClassMergerGraphLens.Builder renamedMembersLens; + private final VerticalClassMergerGraphLens.Builder lensBuilder; // All the bridge methods that have been synthesized during vertical class merging. private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>(); @@ -232,7 +232,7 @@ this.subtypingInfo = appInfo.computeSubtypingInfo(); this.executorService = executorService; this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo); - this.renamedMembersLens = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory()); + this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory()); this.timing = timing; this.mainDexClasses = mainDexClasses; @@ -241,10 +241,6 @@ initializeMergeCandidates(classes); } - private VerticallyMergedClasses getMergedClasses() { - return new VerticallyMergedClasses(mergedClasses, mergedClassesInverse); - } - private void initializeMergeCandidates(Iterable<DexProgramClass> classes) { for (DexProgramClass sourceClass : classes) { DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(sourceClass.type); @@ -351,7 +347,7 @@ || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass) || appInfo.isPinned(sourceClass.type) || pinnedTypes.contains(sourceClass.type) - || appInfo.getNoVerticalClassMergingSet().contains(sourceClass.type)) { + || appInfo.isNoVerticalClassMergingOfType(sourceClass.type)) { return false; } @@ -427,7 +423,7 @@ // before merging [clazz] into its subtype. private boolean isStillMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) { assert isMergeCandidate(sourceClass, targetClass, pinnedTypes); - if (mergedClassesInverse.containsKey(sourceClass.type)) { + if (mergedClasses.containsValue(sourceClass.getType())) { // Do not allow merging the resulting class into its subclass. // TODO(christofferqa): Get rid of this limitation. if (Log.ENABLED) { @@ -441,7 +437,7 @@ // their clinit called - except when the interface has a default method. if ((sourceClass.hasClassInitializer() && targetClass.hasClassInitializer()) || targetClass.classInitializationMayHaveSideEffects( - appView, type -> type == sourceClass.type, Sets.newIdentityHashSet()) + appView, type -> type == sourceClass.type) || (sourceClass.isInterface() && sourceClass.classInitializationMayHaveSideEffects(appView))) { // TODO(herhut): Handle class initializers. @@ -644,16 +640,20 @@ TopDownClassHierarchyTraversal.forProgramClasses(appView) .visit(mergeCandidates, this::mergeClassIfPossible); if (Log.ENABLED) { - Log.debug(getClass(), "Merged %d classes.", mergedClasses.size()); + Log.debug(getClass(), "Merged %d classes.", mergedClasses.keySet().size()); } timing.end(); - if (mergedClasses.isEmpty()) { + VerticallyMergedClasses verticallyMergedClasses = new VerticallyMergedClasses(mergedClasses); + appView.setVerticallyMergedClasses(verticallyMergedClasses); + if (verticallyMergedClasses.isEmpty()) { return null; } timing.begin("fixup"); - VerticalClassMergerGraphLens lens = new TreeFixer().fixupTypeReferences(); + VerticalClassMergerGraphLens lens = + new TreeFixer(appView, lensBuilder, verticallyMergedClasses, synthesizedBridges) + .fixupTypeReferences(); KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo(); keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.keySet())); timing.end(); @@ -806,8 +806,7 @@ assert !mergedClasses.containsKey(targetClass.type); boolean clazzOrTargetClassHasBeenMerged = - mergedClassesInverse.containsKey(clazz.type) - || mergedClassesInverse.containsKey(targetClass.type); + mergedClasses.containsValue(clazz.type) || mergedClasses.containsValue(targetClass.type); if (clazzOrTargetClassHasBeenMerged) { if (!isStillMergeCandidate(clazz, targetClass)) { return; @@ -851,7 +850,7 @@ } if (merged) { // Commit the changes to the graph lens. - renamedMembersLens.merge(merger.getRenamings()); + lensBuilder.merge(merger.getRenamings()); synthesizedBridges.addAll(merger.getSynthesizedBridges()); } if (Log.ENABLED) { @@ -1116,7 +1115,6 @@ source.setStaticFields(null); // Step 4: Record merging. mergedClasses.put(source.type, target.type); - mergedClassesInverse.computeIfAbsent(target.type, key -> new HashSet<>()).add(source.type); assert !abortMerge; return true; } @@ -1190,23 +1188,21 @@ // the code above. However, instructions on the form "invoke-super A.m()" should also be // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes // that have been merged into [holder]. - Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type); - if (mergedTypes != null) { - for (DexType type : mergedTypes) { - DexMethod signatureInType = - application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name); - // Resolution would have succeeded if the method used to be in [type], or if one of - // its super classes declared the method. - boolean resolutionSucceededBeforeMerge = - renamedMembersLens.hasMappingForSignatureInContext(holder, signatureInType) - || appInfo.lookupSuperTarget(signatureInHolder, holder) != null; - if (resolutionSucceededBeforeMerge) { - deferredRenamings.mapVirtualMethodToDirectInType( - signatureInType, - prototypeChanges -> - new MethodLookupResult(newTarget, null, DIRECT, prototypeChanges), - target.type); - } + Set<DexType> mergedTypes = mergedClasses.getKeys(holder.type); + for (DexType type : mergedTypes) { + DexMethod signatureInType = + application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name); + // Resolution would have succeeded if the method used to be in [type], or if one of + // its super classes declared the method. + boolean resolutionSucceededBeforeMerge = + lensBuilder.hasMappingForSignatureInContext(holder, signatureInType) + || appInfo.lookupSuperTarget(signatureInHolder, holder) != null; + if (resolutionSucceededBeforeMerge) { + deferredRenamings.mapVirtualMethodToDirectInType( + signatureInType, + prototypeChanges -> + new MethodLookupResult(newTarget, null, DIRECT, prototypeChanges), + target.type); } } holder = @@ -1469,16 +1465,32 @@ method.accessFlags.setPrivate(); } - private class TreeFixer { + private static class TreeFixer { - private final VerticalClassMergerGraphLens.Builder lensBuilder = - VerticalClassMergerGraphLens.Builder.createBuilderForFixup( - renamedMembersLens, mergedClasses); + private final AppView<AppInfoWithLiveness> appView; + private final DexItemFactory dexItemFactory; + private final VerticalClassMergerGraphLens.Builder lensBuilder; + private final VerticallyMergedClasses mergedClasses; + private final List<SynthesizedBridgeCode> synthesizedBridges; + private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>(); + TreeFixer( + AppView<AppInfoWithLiveness> appView, + VerticalClassMergerGraphLens.Builder lensBuilder, + VerticallyMergedClasses mergedClasses, + List<SynthesizedBridgeCode> synthesizedBridges) { + this.appView = appView; + this.dexItemFactory = appView.dexItemFactory(); + this.lensBuilder = + VerticalClassMergerGraphLens.Builder.createBuilderForFixup(lensBuilder, mergedClasses); + this.mergedClasses = mergedClasses; + this.synthesizedBridges = synthesizedBridges; + } + private VerticalClassMergerGraphLens fixupTypeReferences() { // Globally substitute merged class types in protos and holders. - for (DexProgramClass clazz : appInfo.classes()) { + for (DexProgramClass clazz : appView.appInfo().classes()) { clazz.getMethodCollection().replaceMethods(this::fixupMethod); fixupFields(clazz.staticFields(), clazz::setStaticField); fixupFields(clazz.instanceFields(), clazz::setInstanceField); @@ -1486,7 +1498,7 @@ for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) { synthesizedBridge.updateMethodSignatures(this::fixupMethod); } - VerticalClassMergerGraphLens lens = lensBuilder.build(appView, getMergedClasses()); + VerticalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses); if (lens != null) { new AnnotationFixer(lens).run(appView.appInfo().classes()); } @@ -1523,7 +1535,7 @@ DexField field = encodedField.field; DexType newType = fixupType(field.type); DexType newHolder = fixupType(field.holder); - DexField newField = application.dexItemFactory.createField(newHolder, newType, field.name); + DexField newField = dexItemFactory.createField(newHolder, newType, field.name); if (newField != encodedField.field) { if (!lensBuilder.hasOriginalSignatureMappingFor(newField)) { lensBuilder.map(field, newField); @@ -1534,7 +1546,7 @@ } private DexMethod fixupMethod(DexMethod method) { - return application.dexItemFactory.createMethod( + return dexItemFactory.createMethod( fixupType(method.holder), fixupProto(method.proto), method.name); } @@ -1543,7 +1555,7 @@ if (result == null) { DexType returnType = fixupType(proto.returnType); DexType[] arguments = fixupTypes(proto.parameters.values); - result = application.dexItemFactory.createProto(returnType, arguments); + result = dexItemFactory.createProto(returnType, arguments); protoFixupCache.put(proto, result); } return result; @@ -1551,16 +1563,16 @@ private DexType fixupType(DexType type) { if (type.isArrayType()) { - DexType base = type.toBaseType(application.dexItemFactory); + DexType base = type.toBaseType(dexItemFactory); DexType fixed = fixupType(base); if (base == fixed) { return type; } - return type.replaceBaseType(fixed, application.dexItemFactory); + return type.replaceBaseType(fixed, dexItemFactory); } if (type.isClassType()) { - while (mergedClasses.containsKey(type)) { - type = mergedClasses.get(type); + while (mergedClasses.hasBeenMergedIntoSubtype(type)) { + type = mergedClasses.getTargetFor(type); } } return type; @@ -1713,7 +1725,7 @@ return AbortReason.UNSAFE_INLINING; } - private class SingleTypeMapperGraphLens extends NonIdentityGraphLens { + public class SingleTypeMapperGraphLens extends NonIdentityGraphLens { private final DexType source; private final DexProgramClass target; @@ -1770,7 +1782,7 @@ // changed if the method was publicized by ClassAndMemberPublicizer). MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, type); // Then check if there is a renaming due to the vertical class merger. - DexMethod newMethod = renamedMembersLens.methodMap.get(lookup.getReference()); + DexMethod newMethod = lensBuilder.methodMap.get(lookup.getReference()); if (newMethod == null) { return lookup; } @@ -1806,7 +1818,7 @@ @Override public DexField lookupField(DexField field) { - return renamedMembersLens.fieldMap.getOrDefault(field, field); + return lensBuilder.fieldMap.getOrDefault(field, field); } @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java index a5481fd..2bee133 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -92,10 +92,6 @@ this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges; } - public VerticallyMergedClasses getMergedClasses() { - return mergedClasses; - } - @Override protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { Collection<DexType> originalTypes = mergedClasses.getSourcesFor(previous); @@ -185,7 +181,7 @@ this.dexItemFactory = dexItemFactory; } - static Builder createBuilderForFixup(Builder builder, Map<DexType, DexType> mergedClasses) { + static Builder createBuilderForFixup(Builder builder, VerticallyMergedClasses mergedClasses) { Builder newBuilder = new Builder(builder.dexItemFactory); for (Map.Entry<DexField, DexField> entry : builder.fieldMap.entrySet()) { newBuilder.map( @@ -235,7 +231,7 @@ public VerticalClassMergerGraphLens build( AppView<?> appView, VerticallyMergedClasses mergedClasses) { - if (mergedClasses.getForwardMap().isEmpty()) { + if (mergedClasses.isEmpty()) { return null; } BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse(); @@ -254,11 +250,11 @@ } private DexField getFieldSignatureAfterClassMerging( - DexField field, Map<DexType, DexType> mergedClasses) { + DexField field, VerticallyMergedClasses mergedClasses) { assert !field.holder.isArrayType(); DexType holder = field.holder; - DexType newHolder = mergedClasses.getOrDefault(holder, holder); + DexType newHolder = mergedClasses.getTargetForOrDefault(holder, holder); DexType type = field.type; DexType newType = getTypeAfterClassMerging(type, mergedClasses); @@ -270,11 +266,11 @@ } private DexMethod getMethodSignatureAfterClassMerging( - DexMethod signature, Map<DexType, DexType> mergedClasses) { + DexMethod signature, VerticallyMergedClasses mergedClasses) { assert !signature.holder.isArrayType(); DexType holder = signature.holder; - DexType newHolder = mergedClasses.getOrDefault(holder, holder); + DexType newHolder = mergedClasses.getTargetForOrDefault(holder, holder); DexProto proto = signature.proto; DexProto newProto = @@ -287,16 +283,16 @@ return dexItemFactory.createMethod(newHolder, newProto, signature.name); } - private DexType getTypeAfterClassMerging(DexType type, Map<DexType, DexType> mergedClasses) { + private DexType getTypeAfterClassMerging(DexType type, VerticallyMergedClasses mergedClasses) { if (type.isArrayType()) { DexType baseType = type.toBaseType(dexItemFactory); - DexType newBaseType = mergedClasses.getOrDefault(baseType, baseType); + DexType newBaseType = mergedClasses.getTargetForOrDefault(baseType, baseType); if (newBaseType != baseType) { return type.replaceBaseType(newBaseType, dexItemFactory); } return type; } - return mergedClasses.getOrDefault(type, type); + return mergedClasses.getTargetForOrDefault(type, type); } public boolean hasMappingForSignatureInContext(DexProgramClass context, DexMethod signature) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java index 303df06..f5d4b66 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java +++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -65,9 +65,9 @@ return Comparator // The first item to compare is the synthesizing context type. This is the type used to // choose the context prefix for items. - .comparing(SynthesizingContext::getSynthesizingContextType, DexType::slowCompareTo) + .comparing(SynthesizingContext::getSynthesizingContextType) // To ensure that equals coincides with compareTo == 0, we then compare 'type'. - .thenComparing(c -> c.inputContextType, DexType::slowCompareTo) + .thenComparing(c -> c.inputContextType) .compare(this, other); }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java index 30d232e..01f8825 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.synthesis; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.utils.structural.RepresentativeMap; import com.google.common.hash.HashCode; /** @@ -26,7 +27,7 @@ abstract DexProgramClass getHolder(); - abstract HashCode computeHash(boolean intermediate); + abstract HashCode computeHash(RepresentativeMap map, boolean intermediate); abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate); }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index 1a96106..4d621a3 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.shaking.MainDexClasses; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.structural.RepresentativeMap; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -93,7 +94,7 @@ MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses(); GraphLens graphLens = appView.graphLens(); - List<SyntheticMethodDefinition> methodDefinitions = + Map<DexType, SyntheticMethodDefinition> methodDefinitions = lookupSyntheticMethodDefinitions(application); Collection<List<SyntheticMethodDefinition>> potentialEquivalences = @@ -183,7 +184,7 @@ // Use a tree set to make sure that we have an ordering on the types. // These types are put in an array in annotations in the output and we // need a consistent ordering on them. - TreeSet<DexType> synthesized = new TreeSet<>(DexType::slowCompareTo); + TreeSet<DexType> synthesized = new TreeSet<>(DexType::compareTo); entry.getValue().stream() .map(dexProgramClass -> dexProgramClass.type) .forEach(synthesized::add); @@ -358,7 +359,7 @@ EquivalenceGroup<T> group = groups.get(i); // Two equivalence groups in same context type must be distinct otherwise the assignment // of the synthetic name will be non-deterministic between the two. - assert i == 0 || checkGroupsAreDistict(groups.get(i - 1), group); + assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group); DexType representativeType = createExternalType(context, i, factory); equivalences.put(representativeType, group); } @@ -388,7 +389,7 @@ return groups; } - private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistict( + private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistinct( EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) { assert g1.compareTo(g2) != 0; return true; @@ -417,18 +418,24 @@ } private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences( - List<T> definitions, boolean intermediate) { + Map<DexType, T> definitions, boolean intermediate) { + if (definitions.isEmpty()) { + return Collections.emptyList(); + } + Set<DexType> allTypes = definitions.keySet(); + DexType representative = allTypes.iterator().next(); + RepresentativeMap map = t -> allTypes.contains(t) ? representative : t; Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size()); - for (T definition : definitions) { - HashCode hash = definition.computeHash(intermediate); + for (T definition : definitions.values()) { + HashCode hash = definition.computeHash(map, intermediate); equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition); } return equivalences.values(); } - private List<SyntheticMethodDefinition> lookupSyntheticMethodDefinitions( + private Map<DexType, SyntheticMethodDefinition> lookupSyntheticMethodDefinitions( DexApplication finalApp) { - List<SyntheticMethodDefinition> methods = new ArrayList<>(syntheticItems.size()); + Map<DexType, SyntheticMethodDefinition> methods = new IdentityHashMap<>(syntheticItems.size()); for (SyntheticReference reference : syntheticItems.values()) { SyntheticDefinition definition = reference.lookupDefinition(finalApp::definitionFor); if (definition == null || !(definition instanceof SyntheticMethodDefinition)) { @@ -438,7 +445,7 @@ } SyntheticMethodDefinition method = (SyntheticMethodDefinition) definition; if (SyntheticMethodBuilder.isValidSyntheticMethod(method.getMethod().getDefinition())) { - methods.add(method); + methods.put(method.getHolder().getType(), method); } else { // Failing this check indicates that an optimization has modified the synthetic in a // disruptive way.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java index 8cddcfc..ecc1a81 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.utils.structural.RepresentativeMap; import com.google.common.hash.HashCode; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; @@ -41,14 +42,14 @@ } @Override - HashCode computeHash(boolean intermediate) { + HashCode computeHash(RepresentativeMap map, boolean intermediate) { Hasher hasher = Hashing.sha256().newHasher(); if (intermediate) { // If in intermediate mode, include the context type as sharing is restricted to within a // single context. hasher.putInt(getContext().getSynthesizingContextType().hashCode()); } - method.getDefinition().hashSyntheticContent(hasher); + method.getDefinition().hashSyntheticContent(hasher, map); return hasher.hash(); }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java index ce8f565..b11a901 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.tracereferences; import static com.android.tools.r8.utils.FileUtils.isArchive; +import static com.android.tools.r8.utils.FileUtils.isClassFile; import static com.android.tools.r8.utils.FileUtils.isDexFile; +import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION; import com.android.tools.r8.ArchiveClassFileProvider; import com.android.tools.r8.ClassFileResourceProvider; @@ -25,6 +27,7 @@ import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -33,6 +36,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; @Keep public class TraceReferencesCommand { @@ -96,6 +102,10 @@ return TraceReferencesCommandParser.parse(args, origin, diagnosticsHandler); } + public static Builder parse(Collection<String> args, Origin origin) { + return TraceReferencesCommandParser.parse(args.toArray(new String[args.size()]), origin); + } + public boolean isPrintHelp() { return printHelp; } @@ -151,6 +161,79 @@ return this; } + private static String extractClassDescriptor(byte[] data) { + class ClassNameExtractor extends ClassVisitor { + private String className; + + private ClassNameExtractor() { + super(ASM_VERSION); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + className = name; + } + + String getClassInternalType() { + return className; + } + } + + ClassReader reader = new ClassReader(data); + ClassNameExtractor extractor = new ClassNameExtractor(); + reader.accept( + extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return "L" + extractor.getClassInternalType() + ";"; + } + + private static class SingleClassClassFileResourceProvider implements ClassFileResourceProvider { + private final String descriptor; + private final ProgramResource programResource; + + SingleClassClassFileResourceProvider(Origin origin, byte[] data) { + this.descriptor = extractClassDescriptor(data); + this.programResource = + ProgramResource.fromBytes(origin, Kind.CF, data, ImmutableSet.of(descriptor)); + } + + @Override + public Set<String> getClassDescriptors() { + return ImmutableSet.of(descriptor); + } + + @Override + public ProgramResource getProgramResource(String descriptor) { + return descriptor.equals(this.descriptor) ? programResource : null; + } + } + + private ClassFileResourceProvider singleClassFileClassFileResourceProvider(Path file) + throws IOException { + return new SingleClassClassFileResourceProvider( + new PathOrigin(file), Files.readAllBytes(file)); + } + + private ProgramResourceProvider singleClassFileProgramResourceProvider(Path file) + throws IOException { + byte[] bytes = Files.readAllBytes(file); + String descriptor = extractClassDescriptor(bytes); + return new ProgramResourceProvider() { + + @Override + public Collection<ProgramResource> getProgramResources() { + return ImmutableList.of( + ProgramResource.fromBytes( + new PathOrigin(file), Kind.CF, bytes, ImmutableSet.of(descriptor))); + } + }; + } + private void addLibraryOrTargetFile( Path file, ImmutableList.Builder<ClassFileResourceProvider> builder) { if (!Files.exists(file)) { @@ -165,6 +248,12 @@ } catch (IOException e) { error(new ExceptionDiagnostic(e, new PathOrigin(file))); } + } else if (isClassFile(file)) { + try { + builder.add(singleClassFileClassFileResourceProvider(file)); + } catch (IOException e) { + error(new ExceptionDiagnostic(e)); + } } else { error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file))); } @@ -178,6 +267,12 @@ } if (isArchive(file)) { traceSourceBuilder.add(ArchiveResourceProvider.fromArchive(file, false)); + } else if (isClassFile(file)) { + try { + traceSourceBuilder.add(singleClassFileProgramResourceProvider(file)); + } catch (IOException e) { + error(new ExceptionDiagnostic(e)); + } } else if (isDexFile(file)) { traceSourceBuilder.add( new ProgramResourceProvider() {
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java index f299459..3a3faeb 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
@@ -6,8 +6,10 @@ import com.android.tools.r8.BaseCompilerCommandParser; import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.JdkClassFileProvider; +import com.android.tools.r8.StringConsumer.FileConsumer; +import com.android.tools.r8.StringConsumer.WriterConsumer; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.origin.Origin; -import com.android.tools.r8.tracereferences.TraceReferencesFormattingConsumer.OutputFormat; import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.FlagFile; import com.android.tools.r8.utils.StringDiagnostic; @@ -15,6 +17,7 @@ import com.google.common.collect.Iterables; import java.io.IOException; import java.io.PrintStream; +import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -24,29 +27,40 @@ class TraceReferencesCommandParser { private static final Set<String> OPTIONS_WITH_PARAMETER = - ImmutableSet.of("--lib", "--target", "--source", "--format", "--output"); + ImmutableSet.of("--lib", "--target", "--source", "--output"); static final String USAGE_MESSAGE = String.join( "\n", Iterables.concat( Arrays.asList( - "Usage: tracereferences [options] [@<argfile>]", - " Each <argfile> is a file containing additional arguments (one per line)", + "Usage: tracereferences <command> [<options>] [@<argfile>]", + " Where <command> is one of:", + " --check # Run emitting only diagnostics messages.", + " --print-usage # Traced references will be output in the print-usage", + " # format.", + " --keep-rules [<keep-rules-options>]", + " # Traced references will be output in the keep-rules", + " # format.", + " and each <argfile> is a file containing additional options (one per line)", " and options are:", " --lib <file|jdk-home> # Add <file|jdk-home> runtime library.", " --source <file> # Add <file> as a source for tracing references.", " [--target <file>] # Add <file> as a target for tracing references. When", " # target is not specified all references from source", - " # outside of library are treated as a missing" - + " references.", - " [--format printuses|keep|keepallowobfuscation]", - " # Format of the output. Default is 'printuses'.", - " --output <file> # Output result in <outfile>."), + " # outside of library are treated as a missing", + " # references.", + " --output <file> # Output result in <outfile>. If not passed the", + " # result will go to standard out.", + " # result will go to standard out."), BaseCompilerCommandParser.MAP_DIAGNOSTICS_USAGE_MESSAGE, Arrays.asList( " --version # Print the version of tracereferences.", - " --help # Print this message."))); + " --help # Print this message.", + " and <keep-rule-options> are:", + " --allowobfuscation # Output keep rules with the allowobfuscation", + " # modifier (defaults to rules without the" + + " modifier)"))); /** * Parse the tracereferences command-line. * @@ -76,11 +90,30 @@ .parse(args, origin, TraceReferencesCommand.builder(handler)); } + private enum Command { + CHECK, + PRINTUSAGE, + KEEP_RULES; + } + + private void checkCommandNotSet( + Command command, TraceReferencesCommand.Builder builder, Origin origin) { + if (command != null) { + builder.error(new StringDiagnostic("Multiple commands specified", origin)); + } + } + private TraceReferencesCommand.Builder parse( String[] args, Origin origin, TraceReferencesCommand.Builder builder) { String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error); Path output = null; - OutputFormat format = null; + Command command = null; + boolean allowObfuscation = false; + if (expandedArgs.length == 0) { + builder.error(new StringDiagnostic("Missing command")); + return builder; + } + // Parse options. for (int i = 0; i < expandedArgs.length; i++) { String arg = expandedArgs[i].trim(); String nextArg = null; @@ -97,32 +130,33 @@ continue; } else if (arg.equals("--help")) { builder.setPrintHelp(true); + return builder; } else if (arg.equals("--version")) { builder.setPrintVersion(true); + return builder; + } else if (arg.equals("--check")) { + checkCommandNotSet(command, builder, origin); + command = Command.CHECK; + } else if (arg.equals("--print-usage")) { + checkCommandNotSet(command, builder, origin); + command = Command.PRINTUSAGE; + } else if (arg.equals("--keep-rules")) { + checkCommandNotSet(command, builder, origin); + command = Command.KEEP_RULES; + } else if (arg.equals("--allowobfuscation")) { + allowObfuscation = true; } else if (arg.equals("--lib")) { addLibraryArgument(builder, origin, nextArg); } else if (arg.equals("--target")) { builder.addTargetFiles(Paths.get(nextArg)); } else if (arg.equals("--source")) { builder.addSourceFiles(Paths.get(nextArg)); - } else if (arg.equals("--format")) { - if (format != null) { - builder.error(new StringDiagnostic("--format specified multiple times")); - } - if (nextArg.equals("printuses")) { - format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE; - } - if (nextArg.equals("keep")) { - format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES; - } - if (nextArg.equals("keepallowobfuscation")) { - format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION; - } - if (format == null) { - builder.error(new StringDiagnostic("Unsupported format '" + nextArg + "'")); - } } else if (arg.equals("--output")) { - output = Paths.get(nextArg); + if (output != null) { + builder.error(new StringDiagnostic("Option '--output' passed multiple times.", origin)); + } else { + output = Paths.get(nextArg); + } } else if (arg.startsWith("@")) { builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin)); } else { @@ -133,28 +167,67 @@ i += argsConsumed; continue; } - builder.error(new StringDiagnostic("Unsupported argument '" + arg + "'")); + builder.error(new StringDiagnostic("Unsupported option '" + arg + "'", origin)); } } - if (format == null) { - format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE; + + if (command == null) { + builder.error( + new StringDiagnostic( + "Missing command, specify one of 'check', '--print-usage' or '--keep-rules'", + origin)); + return builder; } - final Path finalOutput = output; - builder.setConsumer( - new TraceReferencesFormattingConsumer(format) { - @Override - public void finished() { - PrintStream out = System.out; - if (finalOutput != null) { - try { - out = new PrintStream(Files.newOutputStream(finalOutput)); - } catch (IOException e) { - builder.error(new ExceptionDiagnostic(e)); + + if (command == Command.CHECK && output != null) { + builder.error( + new StringDiagnostic( + "Using '--output' requires command '--print-usage' or '--keep-rules'", origin)); + return builder; + } + + if (command != Command.KEEP_RULES && allowObfuscation) { + builder.error( + new StringDiagnostic( + "Using '--allowobfuscation' requires command '--keep-rules'", origin)); + return builder; + } + + switch (command) { + case CHECK: + builder.setConsumer(TraceReferencesConsumer.emptyConsumer()); + break; + case KEEP_RULES: + builder.setConsumer( + TraceReferencesKeepRules.builder() + .setAllowObfuscation(allowObfuscation) + .setOutputConsumer( + output != null + ? new FileConsumer(output) + : new WriterConsumer(null, new PrintWriter(System.out))) + .build()); + break; + case PRINTUSAGE: + final Path finalOutput = output; + builder.setConsumer( + new TraceReferencesPrintUsage() { + @Override + public void finished(DiagnosticsHandler handler) { + PrintStream out = System.out; + if (finalOutput != null) { + try { + out = new PrintStream(Files.newOutputStream(finalOutput)); + } catch (IOException e) { + handler.error(new ExceptionDiagnostic(e)); + } + } + out.print(get()); } - } - out.print(get()); - } - }); + }); + break; + default: + throw new Unreachable(); + } return builder; }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java index 5750fe1..f364c72 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.tracereferences; +import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.Keep; import com.android.tools.r8.KeepForSubclassing; import com.android.tools.r8.references.ClassReference; @@ -71,22 +72,64 @@ @Keep interface TracedMethod extends TracedReference<MethodReference, MethodAccessFlags> {} - /** Class has been traced. */ - void acceptType(TracedClass tracedClass); + /** + * Callback when class has been traced. + * + * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics + * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, + * then trace references guaranties to exit with an error. + * + * @param tracedClass Traced class + * @param handler Diagnostics handler for reporting. + */ + void acceptType(TracedClass tracedClass, DiagnosticsHandler handler); - /** Field has been traced. */ - void acceptField(TracedField tracedField); + /** + * Callback when class has been traced. + * + * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics + * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, + * then trace references guaranties to exit with an error. + * + * @param tracedField Traced field + * @param handler Diagnostics handler for reporting. + */ + void acceptField(TracedField tracedField, DiagnosticsHandler handler); - /** Method has been traced. */ - void acceptMethod(TracedMethod tracedMethod); + /** + * Callback when class has been traced. + * + * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics + * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, + * then trace references guaranties to exit with an error. + * + * @param tracedMethod Traced method + * @param handler Diagnostics handler for reporting. + */ + void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler); - /** Package which is required for package private access has been traced. */ - default void acceptPackage(PackageReference pkg) {} + /** + * Callback when package which is required for package private access has been traced. + * + * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics + * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, + * then trace references guaranties to exit with an error. + * + * @param pkg Traced package + * @param handler Diagnostics handler for reporting. + */ + default void acceptPackage(PackageReference pkg, DiagnosticsHandler handler) {} /** * Tracing has finished. There will be no more calls to any of the <code>acceptXXX</code> methods. + * + * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics + * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, + * then trace references guaranties to exit with an error. + * + * @param handler Diagnostics handler for reporting. */ - default void finished() {} + default void finished(DiagnosticsHandler handler) {} static TraceReferencesConsumer emptyConsumer() { return ForwardingConsumer.EMPTY_CONSUMER; @@ -105,37 +148,37 @@ } @Override - public void acceptType(TracedClass tracedClass) { + public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { if (consumer != null) { - consumer.acceptType(tracedClass); + consumer.acceptType(tracedClass, handler); } } @Override - public void acceptField(TracedField tracedField) { + public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { if (consumer != null) { - consumer.acceptField(tracedField); + consumer.acceptField(tracedField, handler); } } @Override - public void acceptMethod(TracedMethod tracedMethod) { + public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { if (consumer != null) { - consumer.acceptMethod(tracedMethod); + consumer.acceptMethod(tracedMethod, handler); } } @Override - public void acceptPackage(PackageReference pkg) { + public void acceptPackage(PackageReference pkg, DiagnosticsHandler handler) { if (consumer != null) { - consumer.acceptPackage(pkg); + consumer.acceptPackage(pkg, handler); } } @Override - public void finished() { + public void finished(DiagnosticsHandler handler) { if (consumer != null) { - consumer.finished(); + consumer.finished(handler); } } }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java deleted file mode 100644 index 67167c4..0000000 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java +++ /dev/null
@@ -1,79 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.tracereferences; - -import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.references.PackageReference; - -class TraceReferencesFormattingConsumer implements TraceReferencesConsumer { - - public enum OutputFormat { - /** Format used with the -printusage flag */ - PRINTUSAGE, - /** Keep rules keeping each of the traced references */ - KEEP_RULES, - /** - * Keep rules with <code>allowobfuscation</code> modifier keeping each of the traced references - */ - KEEP_RULES_WITH_ALLOWOBFUSCATION - } - - private final OutputFormat format; - private final TraceReferencesResult.Builder builder = TraceReferencesResult.builder(); - private boolean finishedCalled = false; - - public TraceReferencesFormattingConsumer(OutputFormat format) { - this.format = format; - } - - @Override - public void acceptType(TracedClass type) { - assert !finishedCalled; - builder.acceptType(type); - } - - @Override - public void acceptField(TracedField field) { - assert !finishedCalled; - builder.acceptField(field); - } - - @Override - public void acceptMethod(TracedMethod method) { - assert !finishedCalled; - builder.acceptMethod(method); - } - - @Override - public void acceptPackage(PackageReference pkg) { - assert !finishedCalled; - builder.acceptPackage(pkg); - } - - @Override - public void finished() { - assert !finishedCalled; - finishedCalled = true; - } - - public String get() { - TraceReferencesResult result = builder.build(); - Formatter formatter; - switch (format) { - case PRINTUSAGE: - formatter = new PrintUsesFormatter(); - break; - case KEEP_RULES: - formatter = new KeepRuleFormatter(false); - break; - case KEEP_RULES_WITH_ALLOWOBFUSCATION: - formatter = new KeepRuleFormatter(true); - break; - default: - throw new Unreachable(); - } - formatter.format(result); - return formatter.get(); - } -}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java new file mode 100644 index 0000000..6940f63 --- /dev/null +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java
@@ -0,0 +1,100 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.tracereferences; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.Keep; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.StringConsumer.FileConsumer; +import java.nio.file.Path; + +/** + * Consumer to format the result of running {@link TraceReferences} as keep rules. + * + * <p>To build an instance of this consumer, use the {@link TraceReferencesKeepRules.Builder} class. + * For example: + * + * <pre> + * TraceReferencesKeepRules consumer = TraceReferencesKeepRules.builder() + * .setAllowObfuscation(true) + * .setOutputPath(Paths.get("references-to-keep.rules")) + * .build(); + * </pre> + */ +@Keep +public class TraceReferencesKeepRules extends TraceReferencesConsumer.ForwardingConsumer { + + private final TraceReferencesResult.Builder traceReferencesResultBuilder; + private final StringConsumer consumer; + private final boolean allowObfuscation; + + private TraceReferencesKeepRules( + TraceReferencesResult.Builder traceReferencesResultBuilder, + StringConsumer consumer, + boolean allowObfuscation) { + super(traceReferencesResultBuilder); + this.traceReferencesResultBuilder = traceReferencesResultBuilder; + this.consumer = consumer; + this.allowObfuscation = allowObfuscation; + } + + /** + * Builder for constructing a {@link TraceReferencesKeepRules]. + * + * <p>A builder is obtained by calling {@link TraceReferencesKeepRules#builder}. + */ + @Keep + public static class Builder { + private StringConsumer consumer; + private boolean allowObfuscation; + + /** + * Indicate if the generated keep rules should have the <code>allobobfuscation</code> modifier. + */ + public Builder setAllowObfuscation(boolean value) { + allowObfuscation = value; + return this; + } + + /** + * Set the output of the keep rules to a file. + * + * @param output Path to write the output to. + */ + public Builder setOutputPath(Path output) { + this.consumer = new FileConsumer(output); + return this; + } + + /** + * Set the output of the keep rules to a {@link com.android.tools.r8.StringConsumer}. + * + * @param consumer Consumer to send the output to. + */ + public Builder setOutputConsumer(StringConsumer consumer) { + this.consumer = consumer; + return this; + } + + /** Build the {@link TraceReferencesKeepRules} instance. */ + public TraceReferencesKeepRules build() { + return new TraceReferencesKeepRules( + TraceReferencesResult.builder(), consumer, allowObfuscation); + } + } + + /** Create a builder for constructing an instance of {@link TraceReferencesKeepRules.Builder}. */ + public static Builder builder() { + return new Builder(); + } + + @Override + public void finished(DiagnosticsHandler handler) { + super.finished(handler); + Formatter formatter = new KeepRuleFormatter(allowObfuscation); + formatter.format(traceReferencesResultBuilder.build()); + consumer.accept(formatter.get(), handler); + consumer.finished(handler); + } +}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesPrintUsage.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesPrintUsage.java new file mode 100644 index 0000000..80e8096 --- /dev/null +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesPrintUsage.java
@@ -0,0 +1,50 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.tracereferences; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.references.PackageReference; + +class TraceReferencesPrintUsage implements TraceReferencesConsumer { + + private final TraceReferencesResult.Builder traceReferencesResultBuilder = + TraceReferencesResult.builder(); + private boolean finishedCalled = false; + + @Override + public void acceptType(TracedClass type, DiagnosticsHandler handler) { + assert !finishedCalled; + traceReferencesResultBuilder.acceptType(type, handler); + } + + @Override + public void acceptField(TracedField field, DiagnosticsHandler handler) { + assert !finishedCalled; + traceReferencesResultBuilder.acceptField(field, handler); + } + + @Override + public void acceptMethod(TracedMethod method, DiagnosticsHandler handler) { + assert !finishedCalled; + traceReferencesResultBuilder.acceptMethod(method, handler); + } + + @Override + public void acceptPackage(PackageReference pkg, DiagnosticsHandler handler) { + assert !finishedCalled; + traceReferencesResultBuilder.acceptPackage(pkg, handler); + } + + @Override + public void finished(DiagnosticsHandler handler) { + assert !finishedCalled; + finishedCalled = true; + } + + public String get() { + Formatter formatter = new PrintUsesFormatter(); + formatter.format(traceReferencesResultBuilder.build()); + return formatter.get(); + } +}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java index f8dab44..e40f3d7 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.tracereferences; +import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.FieldReference; import com.android.tools.r8.references.MethodReference; @@ -48,7 +49,7 @@ private final Set<PackageReference> keepPackageNames = new HashSet<>(); @Override - public void acceptType(TracedClass tracedClass) { + public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { types.add(tracedClass); if (tracedClass.isMissingDefinition()) { this.missingDefinition.add(tracedClass.getReference()); @@ -56,7 +57,7 @@ } @Override - public void acceptField(TracedField tracedField) { + public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { FieldReference field = tracedField.getReference(); fields.computeIfAbsent(field.getHolderClass(), k -> new HashSet<>()).add(tracedField); if (tracedField.isMissingDefinition()) { @@ -65,7 +66,7 @@ } @Override - public void acceptMethod(TracedMethod tracedMethod) { + public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { MethodReference method = tracedMethod.getReference(); methods.computeIfAbsent(method.getHolderClass(), k -> new HashSet<>()).add(tracedMethod); if (tracedMethod.isMissingDefinition()) { @@ -74,12 +75,12 @@ } @Override - public void acceptPackage(PackageReference pkg) { + public void acceptPackage(PackageReference pkg, DiagnosticsHandler handler) { keepPackageNames.add(pkg); } @Override - public void finished() {} + public void finished(DiagnosticsHandler handler) {} TraceReferencesResult build() { missingDefinition.forEach(
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java index 72db701..8b571a8 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java +++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -249,7 +249,7 @@ clazz.forEachProgramMethod(useCollector::registerMethod); clazz.forEachField(useCollector::registerField); } - consumer.finished(); + consumer.finished(diagnostics); useCollector.reportMissingDefinitions(); } @@ -287,10 +287,11 @@ TracedClassImpl tracedClass = new TracedClassImpl(type, clazz); checkMissingDefinition(tracedClass); if (isTargetType(type) || tracedClass.isMissingDefinition()) { - consumer.acceptType(tracedClass); + consumer.acceptType(tracedClass, diagnostics); if (!tracedClass.isMissingDefinition() && clazz.accessFlags.isVisibilityDependingOnPackage()) { - consumer.acceptPackage(Reference.packageFromString(clazz.type.getPackageName())); + consumer.acceptPackage( + Reference.packageFromString(clazz.type.getPackageName()), diagnostics); } } } @@ -305,10 +306,11 @@ TracedFieldImpl tracedField = new TracedFieldImpl(field, baseField); checkMissingDefinition(tracedField); if (isTargetType(field.holder) || tracedField.isMissingDefinition()) { - consumer.acceptField(tracedField); + consumer.acceptField(tracedField, diagnostics); if (!tracedField.isMissingDefinition() && baseField.accessFlags.isVisibilityDependingOnPackage()) { - consumer.acceptPackage(Reference.packageFromString(baseField.holder().getPackageName())); + consumer.acceptPackage( + Reference.packageFromString(baseField.holder().getPackageName()), diagnostics); } } } @@ -323,11 +325,12 @@ DexEncodedMethod definition = method.lookupOnClass(holder); TracedMethodImpl tracedMethod = new TracedMethodImpl(method, definition); if (isTargetType(method.holder) || tracedMethod.isMissingDefinition()) { - consumer.acceptMethod(tracedMethod); + consumer.acceptMethod(tracedMethod, diagnostics); checkMissingDefinition(tracedMethod); if (!tracedMethod.isMissingDefinition() && definition.accessFlags.isVisibilityDependingOnPackage()) { - consumer.acceptPackage(Reference.packageFromString(definition.holder().getPackageName())); + consumer.acceptPackage( + Reference.packageFromString(definition.holder().getPackageName()), diagnostics); } } }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java index f15c049..6e501fc 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -4,14 +4,12 @@ package com.android.tools.r8.utils; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.utils.structural.Ordered; import java.util.Arrays; -import java.util.Comparator; import java.util.List; -/** - * Android API level description - */ -public enum AndroidApiLevel { +/** Android API level description */ +public enum AndroidApiLevel implements Ordered<AndroidApiLevel> { B(1), B_1_1(2), C(3), @@ -70,9 +68,7 @@ } public static List<AndroidApiLevel> getAndroidApiLevelsSorted() { - List<AndroidApiLevel> androidApiLevels = Arrays.asList(AndroidApiLevel.values()); - androidApiLevels.sort(Comparator.comparingInt(AndroidApiLevel::getLevel)); - return androidApiLevels; + return Arrays.asList(AndroidApiLevel.values()); } public static AndroidApiLevel getMinAndroidApiLevel(DexVersion dexVersion) { @@ -157,20 +153,4 @@ return LATEST; } } - - public boolean isLessThan(AndroidApiLevel other) { - return this.level < other.getLevel(); - } - - public boolean isLessThanOrEqualTo(AndroidApiLevel other) { - return this.level <= other.getLevel(); - } - - public boolean isGreaterThan(AndroidApiLevel other) { - return other.isLessThan(this); - } - - public boolean isGreaterThanOrEqualTo(AndroidApiLevel other) { - return other.isLessThanOrEqualTo(this); - } }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java index c426a09..4478209 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.DexFilePerClassFileConsumer; import com.android.tools.r8.DexIndexedConsumer; import com.android.tools.r8.DirectoryClassFileProvider; +import com.android.tools.r8.DumpOptions; import com.android.tools.r8.FeatureSplit; import com.android.tools.r8.OutputMode; import com.android.tools.r8.ProgramResource; @@ -81,13 +82,16 @@ */ public class AndroidApp { + // TODO(b/172887664): Move to DumpOptions and capitalize. private static final String dumpVersionFileName = "r8-version"; private static final String dumpBuildPropertiesFileName = "build.properties"; private static final String dumpDesugaredLibraryFileName = "desugared-library.json"; + private static final String dumpMainDexListResourceFileName = "main-dex-list.txt"; private static final String dumpProgramFileName = "program.jar"; private static final String dumpClasspathFileName = "classpath.jar"; private static final String dumpLibraryFileName = "library.jar"; private static final String dumpConfigFileName = "proguard.config"; + private static final String dumpInputConfigFileName = "proguard_input.config"; private static Map<FeatureSplit, String> dumpFeatureSplitFileNames( FeatureSplitConfiguration featureSplitConfiguration) { @@ -454,7 +458,10 @@ return programResourcesMainDescriptor.get(resource); } - public void dump(Path output, InternalOptions options) { + public void dump(Path output, DumpOptions options, Reporter reporter, DexItemFactory factory) { + if (options == null) { + return; + } int nextDexIndex = 0; OpenOption[] openOptions = new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING}; @@ -462,50 +469,60 @@ writeToZipStream( out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED); writeToZipStream( - out, - dumpBuildPropertiesFileName, - getBuildPropertiesContents(options).getBytes(), - ZipEntry.DEFLATED); - if (options.desugaredLibraryConfiguration.getJsonSource() != null) { + out, dumpBuildPropertiesFileName, options.dumpOptions().getBytes(), ZipEntry.DEFLATED); + if (options.getDesugaredLibraryJsonSource() != null) { writeToZipStream( out, dumpDesugaredLibraryFileName, - options.desugaredLibraryConfiguration.getJsonSource().getBytes(), + options.getDesugaredLibraryJsonSource().getBytes(), ZipEntry.DEFLATED); - if (options.dumpInputToFile != null) { - options.reporter.warning( + if (options.dumpInputToFile()) { + reporter.warning( "Dumping a compilation with desugared library on a file may prevent reproduction," + " use dumpInputToDirectory property instead."); } } - if (options.getProguardConfiguration() != null) { - String proguardConfig = options.getProguardConfiguration().getParsedConfiguration(); + if (options.getParsedProguardConfiguration() != null) { + String proguardConfig = options.getParsedProguardConfiguration(); writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED); } + if (proguardMapInputData != null) { + reporter.warning( + "Dumping proguard map input data may have side effects due to I/O on Paths."); + writeToZipStream( + out, + dumpInputConfigFileName, + proguardMapInputData.getString().getBytes(), + ZipEntry.DEFLATED); + } + if (hasMainDexList()) { + List<String> mainDexList = new ArrayList<>(); + if (hasMainDexListResources()) { + reporter.warning( + "Dumping main dex list resources may have side effects due to I/O on Paths."); + for (StringResource mainDexListResource : getMainDexListResources()) { + mainDexList.add(mainDexListResource.getString()); + } + } + mainDexList.addAll(getMainDexClasses()); + String join = StringUtils.join(mainDexList, "\n"); + writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED); + } nextDexIndex = dumpProgramResources( dumpProgramFileName, - dumpFeatureSplitFileNames(options.featureSplitConfiguration), + options.getFeatureSplitConfiguration(), nextDexIndex, out, - options); + reporter, + factory); nextDexIndex = dumpClasspathResources(nextDexIndex, out); nextDexIndex = dumpLibraryResources(nextDexIndex, out); } catch (IOException | ResourceException e) { - throw options.reporter.fatalError(new ExceptionDiagnostic(e)); + throw reporter.fatalError(new ExceptionDiagnostic(e)); } } - private String getBuildPropertiesContents(InternalOptions options) { - return String.join( - "\n", - ImmutableList.of( - "mode=" + (options.debug ? "debug" : "release"), - "min-api=" + options.minApiLevel, - "tree-shaking=" + options.isTreeShakingEnabled(), - "minification=" + options.isMinificationEnabled())); - } - private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out) throws IOException, ResourceException { nextDexIndex = @@ -547,19 +564,22 @@ private int dumpProgramResources( String archiveName, - Map<FeatureSplit, String> featureSplitArchiveNames, + FeatureSplitConfiguration featureSplitConfiguration, int nextDexIndex, ZipOutputStream out, - InternalOptions options) + Reporter reporter, + DexItemFactory dexItemFactory) throws IOException, ResourceException { + + Map<FeatureSplit, String> featureSplitArchiveNames = + dumpFeatureSplitFileNames(featureSplitConfiguration); Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams = new IdentityHashMap<>(); Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>(); try { - DexItemFactory dexItemFactory = options.dexItemFactory(); - FeatureSplitConfiguration featureSplitConfiguration = options.featureSplitConfiguration; ClassToFeatureSplitMap classToFeatureSplitMap = - ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(options); + ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap( + dexItemFactory, featureSplitConfiguration, reporter); if (featureSplitConfiguration != null) { for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) { ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index c74ed38..9116bb5 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.DesugarGraphConsumer; import com.android.tools.r8.DexFilePerClassFileConsumer; import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.DumpOptions; import com.android.tools.r8.FeatureSplit; import com.android.tools.r8.ProgramConsumer; import com.android.tools.r8.StringConsumer; @@ -40,6 +41,7 @@ import com.android.tools.r8.graph.EnumValueInfoMapCollection; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses; +import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses; import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; import com.android.tools.r8.inspector.internal.InspectorImpl; @@ -227,6 +229,7 @@ public boolean enableNeverMergePrefixes = true; public Set<String> neverMergePrefixes = ImmutableSet.of("j$."); + public boolean classpathInterfacesMayHaveStaticInitialization = false; public boolean libraryInterfacesMayHaveStaticInitialization = false; // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations. @@ -235,6 +238,7 @@ System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null; public boolean enableStaticClassMerging = true; public boolean enableHorizontalClassMerging = true; + public boolean enableHorizontalClassMergingOfKotlinLambdas = true; public boolean enableVerticalClassMerging = true; public boolean enableArgumentRemoval = true; public boolean enableUnusedInterfaceRemoval = true; @@ -333,6 +337,9 @@ // Boolean value indicating that byte code pass through may be enabled. public boolean enableCfByteCodePassThrough = false; + // Contain the contents of the build properties file from the compiler command. + public DumpOptions dumpOptions; + // Hidden marker for classes.dex private boolean hasMarker = false; private Marker marker; @@ -1176,6 +1183,8 @@ // b/172508621 public boolean sortMethodsOnCfOutput = System.getProperty("com.android.tools.r8.sortMethodsOnCfWriting") != null; + public boolean allowDesugaredInput = + System.getProperty("com.android.tools.r8.allowDesugaredInput") != null; } public static class CallSiteOptimizationOptions { @@ -1274,6 +1283,9 @@ public BiConsumer<DexItemFactory, HorizontallyMergedLambdaClasses> horizontallyMergedLambdaClassesConsumer = ConsumerUtils.emptyBiConsumer(); + public BiConsumer<DexItemFactory, StaticallyMergedClasses> staticallyMergedClassesConsumer = + ConsumerUtils.emptyBiConsumer(); + public BiConsumer<DexItemFactory, EnumValueInfoMapCollection> unboxedEnumsConsumer = ConsumerUtils.emptyBiConsumer(); @@ -1338,6 +1350,8 @@ public boolean forceIRForCfToCfDesugar = System.getProperty("com.android.tools.r8.forceIRForCfToCfDesugar") != null; public boolean disableMappingToOriginalProgramVerification = false; + public boolean allowInvalidCfAccessFlags = + System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null; // Flag to allow processing of resources in D8. A data resource consumer still needs to be // specified. @@ -1425,7 +1439,7 @@ public boolean canUseConstClassInstructions(CfVersion cfVersion) { assert isGeneratingClassFiles(); - return cfVersion.isGreaterThanOrEqual(requiredCfVersionForConstClassInstructions()); + return cfVersion.isGreaterThanOrEqualTo(requiredCfVersionForConstClassInstructions()); } public CfVersion requiredCfVersionForConstClassInstructions() {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java index f2358c5..51a3778 100644 --- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java +++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -308,7 +308,7 @@ // Then process the methods, ordered by renamed name. List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet()); - renamedMethodNames.sort(DexString::slowCompareTo); + renamedMethodNames.sort(DexString::compareTo); for (DexString methodName : renamedMethodNames) { List<DexEncodedMethod> methods = methodsByRenamedName.get(methodName); if (methods.size() > 1) {
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java index 51821c0..d3c376f 100644 --- a/src/main/java/com/android/tools/r8/utils/ListUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -47,13 +48,12 @@ return result; } - public static <T> boolean removeFirstMatch(List<T> list, Predicate<T> element) { + public static <T> Optional<T> removeFirstMatch(List<T> list, Predicate<T> element) { int index = firstIndexMatching(list, element); if (index >= 0) { - list.remove(index); - return true; + return Optional.of(list.remove(index)); } - return false; + return Optional.empty(); } public static <T extends Comparable<T>> boolean verifyListIsOrdered(List<T> list) {
diff --git a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java index 1f3ad9d..216f19b 100644 --- a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
@@ -22,4 +22,14 @@ } return null; } + + /** + * If the object is null return the default value, otherwise compute the function with the value. + */ + public static <S, T> T mapNotNullOrDefault(S object, T def, Function<? super S, ? extends T> fn) { + if (object != null) { + return fn.apply(object); + } + return def; + } }
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java index f33ed6f..400ef0c 100644 --- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -68,7 +69,11 @@ } public static void iter(String zipFileStr, OnEntryHandler handler) throws IOException { - try (ZipFile zipFile = new ZipFile(zipFileStr, StandardCharsets.UTF_8)) { + iter(Paths.get(zipFileStr), handler); + } + + public static void iter(Path zipFilePath, OnEntryHandler handler) throws IOException { + try (ZipFile zipFile = new ZipFile(zipFilePath.toFile(), StandardCharsets.UTF_8)) { final Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); @@ -79,6 +84,12 @@ } } + public static byte[] readSingleEntry(Path zipFilePath, String name) throws IOException { + try (ZipFile zipFile = new ZipFile(zipFilePath.toFile(), StandardCharsets.UTF_8)) { + return ByteStreams.toByteArray(zipFile.getInputStream(zipFile.getEntry(name))); + } + } + public static void zip(Path zipFile, Path inputDirectory) throws IOException { List<Path> files = Files.walk(inputDirectory) @@ -226,4 +237,8 @@ return zipFile; } } + + public static String zipEntryNameForClass(Class<?> clazz) { + return DescriptorUtils.getClassBinaryName(clazz) + CLASS_EXTENSION; + } }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java index 7410339..361b761 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java +++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.utils.collections; -import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.LinkedHashSet; @@ -14,13 +13,38 @@ public class BidirectionalManyToOneMap<K, V> { - private final Map<K, V> backing = new IdentityHashMap<>(); - private final Map<V, Set<K>> inverse = new IdentityHashMap<>(); + private final Map<K, V> backing; + private final Map<V, Set<K>> inverse; + + public BidirectionalManyToOneMap() { + this(new IdentityHashMap<>(), new IdentityHashMap<>()); + } + + private BidirectionalManyToOneMap(Map<K, V> backing, Map<V, Set<K>> inverse) { + this.backing = backing; + this.inverse = inverse; + } + + public static <K, V> BidirectionalManyToOneMap<K, V> empty() { + return new BidirectionalManyToOneMap<>(Collections.emptyMap(), Collections.emptyMap()); + } + + public boolean containsKey(K key) { + return backing.containsKey(key); + } + + public boolean containsValue(V value) { + return inverse.containsKey(value); + } public void forEach(BiConsumer<Set<K>, V> consumer) { inverse.forEach((value, keys) -> consumer.accept(keys, value)); } + public V get(K key) { + return backing.get(key); + } + public V getOrDefault(K key, V value) { return backing.getOrDefault(key, value); } @@ -64,13 +88,25 @@ } } + public Set<K> removeValue(V value) { + Set<K> keys = inverse.remove(value); + if (keys == null) { + return Collections.emptySet(); + } + for (K key : keys) { + V removedValue = backing.remove(key); + assert removedValue == value; + } + return keys; + } + public void put(K key, V value) { remove(key); backing.put(key, value); inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key); } - public Collection<V> values() { - return backing.values(); + public Set<V> values() { + return inverse.keySet(); } }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java index 2412cfd..f058bf1 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -53,6 +53,12 @@ return result; } + public static ProgramMethodSet create(ProgramMethodSet methodSet) { + ProgramMethodSet newMethodSet = create(); + newMethodSet.addAll(methodSet); + return newMethodSet; + } + public static ProgramMethodSet createConcurrent() { return new ProgramMethodSet(ConcurrentHashMap::new); }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java index d902126..d625aae 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
@@ -41,13 +41,13 @@ public static SortedProgramMethodSet create(ForEachable<ProgramMethod> methods) { SortedProgramMethodSet result = - new SortedProgramMethodSet(() -> new TreeMap<>(DexMethod::slowCompareTo)); + new SortedProgramMethodSet(() -> new TreeMap<>(DexMethod::compareTo)); methods.forEach(result::add); return result; } public static SortedProgramMethodSet createConcurrent() { - return new SortedProgramMethodSet(() -> new ConcurrentSkipListMap<>(DexMethod::slowCompareTo)); + return new SortedProgramMethodSet(() -> new ConcurrentSkipListMap<>(DexMethod::compareTo)); } public static SortedProgramMethodSet empty() { @@ -64,7 +64,7 @@ @Override public Set<DexEncodedMethod> toDefinitionSet() { Comparator<DexEncodedMethod> comparator = - (x, y) -> x.getReference().slowCompareTo(y.getReference()); + (x, y) -> x.getReference().compareTo(y.getReference()); Set<DexEncodedMethod> definitions = new TreeSet<>(comparator); forEach(method -> definitions.add(method.getDefinition())); return definitions;
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java new file mode 100644 index 0000000..832d172 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
@@ -0,0 +1,39 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexTypeList; +import java.util.Comparator; + +/** Base class for a visitor implementing compareTo on a structural item. */ +public abstract class CompareToVisitor { + + public abstract void visitBool(boolean value1, boolean value2); + + public abstract void visitInt(int value1, int value2); + + public abstract void visitDexString( + DexString string1, DexString string2, Comparator<DexString> comparator); + + public abstract void visitDexType(DexType type1, DexType type2); + + public abstract void visitDexTypeList(DexTypeList types1, DexTypeList types2); + + public void visitDexField(DexField field1, DexField field2) { + visit(field1, field2, field1.getStructuralAccept()); + } + + public void visitDexMethod(DexMethod method1, DexMethod method2) { + visit(method1, method2, method1.getStructuralAccept()); + } + + public abstract <S> void visit(S item1, S item2, StructuralAccept<S> accept); + + @Deprecated + public abstract <S> void visit(S item1, S item2, Comparator<S> comparator); +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java new file mode 100644 index 0000000..b7d016a --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
@@ -0,0 +1,132 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexTypeList; +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; +import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept; +import java.util.Comparator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; + +/** Base class to share most visiting methods */ +public abstract class CompareToVisitorBase extends CompareToVisitor { + + private int order = 0; + + public final boolean stillEqual() { + return order == 0; + } + + public final int getOrder() { + return order; + } + + public final void setOrder(int order) { + this.order = order; + } + + @Override + public final void visitBool(boolean value1, boolean value2) { + if (stillEqual()) { + setOrder(Boolean.compare(value1, value2)); + } + } + + @Override + public final void visitInt(int value1, int value2) { + if (stillEqual()) { + setOrder(Integer.compare(value1, value2)); + } + } + + @Override + public final void visitDexString( + DexString string1, DexString string2, Comparator<DexString> comparator) { + if (stillEqual()) { + setOrder(comparator.compare(string1, string2)); + } + } + + @Override + public final void visitDexTypeList(DexTypeList types1, DexTypeList types2) { + // Comparison is lexicographic with comparisons between items prior to the length of the lists. + if (stillEqual()) { + int length = Math.min(types1.size(), types2.size()); + for (int i = 0; i < length && stillEqual(); i++) { + visitDexType(types1.values[i], types2.values[i]); + } + if (stillEqual()) { + visitInt(types1.size(), types2.size()); + } + } + } + + @Override + public final <S> void visit(S item1, S item2, Comparator<S> comparator) { + if (stillEqual()) { + setOrder(comparator.compare(item1, item2)); + } + } + + @Override + public final <S> void visit(S item1, S item2, StructuralAccept<S> accept) { + if (stillEqual()) { + accept.accept(new ItemSpecification<>(item1, item2, this)); + } + } + + private static class ItemSpecification<T> + extends StructuralSpecification<T, ItemSpecification<T>> { + + private final CompareToVisitorBase parent; + private final T item1; + private final T item2; + + private ItemSpecification(T item1, T item2, CompareToVisitorBase parent) { + this.item1 = item1; + this.item2 = item2; + this.parent = parent; + } + + @Override + public ItemSpecification<T> withAssert(Predicate<T> predicate) { + assert predicate.test(item1); + assert predicate.test(item2); + return this; + } + + @Override + public ItemSpecification<T> withBool(Predicate<T> getter) { + parent.visitBool(getter.test(item1), getter.test(item2)); + return this; + } + + @Override + public ItemSpecification<T> withInt(ToIntFunction<T> getter) { + parent.visitInt(getter.applyAsInt(item1), getter.applyAsInt(item2)); + return this; + } + + @Override + public <S> ItemSpecification<T> withConditionalCustomItem( + Predicate<T> predicate, + Function<T, S> getter, + CompareToAccept<S> compare, + HashingAccept<S> hasher) { + if (parent.stillEqual()) { + boolean test1 = predicate.test(item1); + boolean test2 = predicate.test(item2); + if (test1 && test2) { + compare.accept(getter.apply(item1), getter.apply(item2), parent); + } else { + parent.visitBool(test1, test2); + } + } + return this; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java new file mode 100644 index 0000000..a16575d --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
@@ -0,0 +1,63 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; + +public class CompareToVisitorWithNamingLens extends CompareToVisitorBase { + + public static <T> int run(T item1, T item2, NamingLens namingLens, StructuralAccept<T> visit) { + return run(item1, item2, namingLens, (i1, i2, visitor) -> visitor.visit(i1, i2, visit)); + } + + public static <T> int run( + T item1, T item2, NamingLens namingLens, CompareToAccept<T> compareToAccept) { + CompareToVisitorWithNamingLens state = new CompareToVisitorWithNamingLens(namingLens); + compareToAccept.accept(item1, item2, state); + return state.getOrder(); + } + + private final NamingLens namingLens; + + public CompareToVisitorWithNamingLens(NamingLens namingLens) { + this.namingLens = namingLens; + } + + @Override + public void visitDexType(DexType type1, DexType type2) { + if (stillEqual()) { + namingLens.lookupDescriptor(type1).acceptCompareTo(namingLens.lookupDescriptor(type2), this); + } + } + + @Override + public void visitDexField(DexField field1, DexField field2) { + if (stillEqual()) { + field1.holder.acceptCompareTo(field2.holder, this); + if (stillEqual()) { + namingLens.lookupName(field1).acceptCompareTo(namingLens.lookupName(field2), this); + if (stillEqual()) { + field1.type.acceptCompareTo(field2.type, this); + } + } + } + } + + @Override + public void visitDexMethod(DexMethod method1, DexMethod method2) { + if (stillEqual()) { + method1.holder.acceptCompareTo(method2.holder, this); + if (stillEqual()) { + namingLens.lookupName(method1).acceptCompareTo(namingLens.lookupName(method2), this); + if (stillEqual()) { + method1.proto.acceptCompareTo(method2.proto, this); + } + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java new file mode 100644 index 0000000..f77ab53 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
@@ -0,0 +1,36 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; + +public class CompareToVisitorWithTypeEquivalence extends CompareToVisitorBase { + + public static <T> int run(T item1, T item2, RepresentativeMap map, StructuralAccept<T> visit) { + return run(item1, item2, map, (i1, i2, visitor) -> visitor.visit(i1, i2, visit)); + } + + public static <T> int run( + T item1, T item2, RepresentativeMap map, CompareToAccept<T> compareToAccept) { + CompareToVisitorWithTypeEquivalence state = new CompareToVisitorWithTypeEquivalence(map); + compareToAccept.accept(item1, item2, state); + return state.getOrder(); + } + + private final RepresentativeMap representatives; + + public CompareToVisitorWithTypeEquivalence(RepresentativeMap representatives) { + this.representatives = representatives; + } + + @Override + public void visitDexType(DexType type1, DexType type2) { + if (stillEqual()) { + DexType repr1 = representatives.getRepresentative(type1); + DexType repr2 = representatives.getRepresentative(type2); + repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/DefaultCompareToVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/DefaultCompareToVisitor.java new file mode 100644 index 0000000..9f91d19 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/DefaultCompareToVisitor.java
@@ -0,0 +1,23 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; + +/** + * Default implementation for defining compareTo on a structural type. + * + * <p>Internally this is using CompareToVisitorWithTypeEquivalence with the identity map, but should + * not be assumed to have that implementation. + */ +public class DefaultCompareToVisitor { + + public static <T> int run(T item1, T item2, StructuralAccept<T> visit) { + return run(item1, item2, (i1, i2, visitor) -> visitor.visit(i1, i2, visit)); + } + + public static <T> int run(T item1, T item2, CompareToAccept<T> compareToAccept) { + return CompareToVisitorWithTypeEquivalence.run(item1, item2, t -> t, compareToAccept); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java new file mode 100644 index 0000000..2ec503c --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java
@@ -0,0 +1,24 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept; +import com.google.common.hash.Hasher; + +/** + * Default visitor for hashing a structural item. + * + * <p>Internally this is using HashingVisitorWithTypeEquivalence with the identity map, but should + * not be assumed to have that implementation. + */ +public class DefaultHashingVisitor { + + public static <T> void run(T item, Hasher hasher, StructuralAccept<T> accept) { + run(item, hasher, (i, visitor) -> visitor.visit(i, accept)); + } + + public static <T> void run(T item, Hasher hasher, HashingAccept<T> hashingAccept) { + HashingVisitorWithTypeEquivalence.run(item, hasher, t -> t, hashingAccept); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/Equatable.java b/src/main/java/com/android/tools/r8/utils/structural/Equatable.java new file mode 100644 index 0000000..be49958 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/Equatable.java
@@ -0,0 +1,56 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +public interface Equatable<T> { + + /** + * Typed definition of equality. + * + * <p>Subclasses must implement this and override Object.equals(Object) with equalsImpl. + */ + boolean isEqualTo(T other); + + /** + * An equatable type must define an equality compatible hashing. + * + * <p>Note: that that the declaration here will not enforce an implementation in the concrete + * class and it cannot be defined by a default method. Implementors of Equatable must ensure to + * override it. + */ + @Override + int hashCode(); + + /** + * An equatable type must override Object.equals by the intended implementation below. + * + * <p>Note: that that the declaration here will not enforce an implementation in the concrete + * class and it cannot be defined by a default method. Implementors of Equatable must ensure to + * override it. + */ + @Override + boolean equals(Object other); + + /** + * Implementation for Object.equals(Object). + * + * <p>It is not possible to define default methods on java.lang.Object, thus concrete subclasses + * must manually override equals as: + * + * <pre> + * @Override boolean equals(Object other) { return Equatable.equalsImpl(this, other); } + * </pre> + */ + @SuppressWarnings("unchecked") + static <T extends Equatable<T>> boolean equalsImpl(T self, Object other) { + assert self != null; + if (self == other) { + return true; + } + if (other == null || self.getClass() != other.getClass()) { + return false; + } + return self.isEqualTo((T) other); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java new file mode 100644 index 0000000..ab75fb4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
@@ -0,0 +1,72 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; +import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; + +/** + * Simple hash code implementation. + * + * <p>This visitor relies on the specification of hashCode on all object types. Thus it does not + * have a call-back structure that requires the spec implementation as well as a visitor for the + * recursive decent. There is also no support for overriding the visitation apart from the usual + * override of hashCode(). + */ +public class HashCodeVisitor<T> extends StructuralSpecification<T, HashCodeVisitor<T>> { + + public static <T> int run(T item, StructuralAccept<T> visit) { + HashCodeVisitor<T> visitor = new HashCodeVisitor<>(item); + visit.accept(visitor); + return visitor.hashCode; + } + + private final T item; + + private int hashCode = 0; + + private HashCodeVisitor(T item) { + this.item = item; + } + + private HashCodeVisitor<T> amend(int value) { + // This mirrors the behavior of Objects.hash(values...) / Arrays.hashCode(array). + hashCode = 31 * hashCode + value; + return this; + } + + @Override + public HashCodeVisitor<T> withAssert(Predicate<T> predicate) { + assert predicate.test(item); + return this; + } + + @Override + public HashCodeVisitor<T> withBool(Predicate<T> getter) { + return amend(Boolean.hashCode(getter.test(item))); + } + + @Override + public HashCodeVisitor<T> withInt(ToIntFunction<T> getter) { + return amend(Integer.hashCode(getter.applyAsInt(item))); + } + + @Override + protected <S> HashCodeVisitor<T> withConditionalCustomItem( + Predicate<T> predicate, + Function<T, S> getter, + CompareToAccept<S> compare, + HashingAccept<S> hasher) { + if (predicate.test(item)) { + return amend(getter.apply(item).hashCode()); + } else { + // Use the value 1 for the failing-predicate case such that a different hash is obtained for + // eg, {null, null} and {null}. + return amend(1); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java new file mode 100644 index 0000000..bfd5e0d --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
@@ -0,0 +1,28 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexTypeList; +import com.google.common.hash.Hasher; +import java.util.function.BiConsumer; + +public abstract class HashingVisitor { + + public abstract void visitBool(boolean value); + + public abstract void visitInt(int value); + + public abstract void visitDexString(DexString string); + + public abstract void visitDexType(DexType type); + + public abstract void visitDexTypeList(DexTypeList types); + + public abstract <S> void visit(S item, StructuralAccept<S> accept); + + @Deprecated + public abstract <S> void visit(S item, BiConsumer<S, Hasher> hasher); +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java new file mode 100644 index 0000000..2036b1b --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
@@ -0,0 +1,117 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexTypeList; +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; +import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept; +import com.google.common.hash.Hasher; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; + +/** Visitor for hashing a structural item under some assumed type equivalence. */ +public class HashingVisitorWithTypeEquivalence extends HashingVisitor { + + public static <T> void run( + T item, Hasher hasher, RepresentativeMap map, StructuralAccept<T> accept) { + run(item, hasher, map, (i, visitor) -> visitor.visit(i, accept)); + } + + public static <T> void run( + T item, Hasher hasher, RepresentativeMap map, HashingAccept<T> hashingAccept) { + hashingAccept.accept(item, new HashingVisitorWithTypeEquivalence(hasher, map)); + } + + private final Hasher hash; + private final RepresentativeMap representatives; + + private HashingVisitorWithTypeEquivalence(Hasher hash, RepresentativeMap representatives) { + this.hash = hash; + this.representatives = representatives; + } + + @Override + public void visitBool(boolean value) { + hash.putBoolean(value); + } + + @Override + public void visitInt(int value) { + hash.putInt(value); + } + + @Override + public void visitDexString(DexString string) { + visitInt(string.hashCode()); + } + + @Override + public void visitDexType(DexType type) { + visitDexString(representatives.getRepresentative(type).getDescriptor()); + } + + @Override + public void visitDexTypeList(DexTypeList types) { + types.forEach(this::visitDexType); + } + + @Override + public <S> void visit(S item, StructuralAccept<S> accept) { + accept.accept(new ItemSpecification<>(item, this)); + } + + @Override + public <S> void visit(S item, BiConsumer<S, Hasher> hasher) { + hasher.accept(item, hash); + } + + private static class ItemSpecification<T> + extends StructuralSpecification<T, ItemSpecification<T>> { + + private final HashingVisitorWithTypeEquivalence parent; + private final T item; + + private ItemSpecification(T item, HashingVisitorWithTypeEquivalence parent) { + this.item = item; + this.parent = parent; + } + + @Override + public ItemSpecification<T> withAssert(Predicate<T> predicate) { + assert predicate.test(item); + return this; + } + + @Override + public ItemSpecification<T> withBool(Predicate<T> getter) { + parent.visitBool(getter.test(item)); + return this; + } + + @Override + public ItemSpecification<T> withInt(ToIntFunction<T> getter) { + parent.visitInt(getter.applyAsInt(item)); + return this; + } + + @Override + protected <S> ItemSpecification<T> withConditionalCustomItem( + Predicate<T> predicate, + Function<T, S> getter, + CompareToAccept<S> compare, + HashingAccept<S> hasher) { + boolean test = predicate.test(item); + // Always hash the predicate result to distinguish, eg, {null, null} and {null}. + parent.visitBool(test); + if (test) { + hasher.accept(getter.apply(item), parent); + } + return this; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/Ordered.java b/src/main/java/com/android/tools/r8/utils/structural/Ordered.java new file mode 100644 index 0000000..9c9a588 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/Ordered.java
@@ -0,0 +1,63 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +/** An ordered type is a type with a total order. */ +public interface Ordered<T> extends Equatable<T>, Comparable<T> { + + /** Definition of total order. */ + @Override + int compareTo(T other); + + /** Default equality is now defined by the order. */ + @Override + default boolean isEqualTo(T other) { + assert other != null; + return this == other || compareTo(other) == 0; + } + + static <T extends Ordered<T>> T min(T o1, T o2) { + return o1.isLessThan(o2) ? o1 : o2; + } + + static <T extends Ordered<T>> T max(T o1, T o2) { + return o1.isLessThan(o2) ? o2 : o1; + } + + static <T extends Ordered<T>> T minIgnoreNull(T o1, T o2) { + if (o1 == null) { + return o2; + } + if (o2 == null) { + return o1; + } + return min(o1, o2); + } + + static <T extends Ordered<T>> T maxIgnoreNull(T o1, T o2) { + if (o1 == null) { + return o2; + } + if (o2 == null) { + return o1; + } + return o1.isLessThan(o2) ? o2 : o1; + } + + default boolean isLessThan(T other) { + return compareTo(other) < 0; + } + + default boolean isLessThanOrEqualTo(T other) { + return compareTo(other) <= 0; + } + + default boolean isGreaterThan(T other) { + return compareTo(other) > 0; + } + + default boolean isGreaterThanOrEqualTo(T other) { + return compareTo(other) >= 0; + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/RepresentativeMap.java b/src/main/java/com/android/tools/r8/utils/structural/RepresentativeMap.java new file mode 100644 index 0000000..6a0af38 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/RepresentativeMap.java
@@ -0,0 +1,11 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.graph.DexType; + +@FunctionalInterface +public interface RepresentativeMap { + DexType getRepresentative(DexType type); +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralAccept.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralAccept.java new file mode 100644 index 0000000..1c6d25c --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralAccept.java
@@ -0,0 +1,9 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +@FunctionalInterface +public interface StructuralAccept<T> { + void accept(StructuralSpecification<T, ?> visitor); +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java new file mode 100644 index 0000000..f077c25 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java
@@ -0,0 +1,88 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; + +/** Specified types must implement methods to determine equality, hashing and order. */ +public interface StructuralItem<T extends StructuralItem<T>> extends Ordered<T> { + + T self(); + + StructuralAccept<T> getStructuralAccept(); + + // CompareTo implementation and callbacks. + + @FunctionalInterface + interface CompareToAccept<T> { + void accept(T item1, T item2, CompareToVisitor visitor); + } + + /** + * Implementation of the default compareTo on the item. + * + * <p>This should *not* be overwritten, instead items should overwrite acceptCompareTo which will + * ensure that the effect is in place for any CompareToVisitor. + */ + @Override + default int compareTo(T other) { + return DefaultCompareToVisitor.run(self(), other, StructuralItem::acceptCompareTo); + } + + /** + * Implementation of a compareTo with a type equivalence on an item. + * + * <p>This should *not* be overwritten, instead items should overwrite acceptCompareTo which will + * ensure that the effect is in place for any CompareToVisitor. + */ + default int compareWithTypeEquivalenceTo(T other, RepresentativeMap map) { + return CompareToVisitorWithTypeEquivalence.run( + self(), other, map, StructuralItem::acceptCompareTo); + } + + /** Default accept for compareTo visitors. Override to change behavior. */ + default void acceptCompareTo(T other, CompareToVisitor visitor) { + visitor.visit(self(), other, self().getStructuralAccept()); + } + + // Hashing implemenation and callbacks. + + @FunctionalInterface + interface HashingAccept<T> { + void accept(T item, HashingVisitor visitor); + } + + /** + * Implementation of the default hashing of an item. + * + * <p>This should *not* be overwritten, instead items should overwrite acceptHashing which will + * ensure that the effect is in place for any HashingVisitor. + */ + default void hash(Hasher hasher) { + DefaultHashingVisitor.run(self(), hasher, self().getStructuralAccept()); + } + + /** Hashing method to use from tests to avoid having guava types shared between R8 and tests. */ + default String hashForTesting() { + Hasher hasher = Hashing.sha256().newHasher(); + hash(hasher); + return hasher.hash().toString(); + } + + /** + * Implementation of the default hashing with a type equivalence on the item. + * + * <p>This should *not* be overwritten, instead items should overwrite acceptHashing which will + * ensure that the effect is in place for any HashingVisitor. + */ + default void hashWithTypeEquivalence(Hasher hasher, RepresentativeMap map) { + HashingVisitorWithTypeEquivalence.run(self(), hasher, map, self().getStructuralAccept()); + } + + /** Default accept for hashing visitors. Override to change behavior. */ + default void acceptHashing(HashingVisitor visitor) { + visitor.visit(self(), self().getStructuralAccept()); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java new file mode 100644 index 0000000..bb8f7c7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
@@ -0,0 +1,65 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept; +import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; + +public abstract class StructuralSpecification<T, V extends StructuralSpecification<T, V>> { + + /** + * Basic specification for visiting an item. + * + * <p>This specified the getter for the item as well as all of the methods that are required for + * visiting. Those coincide with the requirements of Specified. + * + * <p>It is preferable to use withStructuralItem. + */ + @Deprecated + public final <S> V withCustomItem( + Function<T, S> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) { + return withConditionalCustomItem(t -> true, getter, compare, hasher); + } + + protected abstract <S> V withConditionalCustomItem( + Predicate<T> predicate, + Function<T, S> getter, + CompareToAccept<S> compare, + HashingAccept<S> hasher); + + /** + * Specification for a "specified" item. + * + * <p>Using this the visiting methods are could based on the implementation of the Specified + * interface. + */ + public final <S extends StructuralItem<S>> V withItem(Function<T, S> getter) { + return withConditionalItem(t -> true, getter); + } + + final <S extends StructuralItem<S>> V withNullableItem(Function<T, S> getter) { + return withConditionalItem(s -> getter.apply(s) != null, getter); + } + + public final <S extends StructuralItem<S>> V withConditionalItem( + Predicate<T> predicate, Function<T, S> getter) { + return withConditionalCustomItem(predicate, getter, S::acceptCompareTo, S::acceptHashing); + } + + /** + * Helper to declare an assert on the item. + * + * <p>Only run if running with -ea. Must be run on any item being visited (ie, both in the case of + * comparisons and equality). + */ + public abstract V withAssert(Predicate<T> predicate); + + // Primitive Java types. These will need overriding to avoid boxing. + public abstract V withBool(Predicate<T> getter); + + public abstract V withInt(ToIntFunction<T> getter); +}
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java index 441be7c..8b7d21e 100644 --- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java +++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.DataResourceProvider.Visitor; import com.android.tools.r8.ProgramResource.Kind; +import com.android.tools.r8.dex.Marker.Tool; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; @@ -42,6 +43,7 @@ @Test public void test() throws Exception { InternalOptions options = new InternalOptions(); + options.dumpOptions = DumpOptions.builder(Tool.D8).build(); String dataResourceName = "my-resource.bin"; byte[] dataResourceData = new byte[] {1, 2, 3}; @@ -67,7 +69,7 @@ .build(); Path dumpFile = temp.newFolder().toPath().resolve("dump.zip"); - appIn.dump(dumpFile, options); + appIn.dump(dumpFile, options.dumpOptions, options.reporter, options.dexItemFactory()); AndroidApp appOut = AndroidApp.builder(options.reporter).addDump(dumpFile).build(); assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java index 9167acd..058c2e5 100644 --- a/src/test/java/com/android/tools/r8/R8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -428,6 +428,44 @@ addInternalKeepRules(sb.toString()); } + public T noClassInlining() { + return noClassInlining(true); + } + + public T noClassInlining(boolean condition) { + if (condition) { + return addOptionsModification(options -> options.enableClassInlining = false); + } + return self(); + } + + public T noClassStaticizing() { + return noClassStaticizing(true); + } + + public T noClassStaticizing(boolean condition) { + if (condition) { + return addOptionsModification(options -> options.enableClassStaticizer = false); + } + return self(); + } + + public T noHorizontalClassMerging() { + return noHorizontalClassMerging(true); + } + + public T noHorizontalClassMerging(boolean condition) { + if (condition) { + return addKeepRules("-" + NoHorizontalClassMergingRule.RULE_NAME + " class *"); + } + return self(); + } + + public T noHorizontalClassMerging(Class<?> clazz) { + return addKeepRules( + "-" + NoHorizontalClassMergingRule.RULE_NAME + " class " + clazz.getTypeName()); + } + public T enableNoUnusedInterfaceRemovalAnnotations() { if (!enableNoUnusedInterfaceRemovalAnnotations) { enableNoUnusedInterfaceRemovalAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java index 3d24da4..abdfb83 100644 --- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector; import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; import com.android.tools.r8.utils.codeinspector.HorizontallyMergedLambdaClassesInspector; +import com.android.tools.r8.utils.codeinspector.StaticallyMergedClassesInspector; import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; import com.google.common.base.Suppliers; import java.io.ByteArrayOutputStream; @@ -147,6 +148,17 @@ dexItemFactory, horizontallyMergedLambdaClasses)))); } + public T addStaticallyMergedClassesInspector( + Consumer<StaticallyMergedClassesInspector> inspector) { + return addOptionsModification( + options -> + options.testing.staticallyMergedClassesConsumer = + ((dexItemFactory, staticallyMergedClasses) -> + inspector.accept( + new StaticallyMergedClassesInspector( + dexItemFactory, staticallyMergedClasses)))); + } + public T addVerticallyMergedClassesInspector( Consumer<VerticallyMergedClassesInspector> inspector) { return addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java index 5f674e0..6f6c949 100644 --- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -251,6 +251,10 @@ return self(); } + public T addPrintSeeds() { + return addKeepRules("-printseeds"); + } + public T allowAccessModification() { return allowAccessModification(true); } @@ -275,6 +279,10 @@ return addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE); } + public T addKeepAttributeSignature() { + return addKeepAttributes(ProguardKeepAttributes.SIGNATURE); + } + public T addKeepAttributeSourceFile() { return addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE); }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java index c9f7d21..81c5c04 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -72,6 +72,7 @@ .transform()) .addKeepMainRule(TestClass.class) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNoVerticalClassMergingAnnotations() .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java index 9990f62..e7d430f 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -5,12 +5,14 @@ package com.android.tools.r8.bridgeremoval.hoisting.testclasses; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest; import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke; public class BridgeHoistingAccessibilityTestClasses { + @NoHorizontalClassMerging @NoVerticalClassMerging public static class A {
diff --git a/src/test/java/com/android/tools/r8/cf/CfVersionTest.java b/src/test/java/com/android/tools/r8/cf/CfVersionTest.java index 48871e8..ea38851 100644 --- a/src/test/java/com/android/tools/r8/cf/CfVersionTest.java +++ b/src/test/java/com/android/tools/r8/cf/CfVersionTest.java
@@ -49,16 +49,16 @@ } private static void assertLessThan(CfVersion less, CfVersion more) { - assertFalse(less.isEqual(more)); + assertFalse(less.isEqualTo(more)); assertEquals(-1, less.compareTo(more)); assertEquals(1, more.compareTo(less)); assertTrue(less.isLessThan(more)); - assertTrue(less.isLessThanOrEqual(more)); + assertTrue(less.isLessThanOrEqualTo(more)); assertFalse(less.isGreaterThan(more)); - assertFalse(less.isGreaterThanOrEqual(more)); + assertFalse(less.isGreaterThanOrEqualTo(more)); assertFalse(more.isLessThan(less)); - assertFalse(more.isLessThanOrEqual(less)); + assertFalse(more.isLessThanOrEqualTo(less)); assertTrue(more.isGreaterThan(less)); - assertTrue(more.isGreaterThanOrEqual(less)); + assertTrue(more.isGreaterThanOrEqualTo(less)); } }
diff --git a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java new file mode 100644 index 0000000..497a1bf --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
@@ -0,0 +1,74 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.cf; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PrintSeedsWithDeserializeLambdaMethodTest extends TestBase { + + private static final Class<?> TEST_CLASS_CF = KeepDeserializeLambdaMethodTestCf.class; + private static final Class<?> TEST_CLASS_DEX = KeepDeserializeLambdaMethodTestDex.class; + + private static final String EXPECTED = + StringUtils.lines("base lambda", KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE); + + @Parameters(name = "{0}") + public static TestParametersCollection params() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private final TestParameters parameters; + + public PrintSeedsWithDeserializeLambdaMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + + private Class<?> getMainClass() { + return parameters.isCfRuntime() ? TEST_CLASS_CF : TEST_CLASS_DEX; + } + + private List<Class<?>> getClasses() { + return ImmutableList.of(KeepDeserializeLambdaMethodTest.class, getMainClass()); + } + + @Test + public void test() throws Exception { + testForR8Compat(parameters.getBackend()) + .addProgramClasses(getClasses()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(getMainClass()) + .addPrintSeeds() + .allowStdoutMessages() + .noMinification() + .noTreeShaking() + .run(parameters.getRuntime(), getMainClass()) + .assertSuccessWithOutput(EXPECTED) + .inspect(this::checkPresenceOfDeserializedLambdas); + } + + private void checkPresenceOfDeserializedLambdas(CodeInspector inspector) { + for (Class<?> clazz : getClasses()) { + MethodSubject method = inspector.clazz(clazz).uniqueMethodWithName("$deserializeLambda$"); + assertEquals( + "Unexpected status for $deserializedLambda$ on " + clazz.getSimpleName(), + parameters.isCfRuntime(), + method.isPresent()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java new file mode 100644 index 0000000..4d7d7fe --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java
@@ -0,0 +1,126 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.horizontal; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestParameters; +import org.junit.Test; + +public class AbstractMethodMergingNonTrivialTest extends HorizontalClassMergingTestBase { + + public AbstractMethodMergingNonTrivialTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class)) + .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("ASub1.f()", "B.f()", "C.f()", "A.g()", "BSub1.g()", "C.g()"); + } + + static class Main { + + public static void main(String[] args) { + (System.currentTimeMillis() > 0 ? new ASub1() : new ASub2()).f(); + (System.currentTimeMillis() > 0 ? new BSub1() : new BSub2()).f(); + (System.currentTimeMillis() > 0 ? new CSub1() : new CSub2()).f(); + (System.currentTimeMillis() > 0 ? new ASub1() : new ASub2()).g(); + (System.currentTimeMillis() > 0 ? new BSub1() : new BSub2()).g(); + (System.currentTimeMillis() > 0 ? new CSub1() : new CSub2()).g(); + } + } + + @NoVerticalClassMerging + abstract static class A { + + public abstract void f(); + + @NeverInline + public void g() { + System.out.println("A.g()"); + } + } + + @NoVerticalClassMerging + abstract static class B { + + @NeverInline + public void f() { + System.out.println("B.f()"); + } + + public abstract void g(); + } + + abstract static class C { + + @NeverInline + public void f() { + System.out.println("C.f()"); + } + + @NeverInline + public void g() { + System.out.println("C.g()"); + } + } + + @NoHorizontalClassMerging + static class ASub1 extends A { + + @Override + public void f() { + System.out.println("ASub1.f()"); + } + } + + @NoHorizontalClassMerging + static class ASub2 extends A { + + @Override + public void f() { + System.out.println("ASub2.f()"); + } + } + + @NoHorizontalClassMerging + static class BSub1 extends B { + + @Override + public void g() { + System.out.println("BSub1.g()"); + } + } + + @NoHorizontalClassMerging + static class BSub2 extends B { + + @Override + public void g() { + System.out.println("BSub2.g()"); + } + } + + @NoHorizontalClassMerging + static class CSub1 extends C {} + + @NoHorizontalClassMerging + static class CSub2 extends C {} +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java new file mode 100644 index 0000000..cc092e9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java
@@ -0,0 +1,132 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.horizontal; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestParameters; +import org.junit.Test; + +public class AbstractMethodMergingTest extends HorizontalClassMergingTestBase { + + public AbstractMethodMergingTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class)) + .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines( + "ASub1.f()", "B.f()", "A.g()", "BSub1.g()", "ASub1.h()", "BSub1.h()"); + } + + static class Main { + + public static void main(String[] args) { + (System.currentTimeMillis() > 0 ? new ASub1() : new ASub2()).f(); + (System.currentTimeMillis() > 0 ? new BSub1() : new BSub2()).f(); + (System.currentTimeMillis() > 0 ? new ASub1() : new ASub2()).g(); + (System.currentTimeMillis() > 0 ? new BSub1() : new BSub2()).g(); + (System.currentTimeMillis() > 0 ? new ASub1() : new ASub2()).h(); + (System.currentTimeMillis() > 0 ? new BSub1() : new BSub2()).h(); + } + } + + @NoVerticalClassMerging + abstract static class A { + + public abstract void f(); + + @NeverInline + public void g() { + System.out.println("A.g()"); + } + + public abstract void h(); + } + + @NoVerticalClassMerging + abstract static class B { + + @NeverInline + public void f() { + System.out.println("B.f()"); + } + + public abstract void g(); + + public abstract void h(); + } + + @NoHorizontalClassMerging + static class ASub1 extends A { + + @Override + public void f() { + System.out.println("ASub1.f()"); + } + + @Override + public void h() { + System.out.println("ASub1.h()"); + } + } + + @NoHorizontalClassMerging + static class ASub2 extends A { + + @Override + public void f() { + System.out.println("ASub2.f()"); + } + + @Override + public void h() { + System.out.println("ASub2.h()"); + } + } + + @NoHorizontalClassMerging + static class BSub1 extends B { + + @Override + public void g() { + System.out.println("BSub1.g()"); + } + + @Override + public void h() { + System.out.println("BSub1.h()"); + } + } + + @NoHorizontalClassMerging + static class BSub2 extends B { + + @Override + public void g() { + System.out.println("BSub2.g()"); + } + + @Override + public void h() { + System.out.println("BSub2.h()"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java new file mode 100644 index 0000000..25bb4ec --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java
@@ -0,0 +1,86 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.horizontal; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestParameters; +import org.junit.Test; + +public class ClassWithInstanceFieldsTest extends HorizontalClassMergingTestBase { + public ClassWithInstanceFieldsTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .enableNeverClassInliningAnnotations() + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A. field: 5, v: a, j: 1", "B. field: b, v: 2, j: 3") + .inspect( + codeInspector -> { + assertThat(codeInspector.clazz(A.class), isPresent()); + assertThat( + codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging)); + }); + } + + @NeverClassInline + public static class A { + public int field; + public String v; + public int j; + + public A(int field, String v, int j) { + this.field = field; + this.v = v; + this.j = j; + } + + @NeverInline + public void foo() { + System.out.println("A. field: " + field + ", v: " + v + ", j: " + j); + } + } + + @NeverClassInline + public static class B { + public String field; + public int v; + public int j; + + public B(String field, int v, int j) { + this.field = field; + this.v = v; + this.j = j; + } + + @NeverInline + public void foo() { + System.out.println("B. field: " + field + ", v: " + v + ", j: " + j); + } + } + + public static class Main { + public static void main(String[] args) { + new A(5, "a", 1).foo(); + new B("b", 2, 3).foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java new file mode 100644 index 0000000..90e407e --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java
@@ -0,0 +1,151 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.horizontal; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; +import static com.android.tools.r8.utils.codeinspector.Matchers.readsInstanceField; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A; +import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B; +import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.FieldSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; + +public class ClassesWithDifferentVisibilityFieldsTest extends HorizontalClassMergingTestBase { + public ClassesWithDifferentVisibilityFieldsTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class)) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines( + "a. v1: 10, v2: 20", "b. v1: 60, v2: 100", "c. v1: 210, v2: 330") + .inspect( + codeInspector -> { + ClassSubject aClassSubject = codeInspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + assertThat( + codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging)); + assertThat(codeInspector.clazz(C.class), isPresent()); + + if (enableHorizontalClassMerging) { + FieldSubject v1Subject = aClassSubject.uniqueFieldWithName("v1"); + FieldSubject v2Subject = aClassSubject.uniqueFieldWithName("v2"); + + MethodSubject methodSubject = aClassSubject.uniqueMethodWithName("getAV1"); + assertThat(methodSubject, isPresent()); + assertThat(methodSubject, readsInstanceField(v1Subject.getDexField())); + + methodSubject = aClassSubject.uniqueMethodWithName("getAV2"); + assertThat(methodSubject, isPresent()); + assertThat(methodSubject, readsInstanceField(v2Subject.getDexField())); + + // The fields v1 and v2 are swapped, because their access modifiers are swapped. + methodSubject = aClassSubject.uniqueMethodWithName("getBV1"); + assertThat(methodSubject, isPresent()); + assertThat(methodSubject, readsInstanceField(v2Subject.getDexField())); + + methodSubject = aClassSubject.uniqueMethodWithName("getBV2"); + assertThat(methodSubject, isPresent()); + assertThat(methodSubject, readsInstanceField(v1Subject.getDexField())); + } + }); + } + + @NeverClassInline + public static class A { + private int v1; + public int v2; + + public A(int v) { + v1 = v; + v2 = 2 * v; + } + + @NeverInline + public int getAV1() { + return v1; + } + + @NeverInline + public int getAV2() { + return v2; + } + + @NeverInline + public void foo() { + System.out.println("a. v1: " + getAV1() + ", v2: " + getAV2()); + } + } + + @NeverClassInline + public static class B { + public int v1; + private int v2; + + public B(int v) { + v1 = 3 * v; + v2 = 5 * v; + } + + @NeverInline + public int getBV1() { + return v1; + } + + @NeverInline + public int getBV2() { + return v2; + } + + @NeverInline + public void foo() { + System.out.println("b. v1: " + getBV1() + ", v2: " + getBV2()); + } + } + + @NeverClassInline + public static class C { + public int v1; + public int v2; + + public C(int v) { + v1 = 7 * v; + v2 = 11 * v; + } + + @NeverInline + public void foo() { + System.out.println("c. v1: " + v1 + ", v2: " + v2); + } + } + + public static class Main { + public static void main(String[] args) { + new A(10).foo(); + new B(20).foo(); + new C(30).foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java index 456d29e..5c03d3b 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
@@ -29,8 +29,8 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel()) - .addHorizontallyMergedClassesInspector( - inspector -> inspector.assertMergedInto(Z.class, Y.class)) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(Z.class, Y.class)) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("bar", "foo y", "foo z") .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java index a30149f..f658909 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -34,7 +34,8 @@ .addKeepMainRule(Main.class) .addOptionsModification( options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) - .addHorizontallyMergedClassesInspector( + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, inspector -> inspector.assertMerged(A.class, B.class).assertMergedIntoDifferentType(B.class)) .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java index 1cb2d2d..6968e1a 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.classmerging.horizontal; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.*; @@ -30,16 +31,9 @@ .assertSuccessWithOutputLines("foo A", "bar 2") .inspect( codeInspector -> { - if (enableHorizontalClassMerging) { - assertThat(codeInspector.clazz(A.class), isPresent()); - assertThat(codeInspector.clazz(B.class), isPresent()); - // TODO(b/163311975): A and B should be merged - // assertThat(codeInspector.clazz(B.class), not(isPresent())); - // TODO(b/165517236): Explicitly check classes have been merged. - } else { - assertThat(codeInspector.clazz(A.class), isPresent()); - assertThat(codeInspector.clazz(B.class), isPresent()); - } + assertThat(codeInspector.clazz(A.class), isPresent()); + assertThat( + codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging)); }); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java similarity index 91% rename from src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesTest.java rename to src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java index 039a65b..2bc2c97 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
@@ -14,8 +14,9 @@ import com.android.tools.r8.TestParameters; import org.junit.Test; -public class NoAbstractClassesTest extends HorizontalClassMergingTestBase { - public NoAbstractClassesTest(TestParameters parameters, boolean enableHorizontalClassMerging) { +public class NoAbstractClassesWithNonAbstractClassesTest extends HorizontalClassMergingTestBase { + public NoAbstractClassesWithNonAbstractClassesTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { super(parameters, enableHorizontalClassMerging); }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java similarity index 62% copy from src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesTest.java copy to src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java index 039a65b..33c8a85 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java
@@ -10,12 +10,11 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; -import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestParameters; import org.junit.Test; -public class NoAbstractClassesTest extends HorizontalClassMergingTestBase { - public NoAbstractClassesTest(TestParameters parameters, boolean enableHorizontalClassMerging) { +public class RemapFieldTest extends HorizontalClassMergingTestBase { + public RemapFieldTest(TestParameters parameters, boolean enableHorizontalClassMerging) { super(parameters, enableHorizontalClassMerging); } @@ -28,67 +27,75 @@ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() - .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, + inspector -> + inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class)) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("bar", "foo c", "foo d", "foo c") + .assertSuccessWithOutputLines("A", "B", "foo: foo c", "B", "foo: bar d") .inspect( codeInspector -> { assertThat(codeInspector.clazz(A.class), isPresent()); - assertThat(codeInspector.clazz(B.class), isPresent()); + assertThat( + codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging)); assertThat(codeInspector.clazz(C.class), isPresent()); assertThat( codeInspector.clazz(D.class), notIf(isPresent(), enableHorizontalClassMerging)); }); } - @NoVerticalClassMerging - public abstract static class A { - public abstract void foo(); + @NeverClassInline + public static class A { + public A() { + System.out.println("A"); + } } @NeverClassInline public static class B { + public B() { + System.out.println("B"); + } + + public void foo(String s) { + System.out.println("foo: " + s); + } + } + + @NeverClassInline + public static class C { + B b; + + public C(B b) { + this.b = b; + } + + @NeverInline + public void foo() { + b.foo("foo c"); + } + } + + @NeverClassInline + public static class D { + B b; + + public D(B b) { + this.b = b; + } + @NeverInline public void bar() { - System.out.println("bar"); - } - } - - @NeverClassInline - public static class C extends A { - - @Override - @NeverInline - public void foo() { - System.out.println("foo c"); - } - } - - @NeverClassInline - public static class D extends A { - - @Override - @NeverInline - public void foo() { - System.out.println("foo d"); + b.foo("bar d"); } } public static class Main { - @NeverInline - public static void foo(A a) { - a.foo(); - } - public static void main(String[] args) { - new B().bar(); - C c = new C(); - - // This test also checks that the synthesized C#foo does not try to call the abstract A#foo. - foo(c); - foo(new D()); - c.foo(); + new A(); + new C(new B()).foo(); + new D(new B()).bar(); } } }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java index f0814bd..a531860 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java
@@ -24,9 +24,7 @@ .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .addOptionsModification( - options -> { - options.enableHorizontalClassMerging = enableHorizontalClassMerging; - }) + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java new file mode 100644 index 0000000..4baa783 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java
@@ -0,0 +1,115 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.vertical; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class MethodCollisionTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public MethodCollisionTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + // TODO(christofferqa): Currently we do not allow merging A into B because we find a + // collision. However, we are free to change the names of private methods, so we should + // handle them similar to fields (i.e., we should allow merging A into B). This would also + // improve the performance of the collision detector, because it would only have to + // consider non-private methods. + .addVerticallyMergedClassesInspector( + VerticallyMergedClassesInspector::assertNoClassesMerged) + .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class); + } + + public static class Main { + + public static void main(String[] args) { + B b = new B(); + b.m(); + + D d = new D(); + d.m(); + + // Ensure that the instantiations are not dead code eliminated. + escape(b); + escape(d); + } + + @NeverInline + static void escape(Object o) { + if (System.currentTimeMillis() < 0) { + System.out.println(o); + } + } + } + + @NoHorizontalClassMerging + public static class A { + + // After class merging, this method will have the same signature as the method B.m, + // unless we handle the collision. + private A m() { + System.out.println("A.m"); + return null; + } + + public void invokeM() { + m(); + } + } + + public static class B extends A { + + private B m() { + System.out.println("B.m"); + invokeM(); + return null; + } + } + + @NoHorizontalClassMerging + public static class C { + + // After class merging, this method will have the same signature as the method D.m, + // unless we handle the collision. + public C m() { + System.out.println("C.m"); + return null; + } + } + + public static class D extends C { + + public D m() { + System.out.println("D.m"); + super.m(); + return null; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java new file mode 100644 index 0000000..f52ff7c --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java
@@ -0,0 +1,110 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.vertical; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class VerticalClassMergerIndirectReflectiveNameTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withCfRuntimes() + .withDexRuntimesStartingFromIncluding(Version.V8_1_0) + .withAllApiLevelsAlsoForCf() + .build(); + } + + public VerticalClassMergerIndirectReflectiveNameTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, A.class, B.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A::foo", "B::foo"); + } + + @Test + public void testR8() throws Exception { + // TODO(b/173099479): This should not throw an assertion-error. + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, A.class, B.class) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString( + "Expected vertically merged class" + + " `com.android.tools.r8.classmerging.vertical." + + "VerticalClassMergerIndirectReflectiveNameTest$A`" + + " to be absent"))); + })); + } + + public static class A { + + @NeverInline + public void foo() { + System.out.println("A::foo"); + } + } + + @NeverClassInline + public static class B extends A { + + @NeverInline + public void bar() { + System.out.println("B::foo"); + } + } + + public static class Main { + + public static String getClassName() { + return "com.android.tools.r8.classmerging.vertical." + + "VerticalClassMergerIndirectReflectiveNameTest$A"; + } + + static { + try { + Class.forName(getClassName()); + } catch (ClassNotFoundException e) { + } + } + + public static void main(String[] args) { + B b = new B(); + b.foo(); + b.bar(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerReflectiveNameTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerReflectiveNameTest.java new file mode 100644 index 0000000..da3165d --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerReflectiveNameTest.java
@@ -0,0 +1,91 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.classmerging.vertical; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class VerticalClassMergerReflectiveNameTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withCfRuntimes() + .withDexRuntimesStartingFromIncluding(Version.V8_1_0) + .withAllApiLevelsAlsoForCf() + .build(); + } + + public VerticalClassMergerReflectiveNameTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, A.class, B.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A::foo", "B::foo"); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, A.class, B.class) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A::foo", "B::foo"); + } + + public static class A { + + @NeverInline + public void foo() { + System.out.println("A::foo"); + } + } + + @NeverClassInline + public static class B extends A { + + @NeverInline + public void bar() { + System.out.println("B::foo"); + } + } + + public static class Main { + + private static final String className = + "com.android.tools.r8.classmerging.vertical.VerticalClassMergerReflectiveNameTest$A"; + + static { + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + } + } + + public static void main(String[] args) { + B b = new B(); + b.foo(); + b.bar(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java index 4a3cc91..d7d31c9 100644 --- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -387,37 +387,6 @@ } @Test - public void testMethodCollision() throws Throwable { - String main = "classmerging.MethodCollisionTest"; - Path[] programFiles = - new Path[] { - CF_DIR.resolve("MethodCollisionTest.class"), - CF_DIR.resolve("MethodCollisionTest$A.class"), - CF_DIR.resolve("MethodCollisionTest$B.class"), - CF_DIR.resolve("MethodCollisionTest$C.class"), - CF_DIR.resolve("MethodCollisionTest$D.class") - }; - // TODO(christofferqa): Currently we do not allow merging A into B because we find a collision. - // However, we are free to change the names of private methods, so we should handle them similar - // to fields (i.e., we should allow merging A into B). This would also improve the performance - // of the collision detector, because it would only have to consider non-private methods. - Set<String> preservedClassNames = - ImmutableSet.of( - "classmerging.MethodCollisionTest", - "classmerging.MethodCollisionTest$A", - "classmerging.MethodCollisionTest$B", - "classmerging.MethodCollisionTest$C", - "classmerging.MethodCollisionTest$D"); - runTest( - testForR8(parameters.getBackend()) - .addKeepRules(getProguardConfig(EXAMPLE_KEEP)) - .allowUnusedProguardConfigurationRules(), - main, - programFiles, - preservedClassNames::contains); - } - - @Test public void testNestedDefaultInterfaceMethodsTest() throws Throwable { String main = "classmerging.NestedDefaultInterfaceMethodsTest"; Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java index 5856c44..ccfaeaf 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.desugar.desugaredlibrary; +import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; @@ -39,6 +40,9 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; public class DesugaredLibraryTestBase extends TestBase { @@ -63,7 +67,7 @@ } protected boolean requiresEmulatedInterfaceCoreLibDesugaring(TestParameters parameters) { - return parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel(); + return parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()); } protected boolean requiresAnyCoreLibDesugaring(TestParameters parameters) { @@ -241,8 +245,7 @@ Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception { Path generatedKeepRules = temp.newFile().toPath(); TraceReferences.run( - "--format", - "keep", + "--keep-rules", "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), "--target", @@ -257,6 +260,48 @@ return FileUtils.readTextFile(generatedKeepRules, Charsets.UTF_8); } + protected static ClassFileInfo extractClassFileInfo(byte[] classFileBytes) { + class ClassFileInfoExtractor extends ClassVisitor { + private String classBinaryName; + private List<String> interfaces = new ArrayList<>(); + private final List<String> methodNames = new ArrayList<>(); + + private ClassFileInfoExtractor() { + super(ASM_VERSION); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + classBinaryName = name; + this.interfaces.addAll(Arrays.asList(interfaces)); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + methodNames.add(name); + return super.visitMethod(access, name, desc, signature, exceptions); + } + + ClassFileInfo getClassFileInfo() { + return new ClassFileInfo(classBinaryName, interfaces, methodNames); + } + } + + ClassReader reader = new ClassReader(classFileBytes); + ClassFileInfoExtractor extractor = new ClassFileInfoExtractor(); + reader.accept( + extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return extractor.getClassFileInfo(); + } + public interface KeepRuleConsumer extends StringConsumer { String get(); @@ -310,4 +355,28 @@ return result; } } + + protected static class ClassFileInfo { + private final String classBinaryName; + private List<String> interfaces; + private final List<String> methodNames; + + ClassFileInfo(String classBinaryNamename, List<String> interfaces, List<String> methodNames) { + this.classBinaryName = classBinaryNamename; + this.interfaces = interfaces; + this.methodNames = methodNames; + } + + public String getClassBinaryName() { + return classBinaryName; + } + + public List<String> getInterfaces() { + return interfaces; + } + + public List<String> getMethodNames() { + return methodNames; + } + } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java new file mode 100644 index 0000000..2a968a0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IteratorTest.java
@@ -0,0 +1,209 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.desugaredlibrary; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.fail; +import static org.hamcrest.CoreMatchers.containsString; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.ZipUtils; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class IteratorTest extends DesugaredLibraryTestBase { + + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + private final boolean canUseDefaultAndStaticInterfaceMethods; + + private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2", "3"); + + @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), + getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); + } + + public IteratorTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + this.canUseDefaultAndStaticInterfaceMethods = + parameters + .getApiLevel() + .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport()); + } + + @Test + public void testIterator() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addInnerClasses(IteratorTest.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + return; + } + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForD8() + .addInnerClasses(IteratorTest.class) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8Cf() throws Exception { + // Use D8 to desugar with Java classfile output. + Path firstJar = + testForD8(Backend.CF) + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(Main.class, MyIterator.class) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), new AbsentKeepRuleConsumer()) + .compile() + .writeToZip(); + + ClassFileInfo info = + extractClassFileInfo( + ZipUtils.readSingleEntry(firstJar, ZipUtils.zipEntryNameForClass(MyIterator.class))); + assertEquals( + MyIterator.class.getTypeName(), + DescriptorUtils.getJavaTypeFromBinaryName(info.getClassBinaryName())); + assertEquals( + canUseDefaultAndStaticInterfaceMethods ? 0 : 1, + info.getInterfaces().stream().filter(name -> name.equals("j$/util/Iterator")).count()); + assertEquals( + canUseDefaultAndStaticInterfaceMethods ? 1 : 2, + info.getMethodNames().stream().filter(name -> name.equals("forEachRemaining")).count()); + + AndroidApiLevel apiLevelNotRequiringDesugaring = AndroidApiLevel.N; + if (parameters.getApiLevel().isLessThan(apiLevelNotRequiringDesugaring)) { + try { + // Use D8 to desugar with Java classfile output. + testForD8(Backend.CF) + .setMinApi(parameters.getApiLevel()) + .addProgramFiles(firstJar) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), new AbsentKeepRuleConsumer()) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString( + "Code has already been library desugared. " + + "Interface Lj$/util/Iterator; is already implemented")))); + fail("Expected failure"); + } catch (CompilationFailedException e) { + // Expected. + } + } + + // Use D8 to desugar with Java classfile output. + Path secondJar = + testForD8(Backend.CF) + .addOptionsModification( + options -> + options.desugarSpecificOptions().allowDesugaredInput = + parameters.getApiLevel().isLessThan(apiLevelNotRequiringDesugaring)) + .setMinApi(parameters.getApiLevel()) + .addProgramFiles(firstJar) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), new AbsentKeepRuleConsumer()) + .compile() + .writeToZip(); + + info = + extractClassFileInfo( + ZipUtils.readSingleEntry(secondJar, ZipUtils.zipEntryNameForClass(MyIterator.class))); + assertEquals( + MyIterator.class.getTypeName(), + DescriptorUtils.getJavaTypeFromBinaryName(info.getClassBinaryName())); + assertEquals( + canUseDefaultAndStaticInterfaceMethods ? 0 : 1, + info.getInterfaces().stream().filter(name -> name.equals("j$/util/Iterator")).count()); + // TODO(b/171867367): This should only be 2. + assertEquals( + canUseDefaultAndStaticInterfaceMethods ? 1 : 3, + info.getMethodNames().stream().filter(name -> name.equals("forEachRemaining")).count()); + + if (parameters.getRuntime().isDex()) { + // Convert to DEX without desugaring and run. + testForD8() + .addProgramFiles(firstJar) + .setMinApi(parameters.getApiLevel()) + .disableDesugaring() + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + collectKeepRulesWithTraceReferences( + firstJar, buildDesugaredLibraryClassFile(parameters.getApiLevel())), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } else { + // Run on the JVM with desugared library on classpath. + testForJvm() + .addProgramFiles(firstJar) + .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel())) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + } + + static class Main { + + public static void main(String[] args) { + Iterator<Integer> iterator = new MyIterator<>(1, 2, 3); + iterator.forEachRemaining(System.out::println); + } + } + + static class MyIterator<E> implements Iterator<E> { + + int index; + E[] items; + + @SafeVarargs + public MyIterator(E... items) { + this.items = items; + } + + @Override + public boolean hasNext() { + return index < items.length; + } + + @Override + public E next() { + return items[index++]; + } + + @Override + public void forEachRemaining(Consumer<? super E> action) { + while (hasNext()) { + action.accept(next()); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java index 92d2394..210216f 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -11,7 +11,9 @@ import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.ZipUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import java.nio.file.Path; @@ -104,7 +106,7 @@ @Test public void testCallBackD8Cf() throws Exception { // Use D8 to desugar with Java classfile output. - Path jar = + Path firstJar = testForD8(Backend.CF) .setMinApi(parameters.getApiLevel()) .addProgramClasses(Impl.class) @@ -114,9 +116,37 @@ .inspect(CallBackConversionTest::assertDuplicatedAPI) .writeToZip(); + ClassFileInfo info = + extractClassFileInfo( + ZipUtils.readSingleEntry(firstJar, ZipUtils.zipEntryNameForClass(Impl.class))); + assertEquals( + Impl.class.getTypeName(), + DescriptorUtils.getJavaTypeFromBinaryName(info.getClassBinaryName())); + assertEquals(2, info.getMethodNames().stream().filter(name -> name.equals("foo")).count()); + + // Use D8 to desugar with Java classfile output. + Path secondJar = + testForD8(Backend.CF) + .setMinApi(parameters.getApiLevel()) + .addProgramFiles(firstJar) + .addLibraryClasses(CustomLibClass.class) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), new AbsentKeepRuleConsumer()) + .compile() + .inspect(CallBackConversionTest::assertDuplicatedAPI) + .writeToZip(); + + info = + extractClassFileInfo( + ZipUtils.readSingleEntry(secondJar, ZipUtils.zipEntryNameForClass(Impl.class))); + assertEquals( + Impl.class.getTypeName(), + DescriptorUtils.getJavaTypeFromBinaryName(info.getClassBinaryName())); + // TODO(b/171867367): This should only be 2. + assertEquals(3, info.getMethodNames().stream().filter(name -> name.equals("foo")).count()); + // Convert to DEX without desugaring and run. testForD8() - .addProgramFiles(jar) + .addProgramFiles(firstJar) .setMinApi(parameters.getApiLevel()) .disableDesugaring() .compile() @@ -125,7 +155,7 @@ this::buildDesugaredLibrary, parameters.getApiLevel(), collectKeepRulesWithTraceReferences( - jar, buildDesugaredLibraryClassFile(parameters.getApiLevel())), + firstJar, buildDesugaredLibraryClassFile(parameters.getApiLevel())), shrinkDesugaredLibrary) .addRunClasspathFiles(CUSTOM_LIB) .run(parameters.getRuntime(), Impl.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonEnumTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonEnumTest.java new file mode 100644 index 0000000..d71b536 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonEnumTest.java
@@ -0,0 +1,80 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.desugaredlibrary.gson; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; +import com.android.tools.r8.utils.BooleanUtils; +import java.lang.reflect.Field; +import java.time.chrono.IsoEra; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class GsonEnumTest extends DesugaredLibraryTestBase { + + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + + @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); + } + + public GsonEnumTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + } + + @Test + public void testCustomCollectionD8() throws Exception { + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForD8() + .addInnerClasses(GsonEnumTest.class) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), Executor.class) + .assertSuccessWithOutputLines("0"); + } + + @Test + public void testCustomCollectionR8() throws Exception { + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForR8(Backend.DEX) + .addInnerClasses(GsonEnumTest.class) + .addKeepMainRule(Executor.class) + .noMinification() + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), Executor.class) + .assertSuccessWithOutputLines("0"); + } + + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + static class Executor { + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { + // For GSON to correctly serialize enums, it needs to be able to access all of the static + // enum fields. + Field bce = IsoEra.class.getField("BCE"); + System.out.println(((IsoEra) bce.get(null)).ordinal()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java index b1a6811..b61e9d8 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -29,6 +29,8 @@ @RunWith(Parameterized.class) public class R8CompiledThroughDexTest extends DesugaredLibraryTestBase { + private static final boolean testExternal = true; + private final TestParameters parameters; @Parameters(name = "{0}") @@ -69,34 +71,41 @@ File ouputFolder = temp.newFolder("output"); // Compile R8 to dex on the JVM. - Path ouputThroughCf = ouputFolder.toPath().resolve("outThroughCf.zip").toAbsolutePath(); - ProcessResult javaProcessResult = - ToolHelper.runJava( - TestRuntime.getCheckedInJdk9(), - Collections.singletonList(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR), - "-Xmx512m", - R8.class.getTypeName(), - "--release", - "--min-api", - Integer.toString(parameters.getApiLevel().getLevel()), - "--output", - ouputThroughCf.toString(), - "--lib", - ToolHelper.JAVA_8_RUNTIME, - "--pg-conf", - R8_KEEP, - ToolHelper.R8_WITH_RELOCATED_DEPS_JAR.toAbsolutePath().toString()); - if (javaProcessResult.exitCode != 0) { - System.out.println(javaProcessResult); + Path outputThroughCf = ouputFolder.toPath().resolve("outThroughCf.zip").toAbsolutePath(); + if (testExternal) { + ProcessResult javaProcessResult = + ToolHelper.runJava( + TestRuntime.getCheckedInJdk9(), + Collections.singletonList(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR), + "-Xmx512m", + R8.class.getTypeName(), + "--release", + "--min-api", + Integer.toString(parameters.getApiLevel().getLevel()), + "--output", + outputThroughCf.toString(), + "--lib", + ToolHelper.JAVA_8_RUNTIME, + "--pg-conf", + R8_KEEP, + ToolHelper.R8_WITH_RELOCATED_DEPS_JAR.toAbsolutePath().toString()); + assertEquals(javaProcessResult.toString(), 0, javaProcessResult.exitCode); + } else { + testForR8(parameters.getBackend()) + .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR) + .addLibraryFiles(ToolHelper.getJava8RuntimeJar()) + .addKeepRuleFiles(Paths.get(R8_KEEP)) + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(outputThroughCf); } - assertEquals(0, javaProcessResult.exitCode); // Compile R8 to Dex on Dex, using the previous dex artifact. // We need the extra parameter --64 to use 64 bits frameworks. Path ouputThroughDex = ouputFolder.toPath().resolve("outThroughDex.zip").toAbsolutePath(); ProcessResult artProcessResult = ToolHelper.runArtRaw( - Collections.singletonList(ouputThroughCf.toAbsolutePath().toString()), + Collections.singletonList(outputThroughCf.toAbsolutePath().toString()), R8.class.getTypeName(), (ToolHelper.ArtCommandBuilder builder) -> builder.appendArtOption("--64").appendArtOption("-Xmx512m"), @@ -118,6 +127,6 @@ assertEquals(0, artProcessResult.exitCode); // Ensure both generated artifacts are equal. - assertTrue(BootstrapCurrentEqualityTest.filesAreEqual(ouputThroughCf, ouputThroughDex)); + assertTrue(BootstrapCurrentEqualityTest.filesAreEqual(outputThroughCf, ouputThroughDex)); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java new file mode 100644 index 0000000..2b72a6f --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/GetDeclaredMethodsErrorRemovalTest.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.softverificationerrorremoval; + +import static com.android.tools.r8.TestRuntime.CfRuntime.getCheckedInJdk8; +import static org.hamcrest.CoreMatchers.containsString; + +import com.android.tools.r8.D8TestRunResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.function.Supplier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class GetDeclaredMethodsErrorRemovalTest extends TestBase { + + private final TestParameters parameters; + private static final String TYPE = + "com.android.tools.r8.desugar.softverificationerrorremoval." + + "GetDeclaredMethodsErrorRemovalTest"; + private static final String EXPECTED_RESULT = + "[void" + + " " + + TYPE + + "$ExampleClass.hello()," + + " void" + + " " + + TYPE + + "$ExampleClass.hello(java.util.function.Supplier)]"; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + public GetDeclaredMethodsErrorRemovalTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testWithoutJavaStub() throws Exception { + D8TestRunResult run = + testForD8() + .addInnerClasses(GetDeclaredMethodsErrorRemovalTest.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class); + if (parameters.getDexRuntimeVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) { + run.assertFailureWithErrorThatMatches(containsString("java.lang.NoClassDefFoundError")); + } else { + run.assertSuccessWithOutputLines(EXPECTED_RESULT); + } + } + + @Test + public void testWithJavaStub() throws Exception { + Path stubs = + javac(getCheckedInJdk8()) + .addSourceFiles(Paths.get("src/test/javaStubs/Supplier.java")) + .compile(); + testForD8() + .addInnerClasses(GetDeclaredMethodsErrorRemovalTest.class) + .addProgramFiles(stubs) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines(EXPECTED_RESULT); + } + + static class TestClass { + + public static void main(String[] args) { + System.out.println(Arrays.toString(ExampleClass.class.getDeclaredMethods())); + } + } + + static class ExampleClass { + void hello() { + System.out.println("hello"); + } + + void hello(Supplier<String> stringSupplier) { + System.out.println(stringSupplier.get()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java new file mode 100644 index 0000000..1a1720e --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/softverificationerrorremoval/SoftVerificationErrorRemovalTest.java
@@ -0,0 +1,94 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.softverificationerrorremoval; + +import static com.android.tools.r8.TestRuntime.CfRuntime.getCheckedInJdk8; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.D8TestRunResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Supplier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class SoftVerificationErrorRemovalTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + public SoftVerificationErrorRemovalTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testWithoutJavaStub() throws Exception { + D8TestRunResult run = + testForD8() + .addInnerClasses(SoftVerificationErrorRemovalTest.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class); + assertVerificationErrorsPresent( + run.getStdErr(), + parameters.getDexRuntimeVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V4_4_4)); + } + + private void assertVerificationErrorsPresent(String stdErr, boolean present) { + assertEquals( + present, + stdErr.contains( + "VFY: unable to find class referenced in signature (Ljava/util/function/Supplier;)")); + assertEquals( + present, + stdErr.contains( + "VFY: unable to resolve interface method 7: Ljava/util/function/Supplier;.get" + + " ()Ljava/lang/Object;")); + } + + @Test + public void testWithJavaStub() throws Exception { + Path stubs = + javac(getCheckedInJdk8()) + .addSourceFiles(Paths.get("src/test/javaStubs/Supplier.java")) + .compile(); + D8TestRunResult run = + testForD8() + .addInnerClasses(SoftVerificationErrorRemovalTest.class) + .addProgramFiles(stubs) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class); + assertVerificationErrorsPresent(run.getStdErr(), false); + } + + static class TestClass { + + public static void main(String[] args) { + ExampleClass exampleClass = new ExampleClass(); + exampleClass.hello(); + } + } + + static class ExampleClass { + void hello() { + System.out.println("hello"); + } + + void hello(Supplier<String> stringSupplier) { + System.out.println(stringSupplier.get()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java index b48e0a93..0fe19bc 100644 --- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java +++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.dex; import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexDebugEvent; import com.android.tools.r8.graph.DexDebugInfo; @@ -23,9 +24,11 @@ private ObjectToOffsetMapping emptyObjectTObjectMapping() { return new ObjectToOffsetMapping( - AppInfo.createInitialAppInfo( - DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter()), null) - .build()), + AppView.createForD8( + AppInfo.createInitialAppInfo( + DexApplication.builder( + new InternalOptions(new DexItemFactory(), new Reporter()), null) + .build())), GraphLens.getIdentityLens(), NamingLens.getIdentityLens(), InitClassLens.getDefault(),
diff --git a/src/test/java/com/android/tools/r8/dex/DexStringTest.java b/src/test/java/com/android/tools/r8/dex/DexStringTest.java index b12d876..ad41738 100644 --- a/src/test/java/com/android/tools/r8/dex/DexStringTest.java +++ b/src/test/java/com/android/tools/r8/dex/DexStringTest.java
@@ -78,8 +78,7 @@ private void check(int expected, DexString s1, DexString s2) { assertEquals(s1.dump() + " " + s2.dump(), expected, Integer.signum(s1.toString().compareTo(s2.toString()))); - assertEquals(s1.dump() + " " + s2.dump(), - expected, Integer.signum(s1.slowCompareTo(s2))); + assertEquals(s1.dump() + " " + s2.dump(), expected, Integer.signum(s1.compareTo(s2))); } private void checkEncodedLength(DexString s, int encodedLength) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java index ab837d3..c494152 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.Diagnostic; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessages; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.StringUtils; @@ -86,10 +87,12 @@ } static List<Object[]> enumUnboxingTestParameters() { - return buildParameters( - getTestParameters().withDexRuntimes().withAllApiLevels().build(), - BooleanUtils.values(), - getAllEnumKeepRules()); + return enumUnboxingTestParameters( + getTestParameters().withDexRuntimes().withAllApiLevels().build()); + } + + static List<Object[]> enumUnboxingTestParameters(TestParametersCollection testParameters) { + return buildParameters(testParameters, BooleanUtils.values(), getAllEnumKeepRules()); } protected static EnumKeepRules[] getAllEnumKeepRules() {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java new file mode 100644 index 0000000..d6eb6d1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
@@ -0,0 +1,97 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.enumunboxing; + +import static org.junit.Assert.assertFalse; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestParameters; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class LambdaEnumUnboxingTest extends EnumUnboxingTestBase { + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final EnumKeepRules enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(getTestParameters().withAllRuntimesAndApiLevels().build()); + } + + public LambdaEnumUnboxingTest( + TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testEnumUnboxing() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepRules(enumKeepRules.getKeepRules()) + .addOptionsModification( + options -> { + if (options.isGeneratingClassFiles()) { + // TODO(b/172568606): Remove this when enabled for CF by default. + assertFalse(options.enableEnumUnboxing); + options.enableEnumUnboxing = true; + } + }) + .enableNeverClassInliningAnnotations() + .enableInliningAnnotations() + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .addOptionsModification(options -> options.testing.enableEnumUnboxingDebugLogs = false) + .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class)) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("0", "0", "1", "0", "0"); + } + + @NeverClassInline + enum MyEnum { + A, + B, + C + } + + static class Main { + + public static void main(String[] args) { + System.out.println(MyEnum.A.ordinal()); + boolean[] booleans = new boolean[] {true, false}; + forEach(booleans, Main::printAndGetEnum); + System.out.println(printAndGetEnum(true).ordinal()); + } + + @NeverInline + private static MyEnum printAndGetEnum(boolean b) { + MyEnum myEnum = b ? MyEnum.A : MyEnum.B; + System.out.println(myEnum.ordinal()); + return myEnum; + } + + @NeverInline + static void forEach(boolean[] booleans, MyBooleanConsumer consumer) { + for (boolean b : booleans) { + consumer.accept(b); + } + } + } + + interface MyBooleanConsumer { + + void accept(Boolean b); + } +}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java index b6e34ad..81d8e11 100644 --- a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java +++ b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
@@ -6,8 +6,11 @@ import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; import static com.google.common.base.Predicates.alwaysFalse; +import static com.google.common.base.Predicates.alwaysTrue; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessages; @@ -18,9 +21,11 @@ import com.android.tools.r8.graph.GenericSignature; import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; import com.android.tools.r8.graph.GenericSignaturePrinter; +import com.android.tools.r8.graph.GenericSignatureTypeRewriter; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.Reporter; +import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -106,4 +111,18 @@ assertEquals(FieldTypeSignature.noSignature(), parsed); return testDiagnosticMessages; } + + @Test + public void testPruningNullTest() { + DexItemFactory factory = new DexItemFactory(); + FieldTypeSignature parsed = + GenericSignature.parseFieldTypeSignature( + "A", "Lfoo/bar/Baz;", Origin.unknown(), factory, new Reporter()); + assertTrue(parsed.hasSignature()); + GenericSignatureTypeRewriter rewriter = + new GenericSignatureTypeRewriter(factory, alwaysTrue(), Function.identity(), null); + FieldTypeSignature rewrittenType = rewriter.rewrite(parsed); + assertNotNull(rewrittenType); + assertTrue(rewrittenType.hasNoSignature()); + } }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java index ddaf85f..318de07 100644 --- a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java +++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -30,6 +30,7 @@ public ChromeProtoRewritingTest(TestParameters parameters) { super(200430, false); + parameters.assertNoneRuntime(); } @Test
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java index 487d60a..fbe9bd8 100644 --- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java +++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java
@@ -9,6 +9,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -25,7 +26,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } public InvokeMultiNewArraySideEffectTest(TestParameters parameters) { @@ -37,7 +38,8 @@ testForR8(parameters.getBackend()) .addInnerClasses(InvokeMultiNewArraySideEffectTest.class) .addKeepMainRule(TestClass.class) - .setMinApi(parameters.getRuntime()) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile() .inspect( inspector -> { @@ -66,7 +68,9 @@ } } + @NoHorizontalClassMerging static class A {} + @NoHorizontalClassMerging static class B {} }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java index 807b061..af047b9 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -13,10 +13,14 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ZipUtils; @@ -58,6 +62,7 @@ private final boolean allowAccessModification; private final TestParameters parameters; private Path outputDir = null; + private String nullabilityClass = "inlining.Nullability"; public R8InliningTest(boolean allowAccessModification, TestParameters parameters) { this.allowAccessModification = allowAccessModification; @@ -94,6 +99,14 @@ return null; } + private void fixInliningNullabilityClass( + DexItemFactory dexItemFactory, HorizontallyMergedClasses horizontallyMergedClasses) { + DexType originalType = + dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor("inlining.Nullability")); + nullabilityClass = + horizontallyMergedClasses.getMergeTargetOrDefault(originalType).toSourceString(); + } + private void generateR8Version(Path out, Path mapFile, boolean inlining) throws Exception { assert parameters.isDexRuntime() || parameters.isCfRuntime(); R8Command.Builder commandBuilder = @@ -126,6 +139,7 @@ // Tests depend on nullability of receiver and argument in general. Learning very accurate // nullability from actual usage in tests bothers what we want to test. o.callSiteOptimizationOptions().disableTypePropagationForTesting(); + o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass; }); } @@ -201,7 +215,7 @@ // a private class in another package. checkAbsent(clazz, "int", "callInterfaceMethod", ImmutableList.of("inlining.IFace")); - clazz = inspector.clazz("inlining.Nullability"); + clazz = inspector.clazz(nullabilityClass); checkAbsentBooleanMethod(clazz, "inlinableWithPublicField"); checkAbsentBooleanMethod(clazz, "inlinableWithControlFlow"); } @@ -309,7 +323,7 @@ final int INLINABLE = allowAccessModification ? 0 : 1; final int NEVER_INLINABLE = 1; - ClassSubject clazz = inspector.clazz("inlining.Nullability"); + ClassSubject clazz = inspector.clazz(nullabilityClass); MethodSubject m; m = clazz.method("int", "inlinable", ImmutableList.of("inlining.A")); @@ -339,7 +353,7 @@ public void invokeOnNonNullReceiver() throws Exception { CodeInspector inspector = new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null); - ClassSubject clazz = inspector.clazz("inlining.Nullability"); + ClassSubject clazz = inspector.clazz(nullabilityClass); MethodSubject m = clazz.method("int", "conditionalOperator", ImmutableList.of("inlining.A")); assertTrue(m.isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java index f751e25..1d1f00e 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -10,6 +10,7 @@ import static org.junit.Assert.assertEquals; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.StringUtils; @@ -57,6 +58,7 @@ // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6. .addOptionsModification(options -> options.inliningInstructionLimit = 6) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .noMinification() .setMinApi(parameters.getApiLevel()) .compile() @@ -138,6 +140,7 @@ } } + @NoHorizontalClassMerging static class PairBuilder<F, S> { F first;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java index 8d73c3a..cf52085 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -129,8 +129,7 @@ Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("testCallOnIface1"))); assertEquals( - Collections.singleton("com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"), - collectTypes(clazz.uniqueMethodWithName("testCallOnIface2"))); + Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("testCallOnIface2"))); assertEquals( Sets.newHashSet(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java index c95fa85..9df6cb2 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
@@ -9,6 +9,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -40,6 +41,7 @@ // Should be able to class inline Builder even when the threshold is low. .addOptionsModification(options -> options.classInliningInstructionAllowance = 3) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::inspect) @@ -121,6 +123,7 @@ } } + @NoHorizontalClassMerging static class Builder { char c1;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java index d26641a..0390712 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
@@ -4,6 +4,9 @@ package com.android.tools.r8.ir.optimize.classinliner.trivial; +import com.android.tools.r8.NoHorizontalClassMerging; + +@NoHorizontalClassMerging public class CycleReferenceAB { private String a;
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java index 177a8a5..f76758c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java +++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.kotlin; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -14,8 +15,9 @@ import static org.junit.Assert.fail; import com.android.tools.r8.KotlinTestBase; -import com.android.tools.r8.OutputMode; -import com.android.tools.r8.R8Command; +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.graph.Code; @@ -23,22 +25,18 @@ import com.android.tools.r8.jasmin.JasminBuilder; import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; import com.android.tools.r8.naming.MemberNaming.MethodSignature; -import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DescriptorUtils; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FieldSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; -import com.google.common.collect.ImmutableList; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import org.junit.Assume; public abstract class AbstractR8KotlinTestBase extends KotlinTestBase { @@ -217,30 +215,6 @@ return code.asDexCode(); } - private String buildProguardRules(String mainClass) { - ProguardRulesBuilder proguardRules = new ProguardRulesBuilder(); - proguardRules.appendWithLineSeparator(keepMainProguardConfiguration(mainClass)); - proguardRules.dontObfuscate(); - if (allowAccessModification) { - proguardRules.allowAccessModification(); - } - return proguardRules.toString(); - } - - protected String keepAllMembers(String className) { - return StringUtils.lines( - "-keep class " + className + " {", - " *;", - "}"); - } - - protected String keepMainMethod(String className) { - return StringUtils.lines( - "-keepclasseswithmembers class " + className + " {", - " public static void main(...);", - "}"); - } - protected String keepClassMethod(String className, MethodSignature methodSignature) { return StringUtils.lines( "-keep class " + className + " {", @@ -255,54 +229,21 @@ "}"); } - protected void runTest(String folder, String mainClass, - AndroidAppInspector inspector) throws Exception { - runTest(folder, mainClass, null, null, inspector); + protected R8TestRunResult runTest(String folder, String mainClass) throws Exception { + return runTest(folder, mainClass, null); } - protected void runTest(String folder, String mainClass, - Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception { - runTest(folder, mainClass, null, optionsConsumer, inspector); - } - - protected void runTest(String folder, String mainClass, - String extraProguardRules, AndroidAppInspector inspector) throws Exception { - runTest(folder, mainClass, extraProguardRules, null, inspector); - } - - protected void runTest(String folder, String mainClass, String extraProguardRules, - Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception { + protected R8TestRunResult runTest( + String folder, String mainClass, ThrowableConsumer<R8FullTestBuilder> configuration) + throws Exception { Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles()); - String proguardRules = buildProguardRules(mainClass); - if (extraProguardRules != null) { - proguardRules += extraProguardRules; - } - // Build classpath for compilation (and java execution) classpath.clear(); classpath.add(getKotlinJarFile(folder)); classpath.add(getJavaJarFile(folder)); classpath.addAll(extraClasspath); - // Build with R8 - AndroidApp.Builder builder = AndroidApp.builder(); - builder.addProgramFiles(classpath); - R8Command.Builder commandBuilder = - ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(Backend.DEX)) - .addLibraryFiles(runtimeJar(Backend.DEX)) - .addProguardConfiguration(ImmutableList.of(proguardRules), Origin.unknown()); - ToolHelper.allowTestProguardOptions(commandBuilder); - AndroidApp app = ToolHelper.runR8(commandBuilder.build(), optionsConsumer); - - // Materialize file for execution. - Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar"); - app.writeToZip(generatedDexFile, OutputMode.DexIndexed); - - // Run with ART. - String artOutput = - ToolHelper.runArtNoVerificationErrors(generatedDexFile.toString(), mainClass); - // Compare with Java. ToolHelper.ProcessResult javaResult = ToolHelper.runJava(classpath, mainClass); if (javaResult.exitCode != 0) { @@ -310,11 +251,22 @@ System.err.println(javaResult.stderr); fail("JVM failed for: " + mainClass); } - assertEquals("JVM and ART output differ", javaResult.stdout, artOutput); - if (inspector != null) { - inspector.inspectApp(app); - } + // Build with R8 + return testForR8(Backend.DEX) + .addProgramFiles(classpath) + .addKeepMainRule(mainClass) + .allowAccessModification(allowAccessModification) + .allowDiagnosticMessages() + .enableProguardTestOptions() + .noMinification() + .apply(configuration) + .compile() + .assertAllWarningMessagesMatch( + containsString("Resource 'META-INF/MANIFEST.MF' already exists.")) + .assertAllInfoMessagesMatch(containsString("Unrecognized Kotlin lambda ")) + .run(mainClass) + .assertSuccessWithOutput(javaResult.stdout); } protected void checkClassExistsInInput(String className) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java index d8419ca..2bb0867 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -12,6 +12,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.code.NewInstance; import com.android.tools.r8.code.SgetObject; @@ -20,7 +23,6 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; @@ -78,184 +80,216 @@ public void testJStyleLambdas() throws Exception { assumeTrue("Only work with -allowaccessmodification", allowAccessModification); final String mainClassName = "class_inliner_lambda_j_style.MainKt"; - runTest( - "class_inliner_lambda_j_style", - mainClassName, - false, - app -> { - CodeInspector inspector = new CodeInspector(app); - assertThat( - inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), isPresent()); - assertThat( - inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), isPresent()); - assertThat( - inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), isPresent()); - }); + runTestWithDefaults( + "class_inliner_lambda_j_style", + mainClassName, + testBuilder -> + testBuilder + // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources + .addKeepRules("-neverinline class * { void test*State*(...); }") + .noClassInlining()) + .inspect( + inspector -> { + assertThat( + inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), + isPresent()); + assertThat( + inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), + isPresent()); + assertThat( + inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), + isPresent()); + }); - runTest( - "class_inliner_lambda_j_style", - mainClassName, - true, - app -> { - CodeInspector inspector = new CodeInspector(app); - Predicate<DexType> lambdaCheck = createLambdaCheck(inspector); - ClassSubject clazz = inspector.clazz(mainClassName); + runTestWithDefaults( + "class_inliner_lambda_j_style", + mainClassName, + testBuilder -> + testBuilder + // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources + .addKeepRules("-neverinline class * { void test*State*(...); }")) + .inspect( + inspector -> { + Predicate<DexType> lambdaCheck = createLambdaCheck(inspector); + ClassSubject clazz = inspector.clazz(mainClassName); - assertEquals( - Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateless")); + assertEquals( + Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateless")); - assertEquals(Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful")); + assertEquals( + Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful")); - assertThat( - inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), - not(isPresent())); + assertThat( + inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), + not(isPresent())); - assertEquals( - Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful2")); + assertEquals( + Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful2")); - assertThat( - inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), - not(isPresent())); + assertThat( + inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), + not(isPresent())); - assertEquals( - Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful3")); + assertEquals( + Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful3")); - assertThat( - inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), - not(isPresent())); - }); + assertThat( + inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), + not(isPresent())); + }); } @Test public void testKStyleLambdas() throws Exception { assumeTrue("Only work with -allowaccessmodification", allowAccessModification); final String mainClassName = "class_inliner_lambda_k_style.MainKt"; - runTest( - "class_inliner_lambda_k_style", - mainClassName, - false, - app -> { - CodeInspector inspector = new CodeInspector(app); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), - isPresent()); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), - isPresent()); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), - isPresent()); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"), - isPresent()); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"), - isPresent()); - assertThat( - inspector.clazz( - "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"), - isPresent()); - assertThat( - inspector.clazz( - "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"), - isPresent()); - assertThat( - inspector.clazz( - "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"), - isPresent()); - }); + runTestWithDefaults( + "class_inliner_lambda_k_style", + mainClassName, + testBuilder -> + testBuilder + // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources + .addKeepRules( + "-neverinline class * { void test*State*(...); }", + "-neverinline class * { void testBigExtraMethod(...); }", + "-neverinline class * { void testBigExtraMethodReturningLambda(...); }") + .noClassInlining()) + .inspect( + inspector -> { + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), + isPresent()); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), + isPresent()); + assertThat( + inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), + isPresent()); + assertThat( + inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"), + isPresent()); + assertThat( + inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"), + isPresent()); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"), + isPresent()); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"), + isPresent()); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"), + isPresent()); + }); - runTest( - "class_inliner_lambda_k_style", - mainClassName, - true, - app -> { - CodeInspector inspector = new CodeInspector(app); - Predicate<DexType> lambdaCheck = createLambdaCheck(inspector); - ClassSubject clazz = inspector.clazz(mainClassName); + runTestWithDefaults( + "class_inliner_lambda_k_style", + mainClassName, + testBuilder -> + testBuilder + // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources + .addKeepRules( + "-neverinline class * { void test*State*(...); }", + "-neverinline class * { void testBigExtraMethod(...); }", + "-neverinline class * { void testBigExtraMethodReturningLambda(...); }")) + .inspect( + inspector -> { + Predicate<DexType> lambdaCheck = createLambdaCheck(inspector); + ClassSubject clazz = inspector.clazz(mainClassName); - assertEquals( - Sets.newHashSet(), - collectAccessedTypes( - lambdaCheck, clazz, "testKotlinSequencesStateless", "kotlin.sequences.Sequence")); + assertEquals( + Sets.newHashSet(), + collectAccessedTypes( + lambdaCheck, + clazz, + "testKotlinSequencesStateless", + "kotlin.sequences.Sequence")); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), - not(isPresent())); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), + not(isPresent())); - assertEquals( - Sets.newHashSet(), - collectAccessedTypes( - lambdaCheck, - clazz, - "testKotlinSequencesStateful", - "int", - "int", - "kotlin.sequences.Sequence")); + assertEquals( + Sets.newHashSet(), + collectAccessedTypes( + lambdaCheck, + clazz, + "testKotlinSequencesStateful", + "int", + "int", + "kotlin.sequences.Sequence")); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), - not(isPresent())); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), + not(isPresent())); - assertEquals( - Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod")); + assertEquals( + Sets.newHashSet(), + collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod")); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), - not(isPresent())); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"), - not(isPresent())); - assertThat( - inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"), - not(isPresent())); + assertThat( + inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), + not(isPresent())); + assertThat( + inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"), + not(isPresent())); + assertThat( + inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"), + not(isPresent())); - assertEquals( - Sets.newHashSet(), - collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda")); + assertEquals( + Sets.newHashSet(), + collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda")); - assertThat( - inspector.clazz( - "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"), - not(isPresent())); - assertThat( - inspector.clazz( - "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"), - not(isPresent())); - assertThat( - inspector.clazz( - "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"), - not(isPresent())); - }); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"), + not(isPresent())); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"), + not(isPresent())); + assertThat( + inspector.clazz( + "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"), + not(isPresent())); + }); } @Test public void testDataClass() throws Exception { assumeTrue("Only work with -allowaccessmodification", allowAccessModification); final String mainClassName = "class_inliner_data_class.MainKt"; - runTest( - "class_inliner_data_class", - mainClassName, - true, - app -> { - CodeInspector inspector = new CodeInspector(app); - ClassSubject clazz = inspector.clazz(mainClassName); - assertTrue( - collectAccessedTypes( - type -> !type.toSourceString().startsWith("java."), - clazz, - "main", - String[].class.getCanonicalName()) - .isEmpty()); - assertEquals( - Lists.newArrayList( - "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"), - collectStaticCalls(clazz, "main", String[].class.getCanonicalName())); - }); + runTestWithDefaults("class_inliner_data_class", mainClassName) + .inspect( + inspector -> { + ClassSubject clazz = inspector.clazz(mainClassName); + assertTrue( + collectAccessedTypes( + type -> !type.toSourceString().startsWith("java."), + clazz, + "main", + String[].class.getCanonicalName()) + .isEmpty()); + assertEquals( + Lists.newArrayList( + "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"), + collectStaticCalls(clazz, "main", String[].class.getCanonicalName())); + }); } - private Set<String> collectAccessedTypes(Predicate<DexType> isTypeOfInterest, - ClassSubject clazz, String methodName, String... params) { + private Set<String> collectAccessedTypes( + Predicate<DexType> isTypeOfInterest, + ClassSubject clazz, + String methodName, + String... params) { assertNotNull(clazz); MethodSignature signature = new MethodSignature(methodName, "void", params); DexCode code = clazz.method(signature).getMethod().getCode().asDexCode(); @@ -270,32 +304,37 @@ .collect(Collectors.toSet()); } - protected void runTest(String folder, String mainClass, - boolean enabled, AndroidAppInspector inspector) throws Exception { - runTest( + private R8TestRunResult runTestWithDefaults(String folder, String mainClass) throws Exception { + return runTestWithDefaults(folder, mainClass, null); + } + + private R8TestRunResult runTestWithDefaults( + String folder, String mainClass, ThrowableConsumer<R8FullTestBuilder> configuration) + throws Exception { + return runTest( folder, mainClass, - // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources - StringUtils.lines( - "-neverinline class * { void test*State*(...); }", - "-neverinline class * { void testBigExtraMethod(...); }", - "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"), - options -> { - options.enableInlining = true; - options.enableClassInlining = enabled; - options.enableLambdaMerging = false; + testBuilder -> + testBuilder + .addOptionsModification( + options -> { + options.enableInlining = true; + options.enableLambdaMerging = false; - // TODO(b/141719453): These limits should be removed if a possible or the test refactored. - // Tests check if specific lambdas are inlined or not, where some of target lambdas have - // at least 4 instructions. - options.inliningInstructionLimit = 4; - options.classInliningInstructionLimit = 40; + // TODO(b/141719453): These limits should be removed if a possible or the test + // refactored. Tests check if specific lambdas are inlined or not, where some + // of target lambdas have at least 4 instructions. + options.inliningInstructionLimit = 4; + options.classInliningInstructionLimit = 40; - // Class inlining depends on the processing order. We therefore insert all call graph - // edges and verify that we can class inline everything under this condition. - options.testing.addCallEdgesForLibraryInvokes = true; - }, - inspector); + // Class inlining depends on the processing order. We therefore insert all + // call graph edges and verify that we can class inline everything under this + // condition. + options.testing.addCallEdgesForLibraryInvokes = true; + + options.enableHorizontalClassMergingOfKotlinLambdas = false; + }) + .apply(configuration)); } private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java index 2cbef3e..bf74d90 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -10,10 +10,10 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; +import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import com.google.common.base.Predicates; import java.util.Collection; @@ -40,48 +40,37 @@ final String mainClassName = "class_staticizer.MainKt"; // Without class staticizer. - runTest( - "class_staticizer", - mainClassName, - false, - app -> { - CodeInspector inspector = new CodeInspector(app); - assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent()); + runTest("class_staticizer", mainClassName, true) + .inspect( + inspector -> { + assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent()); - // The Util class is there, but its instance methods have been inlined. - ClassSubject utilClass = inspector.clazz("class_staticizer.Util"); - assertThat(utilClass, isPresent()); - assertTrue( - utilClass.allMethods().stream() - .filter(Predicates.not(FoundMethodSubject::isStatic)) - .allMatch(FoundMethodSubject::isInstanceInitializer)); - }); + // The Util class is there, but its instance methods have been inlined. + ClassSubject utilClass = inspector.clazz("class_staticizer.Util"); + assertThat(utilClass, isPresent()); + assertTrue( + utilClass.allMethods().stream() + .filter(Predicates.not(FoundMethodSubject::isStatic)) + .allMatch(FoundMethodSubject::isInstanceInitializer)); + }); // With class staticizer. - runTest( - "class_staticizer", - mainClassName, - true, - app -> { - CodeInspector inspector = new CodeInspector(app); - assertThat(inspector.clazz("class_staticizer.Regular$Companion"), not(isPresent())); + runTest("class_staticizer", mainClassName, false) + .inspect( + inspector -> { + assertThat(inspector.clazz("class_staticizer.Regular$Companion"), not(isPresent())); - ClassSubject utilClass = inspector.clazz("class_staticizer.Util"); - assertThat(utilClass, isPresent()); - assertTrue(utilClass.allMethods().stream().allMatch(FoundMethodSubject::isStatic)); - }); + ClassSubject utilClass = inspector.clazz("class_staticizer.Util"); + assertThat(utilClass, isPresent()); + assertTrue(utilClass.allMethods().stream().allMatch(FoundMethodSubject::isStatic)); + }); } - protected void runTest(String folder, String mainClass, - boolean enabled, AndroidAppInspector inspector) throws Exception { - runTest( + protected R8TestRunResult runTest(String folder, String mainClass, boolean noClassStaticizing) + throws Exception { + return runTest( folder, mainClass, - null, - options -> { - options.enableClassInlining = false; - options.enableClassStaticizer = enabled; - }, - inspector); + testBuilder -> testBuilder.noClassInlining().noClassStaticizing(noClassStaticizing)); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java index 782eb68..3c28a7d 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -10,7 +10,6 @@ import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.ImmutableList; import java.util.Collection; @@ -34,43 +33,37 @@ @Test public void testMergingKStyleLambdasAfterUnusedArgumentRemoval() throws Exception { final String mainClassName = "unused_arg_in_lambdas_kstyle.MainKt"; - runTest( - "unused_arg_in_lambdas_kstyle", - mainClassName, - app -> { - CodeInspector inspector = new CodeInspector(app); - inspector.forAllClasses( - classSubject -> { - if (classSubject.getOriginalDescriptor().contains("$ks")) { - MethodSubject init = classSubject.init(ImmutableList.of("int")); - assertThat(init, isPresent()); - // Arity 2 should appear. - assertTrue(init.iterateInstructions(i -> i.isConstNumber(2)).hasNext()); + runTest("unused_arg_in_lambdas_kstyle", mainClassName) + .inspect( + inspector -> + inspector.forAllClasses( + classSubject -> { + if (classSubject.getOriginalDescriptor().contains("$ks")) { + MethodSubject init = classSubject.init(ImmutableList.of("int")); + assertThat(init, isPresent()); + // Arity 2 should appear. + assertTrue(init.iterateInstructions(i -> i.isConstNumber(2)).hasNext()); - MethodSubject invoke = classSubject.uniqueMethodWithName("invoke"); - assertThat(invoke, isPresent()); - assertEquals(2, invoke.getMethod().method.proto.parameters.size()); - } - }); - }); + MethodSubject invoke = classSubject.uniqueMethodWithName("invoke"); + assertThat(invoke, isPresent()); + assertEquals(2, invoke.getMethod().method.proto.parameters.size()); + } + })); } @Test public void testMergingJStyleLambdasAfterUnusedArgumentRemoval() throws Exception { final String mainClassName = "unused_arg_in_lambdas_jstyle.MainKt"; - runTest( - "unused_arg_in_lambdas_jstyle", - mainClassName, - app -> { - CodeInspector inspector = new CodeInspector(app); - inspector.forAllClasses( - classSubject -> { - if (classSubject.getOriginalDescriptor().contains("$js")) { - MethodSubject get = classSubject.uniqueMethodWithName("get"); - assertThat(get, isPresent()); - assertEquals(3, get.getMethod().method.proto.parameters.size()); - } - }); - }); + runTest("unused_arg_in_lambdas_jstyle", mainClassName) + .inspect( + inspector -> + inspector.forAllClasses( + classSubject -> { + if (classSubject.getOriginalDescriptor().contains("$js")) { + MethodSubject get = classSubject.uniqueMethodWithName("get"); + assertThat(get, isPresent()); + assertEquals(3, get.getMethod().method.proto.parameters.size()); + } + })); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java index c82a4a4..10fe60e 100644 --- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -12,7 +12,6 @@ import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FieldSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode; @@ -43,53 +42,51 @@ public void b110196118() throws Exception { final String mainClassName = "unused_singleton.MainKt"; final String moduleName = "unused_singleton.TestModule"; - runTest( - "unused_singleton", - mainClassName, - "-dontobfuscate", - app -> { - CodeInspector inspector = new CodeInspector(app); - ClassSubject main = inspector.clazz(mainClassName); - assertThat(main, isPresent()); + runTest("unused_singleton", mainClassName) + .inspect( + inspector -> { + ClassSubject main = inspector.clazz(mainClassName); + assertThat(main, isPresent()); - MethodSubject mainMethod = main.mainMethod(); - assertThat(mainMethod, isPresent()); + MethodSubject mainMethod = main.mainMethod(); + assertThat(mainMethod, isPresent()); - // The const-string from provideGreeting() has been propagated. - assertTrue( - mainMethod - .iterateInstructions(i -> i.isConstString("Hello", JumboStringMode.ALLOW)) - .hasNext()); + // The const-string from provideGreeting() has been propagated. + assertTrue( + mainMethod + .iterateInstructions(i -> i.isConstString("Hello", JumboStringMode.ALLOW)) + .hasNext()); - // The method provideGreeting() is no longer being invoked -- i.e., we have been able to - // determine that the class initialization of the enclosing class is trivial. - ClassSubject module = inspector.clazz(moduleName); - assertThat(main, isPresent()); - assertEquals( - 0, - mainMethod - .streamInstructions() - .filter(InstructionSubject::isInvoke) - .map(i -> i.getMethod().toSourceString()) - .filter( - invokedMethod -> - !invokedMethod.equals(checkParameterIsNotNullSignature) - && !invokedMethod.equals(printlnSignature) - && !invokedMethod.equals(throwParameterIsNotNullExceptionSignature)) - .count()); + // The method provideGreeting() is no longer being invoked -- i.e., we have been able + // to determine that the class initialization of the enclosing class is trivial. + ClassSubject module = inspector.clazz(moduleName); + assertThat(main, isPresent()); + assertEquals( + 0, + mainMethod + .streamInstructions() + .filter(InstructionSubject::isInvoke) + .map(i -> i.getMethod().toSourceString()) + .filter( + invokedMethod -> + !invokedMethod.equals(checkParameterIsNotNullSignature) + && !invokedMethod.equals(printlnSignature) + && !invokedMethod.equals( + throwParameterIsNotNullExceptionSignature)) + .count()); - // The field `INSTANCE` has been removed entirely. - FieldSubject instance = module.uniqueFieldWithName("INSTANCE"); - assertThat(instance, not(isPresent())); + // The field `INSTANCE` has been removed entirely. + FieldSubject instance = module.uniqueFieldWithName("INSTANCE"); + assertThat(instance, not(isPresent())); - // The class initializer is no longer there. - MethodSubject clinit = module.clinit(); - assertThat(clinit, not(isPresent())); + // The class initializer is no longer there. + MethodSubject clinit = module.clinit(); + assertThat(clinit, not(isPresent())); - // Also, the instance initializer is no longer there, since it is only reachable from the - // class initializer. - MethodSubject init = module.init(ImmutableList.of()); - assertThat(init, not(isPresent())); - }); + // Also, the instance initializer is no longer there, since it is only reachable from + // the class initializer. + MethodSubject init = module.init(ImmutableList.of()); + assertThat(init, not(isPresent())); + }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java index e5ce02c..b4aeced 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.ToolHelper.ProcessResult; @@ -18,14 +19,11 @@ import com.android.tools.r8.naming.MemberNaming; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FieldSubject; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; -import java.util.function.Consumer; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -67,9 +65,6 @@ .addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE) .addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE); - private Consumer<InternalOptions> disableClassStaticizer = - opts -> opts.enableClassStaticizer = false; - @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}") public static Collection<Object[]> data() { return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values()); @@ -85,33 +80,32 @@ final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp"); - runTest( - PROPERTIES_PACKAGE_NAME, - mainClass, - disableClassStaticizer, - (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "primitiveProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "primitiveProp"; + FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(outerClass, getterAccessor); - checkMethodIsKept(outerClass, setterAccessor); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(outerClass, getterAccessor); + checkMethodIsKept(outerClass, setterAccessor); + } + }); } @Test @@ -119,34 +113,34 @@ final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionPropertiesKt", "companionProperties_usePrivateProp"); - runTest( - PROPERTIES_PACKAGE_NAME, - mainClass, - disableClassStaticizer, - (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "privateProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "privateProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(outerClass, getterAccessor); - checkMethodIsKept(outerClass, setterAccessor); - } - }); + checkMethodIsKept(outerClass, getterAccessor); + checkMethodIsKept(outerClass, setterAccessor); + } + }); } @Test @@ -154,33 +148,33 @@ final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionPropertiesKt", "companionProperties_useInternalProp"); - runTest( - PROPERTIES_PACKAGE_NAME, - mainClass, - disableClassStaticizer, - (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "internalProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "internalProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(outerClass, getterAccessor); - checkMethodIsKept(outerClass, setterAccessor); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(outerClass, getterAccessor); + checkMethodIsKept(outerClass, setterAccessor); + } + }); } @Test @@ -188,33 +182,33 @@ final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionPropertiesKt", "companionProperties_usePublicProp"); - runTest( - PROPERTIES_PACKAGE_NAME, - mainClass, - disableClassStaticizer, - (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "publicProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "publicProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(outerClass, getterAccessor); - checkMethodIsKept(outerClass, setterAccessor); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(outerClass, getterAccessor); + checkMethodIsKept(outerClass, setterAccessor); + } + }); } @Test @@ -222,32 +216,32 @@ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_usePrivateLateInitProp"); - runTest( - PROPERTIES_PACKAGE_NAME, - mainClass, - disableClassStaticizer, - (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "privateLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "privateLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(outerClass, getterAccessor); - checkMethodIsKept(outerClass, setterAccessor); - } - }); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(outerClass, getterAccessor); + checkMethodIsKept(outerClass, setterAccessor); + } + }); } @Test @@ -255,23 +249,28 @@ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_useInternalLateInitProp"); - runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "internalLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "internalLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - MemberNaming.MethodSignature getterAccessor = testedClass - .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = testedClass - .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - }); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + }); } @Test @@ -279,23 +278,28 @@ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS; String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_usePublicLateInitProp"); - runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - String propertyName = "publicLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + runTest(PROPERTIES_PACKAGE_NAME, mainClass) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + String propertyName = "publicLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - MemberNaming.MethodSignature getterAccessor = testedClass - .getGetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); - MemberNaming.MethodSignature setterAccessor = testedClass - .getSetterAccessorForProperty(propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty( + propertyName, AccessorKind.FROM_COMPANION); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(outerClass, getterAccessor); - checkMethodIsRemoved(outerClass, setterAccessor); - }); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(outerClass, getterAccessor); + checkMethodIsRemoved(outerClass, setterAccessor); + }); } @Test @@ -303,17 +307,14 @@ TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS; String mainClass = addMainToClasspath("accessors.AccessorKt", "accessor_accessPropertyFromCompanionClass"); - runTest( - "accessors", - mainClass, - disableClassStaticizer, - app -> { - // The classes are removed entirely as a result of member value propagation, inlining, and - // the fact that the classes do not have observable side effects. - CodeInspector codeInspector = new CodeInspector(app); - checkClassIsRemoved(codeInspector, testedClass.getOuterClassName()); - checkClassIsRemoved(codeInspector, testedClass.getClassName()); - }); + runTest("accessors", mainClass, R8TestBuilder::noClassStaticizing) + .inspect( + inspector -> { + // The classes are removed entirely as a result of member value propagation, inlining, + // and the fact that the classes do not have observable side effects. + checkClassIsRemoved(inspector, testedClass.getOuterClassName()); + checkClassIsRemoved(inspector, testedClass.getClassName()); + }); } @Test @@ -322,29 +323,32 @@ TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS; String mainClass = addMainToClasspath("accessors.AccessorKt", "accessor_accessPropertyFromOuterClass"); - runTest("accessors", mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "property"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + runTest("accessors", mainClass) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "property"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - // We cannot inline the getter because we don't know if NPE is preserved. - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - checkMethodIsKept(companionClass, getter); + // We cannot inline the getter because we don't know if NPE is preserved. + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + checkMethodIsKept(companionClass, getter); - // We should always inline the static accessor method. - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); - checkMethodIsRemoved(companionClass, getterAccessor); + // We should always inline the static accessor method. + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); + checkMethodIsRemoved(companionClass, getterAccessor); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -352,15 +356,12 @@ String mainClass = addMainToClasspath( "accessors.PropertyAccessorForInnerClassKt", "noUseOfPropertyAccessorFromInnerClass"); - runTest( - "accessors", - mainClass, - (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - - // Class is removed because the instantiation of the inner class has no side effects. - checkClassIsRemoved(codeInspector, PROPERTY_ACCESS_FOR_INNER_CLASS.getClassName()); - }); + runTest("accessors", mainClass) + .inspect( + inspector -> { + // Class is removed because the instantiation of the inner class has no side effects. + checkClassIsRemoved(inspector, PROPERTY_ACCESS_FOR_INNER_CLASS.getClassName()); + }); } @Test @@ -369,29 +370,30 @@ TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS; String mainClass = addMainToClasspath(testedClass.className + "Kt", "usePrivatePropertyAccessorFromInnerClass"); - runTest("accessors", mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName()); + runTest("accessors", mainClass) + .inspect( + inspector -> { + ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName()); - String propertyName = "privateProp"; - FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, - propertyName); - assertFalse(fieldSubject.getField().accessFlags.isStatic()); + String propertyName = "privateProp"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + assertFalse(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getterAccessor); - checkMethodIsRemoved(classSubject, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getterAccessor); - checkMethodIsKept(classSubject, setterAccessor); - } - }); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getterAccessor); + checkMethodIsRemoved(classSubject, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getterAccessor); + checkMethodIsKept(classSubject, setterAccessor); + } + }); } @Test @@ -400,29 +402,30 @@ TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS; String mainClass = addMainToClasspath(testedClass.className + "Kt", "usePrivateLateInitPropertyAccessorFromInnerClass"); - runTest("accessors", mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName()); + runTest("accessors", mainClass) + .inspect( + inspector -> { + ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName()); - String propertyName = "privateLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, - propertyName); - assertFalse(fieldSubject.getField().accessFlags.isStatic()); + String propertyName = "privateLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + assertFalse(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getterAccessor); - checkMethodIsRemoved(classSubject, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getterAccessor); - checkMethodIsKept(classSubject, setterAccessor); - } - }); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getterAccessor); + checkMethodIsRemoved(classSubject, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getterAccessor); + checkMethodIsKept(classSubject, setterAccessor); + } + }); } @Test @@ -431,19 +434,20 @@ TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_LAMBDA_CLASS; String mainClass = addMainToClasspath(testedClass.className + "Kt", "noUseOfPropertyAccessorFromLambda"); - runTest("accessors", mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "property"; + runTest("accessors", mainClass) + .inspect( + inspector -> { + ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "property"; - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); - checkMethodIsRemoved(classSubject, getterAccessor); - checkMethodIsRemoved(classSubject, setterAccessor); - }); + checkMethodIsRemoved(classSubject, getterAccessor); + checkMethodIsRemoved(classSubject, setterAccessor); + }); } @Test @@ -452,27 +456,29 @@ TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_LAMBDA_CLASS; String mainClass = addMainToClasspath(testedClass.className + "Kt", "usePropertyAccessorFromLambda"); - runTest("accessors", mainClass, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "property"; - FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); - assertFalse(fieldSubject.getField().accessFlags.isStatic()); + runTest("accessors", mainClass) + .inspect( + inspector -> { + ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "property"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + assertFalse(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getterAccessor = - testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); - MemberNaming.MethodSignature setterAccessor = - testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getterAccessor); - checkMethodIsRemoved(classSubject, setterAccessor); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getterAccessor); - checkMethodIsKept(classSubject, setterAccessor); - } - }); + MemberNaming.MethodSignature getterAccessor = + testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); + MemberNaming.MethodSignature setterAccessor = + testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getterAccessor); + checkMethodIsRemoved(classSubject, setterAccessor); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getterAccessor); + checkMethodIsKept(classSubject, setterAccessor); + } + }); } @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java index 59c92ff..ec0ee03 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -11,7 +11,6 @@ import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.MethodSubject; import java.util.Collection; import java.util.Collections; @@ -59,33 +58,39 @@ final MethodSignature testMethodSignature = new MethodSignature("testDataClassGetters", "void", Collections.emptyList()); final String extraRules = keepClassMethod(mainClassName, testMethodSignature); - runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName()); + runTest( + "dataclass", + mainClassName, + testBuilder -> + testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner)) + .inspect( + inspector -> { + ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); - // Getters should be removed after inlining, which is possible only if access is relaxed. - final boolean areGetterPresent = !allowAccessModification; - checkMethodIsKeptOrRemoved(dataClass, NAME_GETTER_METHOD, areGetterPresent); - checkMethodIsKeptOrRemoved(dataClass, AGE_GETTER_METHOD, areGetterPresent); + // Getters should be removed after inlining, which is possible only if access is + // relaxed. + final boolean areGetterPresent = !allowAccessModification; + checkMethodIsKeptOrRemoved(dataClass, NAME_GETTER_METHOD, areGetterPresent); + checkMethodIsKeptOrRemoved(dataClass, AGE_GETTER_METHOD, areGetterPresent); - // No use of componentN functions. - checkMethodIsRemoved(dataClass, COMPONENT1_METHOD); - checkMethodIsRemoved(dataClass, COMPONENT2_METHOD); + // No use of componentN functions. + checkMethodIsRemoved(dataClass, COMPONENT1_METHOD); + checkMethodIsRemoved(dataClass, COMPONENT2_METHOD); - // No use of copy functions. - checkMethodIsRemoved(dataClass, COPY_METHOD); - checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + // No use of copy functions. + checkMethodIsRemoved(dataClass, COPY_METHOD); + checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); - ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName); - MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature); - DexCode dexCode = getDexCode(testMethod); - if (allowAccessModification) { - // Both getters should be inlined - checkMethodIsNeverInvoked(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD); - } else { - checkMethodIsInvokedAtLeastOnce(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD); - } - }); + ClassSubject classSubject = checkClassIsKept(inspector, mainClassName); + MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature); + DexCode dexCode = getDexCode(testMethod); + if (allowAccessModification) { + // Both getters should be inlined + checkMethodIsNeverInvoked(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD); + } else { + checkMethodIsInvokedAtLeastOnce(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD); + } + }); } @Test @@ -94,33 +99,38 @@ final MethodSignature testMethodSignature = new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList()); final String extraRules = keepClassMethod(mainClassName, testMethodSignature); - runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName()); + runTest( + "dataclass", + mainClassName, + testBuilder -> + testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner)) + .inspect( + inspector -> { + ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); - // ComponentN functions should be removed after inlining, which is possible only if access - // is relaxed. - final boolean areComponentMethodsPresent = !allowAccessModification; - checkMethodIsKeptOrRemoved(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent); - checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent); + // ComponentN functions should be removed after inlining, which is possible only if + // access is relaxed. + final boolean areComponentMethodsPresent = !allowAccessModification; + checkMethodIsKeptOrRemoved(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent); + checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent); - // No use of getter. - checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD); - checkMethodIsRemoved(dataClass, AGE_GETTER_METHOD); + // No use of getter. + checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD); + checkMethodIsRemoved(dataClass, AGE_GETTER_METHOD); - // No use of copy functions. - checkMethodIsRemoved(dataClass, COPY_METHOD); - checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + // No use of copy functions. + checkMethodIsRemoved(dataClass, COPY_METHOD); + checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); - ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName); - MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature); - DexCode dexCode = getDexCode(testMethod); - if (allowAccessModification) { - checkMethodIsNeverInvoked(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD); - } else { - checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD); - } - }); + ClassSubject classSubject = checkClassIsKept(inspector, mainClassName); + MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature); + DexCode dexCode = getDexCode(testMethod); + if (allowAccessModification) { + checkMethodIsNeverInvoked(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD); + } else { + checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD); + } + }); } @Test @@ -129,33 +139,38 @@ final MethodSignature testMethodSignature = new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList()); final String extraRules = keepClassMethod(mainClassName, testMethodSignature); - runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName()); + runTest( + "dataclass", + mainClassName, + testBuilder -> + testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner)) + .inspect( + inspector -> { + ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); - boolean component2IsPresent = !allowAccessModification; - checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, component2IsPresent); + boolean component2IsPresent = !allowAccessModification; + checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, component2IsPresent); - // Function component1 is not used. - checkMethodIsRemoved(dataClass, COMPONENT1_METHOD); + // Function component1 is not used. + checkMethodIsRemoved(dataClass, COMPONENT1_METHOD); - // No use of getter. - checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD); - checkMethodIsRemoved(dataClass, AGE_GETTER_METHOD); + // No use of getter. + checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD); + checkMethodIsRemoved(dataClass, AGE_GETTER_METHOD); - // No use of copy functions. - checkMethodIsRemoved(dataClass, COPY_METHOD); - checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + // No use of copy functions. + checkMethodIsRemoved(dataClass, COPY_METHOD); + checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); - ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName); - MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature); - DexCode dexCode = getDexCode(testMethod); - if (allowAccessModification) { - checkMethodIsNeverInvoked(dexCode, COMPONENT2_METHOD); - } else { - checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT2_METHOD); - } - }); + ClassSubject classSubject = checkClassIsKept(inspector, mainClassName); + MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature); + DexCode dexCode = getDexCode(testMethod); + if (allowAccessModification) { + checkMethodIsNeverInvoked(dexCode, COMPONENT2_METHOD); + } else { + checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT2_METHOD); + } + }); } @Test @@ -164,13 +179,18 @@ final MethodSignature testMethodSignature = new MethodSignature("testDataClassCopy", "void", Collections.emptyList()); final String extraRules = keepClassMethod(mainClassName, testMethodSignature); - runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName()); + runTest( + "dataclass", + mainClassName, + testBuilder -> + testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner)) + .inspect( + inspector -> { + ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); - checkMethodIsRemoved(dataClass, COPY_METHOD); - checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); - }); + checkMethodIsRemoved(dataClass, COPY_METHOD); + checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + }); } @Test @@ -179,12 +199,17 @@ final MethodSignature testMethodSignature = new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList()); final String extraRules = keepClassMethod(mainClassName, testMethodSignature); - runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName()); + runTest( + "dataclass", + mainClassName, + testBuilder -> + testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner)) + .inspect( + inspector -> { + ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName()); - checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); - }); + checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD); + }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java index 7a9635e..770d6cc 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -8,11 +8,11 @@ import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.util.Collection; import java.util.Collections; +import kotlin.jvm.internal.Intrinsics; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -39,20 +39,31 @@ new MethodSignature("expectsNonNullParameters", "java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String"))); - runTest("intrinsics", "intrinsics.IntrinsicsKt", extraRules, (app) -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject intrinsicsClass = checkClassIsKept( - codeInspector, KOTLIN_INTRINSICS_CLASS.getClassName()); - - checkMethodsPresence(intrinsicsClass, - ImmutableMap.<MethodSignature, Boolean>builder() - .put(new MethodSignature("throwParameterIsNullException", - "void", Collections.singletonList("java.lang.String")), - true) - .put(new MethodSignature("checkParameterIsNotNull", - "void", Lists.newArrayList("java.lang.Object", "java.lang.String")), - allowAccessModification ? false /* should be inlined*/ : true) - .build()); - }); + runTest( + "intrinsics", + "intrinsics.IntrinsicsKt", + testBuilder -> + testBuilder.addKeepRules(extraRules).noHorizontalClassMerging(Intrinsics.class)) + .inspect( + inspector -> { + ClassSubject intrinsicsClass = + checkClassIsKept(inspector, KOTLIN_INTRINSICS_CLASS.getClassName()); + checkMethodsPresence( + intrinsicsClass, + ImmutableMap.<MethodSignature, Boolean>builder() + .put( + new MethodSignature( + "throwParameterIsNullException", + "void", + Collections.singletonList("java.lang.String")), + true) + .put( + new MethodSignature( + "checkParameterIsNotNull", + "void", + Lists.newArrayList("java.lang.Object", "java.lang.String")), + !allowAccessModification) + .build()); + }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java index f572789..30511bb 100644 --- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FieldSubject; import java.util.Collection; import java.util.function.Consumer; @@ -109,13 +108,13 @@ String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_noUseOfProperties"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - checkClassIsRemoved(codeInspector, MUTABLE_PROPERTY_CLASS.getClassName()); - }); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName()); + }); } @Test @@ -123,28 +122,28 @@ String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrivateProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, MUTABLE_PROPERTY_CLASS.getClassName()); - String propertyName = "privateProp"; - FieldSubject fieldSubject = - checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); - if (!allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName()); + String propertyName = "privateProp"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + if (!allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } - // Private property has no getter or setter. - checkMethodIsAbsent( - classSubject, MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName)); - checkMethodIsAbsent( - classSubject, MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName)); - }); + // Private property has no getter or setter. + checkMethodIsAbsent( + classSubject, MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName)); + checkMethodIsAbsent( + classSubject, MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName)); + }); } @Test @@ -152,27 +151,27 @@ String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useProtectedProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, MUTABLE_PROPERTY_CLASS.getClassName()); - String propertyName = "protectedProp"; - FieldSubject fieldSubject = - checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName()); + String propertyName = "protectedProp"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); - // Protected property has private field. - MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getter); - } - }); + // Protected property has private field. + MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getter); + } + }); } @Test @@ -180,27 +179,27 @@ String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useInternalProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, MUTABLE_PROPERTY_CLASS.getClassName()); - String propertyName = "internalProp"; - FieldSubject fieldSubject = - checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName()); + String propertyName = "internalProp"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); - // Internal property has private field - MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getter); - } - }); + // Internal property has private field + MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getter); + } + }); } @Test @@ -208,27 +207,27 @@ String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePublicProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, MUTABLE_PROPERTY_CLASS.getClassName()); - String propertyName = "publicProp"; - FieldSubject fieldSubject = - checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName()); + String propertyName = "publicProp"; + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName); - // Public property has private field - MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getter); - } - }); + // Public property has private field + MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getter); + } + }); } @Test @@ -236,28 +235,28 @@ String mainClass = addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrimitiveProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, MUTABLE_PROPERTY_CLASS.getClassName()); - String propertyName = "primitiveProp"; - FieldSubject fieldSubject = checkFieldIsKept(classSubject, "int", propertyName); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName()); + String propertyName = "primitiveProp"; + FieldSubject fieldSubject = checkFieldIsKept(classSubject, "int", propertyName); - MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); - MethodSignature setter = MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getter); - checkMethodIsRemoved(classSubject, setter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getter); - checkMethodIsKept(classSubject, setter); - } - }); + MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName); + MethodSignature setter = MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getter); + checkMethodIsRemoved(classSubject, setter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getter); + checkMethodIsKept(classSubject, setter); + } + }); } @Test @@ -265,13 +264,13 @@ String mainClass = addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_noUseOfProperties"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - checkClassIsRemoved(codeInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); - }); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()); + }); } @Test @@ -279,26 +278,26 @@ String mainClass = addMainToClasspath( "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); - String propertyName = "privateLateInitProp"; - FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); - assertTrue("Field is absent", fieldSubject.isPresent()); - if (!allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()); + String propertyName = "privateLateInitProp"; + FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); + assertTrue("Field is absent", fieldSubject.isPresent()); + if (!allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } - // Private late init property have no getter or setter. - checkMethodIsAbsent( - classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); - checkMethodIsAbsent( - classSubject, LATE_INIT_PROPERTY_CLASS.getSetterForProperty(propertyName)); - }); + // Private late init property have no getter or setter. + checkMethodIsAbsent( + classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); + checkMethodIsAbsent( + classSubject, LATE_INIT_PROPERTY_CLASS.getSetterForProperty(propertyName)); + }); } @Test @@ -306,24 +305,24 @@ String mainClass = addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_useProtectedLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); - String propertyName = "protectedLateInitProp"; - FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); - assertTrue("Field is absent", fieldSubject.isPresent()); - if (!allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isProtected()); - } + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()); + String propertyName = "protectedLateInitProp"; + FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); + assertTrue("Field is absent", fieldSubject.isPresent()); + if (!allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isProtected()); + } - // Protected late init property have protected getter - checkMethodIsRemoved( - classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); - }); + // Protected late init property have protected getter + checkMethodIsRemoved( + classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); + }); } @Test @@ -331,22 +330,22 @@ String mainClass = addMainToClasspath( "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); - String propertyName = "internalLateInitProp"; - FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); - assertTrue("Field is absent", fieldSubject.isPresent()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()); + String propertyName = "internalLateInitProp"; + FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); + assertTrue("Field is absent", fieldSubject.isPresent()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - // Internal late init property have protected getter - checkMethodIsRemoved( - classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); - }); + // Internal late init property have protected getter + checkMethodIsRemoved( + classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); + }); } @Test @@ -354,22 +353,22 @@ String mainClass = addMainToClasspath( "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, LATE_INIT_PROPERTY_CLASS.getClassName()); - String propertyName = "publicLateInitProp"; - FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); - assertTrue("Field is absent", fieldSubject.isPresent()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()); + String propertyName = "publicLateInitProp"; + FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName); + assertTrue("Field is absent", fieldSubject.isPresent()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - // Internal late init property have protected getter - checkMethodIsRemoved( - classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); - }); + // Internal late init property have protected getter + checkMethodIsRemoved( + classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName)); + }); } @Test @@ -377,13 +376,13 @@ String mainClass = addMainToClasspath( "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - checkClassIsRemoved(codeInspector, USER_DEFINED_PROPERTY_CLASS.getClassName()); - }); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName()); + }); } @Test @@ -391,29 +390,30 @@ String mainClass = addMainToClasspath( "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject classSubject = - checkClassIsKept(codeInspector, USER_DEFINED_PROPERTY_CLASS.getClassName()); - String propertyName = "durationInSeconds"; - // The 'wrapper' property is not assigned to a backing field, it only relies on the - // wrapped property. - checkFieldIsAbsent(classSubject, "int", "durationInSeconds"); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject classSubject = + checkClassIsKept(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName()); + String propertyName = "durationInSeconds"; + // The 'wrapper' property is not assigned to a backing field, it only relies on the + // wrapped property. + checkFieldIsAbsent(classSubject, "int", "durationInSeconds"); - FieldSubject fieldSubject = - checkFieldIsKept(classSubject, "int", "durationInMilliSeconds"); - MethodSignature getter = USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(classSubject, getter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(classSubject, getter); - } - }); + FieldSubject fieldSubject = + checkFieldIsKept(classSubject, "int", "durationInMilliSeconds"); + MethodSignature getter = + USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(classSubject, getter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(classSubject, getter); + } + }); } @Test @@ -421,32 +421,32 @@ String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, "properties.CompanionProperties"); - ClassSubject companionClass = - checkClassIsKept(codeInspector, COMPANION_PROPERTY_CLASS.getClassName()); - String propertyName = "primitiveProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, "properties.CompanionProperties"); + ClassSubject companionClass = + checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName()); + String propertyName = "primitiveProp"; + FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } - MemberNaming.MethodSignature getter = - COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, getter); + MemberNaming.MethodSignature getter = + COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, getter); - MemberNaming.MethodSignature setter = - COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, setter); - }); + MemberNaming.MethodSignature setter = + COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, setter); + }); } @Test @@ -454,37 +454,38 @@ String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, "properties.CompanionProperties"); - ClassSubject companionClass = - checkClassIsKept(codeInspector, COMPANION_PROPERTY_CLASS.getClassName()); - String propertyName = "privateProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, "properties.CompanionProperties"); + ClassSubject companionClass = + checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName()); + String propertyName = "privateProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = - COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = - COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = + COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = + COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); - // Because the getter/setter are private, they can only be called from another method in - // the class. If this is an instance method, they will be called on 'this' which is known - // to be non-null, thus the getter/setter can be inlined if their code is small enough. - // Because the backing field is private, they will call into an accessor (static) method. - // If access relaxation is enabled, this accessor can be removed. - checkMethodIsAbsent(companionClass, getter); - checkMethodIsAbsent(companionClass, setter); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + // Because the getter/setter are private, they can only be called from another method + // in the class. If this is an instance method, they will be called on 'this' which is + // known to be non-null, thus the getter/setter can be inlined if their code is small + // enough. Because the backing field is private, they will call into an accessor + // (static) method. If access relaxation is enabled, this accessor can be removed. + checkMethodIsAbsent(companionClass, getter); + checkMethodIsAbsent(companionClass, setter); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -492,31 +493,32 @@ String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_useInternalProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, "properties.CompanionProperties"); - ClassSubject companionClass = - checkClassIsKept(codeInspector, COMPANION_PROPERTY_CLASS.getClassName()); - String propertyName = "internalProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, "properties.CompanionProperties"); + ClassSubject companionClass = + checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName()); + String propertyName = "internalProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } - MemberNaming.MethodSignature getter = - COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, getter); - MemberNaming.MethodSignature setter = - COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, setter); - }); + MemberNaming.MethodSignature getter = + COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, getter); + MemberNaming.MethodSignature setter = + COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, setter); + }); } @Test @@ -524,32 +526,33 @@ String mainClass = addMainToClasspath( "properties.CompanionPropertiesKt", "companionProperties_usePublicProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, "properties.CompanionProperties"); - ClassSubject companionClass = - checkClassIsKept(codeInspector, COMPANION_PROPERTY_CLASS.getClassName()); - String propertyName = "publicProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, "properties.CompanionProperties"); + ClassSubject companionClass = + checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName()); + String propertyName = "publicProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } - MemberNaming.MethodSignature getter = - COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, getter); + MemberNaming.MethodSignature getter = + COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, getter); - MemberNaming.MethodSignature setter = - COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, setter); - }); + MemberNaming.MethodSignature setter = + COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, setter); + }); } @Test @@ -558,34 +561,35 @@ String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_usePrivateLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "privateLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "privateLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - // Because the getter/setter are private, they can only be called from another method in - // the class. If this is an instance method, they will be called on 'this' which is known - // to be non-null, thus the getter/setter can be inlined if their code is small enough. - // Because the backing field is private, they will call into an accessor (static) method. - // If access relaxation is enabled, this accessor can be removed. - checkMethodIsAbsent(companionClass, getter); - checkMethodIsAbsent(companionClass, setter); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + // Because the getter/setter are private, they can only be called from another method + // in the class. If this is an instance method, they will be called on 'this' which is + // known to be non-null, thus the getter/setter can be inlined if their code is small + // enough. Because the backing field is private, they will call into an accessor + // (static) method. If access relaxation is enabled, this accessor can be removed. + checkMethodIsAbsent(companionClass, getter); + checkMethodIsAbsent(companionClass, setter); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -594,25 +598,26 @@ String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_useInternalLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "internalLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "internalLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, getter); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, getter); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, setter); - }); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, setter); + }); } @Test @@ -621,25 +626,26 @@ String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt", "companionLateInitProperties_usePublicLateInitProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject outerClass = - checkClassIsKept(codeInspector, testedClass.getOuterClassName()); - ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "publicLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject outerClass = + checkClassIsKept(inspector, testedClass.getOuterClassName()); + ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "publicLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, getter); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, getter); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - checkMethodIsRemoved(companionClass, setter); - }); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + checkMethodIsRemoved(companionClass, setter); + }); } @Test @@ -648,36 +654,36 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "primitiveProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "primitiveProp"; + FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - if (allowAccessModification) { - // Getter and setter is inlined because the constructor of ObjectProperties is - // considered trivial, which implies that the member value propagation marks the - // INSTANCE field as being non-null. - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - } else { - checkMethodIsKept(objectClass, getter); - checkMethodIsKept(objectClass, setter); - } + if (allowAccessModification) { + // Getter and setter is inlined because the constructor of ObjectProperties is + // considered trivial, which implies that the member value propagation marks the + // INSTANCE field as being non-null. + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + } else { + checkMethodIsKept(objectClass, getter); + checkMethodIsKept(objectClass, setter); + } - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -686,29 +692,30 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_usePrivateProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "privateProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "privateProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - // A private property has no getter/setter. - checkMethodIsAbsent(objectClass, getter); - checkMethodIsAbsent(objectClass, setter); + // A private property has no getter/setter. + checkMethodIsAbsent(objectClass, getter); + checkMethodIsAbsent(objectClass, setter); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -717,36 +724,37 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_useInternalProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "internalProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "internalProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - if (allowAccessModification) { - // Getter and setter is inlined because the constructor of ObjectProperties is - // considered trivial, which implies that the member value propagation marks the - // INSTANCE field as being non-null. - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - } else { - checkMethodIsKept(objectClass, getter); - checkMethodIsKept(objectClass, setter); - } + if (allowAccessModification) { + // Getter and setter is inlined because the constructor of ObjectProperties is + // considered trivial, which implies that the member value propagation marks the + // INSTANCE field as being non-null. + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + } else { + checkMethodIsKept(objectClass, getter); + checkMethodIsKept(objectClass, setter); + } - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -755,36 +763,37 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_usePublicProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "publicProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "publicProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - if (allowAccessModification) { - // Getter and setter is inlined because the constructor of ObjectProperties is - // considered trivial, which implies that the member value propagation marks the - // INSTANCE field as being non-null. - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - } else { - checkMethodIsKept(objectClass, getter); - checkMethodIsKept(objectClass, setter); - } + if (allowAccessModification) { + // Getter and setter is inlined because the constructor of ObjectProperties is + // considered trivial, which implies that the member value propagation marks the + // INSTANCE field as being non-null. + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + } else { + checkMethodIsKept(objectClass, getter); + checkMethodIsKept(objectClass, setter); + } - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -793,29 +802,30 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "privateLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "privateLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - // A private property has no getter/setter. - checkMethodIsAbsent(objectClass, getter); - checkMethodIsAbsent(objectClass, setter); + // A private property has no getter/setter. + checkMethodIsAbsent(objectClass, getter); + checkMethodIsAbsent(objectClass, setter); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -824,23 +834,24 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "internalLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "internalLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - }); + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + }); } @Test @@ -849,23 +860,24 @@ String mainClass = addMainToClasspath( "properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "publicLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "publicLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - }); + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + }); } @Test @@ -874,29 +886,29 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_usePrimitiveProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "primitiveProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "primitiveProp"; + FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(objectClass, getter); - checkMethodIsKept(objectClass, setter); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(objectClass, getter); + checkMethodIsKept(objectClass, setter); + } + }); } @Test @@ -905,28 +917,29 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_usePrivateProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "privateProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "privateProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - // A private property has no getter/setter. - checkMethodIsAbsent(objectClass, getter); - checkMethodIsAbsent(objectClass, setter); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + // A private property has no getter/setter. + checkMethodIsAbsent(objectClass, getter); + checkMethodIsAbsent(objectClass, setter); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -935,28 +948,31 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_useInternalProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "internalProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "internalProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - // We expect getter to be inlined when access (of the backing field) is relaxed to public. - // Note: the setter is considered as a regular method (because of KotlinC adding extra - // null checks), thus we cannot say if the setter would be inlined or not by R8. - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(objectClass, getter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(objectClass, getter); - } - }); + // We expect getter to be inlined when access (of the backing field) is relaxed to + // public. + // + // Note: the setter is considered as a regular method (because of KotlinC adding extra + // null checks), thus we cannot say if the setter would be inlined or not by R8. + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(objectClass, getter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(objectClass, getter); + } + }); } @Test @@ -965,29 +981,30 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_usePublicProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "publicProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "publicProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - // We expect getter to be inlined when access (of the backing field) is relaxed to public. - // On the other hand, the setter is considered as a regular method (because of null - // checks), thus we cannot say if it can be inlined or not. - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + // We expect getter to be inlined when access (of the backing field) is relaxed to + // public. On the other hand, the setter is considered as a regular method (because of + // null checks), thus we cannot say if it can be inlined or not. + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - checkMethodIsRemoved(objectClass, getter); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - checkMethodIsKept(objectClass, getter); - } - }); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + checkMethodIsRemoved(objectClass, getter); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + checkMethodIsKept(objectClass, getter); + } + }); } @Test @@ -996,28 +1013,29 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject fileClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "privateLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(fileClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject fileClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "privateLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(fileClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - // A private property has no getter/setter. - checkMethodIsAbsent(fileClass, getter); - checkMethodIsAbsent(fileClass, setter); - if (allowAccessModification) { - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - } else { - assertTrue(fieldSubject.getField().accessFlags.isPrivate()); - } - }); + // A private property has no getter/setter. + checkMethodIsAbsent(fileClass, getter); + checkMethodIsAbsent(fileClass, setter); + if (allowAccessModification) { + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + } else { + assertTrue(fieldSubject.getField().accessFlags.isPrivate()); + } + }); } @Test @@ -1026,25 +1044,26 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "internalLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "internalLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - // Field is public and getter/setter is only called from one place so we expect to always - // inline it. - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - }); + // Field is public and getter/setter is only called from one place so we expect to + // always inline it. + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + }); } @Test @@ -1053,23 +1072,24 @@ String mainClass = addMainToClasspath( "properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp"); runTest( - PACKAGE_NAME, - mainClass, - disableAggressiveClassOptimizations, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName()); - String propertyName = "publicLateInitProp"; - FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); - assertTrue(fieldSubject.getField().accessFlags.isStatic()); + PACKAGE_NAME, + mainClass, + testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations)) + .inspect( + inspector -> { + ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName()); + String propertyName = "publicLateInitProp"; + FieldSubject fieldSubject = + checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName); + assertTrue(fieldSubject.getField().accessFlags.isStatic()); - MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); - MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); + MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName); + MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName); - checkMethodIsRemoved(objectClass, getter); - checkMethodIsRemoved(objectClass, setter); - assertTrue(fieldSubject.getField().accessFlags.isPublic()); - }); + checkMethodIsRemoved(objectClass, getter); + checkMethodIsRemoved(objectClass, setter); + assertTrue(fieldSubject.getField().accessFlags.isPublic()); + }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java index a144a0e..d56d0f0 100644 --- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -9,7 +9,6 @@ import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.ImmutableList; import java.util.Collection; @@ -40,24 +39,21 @@ ImmutableList.of("java.util.Collection", STRING, STRING, "java.lang.Integer")); final String mainClassName = ex1.getClassName(); - final String extraRules = - keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature); - runTest( - FOLDER, - mainClassName, - extraRules, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject clazz = checkClassIsKept(codeInspector, ex1.getClassName()); + final String extraRules = neverInlineMethod(mainClassName, testMethodSignature); + runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules)) + .inspect( + inspector -> { + ClassSubject clazz = checkClassIsKept(inspector, ex1.getClassName()); - MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature); - long ifzCount = - testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count(); - long paramNullCheckCount = countCall(testMethod, "Intrinsics", "checkParameterIsNotNull"); - // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null. - assertEquals(2, ifzCount); - assertEquals(0, paramNullCheckCount); - }); + MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature); + long ifzCount = + testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count(); + long paramNullCheckCount = + countCall(testMethod, "Intrinsics", "checkParameterIsNotNull"); + // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null. + assertEquals(2, ifzCount); + assertEquals(0, paramNullCheckCount); + }); } @Test @@ -67,22 +63,21 @@ new MethodSignature("aOrDefault", STRING, ImmutableList.of(STRING, STRING)); final String mainClassName = ex2.getClassName(); - final String extraRules = - keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature); - runTest(FOLDER, mainClassName, extraRules, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject clazz = checkClassIsKept(codeInspector, ex2.getClassName()); + final String extraRules = neverInlineMethod(mainClassName, testMethodSignature); + runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules)) + .inspect( + inspector -> { + ClassSubject clazz = checkClassIsKept(inspector, ex2.getClassName()); - MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature); - long ifzCount = - testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count(); - long paramNullCheckCount = - countCall(testMethod, "Intrinsics", "checkParameterIsNotNull"); - // ?: in aOrDefault - assertEquals(1, ifzCount); - assertEquals(allowAccessModification ? 0 : 1, paramNullCheckCount); - }); + MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature); + long ifzCount = + testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count(); + long paramNullCheckCount = + countCall(testMethod, "Intrinsics", "checkParameterIsNotNull"); + // ?: in aOrDefault + assertEquals(1, ifzCount); + assertEquals(allowAccessModification ? 0 : 1, paramNullCheckCount); + }); } @Test @@ -92,19 +87,18 @@ new MethodSignature("neverThrowNPE", "void", ImmutableList.of("non_null.Foo")); final String mainClassName = ex3.getClassName(); - final String extraRules = - keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature); - runTest(FOLDER, mainClassName, extraRules, - app -> { - CodeInspector codeInspector = new CodeInspector(app); - ClassSubject clazz = checkClassIsKept(codeInspector, ex3.getClassName()); + final String extraRules = neverInlineMethod(mainClassName, testMethodSignature); + runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules)) + .inspect( + inspector -> { + ClassSubject clazz = checkClassIsKept(inspector, ex3.getClassName()); - MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature); - long ifzCount = - testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count(); - // !! operator inside explicit null check should be gone. - // One explicit null-check as well as 4 bar? accesses. - assertEquals(5, ifzCount); - }); + MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature); + long ifzCount = + testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count(); + // !! operator inside explicit null check should be gone. + // One explicit null-check as well as 4 bar? accesses. + assertEquals(5, ifzCount); + }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java index bf17ac0..f28f68b 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.kotlin.lambda; import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assume.assumeTrue; @@ -55,11 +54,8 @@ .addLibraryFiles(ToolHelper.getKotlinStdlibJar()) .addProgramFiles(ktClasses) .addKeepMainRule("**.B143165163Kt") - .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) .compile() - // TODO(b/143165163): better not output info like this. - .assertAllInfoMessagesMatch(containsString("unexpected static method")) .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar()) .run(parameters.getRuntime(), pkg + ".B143165163Kt") .assertSuccessWithOutputLines("outer foo bar", "outer foo default"); @@ -81,11 +77,9 @@ .addProgramFiles(ToolHelper.getKotlinStdlibJar()) .addProgramFiles(ktClasses) .addKeepMainRule("**.B143165163Kt") - .allowDiagnosticMessages() + .allowDiagnosticWarningMessages() .setMinApi(parameters.getApiLevel()) .compile() - // TODO(b/143165163): better not output info like this. - .assertAllInfoMessagesMatch(containsString("does not implement any interfaces")) .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")) .run(parameters.getRuntime(), pkg + ".B143165163Kt") .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java index 9f51143..4f3ea88 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -15,18 +15,14 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.optimize.lambda.CaptureSignature; import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase; -import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.Lists; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -35,21 +31,16 @@ public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase { private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function"; private static final String KOTLIN_FUNCTION_IFACE_STR = "kotlin.jvm.functions.Function"; - private static final String KEEP_INNER_AND_ENCLOSING = - "-keepattributes InnerClasses,EnclosingMethod\n"; - private static final String KEEP_SIGNATURE_INNER_ENCLOSING = - "-keepattributes Signature,InnerClasses,EnclosingMethod\n"; - private Consumer<InternalOptions> getOptionsModifier() { - return opts -> { - opts.enableClassInlining = false; - // The test checks that the generated lambdas inherit from Function, which is not true if - // the unused interface removal is enabled. - opts.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval; - // Ensure that enclosing method and inner class attributes are kept even on classes that are - // not explicitly mentioned by a keep rule. - opts.forceProguardCompatibility = true; - }; + private void configure(InternalOptions options) { + options.enableClassInlining = false; + // The test checks that the generated lambdas inherit from Function, which is not true if + // the unused interface removal is enabled. + options.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval; + // Ensure that enclosing method and inner class attributes are kept even on classes that are + // not explicitly mentioned by a keep rule. + options.forceProguardCompatibility = true; + options.enableHorizontalClassMergingOfKotlinLambdas = false; } private final boolean enableUnusedInterfaceRemoval; @@ -173,10 +164,6 @@ final List<DexClass> lambdas = new ArrayList<>(); final List<DexClass> groups = new ArrayList<>(); - Verifier(AndroidApp app) throws IOException, ExecutionException { - this(new CodeInspector(app)); - } - Verifier(CodeInspector codeInspector) { this.codeInspector = codeInspector; initGroupsAndLambdas(); @@ -304,242 +291,264 @@ public void testTrivialKs() throws Exception { final String mainClassName = "lambdas_kstyle_trivial.MainKt"; runTest( - "lambdas_kstyle_trivial", - mainClassName, - "-keepunusedarguments class * extends kotlin.jvm.internal.Lambda { invoke(int, short); }", - getOptionsModifier(), - app -> { - if (enableUnusedInterfaceRemoval) { - // Only test that the code generates the same output as the input code does on the JVM. - return; - } + "lambdas_kstyle_trivial", + mainClassName, + testBuilder -> + testBuilder + .addKeepRules( + "-keepunusedarguments class * extends kotlin.jvm.internal.Lambda {" + + " invoke(int, short); }") + .addOptionsModification(this::configure)) + .inspect( + inspector -> { + if (enableUnusedInterfaceRemoval) { + // Only test that the code generates the same output as the input code does on the + // JVM. + return; + } - Verifier verifier = new Verifier(app); - String pkg = "lambdas_kstyle_trivial"; + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_kstyle_trivial"; - verifier.assertLambdaGroups( - allowAccessModification - ? new Group[] { - kstyle("", 0, 4), - kstyle("", 1, 9), - kstyle("", 2, 2), // -\ - kstyle("", 2, 5), // - 3 groups different by main method - kstyle("", 2, 4), // -/ - kstyle("", 3, 2), - kstyle("", 22, 2) - } - : new Group[] { - kstyle(pkg, 0, 2), - kstyle(pkg, 1, 5), - kstyle(pkg, 2, 5), // - 2 groups different by main method - kstyle(pkg, 2, 4), // -/ - kstyle(pkg, 3, 2), - kstyle(pkg, 22, 2), - kstyle(pkg + "/inner", 0, 2), - kstyle(pkg + "/inner", 1, 4) - }); + verifier.assertLambdaGroups( + allowAccessModification + ? new Group[] { + kstyle("", 0, 4), + kstyle("", 1, 9), + kstyle("", 2, 2), // -\ + kstyle("", 2, 5), // - 3 groups different by main method + kstyle("", 2, 4), // -/ + kstyle("", 3, 2), + kstyle("", 22, 2) + } + : new Group[] { + kstyle(pkg, 0, 2), + kstyle(pkg, 1, 5), + kstyle(pkg, 2, 5), // - 2 groups different by main method + kstyle(pkg, 2, 4), // -/ + kstyle(pkg, 3, 2), + kstyle(pkg, 22, 2), + kstyle(pkg + "/inner", 0, 2), + kstyle(pkg + "/inner", 1, 4) + }); - verifier.assertLambdas( - allowAccessModification - ? new Lambda[] {} - : new Lambda[] { - new Lambda(pkg, "MainKt$testStateless$8", 2), - new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2) - }); - }); + verifier.assertLambdas( + allowAccessModification + ? new Lambda[] {} + : new Lambda[] { + new Lambda(pkg, "MainKt$testStateless$8", 2), + new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2) + }); + }); } @Test public void testCapturesKs() throws Exception { final String mainClassName = "lambdas_kstyle_captures.MainKt"; runTest( - "lambdas_kstyle_captures", - mainClassName, - getOptionsModifier(), - app -> { - if (enableUnusedInterfaceRemoval) { - // Only test that the code generates the same output as the input code does on the JVM. - return; - } + "lambdas_kstyle_captures", + mainClassName, + testBuilder -> testBuilder.addOptionsModification(this::configure)) + .inspect( + inspector -> { + if (enableUnusedInterfaceRemoval) { + // Only test that the code generates the same output as the input code does on the + // JVM. + return; + } - Verifier verifier = new Verifier(app); - String pkg = "lambdas_kstyle_captures"; - String grpPkg = allowAccessModification ? "" : pkg; + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_kstyle_captures"; + String grpPkg = allowAccessModification ? "" : pkg; - verifier.assertLambdaGroups( - kstyle(grpPkg, "LLL", 0), - kstyle(grpPkg, "ILL", 0), - kstyle(grpPkg, "III", 0), - kstyle(grpPkg, "BCDFIJLLLLSZ", 0), - kstyle(grpPkg, "BCDFIJLLSZ", 0)); + verifier.assertLambdaGroups( + kstyle(grpPkg, "LLL", 0), + kstyle(grpPkg, "ILL", 0), + kstyle(grpPkg, "III", 0), + kstyle(grpPkg, "BCDFIJLLLLSZ", 0), + kstyle(grpPkg, "BCDFIJLLSZ", 0)); - verifier.assertLambdas( - new Lambda(pkg, "MainKt$test1$15", 0), - new Lambda(pkg, "MainKt$test2$10", 0), - new Lambda(pkg, "MainKt$test2$11", 0), - new Lambda(pkg, "MainKt$test2$9", 0)); - }); + verifier.assertLambdas( + new Lambda(pkg, "MainKt$test1$15", 0), + new Lambda(pkg, "MainKt$test2$10", 0), + new Lambda(pkg, "MainKt$test2$11", 0), + new Lambda(pkg, "MainKt$test2$9", 0)); + }); } @Test public void testGenericsNoSignatureKs() throws Exception { final String mainClassName = "lambdas_kstyle_generics.MainKt"; runTest( - "lambdas_kstyle_generics", - mainClassName, - getOptionsModifier(), - app -> { - if (enableUnusedInterfaceRemoval) { - // Only test that the code generates the same output as the input code does on the JVM. - return; - } + "lambdas_kstyle_generics", + mainClassName, + testBuilder -> testBuilder.addOptionsModification(this::configure)) + .inspect( + inspector -> { + if (enableUnusedInterfaceRemoval) { + // Only test that the code generates the same output as the input code does on the + // JVM. + return; + } - Verifier verifier = new Verifier(app); - String pkg = "lambdas_kstyle_generics"; - String grpPkg = allowAccessModification ? "" : pkg; + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_kstyle_generics"; + String grpPkg = allowAccessModification ? "" : pkg; - verifier.assertLambdaGroups( - kstyle(grpPkg, 1, 3), // Group for Any - kstyle(grpPkg, "L", 1), // Group for Beta - kstyle(grpPkg, "LS", 1), // Group for Gamma - kstyle(grpPkg, 1, 2) // Group for int - ); + verifier.assertLambdaGroups( + kstyle(grpPkg, 1, 3), // Group for Any + kstyle(grpPkg, "L", 1), // Group for Beta + kstyle(grpPkg, "LS", 1), // Group for Gamma + kstyle(grpPkg, 1, 2) // Group for int + ); - verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1)); - }); + verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1)); + }); } @Test public void testInnerClassesAndEnclosingMethodsKs() throws Exception { final String mainClassName = "lambdas_kstyle_generics.MainKt"; runTest( - "lambdas_kstyle_generics", - mainClassName, - KEEP_INNER_AND_ENCLOSING, - getOptionsModifier(), - app -> { - if (enableUnusedInterfaceRemoval) { - // Only test that the code generates the same output as the input code does on the JVM. - return; - } + "lambdas_kstyle_generics", + mainClassName, + testBuilder -> + testBuilder + .addKeepAttributeInnerClassesAndEnclosingMethod() + .addOptionsModification(this::configure)) + .inspect( + inspector -> { + if (enableUnusedInterfaceRemoval) { + // Only test that the code generates the same output as the input code does on the + // JVM. + return; + } - Verifier verifier = new Verifier(app); - String pkg = "lambdas_kstyle_generics"; - String grpPkg = allowAccessModification ? "" : pkg; + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_kstyle_generics"; + String grpPkg = allowAccessModification ? "" : pkg; - verifier.assertLambdaGroups( - kstyle(grpPkg, 1, 3), // Group for Any - kstyle(grpPkg, "L", 1), // Group for Beta // First - kstyle(grpPkg, "L", 1), // Group for Beta // Second - kstyle(grpPkg, "LS", 1), // Group for Gamma // First - kstyle(grpPkg, "LS", 1), // Group for Gamma // Second - kstyle(grpPkg, 1, 2) // Group for int - ); + verifier.assertLambdaGroups( + kstyle(grpPkg, 1, 3), // Group for Any + kstyle(grpPkg, "L", 1), // Group for Beta // First + kstyle(grpPkg, "L", 1), // Group for Beta // Second + kstyle(grpPkg, "LS", 1), // Group for Gamma // First + kstyle(grpPkg, "LS", 1), // Group for Gamma // Second + kstyle(grpPkg, 1, 2) // Group for int + ); - verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1)); - }); + verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1)); + }); } @Test public void testGenericsSignatureInnerEnclosingKs() throws Exception { final String mainClassName = "lambdas_kstyle_generics.MainKt"; runTest( - "lambdas_kstyle_generics", - mainClassName, - KEEP_SIGNATURE_INNER_ENCLOSING, - getOptionsModifier(), - app -> { - if (enableUnusedInterfaceRemoval) { - // Only test that the code generates the same output as the input code does on the JVM. - return; - } + "lambdas_kstyle_generics", + mainClassName, + // KEEP_SIGNATURE_INNER_ENCLOSING, + testBuilder -> + testBuilder + .addKeepAttributeInnerClassesAndEnclosingMethod() + .addKeepAttributeSignature() + .addOptionsModification(this::configure)) + .inspect( + inspector -> { + if (enableUnusedInterfaceRemoval) { + // Only test that the code generates the same output as the input code does on the + // JVM. + return; + } - Verifier verifier = new Verifier(app); - String pkg = "lambdas_kstyle_generics"; - String grpPkg = allowAccessModification ? "" : pkg; + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_kstyle_generics"; + String grpPkg = allowAccessModification ? "" : pkg; - verifier.assertLambdaGroups( - kstyle(grpPkg, 1, 3), // Group for Any - kstyle(grpPkg, "L", 1), // Group for Beta in First - kstyle(grpPkg, "L", 1), // Group for Beta in Second - kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First - kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First - kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second - kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second - kstyle(grpPkg, 1, 2) // Group for int - ); + verifier.assertLambdaGroups( + kstyle(grpPkg, 1, 3), // Group for Any + kstyle(grpPkg, "L", 1), // Group for Beta in First + kstyle(grpPkg, "L", 1), // Group for Beta in Second + kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First + kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First + kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second + kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second + kstyle(grpPkg, 1, 2) // Group for int + ); - verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1)); - }); + verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1)); + }); } @Test public void testTrivialJs() throws Exception { final String mainClassName = "lambdas_jstyle_trivial.MainKt"; runTest( - "lambdas_jstyle_trivial", - mainClassName, - getOptionsModifier(), - app -> { - Verifier verifier = new Verifier(app); - String pkg = "lambdas_jstyle_trivial"; - String grp = allowAccessModification ? "" : pkg; + "lambdas_jstyle_trivial", + mainClassName, + testBuilder -> testBuilder.addOptionsModification(this::configure)) + .inspect( + inspector -> { + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_jstyle_trivial"; + String grp = allowAccessModification ? "" : pkg; - String supplier = "lambdas_jstyle_trivial.Lambdas$Supplier"; - String intSupplier = "lambdas_jstyle_trivial.Lambdas$IntSupplier"; - String consumer = "lambdas_jstyle_trivial.Lambdas$Consumer"; - String intConsumer = "lambdas_jstyle_trivial.Lambdas$IntConsumer"; - String multiFunction = "lambdas_jstyle_trivial.Lambdas$MultiFunction"; + String supplier = "lambdas_jstyle_trivial.Lambdas$Supplier"; + String intSupplier = "lambdas_jstyle_trivial.Lambdas$IntSupplier"; + String consumer = "lambdas_jstyle_trivial.Lambdas$Consumer"; + String intConsumer = "lambdas_jstyle_trivial.Lambdas$IntConsumer"; + String multiFunction = "lambdas_jstyle_trivial.Lambdas$MultiFunction"; - verifier.assertLambdaGroups( - jstyle(grp, 0, intSupplier, 2), - jstyle(grp, "L", 0, supplier), - jstyle(grp, "LL", 0, supplier), - jstyle(grp, "LLL", 0, supplier), - jstyle(grp, 1, intConsumer, allowAccessModification ? 3 : 2), - jstyle(grp, "I", 1, consumer), - jstyle(grp, "II", 1, consumer), - jstyle(grp, "III", 1, consumer), - jstyle(grp, "IIII", 1, consumer), - jstyle(grp, 3, multiFunction, 2), - jstyle(grp, 3, multiFunction, 2), - jstyle(grp, 3, multiFunction, 4), - jstyle(grp, 3, multiFunction, 6)); + verifier.assertLambdaGroups( + jstyle(grp, 0, intSupplier, 2), + jstyle(grp, "L", 0, supplier), + jstyle(grp, "LL", 0, supplier), + jstyle(grp, "LLL", 0, supplier), + jstyle(grp, 1, intConsumer, allowAccessModification ? 3 : 2), + jstyle(grp, "I", 1, consumer), + jstyle(grp, "II", 1, consumer), + jstyle(grp, "III", 1, consumer), + jstyle(grp, "IIII", 1, consumer), + jstyle(grp, 3, multiFunction, 2), + jstyle(grp, 3, multiFunction, 2), + jstyle(grp, 3, multiFunction, 4), + jstyle(grp, 3, multiFunction, 6)); - verifier.assertLambdas( - allowAccessModification - ? new Lambda[] { - new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1), - new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1) - } - : new Lambda[] { - new Lambda(pkg + "/inner", "InnerKt$testInner1$1", 1), - new Lambda(pkg + "/inner", "InnerKt$testInner1$2", 1), - new Lambda(pkg + "/inner", "InnerKt$testInner1$3", 1), - new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1), - new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1) - }); - }); + verifier.assertLambdas( + allowAccessModification + ? new Lambda[] { + new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1), + new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1) + } + : new Lambda[] { + new Lambda(pkg + "/inner", "InnerKt$testInner1$1", 1), + new Lambda(pkg + "/inner", "InnerKt$testInner1$2", 1), + new Lambda(pkg + "/inner", "InnerKt$testInner1$3", 1), + new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1), + new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1) + }); + }); } @Test public void testSingleton() throws Exception { final String mainClassName = "lambdas_singleton.MainKt"; runTest( - "lambdas_singleton", - mainClassName, - "-nohorizontalclassmerging class *", - getOptionsModifier(), - app -> { - Verifier verifier = new Verifier(app); - String pkg = "lambdas_singleton"; - String grp = allowAccessModification ? "" : pkg; + "lambdas_singleton", + mainClassName, + testBuilder -> + testBuilder.addOptionsModification(this::configure).noHorizontalClassMerging()) + .inspect( + inspector -> { + Verifier verifier = new Verifier(inspector); + String pkg = "lambdas_singleton"; + String grp = allowAccessModification ? "" : pkg; - verifier.assertLambdaGroups( - kstyle(grp, 1, 1 /* 1 out of 5 lambdas in the group */), - jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */)); + verifier.assertLambdaGroups( + kstyle(grp, 1, 1 /* 1 out of 5 lambdas in the group */), + jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */)); - verifier.assertLambdas(/* None */ ); - }); + verifier.assertLambdas(/* None */ ); + }); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java index 5ba846c..d6e8c53 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
@@ -6,21 +6,13 @@ import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.InternalOptions; import java.util.Collection; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase { - private Consumer<InternalOptions> optionsModifier = - o -> { - o.enableInlining = true; - o.enableClassInlining = true; - o.enableLambdaMerging = true; - }; @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}") public static Collection<Object[]> data() { @@ -35,6 +27,15 @@ @Test public void testMergingKStyleLambdasAndReprocessing() throws Exception { final String mainClassName = "reprocess_merged_lambdas_kstyle.MainKt"; - runTest("reprocess_merged_lambdas_kstyle", mainClassName, optionsModifier, null); + runTest( + "reprocess_merged_lambdas_kstyle", + mainClassName, + testBuilder -> + testBuilder.addOptionsModification( + options -> { + options.enableInlining = true; + options.enableClassInlining = true; + options.enableLambdaMerging = true; + })); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java index 446d982..127c1e3 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -6,22 +6,13 @@ import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase; import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.InternalOptions; import java.util.Collection; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class KotlinLambdaMergingWithSmallInliningBudgetTest extends AbstractR8KotlinTestBase { - private Consumer<InternalOptions> optionsModifier = - o -> { - o.enableInlining = true; - o.enableClassInlining = true; - o.enableLambdaMerging = true; - o.inliningInstructionAllowance = 3; - }; @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}") public static Collection<Object[]> data() { @@ -36,6 +27,11 @@ @Test public void testJStyleRunnable() throws Exception { final String mainClassName = "lambdas_jstyle_runnable.MainKt"; - runTest("lambdas_jstyle_runnable", mainClassName, optionsModifier, null); + runTest( + "lambdas_jstyle_runnable", + mainClassName, + testBuilder -> + testBuilder.addOptionsModification( + options -> options.inliningInstructionAllowance = 3)); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java index a06dcf8..d764d5f 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
@@ -125,6 +125,8 @@ .addKeepClassAndMembersRules(baseClassName) .addKeepClassAndMembersRules(featureKtClassNamet) .addKeepClassAndMembersRules(FeatureAPI.class) + .addOptionsModification( + options -> options.enableHorizontalClassMergingOfKotlinLambdas = false) .setMinApi(parameters.getApiLevel()) .noMinification() // The check cast inspection above relies on original names. .addFeatureSplit(
diff --git a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java index be7230d..0ab866f 100644 --- a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java +++ b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -51,6 +52,7 @@ } @NeverClassInline + @NoHorizontalClassMerging public static class C extends A { public int f0; @@ -98,6 +100,7 @@ .addKeepRules("-overloadaggressively", "-obfuscationdictionary " + dictionary.toString()) .addKeepMainRule(Runner.class) .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .run(parameters.getRuntime(), Runner.class, "HELLO", "WORLD")
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java index f2e1a60..a853b95 100644 --- a/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java +++ b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
@@ -27,7 +27,7 @@ "-obfuscationdictionary " + dictionary.toString()) // Minify the interface methods in alphabetic order. .addOptionsModification( - options -> options.testing.minifier.interfaceMethodOrdering = DexMethod::slowCompareTo) + options -> options.testing.minifier.interfaceMethodOrdering = DexMethod::compareTo) .compile(); }
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderPackageInfoTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderPackageInfoTest.java new file mode 100644 index 0000000..c889dbb --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderPackageInfoTest.java
@@ -0,0 +1,39 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.naming; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ProguardMapReaderPackageInfoTest extends TestBase { + + private final TestParameters parameters; + private final String MAPPING = + StringUtils.joinLines( + "android.support.v4.internal.package-info -> android.support.v4.internal.package-info:", + "# {\"id\":\"sourceFile\",\"fileName\":\"SourceFile\"}"); + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public ProguardMapReaderPackageInfoTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void parseMappings() throws IOException { + ClassNameMapper.mapperFromString(MAPPING); + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java index 1ab9fe1..668a8a0 100644 --- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java +++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -26,7 +26,7 @@ private static final String ROOT = ToolHelper.EXAMPLES_BUILD_DIR; private static final String EXAMPLE_MAP = "throwing/throwing.map"; private static final String EXAMPLE_MAP_WITH_PACKAGE_INFO = - "dagger.android.package-info -> dagger.android.package-info\n"; + "dagger.android.package-info -> dagger.android.package-info:\n"; @Test public void parseThrowingMap() throws IOException { @@ -141,7 +141,7 @@ @Test public void parseMapWithPackageInfo() throws IOException { ClassNameMapper mapper = ClassNameMapper.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO); - Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().original.isEmpty()); + assertEquals(1, mapper.getObfuscatedToOriginalMapping().original.size()); } @Test
diff --git a/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonTest.java b/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonTest.java index 5aaf063..f37c70c 100644 --- a/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonTest.java +++ b/src/test/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonTest.java
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -21,7 +22,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } public ClassNameComparisonTest(TestParameters parameters) { @@ -33,7 +34,8 @@ testForR8(parameters.getBackend()) .addInnerClasses(ClassNameComparisonTest.class) .addKeepMainRule(TestClass.class) - .setMinApi(parameters.getRuntime()) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile() .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutputLines("Hello!", "Hello " + B.class.getName() + "!"); @@ -64,7 +66,9 @@ } } + @NoHorizontalClassMerging static class A {} + @NoHorizontalClassMerging static class B {} }
diff --git a/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java b/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java index 1781d9f..86fd5d0 100644 --- a/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java +++ b/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.StringUtils; @@ -54,7 +55,7 @@ new ClassTestParameter(InstancePutTestClass.class), new ClassTestParameter(InvokeTestClass.class), new ClassTestParameter(StaticPutTestClass.class)), - getTestParameters().withAllRuntimes().build()); + getTestParameters().withAllRuntimesAndApiLevels().build()); } public IdenticalBlockSuffixSharingWithArrayTypesTest( @@ -70,7 +71,7 @@ String expectedOutput = StringUtils.lines("42"); testForD8() .addInnerClasses(IdenticalBlockSuffixSharingWithArrayTypesTest.class) - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::verifyInstructionCount) .run(parameters.getRuntime(), clazz) @@ -85,7 +86,8 @@ .addKeepMainRule(clazz) .enableNeverClassInliningAnnotations() .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::verifyInstructionCount) .run(parameters.getRuntime(), clazz) @@ -233,7 +235,9 @@ interface K extends I {} + @NoHorizontalClassMerging class A implements I {} + @NoHorizontalClassMerging class B implements I {} }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java new file mode 100644 index 0000000..61526d4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
@@ -0,0 +1,83 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.repackage; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestParameters; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class RepackageAnnotationTest extends RepackageTestBase { + + private final String EXPECTED = "Hello World"; + + public RepackageAnnotationTest( + String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) { + super(flattenPackageHierarchyOrRepackageClasses, parameters); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, Annotation.class, A.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, A.class, Annotation.class) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .addKeepRuntimeVisibleAnnotations() + .addKeepRules( + "-keep,allowobfuscation @interface " + Annotation.class.getTypeName() + " {", + " *;", + "}") + .apply(this::configureRepackaging) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + public static class Main { + + public static void main(String[] args) { + System.out.println(new A().getAnnotationValues()); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public @interface Annotation { + + String f1(); + + String f2(); + } + + @Annotation(f1 = "Hello", f2 = "World") + @NeverClassInline + public static class A { + + @NeverInline + public String getAnnotationValues() { + Annotation annotation = A.class.getAnnotation(Annotation.class); + if (annotation == null) { + return null; + } + return annotation.f1() + " " + annotation.f2(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java index 47a1dcd..81de378 100644 --- a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java +++ b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
@@ -7,6 +7,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.TestParameters; @@ -46,6 +47,7 @@ .addKeepAttributeInnerClassesAndEnclosingMethod() .apply(this::configureRepackaging) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNoStaticClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() @@ -67,6 +69,7 @@ } } + @NoHorizontalClassMerging @NoStaticClassMerging public static class Outer {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java index 150a7cc..da09e88 100644 --- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java +++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -112,6 +112,7 @@ .allowAccessModification(allowAccessModification) .apply(this::configureRepackaging) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNoStaticClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile()
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java index 4f47751..6ee9df6 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java index fdc4a9c..7a80bb3 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect { @@ -15,6 +17,7 @@ Helper.test(); } + @NoHorizontalClassMerging @NoStaticClassMerging public static class Helper {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java index 0f9ae96..f8af76d 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateKeptMethodOnReachableClassDirect {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java index 68aa391..279d85b 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateKeptMethodOnReachableClassIndirect { @@ -15,6 +17,7 @@ Helper.test(); } + @NoHorizontalClassMerging @NoStaticClassMerging public static class Helper {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java index de0e859..a333df7 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java index 13fef6e..040dd72 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect { @@ -15,6 +17,7 @@ Helper.test(); } + @NoHorizontalClassMerging @NoStaticClassMerging public static class Helper {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java index fc92f85..a7bb32f 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateMethodOnKeptClassDirect {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java index c30cd59..2319ca4 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
@@ -5,9 +5,11 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; import com.android.tools.r8.NoVerticalClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateMethodOnKeptClassIndirect {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java index 73f2d6e..07872c6b 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateMethodOnReachableClassDirect {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java index 54cbea9..d528244 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPackagePrivateMethodOnReachableClassIndirect { @@ -15,6 +17,7 @@ Helper.test(); } + @NoHorizontalClassMerging @NoStaticClassMerging public static class Helper {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java index cfb5aa4..d99a9b5 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPublicKeptMethodAllowRenamingOnReachableClass {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java index 75d353c..8880ca5 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPublicKeptMethodOnReachableClass {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java index b77f419..d3fa44b 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPublicMethodOnKeptClass {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java index f71b585..9578cc7 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPublicMethodOnKeptClassAllowRenaming {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java index 87ed71f..ec21ae7 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class AccessPublicMethodOnReachableClass {
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java index 10f12bf..22c5aa0 100644 --- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java +++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
@@ -5,8 +5,10 @@ package com.android.tools.r8.repackage.testclasses.repackagetest; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class ReachableClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java new file mode 100644 index 0000000..ead99d1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java
@@ -0,0 +1,74 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.rewrite.enums; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.transformers.MethodTransformer; +import java.io.IOException; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class EnumInvalidValuesLengthTest extends TestBase { + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public EnumInvalidValuesLengthTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testValuesLength() throws Exception { + Assume.assumeTrue( + "TODO(b/172903562): Breaks on dex due to enum unboxing", parameters.isCfRuntime()); + testForR8(parameters.getBackend()) + .addKeepMainRule(EnumInvalidValuesLengthTest.Main.class) + .addProgramClasses(EnumInvalidValuesLengthTest.Main.class) + .addProgramClassFileData(transformValues(MyEnum.class)) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), EnumInvalidValuesLengthTest.Main.class) + .assertSuccessWithOutputLines("5"); + } + + private byte[] transformValues(Class<MyEnum> myEnumClass) throws IOException { + return transformer(myEnumClass) + .addMethodTransformer( + new MethodTransformer() { + @Override + public void visitInsn(int opcode) { + if (opcode == Opcodes.ICONST_3) { + // This is the constant determining the size of the values array. + super.visitInsn(Opcodes.ICONST_5); + return; + } + super.visitInsn(opcode); + } + }) + .transform(); + } + + @NeverClassInline + enum MyEnum { + A, + B, + C; + } + + public static class Main { + public static void main(String[] args) { + System.out.println(MyEnum.values().length); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java deleted file mode 100644 index 176e6f5..0000000 --- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java +++ /dev/null
@@ -1,168 +0,0 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.rewrite.enums; - -import static junit.framework.TestCase.assertTrue; - -import com.android.tools.r8.NeverClassInline; -import com.android.tools.r8.NeverInline; -import com.android.tools.r8.TestBase; -import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.utils.codeinspector.CodeInspector; -import com.android.tools.r8.utils.codeinspector.FoundClassSubject; -import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; -import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Collections; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) -public class EnumValuesLengthTest extends TestBase { - - private final TestParameters parameters; - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); - } - - public EnumValuesLengthTest(TestParameters parameters) { - this.parameters = parameters; - } - - @Test - public void testValuesLengthRemoved() throws Exception { - testForR8(parameters.getBackend()) - .addKeepMainRule(Main.class) - .addInnerClasses(EnumValuesLengthTest.class) - .noMinification() - .setMinApi(parameters.getApiLevel()) - .addOptionsModification( - opt -> { - opt.enableEnumValueOptimization = true; - // We need to keep the switch map to ensure kept switch maps have their - // values array length rewritten. - opt.enableEnumSwitchMapRemoval = false; - }) - .compile() - .inspect(this::assertValuesLengthRemoved) - .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("0", "2", "5", "a", "D", "c", "D"); - } - - @Test - public void testValuesLengthSwitchMapRemoved() throws Exception { - // Make sure SwitchMap can still be removed with valuesLength optimization. - assertSwitchMapPresent(); - testForR8(parameters.getBackend()) - .addKeepMainRule(Main.class) - .addInnerClasses(EnumValuesLengthTest.class) - .noMinification() - .setMinApi(parameters.getApiLevel()) - .addOptionsModification( - opt -> { - opt.enableEnumValueOptimization = true; - }) - .compile() - .inspect(this::assertSwitchMapRemoved) - .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("0", "2", "5", "a", "D", "c", "D"); - } - - private void assertSwitchMapPresent() throws IOException { - Collection<Path> classFilesForInnerClasses = - ToolHelper.getClassFilesForInnerClasses( - Collections.singletonList(EnumValuesLengthTest.class)); - assertTrue(classFilesForInnerClasses.stream().anyMatch(p -> p.toString().endsWith("$1.class"))); - } - - private void assertSwitchMapRemoved(CodeInspector inspector) { - assertTrue(inspector.allClasses().stream().noneMatch(c -> c.getOriginalName().endsWith("$1"))); - } - - private void assertValuesLengthRemoved(CodeInspector inspector) { - for (FoundClassSubject clazz : inspector.allClasses()) { - clazz.forAllMethods(this::assertValuesLengthRemoved); - } - } - - private void assertValuesLengthRemoved(FoundMethodSubject method) { - assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayLength)); - assertTrue( - method - .streamInstructions() - .noneMatch( - instr -> - instr.isInvokeStatic() && instr.getMethod().name.toString().equals("values"))); - } - - public static class Main { - - @NeverClassInline - enum E0 {} - - @NeverClassInline - enum E2 { - A, - B - } - - @NeverClassInline - enum E5 { - A, - B, - C, - D, - E - } - - @NeverClassInline - enum EUnusedValues { - A, - B, - C - } - - @NeverClassInline - enum ESwitch { - A, - B, - C, - D - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - public static void main(String[] args) { - EUnusedValues.values(); - System.out.println(E0.values().length); - System.out.println(E2.values().length); - System.out.println(E5.values().length); - System.out.println(switchOn(ESwitch.A)); - System.out.println(switchOn(ESwitch.B)); - System.out.println(switchOn(ESwitch.C)); - System.out.println(switchOn(ESwitch.D)); - } - - // SwitchMaps feature an array length on values, and some of them are not removed. - @NeverInline - static char switchOn(ESwitch e) { - switch (e) { - case A: - return 'a'; - case C: - return 'c'; - default: - return 'D'; - } - } - } -}
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java index 745e5b7..d408a4f 100644 --- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java +++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -54,6 +55,7 @@ .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getRuntime()) .compile() .inspect( @@ -155,6 +157,7 @@ @NeverClassInline @NoVerticalClassMerging + @NoHorizontalClassMerging static class InstanceFieldWithInitialization_Z { boolean alwaysFalse; InstanceFieldWithInitialization_Z() { @@ -163,6 +166,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class InstanceFieldWithNonTrivialInitialization_Z extends InstanceFieldWithInitialization_Z { boolean alwaysTrue; @@ -182,6 +186,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class InstanceFieldWithInitialization_I { int alwaysZero; InstanceFieldWithInitialization_I() { @@ -190,6 +195,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class InstanceFieldWithRange_I { int alwaysLessThanEight; InstanceFieldWithRange_I() { @@ -215,6 +221,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class InstanceFieldWithInitialization_L { Object alwaysNull; InstanceFieldWithInitialization_L() {
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java index fd676a3..b5aba75 100644 --- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java +++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -248,7 +248,7 @@ .compile() .graphInspector(); // TODO(b/159418523): Should the reference be equal to the result with the conditional rule? - assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, false); + assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true); } private void assertRetainedClassesEqual(
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java index 9dfff6e..ef2e860 100644 --- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java +++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -56,7 +56,8 @@ @Test public void testKeptClassFieldAndMethodFull() throws Exception { - runTest(testForR8(parameters.getBackend()), false); + // TODO(b/172999267): The below should be true + runTest(testForR8(parameters.getBackend()), true); } @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java index 23a5616..8bde97b 100644 --- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java +++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.shaking.b134858535; -import static org.hamcrest.CoreMatchers.containsString; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.TestBase; @@ -28,13 +27,9 @@ .addProgramClassFileData(EventPublisher$bDump.dump()) .addKeepClassRules(Interface.class) .addKeepMainRule(Main.class) - .allowDiagnosticInfoMessages() .setMinApi(AndroidApiLevel.L) - .compile() - // TODO(b/157537996): Handle JStyle lambdas with private methods. - .assertAllInfoMessagesMatch( - containsString( - "Unrecognized Kotlin lambda" - + " [com.android.tools.r8.shaking.b134858535.EventPublisher$b]")); + .addHorizontallyMergedLambdaClassesInspector( + inspector -> inspector.assertClassNotMerged(EventPublisher$b.class)) + .compile(); } }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java index 943e680..fe71482 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -61,7 +62,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, J.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java index a052428..e7a460d 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -61,7 +62,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, I.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java index d20f13a..ec1018f 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -64,7 +65,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, A.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java index bb59d8c..15b7662 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -64,7 +65,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, J.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java index a3e93ae..65b4bcb 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -64,7 +65,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, I.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java index 385b549..d73e875 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -66,9 +67,12 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); - // TODO(b/172049649): Initialization of A does not have side effects. - assertMayHaveClassInitializationSideEffects(appView, A.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); + assertNoClassInitializationSideEffects(appView, A.class); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java index c69a6d0..18ce5f2 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -80,7 +81,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, A.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubInterfaceTest.java new file mode 100644 index 0000000..edcfaf0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -0,0 +1,102 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.clinit; + +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubInterfaceTest + extends ClassMayHaveInitializationSideEffectsTestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubInterfaceTest( + TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8() + .addInnerClasses(getClass()) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithEmptyOutput(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .allowStdoutMessages() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithEmptyOutput(); + } + + @Test + public void testJvm() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addTestClasspath() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithEmptyOutput(); + } + + @Test + public void testClassInitializationMayHaveSideEffects() throws Exception { + AppView<AppInfoWithLiveness> appView = + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); + assertNoClassInitializationSideEffects(appView, J.class); + } + + static class TestClass { + + public static void main(String[] args) { + J.greet(); + } + } + + interface I { + + Greeter iGreeter = new Greeter("I"); + + default void m() {} + } + + interface J extends I { + + static void greet() {} + } + + static class Greeter { + + Greeter(String greeting) { + System.out.println(greeting); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java index a31b2ed..96f64f8 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -79,7 +80,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, A.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java index 1977be8..a751dc3 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -65,7 +66,11 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); assertMayHaveClassInitializationSideEffects(appView, J.class); }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java index 3d88fe8..4bb1e9c 100644 --- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.shaking.AppInfoWithLiveness; import org.junit.Test; @@ -67,9 +68,12 @@ @Test public void testClassInitializationMayHaveSideEffects() throws Exception { AppView<AppInfoWithLiveness> appView = - computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class); - // TODO(b/172049649): The initialization of J does not trigger the <clinit> of I. - assertMayHaveClassInitializationSideEffects(appView, J.class); + computeAppViewWithLiveness( + buildInnerClasses(getClass()) + .addLibraryFile(ToolHelper.getMostRecentAndroidJar()) + .build(), + TestClass.class); + assertNoClassInitializationSideEffects(appView, J.class); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java index 4536cd9..1387558 100644 --- a/src/test/java/com/android/tools/r8/shaking/testrules/A.java +++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -4,8 +4,10 @@ package com.android.tools.r8.shaking.testrules; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoStaticClassMerging; +@NoHorizontalClassMerging @NoStaticClassMerging public class A {
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java index 41533ec..d4e5972 100644 --- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java +++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -42,6 +42,7 @@ return testForR8(parameters.getBackend()) .addProgramClasses(Main.class, A.class, B.class, C.class) .addKeepRules(proguardConfiguration) + .enableNoHorizontalClassMergingAnnotations() .enableNoStaticClassMergingAnnotations() .enableProguardTestOptions() .compile()
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java index 0074a89..a1b4180 100644 --- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java +++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.DiagnosticsChecker; +import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -41,13 +42,13 @@ boolean acceptMethodCalled; @Override - public void acceptType(TracedClass tracedClass) { + public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { assertFalse(tracedClass.isMissingDefinition()); tracedTypes.add(tracedClass.getReference()); } @Override - public void acceptField(TracedField tracedField) { + public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { acceptFieldCalled = true; assertFalse(tracedField.isMissingDefinition()); assertEquals( @@ -57,7 +58,7 @@ } @Override - public void acceptMethod(TracedMethod tracedMethod) { + public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { acceptMethodCalled = true; assertFalse(tracedMethod.isMissingDefinition()); assertEquals(
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java index 77c1e53..d354df7 100644 --- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java +++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -9,24 +9,27 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.DiagnosticsChecker; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.references.Reference; -import com.android.tools.r8.tracereferences.TraceReferencesFormattingConsumer.OutputFormat; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.StringUtils; -import com.android.tools.r8.utils.ZipUtils; import com.android.tools.r8.utils.ZipUtils.ZipBuilder; import com.google.common.collect.ImmutableList; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -62,10 +65,21 @@ @Test(expected = CompilationFailedException.class) public void emptyRunCommandLine() throws Throwable { DiagnosticsChecker.checkErrorsContains( - "No library specified", + "Missing command", handler -> { TraceReferences.run( - TraceReferencesCommand.parse(new String[] {""}, Origin.unknown(), handler).build()); + TraceReferencesCommand.parse(new String[] {}, Origin.unknown(), handler).build()); + }); + } + + @Test(expected = CompilationFailedException.class) + public void unsupportedCommandCommandLine() throws Throwable { + DiagnosticsChecker.checkErrorsContains( + "Missing command, specify one of 'check', '--print-usage' or '--keep-rules'", + handler -> { + TraceReferences.run( + TraceReferencesCommand.parse(new String[] {"--xxx"}, Origin.unknown(), handler) + .build()); }); } @@ -89,7 +103,7 @@ TraceReferences.run( TraceReferencesCommand.parse( new String[] { - "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString() + "--check", "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString() }, Origin.unknown(), handler) @@ -98,24 +112,39 @@ } @Test(expected = CompilationFailedException.class) - public void invalidFormatCommandLine() throws Throwable { + public void multipleCommandsSpecified() throws Throwable { DiagnosticsChecker.checkErrorsContains( - "Unsupported format 'xxx'", + "Multiple commands specified", handler -> { TraceReferences.run( TraceReferencesCommand.parse( - new String[] {"--format", "xxx"}, Origin.unknown(), handler) + new String[] {"--check", "--keep-rules"}, Origin.unknown(), handler) .build()); }); } @Test(expected = CompilationFailedException.class) - public void missingFormatCommandLine() throws Throwable { + public void allowobfuscationWithoutKeepRule() throws Throwable { DiagnosticsChecker.checkErrorsContains( - "Missing parameter for --format", + "Using '--allowobfuscation' requires command '--keep-rules'", handler -> { TraceReferences.run( - TraceReferencesCommand.parse(new String[] {"--format"}, Origin.unknown(), handler) + TraceReferencesCommand.parse( + new String[] {"--check", "--allowobfuscation"}, Origin.unknown(), handler) + .build()); + }); + } + + @Test(expected = CompilationFailedException.class) + public void allowobfuscationMultiple() throws Throwable { + DiagnosticsChecker.checkErrorsContains( + "No library specified", + handler -> { + TraceReferences.run( + TraceReferencesCommand.parse( + new String[] {"--keep-rules", "--allowobfuscation", "--allowobfuscation"}, + Origin.unknown(), + handler) .build()); }); } @@ -123,11 +152,23 @@ @Test(expected = CompilationFailedException.class) public void multipleFormatsCommandLine() throws Throwable { DiagnosticsChecker.checkErrorsContains( - "--format specified multiple times", + "Using '--output' requires command '--print-usage' or '--keep-rules'", handler -> { TraceReferences.run( TraceReferencesCommand.parse( - new String[] {"--format", "printuses", "--format", "keep"}, + new String[] {"--check", "--output", "xxx"}, Origin.unknown(), handler) + .build()); + }); + } + + @Test(expected = CompilationFailedException.class) + public void outputMultiple() throws Throwable { + DiagnosticsChecker.checkErrorsContains( + "Option '--output' passed multiple times", + handler -> { + TraceReferences.run( + TraceReferencesCommand.parse( + new String[] {"--keep-rules", "--output", "xxx", "--output", "xxx"}, Origin.unknown(), handler) .build()); @@ -136,13 +177,44 @@ private String formatName(OutputFormat format) { if (format == OutputFormat.PRINTUSAGE) { - return "printuses"; + return "--print-usage"; } if (format == OutputFormat.KEEP_RULES) { - return "keep"; + return "--keep-rules"; } assertSame(format, OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION); - return "keepallowobfuscation"; + return "--keep-rules"; + } + + enum OutputFormat { + /** Format used with the -printusage flag */ + PRINTUSAGE, + /** Keep rules keeping each of the traced references */ + KEEP_RULES, + /** + * Keep rules with <code>allowobfuscation</code> modifier keeping each of the traced references + */ + KEEP_RULES_WITH_ALLOWOBFUSCATION + } + + private static class StringValueStringConsumer implements StringConsumer { + private StringBuilder builder = new StringBuilder(); + private boolean finished = false; + + @Override + public void accept(String string, DiagnosticsHandler handler) { + builder.append(string); + } + + @Override + public void finished(DiagnosticsHandler handler) { + finished = true; + } + + String get() { + assert finished; + return builder.toString(); + } } public void runAndCheckOutput( @@ -155,7 +227,14 @@ Path dir = temp.newFolder().toPath(); Path output = dir.resolve("output.txt"); DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker(); - TraceReferencesFormattingConsumer consumer = new TraceReferencesFormattingConsumer(format); + StringValueStringConsumer stringConsumer = new StringValueStringConsumer(); + TraceReferencesConsumer consumer = + format == OutputFormat.PRINTUSAGE + ? new TraceReferencesPrintUsage() + : TraceReferencesKeepRules.builder() + .setAllowObfuscation(format == OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION) + .setOutputConsumer(stringConsumer) + .build(); try { TraceReferences.run( TraceReferencesCommand.builder(diagnosticsChecker) @@ -164,7 +243,11 @@ .addSourceFiles(sourceJar) .setConsumer(consumer) .build()); - assertEquals(expected, consumer.get()); + if (consumer instanceof TraceReferencesPrintUsage) { + assertEquals(expected, ((TraceReferencesPrintUsage) consumer).get()); + } else { + assertEquals(expected, stringConsumer.get()); + } if (diagnosticsCheckerConsumer != null) { diagnosticsCheckerConsumer.accept(diagnosticsChecker); } else { @@ -179,17 +262,23 @@ throw e; } - TraceReferences.run( - TraceReferencesCommand.parse( - new String[] { - "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), - "--target", targetJar.toString(), - "--source", sourceJar.toString(), - "--output", output.toString(), - "--format", formatName(format) - }, - Origin.unknown()) - .build()); + List<String> args = new ArrayList<>(); + args.add(formatName(format)); + if (format == OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION) { + args.add("--allowobfuscation"); + } + args.addAll( + ImmutableList.of( + "--lib", + ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), + "--target", + targetJar.toString(), + "--source", + sourceJar.toString(), + "--output", + output.toString())); + + TraceReferences.run(TraceReferencesCommand.parse(args, Origin.unknown()).build()); assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8)); } @@ -202,6 +291,16 @@ runAndCheckOutput(targetClasses, sourceClasses, format, expected, null); } + private Path zipWithTestClasses(Path zipFile, List<Class<?>> targetClasses) throws IOException { + return ZipBuilder.builder(zipFile) + .addFilesRelative( + ToolHelper.getClassPathForTests(), + targetClasses.stream() + .map(ToolHelper::getClassFileForTestClass) + .collect(Collectors.toList())) + .build(); + } + public void runAndCheckOutput( List<Class<?>> targetClasses, List<Class<?>> sourceClasses, @@ -210,22 +309,8 @@ Consumer<DiagnosticsChecker> diagnosticsCheckerConsumer) throws Throwable { Path dir = temp.newFolder().toPath(); - Path targetJar = - ZipBuilder.builder(dir.resolve("target.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - targetClasses.stream() - .map(ToolHelper::getClassFileForTestClass) - .collect(Collectors.toList())) - .build(); - Path sourceJar = - ZipBuilder.builder(dir.resolve("source.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - sourceClasses.stream() - .map(ToolHelper::getClassFileForTestClass) - .collect(Collectors.toList())) - .build(); + Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), targetClasses); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), sourceClasses); runAndCheckOutput(targetJar, sourceJar, format, expected, diagnosticsCheckerConsumer); } @@ -276,6 +361,118 @@ "-keeppackagenames com.android.tools.r8.tracereferences"))); } + @Test + public void test_noOutput() throws Throwable { + Path dir = temp.newFolder().toPath(); + Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(Target.class)); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class)); + PrintStream originalOut = System.out; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + TraceReferences.run( + TraceReferencesCommand.builder() + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) + .addTargetFiles(targetJar) + .addSourceFiles(sourceJar) + .setConsumer(TraceReferencesConsumer.emptyConsumer()) + .build()); + assertEquals(0, baos.size()); + } finally { + System.setOut(originalOut); + } + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + TraceReferences.run( + TraceReferencesCommand.parse( + new String[] { + "--lib", + ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), + "--check", + "--target", + targetJar.toString(), + "--source", + sourceJar.toString(), + }, + Origin.unknown()) + .build()); + assertEquals(0, baos.size()); + } finally { + System.setOut(originalOut); + } + } + + @Test + public void test_stdoutOutput() throws Throwable { + String expected = + StringUtils.lines( + ImmutableList.of( + "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target", + "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: void" + + " method(int)", + "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: int" + + " field")); + Path dir = temp.newFolder().toPath(); + Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(Target.class)); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class)); + PrintStream originalOut = System.out; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + TraceReferences.run( + TraceReferencesCommand.parse( + new String[] { + "--lib", + ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), + "--target", + targetJar.toString(), + "--source", + sourceJar.toString(), + "--print-usage", + }, + Origin.unknown()) + .build()); + assertEquals(expected, baos.toString(Charsets.UTF_8.name())); + } finally { + System.setOut(originalOut); + } + } + + public void classFileInput() throws Throwable { + String expected = + StringUtils.lines( + ImmutableList.of( + "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target", + "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: void" + + " method(int)", + "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: int" + + " field")); + TraceReferencesPrintUsage consumer = new TraceReferencesPrintUsage(); + TraceReferences.run( + TraceReferencesCommand.builder() + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) + .addTargetFiles(ToolHelper.getClassFileForTestClass(Target.class)) + .addSourceFiles(ToolHelper.getClassFileForTestClass(Source.class)) + .setConsumer(consumer) + .build()); + assertEquals(expected, consumer.get()); + + Path output = temp.newFile().toPath(); + TraceReferences.run( + TraceReferencesCommand.parse( + new String[] { + "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), + "--target", ToolHelper.getClassFileForTestClass(Target.class).toString(), + "--source", ToolHelper.getClassFileForTestClass(Source.class).toString(), + "--output", output.toString(), + }, + Origin.unknown()) + .build()); + assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8)); + } + private void checkTargetMissing(DiagnosticsChecker diagnosticsChecker) { Field field; Method method; @@ -344,32 +541,19 @@ public void testMissingReference_errorToWarning() throws Throwable { Path dir = temp.newFolder().toPath(); Path targetJar = - ZipBuilder.builder(dir.resolve("target.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - ToolHelper.getClassFileForTestClass(OtherTarget.class)) - .build(); - Path sourceJar = - ZipBuilder.builder(dir.resolve("source.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - ToolHelper.getClassFileForTestClass(Source.class)) - .build(); - Path output = dir.resolve("output.txt"); + zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(OtherTarget.class)); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class)); DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker(); TraceReferences.run( TraceReferencesCommand.parse( new String[] { + "--check", "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(), "--target", targetJar.toString(), "--source", sourceJar.toString(), - "--output", - output.toString(), - "--format", - formatName(OutputFormat.PRINTUSAGE), "--map-diagnostics:MissingDefinitionsDiagnostic", "error", "warning" @@ -407,12 +591,7 @@ .addBytes( DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved()) .build(); - Path sourceJar = - ZipBuilder.builder(dir.resolve("source.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - ToolHelper.getClassFileForTestClass(Source.class)) - .build(); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class)); try { runAndCheckOutput( targetJar, @@ -436,16 +615,7 @@ .addBytes( DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved()) .build(); - Path sourceJar = - ZipBuilder.builder(dir.resolve("source.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - ToolHelper.getClassFileForTestClass(Source.class)) - .build(); - ZipUtils.zip( - sourceJar, - ToolHelper.getClassPathForTests(), - ToolHelper.getClassFileForTestClass(Source.class)); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class)); try { runAndCheckOutput( targetJar, @@ -473,12 +643,7 @@ .addBytes( DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved()) .build(); - Path sourceJar = - ZipBuilder.builder(dir.resolve("source.jar")) - .addFilesRelative( - ToolHelper.getClassPathForTests(), - ToolHelper.getClassFileForTestClass(Source.class)) - .build(); + Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class)); try { runAndCheckOutput( targetJar,
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java index b21d409..551c8a1 100644 --- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java +++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
@@ -7,12 +7,14 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.DiagnosticsChecker; +import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ZipUtils.ZipBuilder; import com.google.common.collect.ImmutableList; @@ -193,6 +195,80 @@ } } + static class FailingConsumer implements TraceReferencesConsumer { + private final String where; + + FailingConsumer(String where) { + this.where = where; + } + + @Override + public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { + if (where.equals("acceptType")) { + handler.error(new StringDiagnostic("Error in " + where)); + } + } + + @Override + public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { + if (where.equals("acceptField")) { + handler.error(new StringDiagnostic("Error in " + where)); + } + } + + @Override + public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { + if (where.equals("acceptMethod")) { + handler.error(new StringDiagnostic("Error in " + where)); + } + } + + @Override + public void finished(DiagnosticsHandler handler) { + if (where.equals("finished")) { + handler.error(new StringDiagnostic("Error in " + where)); + } + } + } + + @Test + public void traceReferencesConsumerError() throws Throwable { + Path dir = temp.newFolder().toPath(); + Path targetJar = + ZipBuilder.builder(dir.resolve("target.jar")) + .addFilesRelative( + ToolHelper.getClassPathForTests(), + ToolHelper.getClassFileForTestClass(Target.class), + ToolHelper.getClassFileForTestClass(Target1.class), + ToolHelper.getClassFileForTestClass(Target2.class), + ToolHelper.getClassFileForTestClass(Target3.class)) + .build(); + Path sourceJar = + ZipBuilder.builder(dir.resolve("source.jar")) + .addFilesRelative( + ToolHelper.getClassPathForTests(), + ToolHelper.getClassFileForTestClass(Source.class)) + .build(); + + for (String where : new String[] {"acceptType", "acceptField", "acceptMethod", "finished"}) { + try { + DiagnosticsChecker.checkErrorsContains( + "Error in " + where, + handler -> + TraceReferences.run( + TraceReferencesCommand.builder(handler) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) + .addSourceFiles(sourceJar) + .addTargetFiles(targetJar) + .setConsumer(new FailingConsumer(where)) + .build())); + fail("Unexpected success"); + } catch (CompilationFailedException e) { + // Expected. + } + } + } + static class Target1 {} static class Target2 {}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java index 1387c3f..b5b26b0 100644 --- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java +++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.DiagnosticsChecker; +import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -39,14 +40,14 @@ boolean acceptMethodCalled; @Override - public void acceptType(TracedClass tracedClass) { + public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { acceptTypeCalled = true; assertEquals(Reference.classFromClass(Target.class), tracedClass.getReference()); assertTrue(tracedClass.isMissingDefinition()); } @Override - public void acceptField(TracedField tracedField) { + public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { acceptFieldCalled = true; assertEquals( Reference.classFromClass(Target.class), tracedField.getReference().getHolderClass()); @@ -55,7 +56,7 @@ } @Override - public void acceptMethod(TracedMethod tracedMethod) { + public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { acceptMethodCalled = true; assertEquals( Reference.classFromClass(Target.class), tracedMethod.getReference().getHolderClass());
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java index a440523..0d99817 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -10,7 +10,10 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; +import java.util.Set; +import java.util.function.BiConsumer; public class HorizontallyMergedClassesInspector { @@ -23,6 +26,14 @@ this.horizontallyMergedClasses = horizontallyMergedClasses; } + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + horizontallyMergedClasses.forEachMergeGroup(consumer); + } + + public DexType getTarget(DexType clazz) { + return horizontallyMergedClasses.getMergeTargetOrDefault(clazz); + } + public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) { assertEquals( horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from, dexItemFactory)),
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java index 4b3579f..421b54a 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java
@@ -5,10 +5,14 @@ package com.android.tools.r8.utils.codeinspector; import static com.android.tools.r8.TestBase.toDexType; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses; +import java.util.Set; +import java.util.function.BiConsumer; public class HorizontallyMergedLambdaClassesInspector { @@ -33,4 +37,13 @@ } return this; } + + public HorizontallyMergedLambdaClassesInspector assertClassNotMerged(Class<?> clazz) { + assertFalse(horizontallyMergedLambdaClasses.hasBeenMerged(toDexType(clazz, dexItemFactory))); + return this; + } + + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + horizontallyMergedLambdaClasses.forEachMergeGroup(consumer); + } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java new file mode 100644 index 0000000..0b45f27 --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java
@@ -0,0 +1,27 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils.codeinspector; + +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses; +import java.util.Set; +import java.util.function.BiConsumer; + +public class StaticallyMergedClassesInspector { + + private final DexItemFactory dexItemFactory; + private final StaticallyMergedClasses staticallyMergedClasses; + + public StaticallyMergedClassesInspector( + DexItemFactory dexItemFactory, StaticallyMergedClasses staticallyMergedClasses) { + this.dexItemFactory = dexItemFactory; + this.staticallyMergedClasses = staticallyMergedClasses; + } + + public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) { + staticallyMergedClasses.forEachMergeGroup(consumer); + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java index bd64711..05031f2 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
@@ -32,4 +32,9 @@ } return this; } + + public VerticallyMergedClassesInspector assertNoClassesMerged() { + assertTrue(verticallyMergedClasses.isEmpty()); + return this; + } }
diff --git a/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsCustomOrderTest.java b/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsCustomOrderTest.java new file mode 100644 index 0000000..cfea69c --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsCustomOrderTest.java
@@ -0,0 +1,170 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.HashSet; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class StructuralItemsCustomOrderTest extends TestBase { + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public StructuralItemsCustomOrderTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + final B b1 = new B(1); + final B b2 = new B(2); + final B b2_copy = new B(2); + + final A a1b1 = new A(1, b1); + final A a2b1 = new A(2, b1); + final A a1b2 = new A(1, b2); + final A a2b2 = new A(2, b2); + final A a1b2_copy = new A(1, b2_copy); + final A a2b2_copy = new A(2, b2_copy); + + @Test + public void testOrder() { + assertFalse(b1.isLessThan(b2)); + assertTrue(b2.isEqualTo(b2_copy)); + assertTrue(b2.isLessThan(b1)); + + assertFalse(a1b1.isLessThan(a1b2)); + assertTrue(a1b2.isLessThan(a2b1)); + assertFalse(a2b1.isLessThan(a1b2_copy)); + } + + @Test + public void testOrderWithIdentityEquivalence() { + // These mirror the above exactly but using the compare result. + RepresentativeMap map = t -> t; + + assertFalse(b1.compareWithTypeEquivalenceTo(b2, map) < 0); + assertTrue(b2.compareWithTypeEquivalenceTo(b2_copy, map) == 0); + assertTrue(b2.compareWithTypeEquivalenceTo(b1, map) < 0); + + assertFalse(a1b1.compareWithTypeEquivalenceTo(a1b2, map) < 0); + assertTrue(a1b2.compareWithTypeEquivalenceTo(a2b1, map) < 0); + assertFalse(a2b1.compareWithTypeEquivalenceTo(a1b2_copy, map) < 0); + } + + @Test + public void testEquals() { + assertFalse(b1.isEqualTo(b2)); + assertTrue(b2.isEqualTo(b2_copy)); + assertEquals(b2, b2_copy); + + assertFalse(a2b2.isEqualTo(a2b1)); + assertTrue(a1b2.isEqualTo(a1b2_copy)); + assertEquals(a2b2, a2b2_copy); + + // Type incompatible check should still work. + assertNotEquals(b1, a1b1); + } + + @Test + public void testHashCode() { + Set<B> bs = new HashSet<>(ImmutableList.of(b1, b2, b2_copy)); + assertEquals(ImmutableSet.of(b1, b2), bs); + + Set<A> as = new HashSet<>(ImmutableList.of(a1b1, a1b2, a2b1, a2b2, a1b2_copy, a2b2_copy)); + assertEquals(ImmutableSet.of(a1b1, a1b2, a2b1, a2b2), as); + + // If these collide it is a poor hashing algorithm... + assertNotEquals(b1.hashCode(), b2.hashCode()); + } + + private static class A implements StructuralItem<A> { + + private final int x; + private final B b; + + private static void accept(StructuralSpecification<A, ?> spec) { + spec.withInt(a -> a.x).withItem(a -> a.b); + } + + public A(int x, B b) { + this.x = x; + this.b = b; + } + + @Override + public StructuralAccept<A> getStructuralAccept() { + return A::accept; + } + + @Override + public A self() { + return this; + } + + @Override + public final boolean equals(Object other) { + return Equatable.equalsImpl(this, other); + } + + @Override + public final int hashCode() { + return HashCodeVisitor.run(this, A::accept); + } + } + + private static class B implements StructuralItem<B> { + + private final int y; + + private static void accept(StructuralSpecification<B, ?> spec) { + spec.withInt(b -> b.y); + } + + public B(int y) { + this.y = y; + } + + @Override + public StructuralAccept<B> getStructuralAccept() { + return B::accept; + } + + @Override + public B self() { + return this; + } + + @Override + public final boolean equals(Object other) { + return Equatable.equalsImpl(this, other); + } + + @Override + public final int hashCode() { + return HashCodeVisitor.run(this, B::accept); + } + + // Override allowing a change to the order of any type of compare-to visitation, e.g., with + // and without a type equivalence map. + @Override + public void acceptCompareTo(B other, CompareToVisitor visitor) { + visitor.visit(other, this, B::accept); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java b/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java new file mode 100644 index 0000000..64481c3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java
@@ -0,0 +1,182 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils.structural; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class StructuralItemsTest extends TestBase { + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public StructuralItemsTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + final B b1 = new B(1); + final B b2 = new B(2); + final B b2_copy = new B(2); + + final A a1b1 = new A(1, b1); + final A a2b1 = new A(2, b1); + final A a1b2 = new A(1, b2); + final A a2b2 = new A(2, b2); + final A a1b2_copy = new A(1, b2_copy); + final A a2b2_copy = new A(2, b2_copy); + + @Test + public void testOrder() { + assertTrue(b1.isLessThan(b2)); + assertTrue(b2.isEqualTo(b2_copy)); + assertFalse(b2.isLessThan(b1)); + + assertTrue(a1b1.isLessThan(a1b2)); + assertTrue(a1b2.isLessThan(a2b1)); + assertFalse(a2b1.isLessThan(a1b2_copy)); + + assertEquals(b1, Ordered.minIgnoreNull(null, b1)); + assertEquals(b1, Ordered.minIgnoreNull(b1, null)); + assertEquals(b1, Ordered.maxIgnoreNull(null, b1)); + assertEquals(b1, Ordered.maxIgnoreNull(b1, null)); + } + + @Test + public void testEquals() { + assertFalse(b1.isEqualTo(b2)); + assertTrue(b2.isEqualTo(b2_copy)); + assertEquals(b2, b2_copy); + + assertFalse(a2b2.isEqualTo(a2b1)); + assertTrue(a1b2.isEqualTo(a1b2_copy)); + assertEquals(a2b2, a2b2_copy); + + // Type incompatible check should still work. + assertNotEquals(b1, a1b1); + } + + @Test + public void testHashCode() { + assertEquals(b2.hashCode(), b2_copy.hashCode()); + assertEquals(b2, b2_copy); + Set<B> bs = new HashSet<>(ImmutableList.of(b1, b2, b2_copy)); + assertEquals(ImmutableSet.of(b1, b2), bs); + + Set<A> as = new HashSet<>(ImmutableList.of(a1b1, a1b2, a2b1, a2b2, a1b2_copy, a2b2_copy)); + assertEquals(ImmutableSet.of(a1b1, a1b2, a2b1, a2b2), as); + + // If these collide it is a poor hashing algorithm... + assertNotEquals(b1.hashCode(), b2.hashCode()); + } + + private String getHash(StructuralItem<?> item) { + return item.hashForTesting(); + } + + @Test + public void testHashing() { + assertEquals(getHash(b2), getHash(b2_copy)); + assertEquals(getHash(b2), getHash(b2_copy)); + Set<String> bs = new HashSet<>(ImmutableList.of(getHash(b1), getHash(b2), getHash(b2_copy))); + assertEquals(ImmutableSet.of(getHash(b1), getHash(b2)), bs); + + Set<String> as = + ImmutableList.of(a1b1, a1b2, a2b1, a2b2, a1b2_copy, a2b2_copy).stream() + .map(this::getHash) + .collect(Collectors.toSet()); + assertEquals( + ImmutableSet.of(a1b1, a1b2, a2b1, a2b2).stream() + .map(this::getHash) + .collect(Collectors.toSet()), + as); + + // If these collide it is a poor hashing algorithm... + assertNotEquals(getHash(b1), getHash(b2)); + } + + private static class A implements StructuralItem<A> { + + private final int x; + private final B b; + + private static void accept(StructuralSpecification<A, ?> spec) { + spec.withInt(a -> a.x).withItem(a -> a.b); + } + + public A(int x, B b) { + this.x = x; + this.b = b; + } + + @Override + public StructuralAccept<A> getStructuralAccept() { + return A::accept; + } + + @Override + public A self() { + return this; + } + + @Override + public boolean equals(Object other) { + return Equatable.equalsImpl(this, other); + } + + @Override + public int hashCode() { + return HashCodeVisitor.run(this, A::accept); + } + } + + private static class B implements StructuralItem<B> { + + private final int y; + + private static void accept(StructuralSpecification<B, ?> spec) { + spec.withInt(b -> b.y); + } + + public B(int y) { + this.y = y; + } + + @Override + public StructuralAccept<B> getStructuralAccept() { + return B::accept; + } + + @Override + public B self() { + return this; + } + + @Override + public boolean equals(Object other) { + return Equatable.equalsImpl(this, other); + } + + @Override + public int hashCode() { + return HashCodeVisitor.run(this, B::accept); + } + } +}
diff --git a/src/test/javaStubs/Supplier.java b/src/test/javaStubs/Supplier.java new file mode 100644 index 0000000..f9c4860 --- /dev/null +++ b/src/test/javaStubs/Supplier.java
@@ -0,0 +1,9 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package java.util.function; + +public interface Supplier { + Object get(); +}
diff --git a/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1 new file mode 100644 index 0000000..43b6ec0 --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1
@@ -0,0 +1 @@ +54e1cfb2bd83e005ccd07179958261d5ed2c7102 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1 new file mode 100644 index 0000000..e4fdd09 --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1
@@ -0,0 +1 @@ +158d1e78d2055793960120b1c58654f83cd6d4d3 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1 new file mode 100644 index 0000000..5e613de --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1
@@ -0,0 +1 @@ +0a6e35687efada2890624783e9936047ed10b434 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1 new file mode 100644 index 0000000..3fb24df --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1
@@ -0,0 +1 @@ +d1c89d1a22c716d3c9e2c8b7b725bc4716d12ea6 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1 new file mode 100644 index 0000000..9562d67 --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1
@@ -0,0 +1 @@ +2e7d404796f7c4b20f47957fef00755665623526 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetsurvey.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetsurvey.tar.gz.sha1 new file mode 100644 index 0000000..736edd4 --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/jetsurvey.tar.gz.sha1
@@ -0,0 +1 @@ +0dc41fbe14dbfb3bfc70ed64ff129b311bfcbf94 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1 new file mode 100644 index 0000000..d6c218c --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1
@@ -0,0 +1 @@ +9bb9c1cc3fea6d4ceb33df8e99057caa1bbe94f6 \ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/rally.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/rally.tar.gz.sha1 new file mode 100644 index 0000000..3947935 --- /dev/null +++ b/third_party/opensource-apps/android/compose-samples/rally.tar.gz.sha1
@@ -0,0 +1 @@ +86f3577a407a3cc3883f2d016d23879f3e8bd64e \ No newline at end of file
diff --git a/third_party/opensource-apps/kiss.tar.gz.sha1 b/third_party/opensource-apps/kiss.tar.gz.sha1 new file mode 100644 index 0000000..6d9e214 --- /dev/null +++ b/third_party/opensource-apps/kiss.tar.gz.sha1
@@ -0,0 +1 @@ +ae4ff6d906f2840ee6a855e989e8b563f948ba73 \ No newline at end of file
diff --git a/third_party/opensource-apps/materialistic.tar.gz.sha1 b/third_party/opensource-apps/materialistic.tar.gz.sha1 new file mode 100644 index 0000000..174a3fb --- /dev/null +++ b/third_party/opensource-apps/materialistic.tar.gz.sha1
@@ -0,0 +1 @@ +e392c3c4a607bbfe3ce0d2827475f388ab8b33d9 \ No newline at end of file
diff --git a/third_party/opensource-apps/minimal-todo.tar.gz.sha1 b/third_party/opensource-apps/minimal-todo.tar.gz.sha1 new file mode 100644 index 0000000..1bbe241 --- /dev/null +++ b/third_party/opensource-apps/minimal-todo.tar.gz.sha1
@@ -0,0 +1 @@ +70f5f95146d09c6777c88e4d556ec7419ffbe35b \ No newline at end of file
diff --git a/third_party/opensource-apps/muzei.tar.gz.sha1 b/third_party/opensource-apps/muzei.tar.gz.sha1 new file mode 100644 index 0000000..d8739a4 --- /dev/null +++ b/third_party/opensource-apps/muzei.tar.gz.sha1
@@ -0,0 +1 @@ +39e660b93c0efc403d5d6ab7a3e947f18c0c6bb0 \ No newline at end of file
diff --git a/third_party/opensource-apps/newpipe.tar.gz.sha1 b/third_party/opensource-apps/newpipe.tar.gz.sha1 new file mode 100644 index 0000000..ee2d9cf --- /dev/null +++ b/third_party/opensource-apps/newpipe.tar.gz.sha1
@@ -0,0 +1 @@ +9f824b7c6e0e923401def9006d98d2f70ef175c3 \ No newline at end of file
diff --git a/third_party/opensource-apps/rover-android.tar.gz.sha1 b/third_party/opensource-apps/rover-android.tar.gz.sha1 new file mode 100644 index 0000000..043d3e4 --- /dev/null +++ b/third_party/opensource-apps/rover-android.tar.gz.sha1
@@ -0,0 +1 @@ +50562618faf112ec73219488fa9b3705dc42a38e \ No newline at end of file
diff --git a/third_party/opensource-apps/santa-tracker.tar.gz.sha1 b/third_party/opensource-apps/santa-tracker.tar.gz.sha1 new file mode 100644 index 0000000..ea1af87 --- /dev/null +++ b/third_party/opensource-apps/santa-tracker.tar.gz.sha1
@@ -0,0 +1 @@ +5348b1c66c86c09c7d3bafd65b0d5417df78bcc8 \ No newline at end of file
diff --git a/third_party/opensource-apps/signal-android.tar.gz.sha1 b/third_party/opensource-apps/signal-android.tar.gz.sha1 new file mode 100644 index 0000000..131500f --- /dev/null +++ b/third_party/opensource-apps/signal-android.tar.gz.sha1
@@ -0,0 +1 @@ +2dbd3f913897b4ec9e25598d783579dbb74fab24 \ No newline at end of file
diff --git a/third_party/opensource-apps/simple-calendar.tar.gz.sha1 b/third_party/opensource-apps/simple-calendar.tar.gz.sha1 new file mode 100644 index 0000000..093d104 --- /dev/null +++ b/third_party/opensource-apps/simple-calendar.tar.gz.sha1
@@ -0,0 +1 @@ +118c5a291b675393698e4a2c0a0e1891a0933a8b \ No newline at end of file
diff --git a/third_party/opensource-apps/simple-camera.tar.gz.sha1 b/third_party/opensource-apps/simple-camera.tar.gz.sha1 new file mode 100644 index 0000000..3d88a7a --- /dev/null +++ b/third_party/opensource-apps/simple-camera.tar.gz.sha1
@@ -0,0 +1 @@ +19aba96f4c6b9751844ff668202a4591136e9702 \ No newline at end of file
diff --git a/third_party/opensource-apps/simple-file-manager.tar.gz.sha1 b/third_party/opensource-apps/simple-file-manager.tar.gz.sha1 new file mode 100644 index 0000000..00c4197 --- /dev/null +++ b/third_party/opensource-apps/simple-file-manager.tar.gz.sha1
@@ -0,0 +1 @@ +5f93c93b767b755fdec101961d9599928bf0aec8 \ No newline at end of file
diff --git a/third_party/opensource-apps/simple-gallery.tar.gz.sha1 b/third_party/opensource-apps/simple-gallery.tar.gz.sha1 new file mode 100644 index 0000000..b2d42db --- /dev/null +++ b/third_party/opensource-apps/simple-gallery.tar.gz.sha1
@@ -0,0 +1 @@ +7d086ce14817cf834cdbf99338aeace3f198bc67 \ No newline at end of file
diff --git a/third_party/opensource-apps/sqldelight.tar.gz.sha1 b/third_party/opensource-apps/sqldelight.tar.gz.sha1 new file mode 100644 index 0000000..3a186f8 --- /dev/null +++ b/third_party/opensource-apps/sqldelight.tar.gz.sha1
@@ -0,0 +1 @@ +31db97e3e578398a6e036390da81732717b5e0b7 \ No newline at end of file
diff --git a/third_party/opensource-apps/tachiyomi.tar.gz.sha1 b/third_party/opensource-apps/tachiyomi.tar.gz.sha1 new file mode 100644 index 0000000..a8c7a0c --- /dev/null +++ b/third_party/opensource-apps/tachiyomi.tar.gz.sha1
@@ -0,0 +1 @@ +a88063982149445bcc177856b3b6c265dc0d5ea7 \ No newline at end of file
diff --git a/third_party/opensource-apps/tivi.tar.gz.sha1 b/third_party/opensource-apps/tivi.tar.gz.sha1 new file mode 100644 index 0000000..6bd16e2 --- /dev/null +++ b/third_party/opensource-apps/tivi.tar.gz.sha1
@@ -0,0 +1 @@ +2f4adb11dcc8c56f377ee9945d47e88313bc5855 \ No newline at end of file
diff --git a/third_party/opensource-apps/tusky.tar.gz.sha1 b/third_party/opensource-apps/tusky.tar.gz.sha1 new file mode 100644 index 0000000..7eecbaf --- /dev/null +++ b/third_party/opensource-apps/tusky.tar.gz.sha1
@@ -0,0 +1 @@ +e546ffad98f75d0db7be39bfa7147f8eaa78d0aa \ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py index 48c596b..5c818d6 100755 --- a/tools/compiledump.py +++ b/tools/compiledump.py
@@ -38,6 +38,7 @@ help='Compiler version to use (default read from dump version file).' 'Valid arguments are:' ' "master" to run from your own tree,' + ' "source" to run from build classes directly,' ' "X.Y.Z" to run a specific version, or' ' <hash> to run that hash from master.', default=None) @@ -87,6 +88,12 @@ help='Set desugared-library (default set from dump)', default=None) parser.add_argument( + '--disable-desugared-lib', + help='Disable desugared-libary if it will be set from dump', + default=False, + action='store_true' + ) + parser.add_argument( '--loop', help='Run the compilation in a loop', default=False, @@ -131,6 +138,14 @@ def desugared_library_json(self): return self.if_exists('desugared-library.json') + def proguard_input_map(self): + if self.if_exists('proguard_input.config'): + print "Unimplemented: proguard_input configuration." + + def main_dex_resource(self): + if self.if_exists('main-dex-list.txt'): + print "Unimplemented: main-dex-list." + def build_properties_file(self): return self.if_exists('build.properties') @@ -209,6 +224,8 @@ def download_distribution(args, version, temp): if version == 'master': return utils.R8_JAR if args.nolib else utils.R8LIB_JAR + if version == 'source': + return '%s:%s' % (utils.BUILD_JAVA_MAIN_DIR, utils.ALL_DEPS_JAR) name = 'r8.jar' if args.nolib else 'r8lib.jar' source = archive.GetUploadDestination(version, name, is_hash(version)) dest = os.path.join(temp, 'r8.jar') @@ -219,12 +236,14 @@ wrapper_file = os.path.join( utils.REPO_ROOT, 'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java') - subprocess.check_output([ + cmd = [ jdk.GetJavacExecutable(), wrapper_file, '-d', temp, '-cp', dist, - ]) + ] + utils.PrintCmd(cmd) + subprocess.check_output(cmd) return temp def is_hash(version): @@ -248,6 +267,8 @@ min_api = determine_min_api(args, build_properties) classfile = determine_class_file(args, build_properties) jar = args.r8_jar if args.r8_jar else download_distribution(args, version, temp) + if ':' not in jar and not os.path.exists(jar): + error("Distribution does not exist: " + jar) wrapper_dir = prepare_wrapper(jar, temp) cmd = [jdk.GetJavaExecutable()] if args.debug_agent: @@ -261,6 +282,8 @@ cmd.append('-ea') if args.printtimes: cmd.append('-Dcom.android.tools.r8.printtimes=1') + if hasattr(args, 'properties'): + cmd.extend(args.properties); cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)]) if compiler == 'd8': cmd.append('com.android.tools.r8.D8') @@ -278,7 +301,7 @@ cmd.extend(['--lib', dump.library_jar()]) if dump.classpath_jar(): cmd.extend(['--classpath', dump.classpath_jar()]) - if dump.desugared_library_json(): + if dump.desugared_library_json() and not args.disable_desugared_lib: cmd.extend(['--desugared-lib', dump.desugared_library_json()]) if compiler != 'd8' and dump.config_file(): if hasattr(args, 'config_file_consumer') and args.config_file_consumer: @@ -299,7 +322,7 @@ return 0 except subprocess.CalledProcessError, e: print e.output - if not args.nolib: + if not args.nolib and version != 'source': stacktrace = os.path.join(temp, 'stacktrace') open(stacktrace, 'w+').write(e.output) local_map = utils.R8LIB_MAP if version == 'master' else None
diff --git a/tools/git_sync_cl_chain.py b/tools/git_sync_cl_chain.py index 5a229bd..66713fa 100755 --- a/tools/git_sync_cl_chain.py +++ b/tools/git_sync_cl_chain.py
@@ -126,7 +126,7 @@ has_seen_open_branch = True has_seen_local_branch = has_seen_local_branch or (status == 'None') - if options.upload: + if options.upload and status != 'closed': if has_seen_local_branch: print( 'Cannot upload branch %s since it comes after a local branch'
diff --git a/tools/retrace.py b/tools/retrace.py index 3be9771..70e3099 100755 --- a/tools/retrace.py +++ b/tools/retrace.py
@@ -40,6 +40,11 @@ '--stacktrace', help='Path to stacktrace file.', default=None) + parser.add_argument( + '--quiet', + default=None, + action='store_true', + help='Disables diagnostics printing to stdout.') return parser.parse_args() @@ -67,9 +72,10 @@ hash_or_version, args.stacktrace, args.commit_hash is not None, - args.no_r8lib) + args.no_r8lib, + quiet=args.quiet) -def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib): +def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib, quiet=False): if hash_or_version: download_path = archive.GetUploadDestination( hash_or_version, @@ -93,7 +99,7 @@ if stacktrace: retrace_args.append(stacktrace) - utils.PrintCmd(retrace_args) + utils.PrintCmd(retrace_args, quiet=quiet) return subprocess.call(retrace_args)
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py index bf5d4c7..03687af 100755 --- a/tools/run_on_app_dump.py +++ b/tools/run_on_app_dump.py
@@ -7,12 +7,12 @@ import apk_masseur import compiledump import gradle -import jdk import optparse import os import shutil import sys import time +import update_prebuilds_in_android import utils import zipfile @@ -38,14 +38,27 @@ defaults = { 'id': None, 'name': None, + 'collections': [], 'dump_app': None, 'apk_app': None, + 'dump_test': None, 'apk_test': None, 'skip': False, 'url': None, # url is not used but nice to have for updating apps 'revision': None, 'folder': None, 'skip_recompilation': False, + 'compiler_properties': [], + } + # This below does not work in python3 + defaults.update(fields.items()) + self.__dict__ = defaults + + +class AppCollection(object): + def __init__(self, fields): + defaults = { + 'name': None } # This below does not work in python3 defaults.update(fields.items()) @@ -89,8 +102,6 @@ 'url': 'https://github.com/christofferqa/AntennaPod.git', 'revision': '77e94f4783a16abe9cc5b78dc2d2b2b1867d8c06', 'folder': 'antennapod', - # TODO(b/172450929): Fix recompilation - 'skip_recompilation': True }), App({ 'id': 'com.example.applymapping', @@ -112,8 +123,9 @@ 'url': 'https://github.com/mkj-gram/chanu.git', 'revision': '6e53458f167b6d78398da60c20fd0da01a232617', 'folder': 'chanu', - # TODO(b/172535996): Fix recompilation - 'skip_recompilation': True + # The app depends on a class file that has access flags interface but + # not abstract + 'compiler_properties': ['-Dcom.android.tools.r8.allowInvalidCfAccessFlags=true'] }), # TODO(b/172539375): Monkey runner fails on recompilation. App({ @@ -131,14 +143,12 @@ 'dump_app': 'dump_app.zip', 'apk_app': 'app-debug.apk', # TODO(b/172549283): Compiling tests fails - 'id_test': 'com.example.applymapping.test', + 'id_test': 'com.google.samples.apps.sunflower.test', 'dump_test': 'dump_test.zip', 'apk_test': 'app-debug-androidTest.apk', 'url': 'https://github.com/android/sunflower', 'revision': '0c4c88fdad2a74791199dffd1a6559559b1dbd4a', 'folder': 'sunflower', - # TODO(b/172548728): Fix recompilation - 'skip_recompilation': True }), # TODO(b/172565385): Monkey runner fails on recompilation App({ @@ -151,6 +161,175 @@ 'folder': 'iosched', }), App({ + 'id': 'fr.neamar.kiss', + 'name': 'KISS', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release.apk', + # TODO(b/172569220): Running tests fails due to missing keep rules + 'id_test': 'fr.neamar.kiss.test', + 'dump_test': 'dump_test.zip', + 'apk_test': 'app-release-androidTest.apk', + 'url': 'https://github.com/Neamar/KISS', + 'revision': '8ccffaadaf0d0b8fc4418ed2b4281a0935d3d971', + 'folder': 'kiss', + }), + # TODO(b/172577344): Monkey runner not working. + App({ + 'id': 'io.github.hidroh.materialistic', + 'name': 'materialistic', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release.apk', + 'url': 'https://github.com/christofferqa/materialistic.git', + 'revision': '2b2b2ee25ce9e672d5aab1dc90a354af1522b1d9', + 'folder': 'materialistic', + }), + App({ + 'id': 'com.avjindersinghsekhon.minimaltodo', + 'name': 'MinimalTodo', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release.apk', + 'url': 'https://github.com/christofferqa/Minimal-Todo', + 'revision': '9d8c73746762cd376b718858ec1e8783ca07ba7c', + 'folder': 'minimal-todo', + }), + App({ + 'id': 'net.nurik.roman.muzei', + 'name': 'muzei', + 'dump_app': 'dump_app.zip', + 'apk_app': 'muzei-release.apk', + 'url': 'https://github.com/romannurik/muzei', + 'revision': '9eac6e98aebeaf0ae40bdcd85f16dd2886551138', + 'folder': 'muzei', + }), + # TODO(b/172806281): Monkey runner does not work. + App({ + 'id': 'org.schabi.newpipe', + 'name': 'NewPipe', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/TeamNewPipe/NewPipe', + 'revision': 'f4435f90313281beece70c544032f784418d85fa', + 'folder': 'newpipe', + }), + # TODO(b/172806808): Monkey runner does not work. + App({ + 'id': 'io.rover.app.debug', + 'name': 'Rover', + 'dump_app': 'dump_app.zip', + 'apk_app': 'example-app-release-unsigned.apk', + 'url': 'https://github.com/RoverPlatform/rover-android', + 'revision': '94342117097770ea3ca2c6df6ab496a1a55c3ce7', + 'folder': 'rover-android', + }), + App({ + 'id': 'io.rover.app.debug', + 'name': 'Rover', + 'dump_app': 'dump_app.zip', + 'apk_app': 'example-app-release-unsigned.apk', + 'url': 'https://github.com/RoverPlatform/rover-android', + 'revision': '94342117097770ea3ca2c6df6ab496a1a55c3ce7', + 'folder': 'rover-android', + }), + # TODO(b/172808159): Monkey runner does not work + App({ + 'id': 'com.google.android.apps.santatracker', + 'name': 'SantaTracker', + 'dump_app': 'dump_app.zip', + 'apk_app': 'santa-tracker-release.apk', + 'url': 'https://github.com/christofferqa/santa-tracker-android', + 'revision': '8dee74be7d9ee33c69465a07088c53087d24a6dd', + 'folder': 'santa-tracker', + }), + App({ + 'id': 'org.thoughtcrime.securesms', + 'name': 'Signal', + 'dump_app': 'dump_app.zip', + 'apk_app': 'Signal-Android-play-prod-universal-release-4.76.2.apk', + # TODO(b/172812839): Instrumentation test fails. + 'id_test': 'org.thoughtcrime.securesms.test', + 'dump_test': 'dump_test.zip', + 'apk_test': 'Signal-Android-play-prod-release-androidTest.apk', + 'url': 'https://github.com/signalapp/Signal-Android', + 'revision': '91ca19f294362ccee2c2b43c247eba228e2b30a1', + 'folder': 'signal-android', + }), + # TODO(b/172815827): Monkey runner does not work + App({ + 'id': 'com.simplemobiletools.calendar.pro', + 'name': 'Simple-Calendar', + 'dump_app': 'dump_app.zip', + 'apk_app': 'calendar-release.apk', + 'url': 'https://github.com/SimpleMobileTools/Simple-Calendar', + 'revision': '906209874d0a091c7fce5a57972472f272d6b068', + 'folder': 'simple-calendar', + }), + # TODO(b/172815534): Monkey runner does not work + App({ + 'id': 'com.simplemobiletools.camera.pro', + 'name': 'Simple-Camera', + 'dump_app': 'dump_app.zip', + 'apk_app': 'camera-release.apk', + 'url': 'https://github.com/SimpleMobileTools/Simple-Camera', + 'revision': 'ebf9820c51e960912b3238287e30a131244fdee6', + 'folder': 'simple-camera', + }), + App({ + 'id': 'com.simplemobiletools.filemanager.pro', + 'name': 'Simple-File-Manager', + 'dump_app': 'dump_app.zip', + 'apk_app': 'file-manager-release.apk', + 'url': 'https://github.com/SimpleMobileTools/Simple-File-Manager', + 'revision': '2b7fa68ea251222cc40cf6d62ad1de260a6f54d9', + 'folder': 'simple-file-manager', + }), + App({ + 'id': 'com.simplemobiletools.gallery.pro', + 'name': 'Simple-Gallery', + 'dump_app': 'dump_app.zip', + 'apk_app': 'gallery-326-foss-release.apk', + 'url': 'https://github.com/SimpleMobileTools/Simple-Gallery', + 'revision': '564e56b20d33b28d0018c8087ec705beeb60785e', + 'folder': 'simple-gallery', + }), + App({ + 'id': 'com.example.sqldelight.hockey', + 'name': 'SQLDelight', + 'dump_app': 'dump_app.zip', + 'apk_app': 'android-release.apk', + 'url': 'https://github.com/christofferqa/sqldelight', + 'revision': '2e67a1126b6df05e4119d1e3a432fde51d76cdc8', + 'folder': 'sqldelight', + }), + # TODO(b/172824096): Monkey runner does not work. + App({ + 'id': 'eu.kanade.tachiyomi', + 'name': 'Tachiyomi', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-dev-release.apk', + 'url': 'https://github.com/inorichi/tachiyomi', + 'revision': '8aa6486bf76ab9a61a5494bee284b1a5e9180bf3', + 'folder': 'tachiyomi', + }), + # TODO(b/172862042): Monkey runner does not work. + App({ + 'id': 'app.tivi', + 'name': 'Tivi', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release.apk', + 'url': 'https://github.com/chrisbanes/tivi', + 'revision': '8e2ddd8fe2d343264a66aa1ef8acbd4cc587e8ce', + 'folder': 'tivi', + }), + App({ + 'id': 'com.keylesspalace.tusky', + 'name': 'Tusky', + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-blue-release.apk', + 'url': 'https://github.com/tuskyapp/Tusky', + 'revision': '814a9b8f9bacf8d26f712b06a0313a3534a2be95', + 'folder': 'tusky', + }), + App({ 'id': 'org.wikipedia', 'name': 'Wikipedia', 'dump_app': 'dump_app.zip', @@ -159,6 +338,107 @@ 'revision': '0fa7cad843c66313be8e25790ef084cf1a1fa67e', 'folder': 'wikipedia', }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'androidx.compose.samples.crane', + 'name': 'compose-crane', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/crane', + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.jetcaster', + 'name': 'compose-jetcaster', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/jetcaster', + # TODO(b/173176042): Fix recompilation + 'skip_recompilation': True, + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.compose.jetchat', + 'name': 'compose-jetchat', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/jetchat', + # TODO(b/173176042): Fix recompilation + 'skip_recompilation': True, + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.jetnews', + 'name': 'compose-jetnews', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/jetnews', + # TODO(b/173176042): Fix recompilation + 'skip_recompilation': True, + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.jetsnack', + 'name': 'compose-jetsnack', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/jetsnack', + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.compose.jetsurvey', + 'name': 'compose-jetsurvey', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/jetsurvey', + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.owl', + 'name': 'compose-owl', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/owl', + }), + # TODO(b/173167253): Check if monkey testing works. + App({ + 'id': 'com.example.compose.rally', + 'name': 'compose-rally', + 'collections': ['compose-samples'], + 'dump_app': 'dump_app.zip', + 'apk_app': 'app-release-unsigned.apk', + 'url': 'https://github.com/android/compose-samples', + 'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8', + 'folder': 'android/compose-samples/rally', + }), +] + + +APP_COLLECTIONS = [ + AppCollection({ + 'name': 'compose-samples', + }) ] @@ -188,7 +468,11 @@ def is_full_r8(shrinker): - return '-full' not in shrinker + return '-full' in shrinker + + +def version_is_built_jar(version): + return version != 'master' and version != 'source' def compute_size_of_dex_files_in_package(path): @@ -208,6 +492,13 @@ return os.path.join(app_dir, app.dump_test) +def get_r8_jar(options, temp_dir, shrinker): + if (options.version == 'source'): + return None + return os.path.join( + temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar') + + def get_results_for_app(app, options, temp_dir): app_folder = app.folder if app.folder else app.name + "_" + app.revision app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder) @@ -255,7 +546,7 @@ app.name, shrinker)) print('To compile locally: ' - 'tools/run_on_as_app.py --shrinker {} --r8-compilation-steps {} ' + 'tools/run_on_app_dump.py --shrinker {} --r8-compilation-steps {} ' '--app {}'.format( shrinker, options.r8_compilation_steps, @@ -263,7 +554,9 @@ print('HINT: use --shrinker r8-nolib --no-build if you have a local R8.jar') recomp_jar = None status = 'success' - compilation_steps = 1 if app.skip_recompilation else options.r8_compilation_steps; + if options.r8_compilation_steps < 1: + return + compilation_steps = 1 if app.skip_recompilation else options.r8_compilation_steps for compilation_step in range(0, compilation_steps): if status != 'success': break @@ -312,7 +605,8 @@ app.id, options.emulator_id, app_apk_destination, options.monkey_events, options.quiet, is_logging_enabled_for(app, options)) else 'failed' - if result.get('build_status') == 'success' and options.run_tests: + if (result.get('build_status') == 'success' + and options.run_tests and app.dump_test): if not os.path.isfile(app_apk_destination): apk_masseur.masseur( original_app_apk, dex=app_jar, resources='META-INF/services/*', @@ -348,55 +642,55 @@ def build_app_with_shrinker(app, options, temp_dir, app_dir, shrinker, compilation_step_index, compilation_steps, prev_recomp_jar): - r8jar = os.path.join( - temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar') args = AttrDict({ 'dump': dump_for_app(app_dir, app), - 'r8_jar': r8jar, + 'r8_jar': get_r8_jar(options, temp_dir, shrinker), 'ea': False if options.disable_assertions else True, - 'version': 'master', + 'version': options.version, 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8', 'debug_agent': options.debug_agent, 'program_jar': prev_recomp_jar, 'nolib': not is_minified_r8(shrinker), 'config_file_consumer': remove_print_lines, + 'properties': app.compiler_properties, + 'disable_desugared_lib': False, }) - compile_result = compiledump.run1(temp_dir, args, []) - - out_jar = os.path.join(temp_dir, "out.jar") - out_mapping = os.path.join(temp_dir, "out.jar.map") app_jar = os.path.join( temp_dir, '{}_{}_{}_dex_out.jar'.format( app.name, shrinker, compilation_step_index)) app_mapping = os.path.join( temp_dir, '{}_{}_{}_dex_out.jar.map'.format( app.name, shrinker, compilation_step_index)) - - if compile_result != 0 or not os.path.isfile(out_jar): - assert False, "Compilation of app_jar failed" - shutil.move(out_jar, app_jar) - shutil.move(out_mapping, app_mapping) - recomp_jar = None - if compilation_step_index < compilation_steps - 1: - args['classfile'] = True - args['min_api'] = "10000" - compile_result = compiledump.run1(temp_dir, args, []) - if compile_result == 0: - recomp_jar = os.path.join( - temp_dir, '{}_{}_{}_cf_out.jar'.format( - app.name, shrinker, compilation_step_index)) - shutil.move(out_jar, recomp_jar) + + with utils.TempDir() as compile_temp_dir: + compile_result = compiledump.run1(compile_temp_dir, args, []) + out_jar = os.path.join(compile_temp_dir, "out.jar") + out_mapping = os.path.join(compile_temp_dir, "out.jar.map") + + if compile_result != 0 or not os.path.isfile(out_jar): + assert False, 'Compilation of {} failed'.format(dump_for_app(app_dir, app)) + shutil.move(out_jar, app_jar) + shutil.move(out_mapping, app_mapping) + + if compilation_step_index < compilation_steps - 1: + args['classfile'] = True + args['min_api'] = "10000" + args['disable_desugared_lib'] = True + compile_result = compiledump.run1(compile_temp_dir, args, []) + if compile_result == 0: + recomp_jar = os.path.join( + temp_dir, '{}_{}_{}_cf_out.jar'.format( + app.name, shrinker, compilation_step_index)) + shutil.move(out_jar, recomp_jar) return (app_jar, app_mapping, recomp_jar) def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker, compilation_step_index, mapping): - r8jar = os.path.join( - temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar') def rewrite_file(file): remove_print_lines(file) @@ -410,9 +704,9 @@ args = AttrDict({ 'dump': dump_test_for_app(app_dir, app), - 'r8_jar': r8jar, + 'r8_jar': get_r8_jar(options, temp_dir, shrinker), 'ea': False if options.disable_assertions else True, - 'version': 'master', + 'version': options.version, 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8', 'debug_agent': options.debug_agent, 'nolib': not is_minified_r8(shrinker), @@ -421,17 +715,16 @@ 'config_file_consumer': rewrite_file }) - compile_result = compiledump.run1(temp_dir, args, []) - - out_jar = os.path.join(temp_dir, "out.jar") test_jar = os.path.join( temp_dir, '{}_{}_{}_test_out.jar'.format( app.name, shrinker, compilation_step_index)) - if compile_result != 0 or not os.path.isfile(out_jar): - return None - - shutil.move(out_jar, test_jar) + with utils.TempDir() as compile_temp_dir: + compile_result = compiledump.run1(compile_temp_dir, args, []) + out_jar = os.path.join(compile_temp_dir, "out.jar") + if compile_result != 0 or not os.path.isfile(out_jar): + return None + shutil.move(out_jar, test_jar) return test_jar @@ -538,6 +831,10 @@ help='What app to run on', choices=[app.name for app in APPS], action='append') + result.add_option('--app-collection', '--app_collection', + help='What app collection to run', + choices=[collection.name for collection in APP_COLLECTIONS], + action='append') result.add_option('--bot', help='Running on bot, use third_party dependency.', default=False, @@ -610,13 +907,26 @@ help='The shrinkers to use (by default, all are run)', action='append') result.add_option('--version', + default='master', help='The version of R8 to use (e.g., 1.4.51)') (options, args) = result.parse_args(argv) - if options.app: - options.apps = [app for app in APPS if app.name in options.app] + + if options.app or options.app_collection: + if not options.app: + options.app = [] + if not options.app_collection: + options.app_collection = [] + options.apps = [ + app + for app in APPS + if app.name in options.app + or any(collection in options.app_collection + for collection in app.collections)] del options.app + del options.app_collection else: options.apps = APPS + if options.app_logging_filter: for app_name in options.app_logging_filter: assert any(app.name == app_name for app in options.apps) @@ -626,7 +936,7 @@ else: options.shrinker = [shrinker for shrinker in SHRINKERS] - if options.hash or options.version: + if options.hash or version_is_built_jar(options.version): # No need to build R8 if a specific version should be used. options.no_build = True if 'r8-nolib' in options.shrinker: @@ -666,16 +976,16 @@ as_utils.MoveFile( os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'), quiet=options.quiet) - elif options.version: - # Download r8-<version>.jar from - # https://storage.googleapis.com/r8-releases/raw/. - target = 'r8-{}.jar'.format(options.version) - update_prebuilds_in_android.download_version( - temp_dir, 'com/android/tools/r8/' + options.version, target) - as_utils.MoveFile( - os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'), - quiet=options.quiet) - else: + elif version_is_built_jar(options.version): + # Download r8-<version>.jar from + # https://storage.googleapis.com/r8-releases/raw/. + target = 'r8-{}.jar'.format(options.version) + update_prebuilds_in_android.download_version( + temp_dir, 'com/android/tools/r8/' + options.version, target) + as_utils.MoveFile( + os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'), + quiet=options.quiet) + elif options.version == 'master': if not (options.no_build or options.golem): gradle.RunGradle(['r8', '-Pno_internal']) build_r8lib = False @@ -699,7 +1009,12 @@ continue result_per_shrinker_per_app.append( (app, get_results_for_app(app, options, temp_dir))) - return log_results_for_apps(result_per_shrinker_per_app, options) + errors = log_results_for_apps(result_per_shrinker_per_app, options) + if errors > 0: + dest = 'gs://r8-test-results/r8-libs/' + str(int(time.time())) + utils.upload_file_to_cloud_storage(os.path.join(temp_dir, 'r8lib.jar'), dest) + print('R8lib saved to %s' % dest) + return errors def success(message):
diff --git a/tools/test.py b/tools/test.py index d7987a7..0c70124 100755 --- a/tools/test.py +++ b/tools/test.py
@@ -149,6 +149,13 @@ ' and empty (for no runtimes).') result.add_option('--print-hanging-stacks', '--print_hanging_stacks', default=-1, type="int", help='Print hanging stacks after timeout in seconds') + result.add_option('--print-full-stacktraces', '--print_full_stacktraces', + default=False, action='store_true', + help='Print the full stacktraces without any filtering applied') + result.add_option( + '--print-obfuscated-stacktraces', '--print_obfuscated_stacktraces', + default=False, action='store_true', + help='Print the obfuscated stacktraces') return result.parse_args() def archive_failures(): @@ -202,6 +209,10 @@ gradle_args.append('-Pdisable_assertions') if options.with_code_coverage: gradle_args.append('-Pwith_code_coverage') + if options.print_full_stacktraces: + gradle_args.append('-Pprint_full_stacktraces') + if options.print_obfuscated_stacktraces: + gradle_args.append('-Pprint_obfuscated_stacktraces') if os.name == 'nt': # temporary hack gradle_args.append('-Pno_internal')
diff --git a/tools/utils.py b/tools/utils.py index 0ece764..5396c0a 100644 --- a/tools/utils.py +++ b/tools/utils.py
@@ -29,6 +29,7 @@ BUILD = os.path.join(REPO_ROOT, 'build') BUILD_DEPS_DIR = os.path.join(BUILD, 'deps') BUILD_MAIN_DIR = os.path.join(BUILD, 'classes', 'main') +BUILD_JAVA_MAIN_DIR = os.path.join(BUILD, 'classes', 'java', 'main') BUILD_TEST_DIR = os.path.join(BUILD, 'classes', 'test') LIBS = os.path.join(BUILD, 'libs') GENERATED_LICENSE_DIR = os.path.join(BUILD, 'generatedLicense') @@ -43,6 +44,7 @@ R8_SRC = 'sourceJar' LIBRARY_DESUGAR_CONVERSIONS = 'buildLibraryDesugarConversions' +ALL_DEPS_JAR = os.path.join(LIBS, 'deps_all.jar') D8_JAR = os.path.join(LIBS, 'd8.jar') R8_JAR = os.path.join(LIBS, 'r8.jar') R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')