Merge commit 'e8fe8ec39fe72b9ba4c1b92abd920a828e09a2b0' into dev-release
diff --git a/build.gradle b/build.gradle index 5153337..558919e 100644 --- a/build.gradle +++ b/build.gradle
@@ -1938,11 +1938,6 @@ systemProperty 'runtimes', project.property('runtimes') } - if (project.hasProperty('horizontalClassMerging')) { - println "NOTE: Running with horizontal class merging" - systemProperty 'com.android.tools.r8.horizontalClassMerging', 'true' - } - if (project.hasProperty('slow_tests')) { systemProperty 'slow_tests', project.property('slow_tests') }
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg index ee1883c..cd4e40c 100644 --- a/infra/config/global/cr-buildbucket.cfg +++ b/infra/config/global/cr-buildbucket.cfg
@@ -176,15 +176,6 @@ } } builders { - name: "linux_horizontal" - mixins: "linux" - mixins: "normal" - recipe { - properties: "tool:r8" - properties: "horizontal_class_merging:True" - } - } - builders { name: "linux_release" mixins: "normal" mixins: "linux"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg index bec8477..29b0731 100644 --- a/infra/config/global/luci-milo.cfg +++ b/infra/config/global/luci-milo.cfg
@@ -31,11 +31,6 @@ short_name: "jdk8_9" } builders { - name: "buildbucket/luci.r8.ci/linux_horizontal" - category: "R8" - short_name: "horizontal" - } - builders { name: "buildbucket/luci.r8.ci/linux-android-4.0.4" category: "R8" short_name: "4.0.4"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg index 4ed7592..040d32b 100644 --- a/infra/config/global/luci-notify.cfg +++ b/infra/config/global/luci-notify.cfg
@@ -44,11 +44,6 @@ repository: "https://r8.googlesource.com/r8" } builders { - name: "linux_horizontal" - bucket: "ci" - repository: "https://r8.googlesource.com/r8" - } - builders { name: "linux_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 634885f..3c01353 100644 --- a/infra/config/global/luci-scheduler.cfg +++ b/infra/config/global/luci-scheduler.cfg
@@ -30,7 +30,6 @@ triggers: "linux-jdk8" triggers: "linux-jdk9" triggers: "linux-jdk8_9" - triggers: "linux_horizontal" triggers: "linux-android-4.0.4" triggers: "linux-android-4.4.4" triggers: "linux-android-5.1.1" @@ -182,21 +181,6 @@ } } - -job { - id: "linux_horizontal" - acl_sets: "default" - triggering_policy: { - kind: GREEDY_BATCHING - max_concurrent_invocations: 3 - } - buildbucket { - server: "cr-buildbucket.appspot.com" - bucket: "luci.r8.ci" - builder: "linux_horizontal" - } -} - job { id: "linux-android-4.0.4" acl_sets: "default"
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json index 945ad60..98ceb80 100644 --- a/src/library_desugar/desugar_jdk_libs.json +++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,9 +2,10 @@ "configuration_format_version": 3, "group_id" : "com.tools.android", "artifact_id" : "desugar_jdk_libs", - "version": "1.0.12", + "version": "1.1.0", "required_compilation_api_level": 26, "synthesized_library_classes_package_prefix": "j$.", + "support_all_callbacks_from_library": true, "common_flags": [ { "api_level_below_or_equal": 25,
diff --git a/src/library_desugar/desugar_jdk_libs_alternative_3.json b/src/library_desugar/desugar_jdk_libs_alternative_3.json new file mode 100644 index 0000000..628fc53 --- /dev/null +++ b/src/library_desugar/desugar_jdk_libs_alternative_3.json
@@ -0,0 +1,258 @@ +{ + "configuration_format_version": 3, + "group_id" : "com.tools.android", + "artifact_id" : "desugar_jdk_libs_alternative_3", + "version": "1.1.0", + "required_compilation_api_level": 26, + "synthesized_library_classes_package_prefix": "j$.", + "support_all_callbacks_from_library": false, + "common_flags": [ + { + "api_level_below_or_equal": 25, + "wrapper_conversion": [ + "java.time.Clock" + ] + }, + { + "api_level_below_or_equal": 23, + "wrapper_conversion": [ + "java.util.PrimitiveIterator$OfDouble", + "java.util.PrimitiveIterator$OfInt", + "java.util.PrimitiveIterator$OfLong", + "java.util.Spliterator", + "java.util.Spliterator$OfDouble", + "java.util.Spliterator$OfInt", + "java.util.Spliterator$OfLong", + "java.util.Spliterator$OfPrimitive", + "java.util.function.BiConsumer", + "java.util.function.BiFunction", + "java.util.function.BiPredicate", + "java.util.function.BinaryOperator", + "java.util.function.Consumer", + "java.util.function.DoubleBinaryOperator", + "java.util.function.DoubleConsumer", + "java.util.function.DoubleFunction", + "java.util.function.DoublePredicate", + "java.util.function.DoubleToIntFunction", + "java.util.function.DoubleToLongFunction", + "java.util.function.DoubleUnaryOperator", + "java.util.function.Function", + "java.util.function.IntBinaryOperator", + "java.util.function.IntConsumer", + "java.util.function.IntFunction", + "java.util.function.IntPredicate", + "java.util.function.IntToDoubleFunction", + "java.util.function.IntToLongFunction", + "java.util.function.IntUnaryOperator", + "java.util.function.LongBinaryOperator", + "java.util.function.LongConsumer", + "java.util.function.LongFunction", + "java.util.function.LongPredicate", + "java.util.function.LongToDoubleFunction", + "java.util.function.LongToIntFunction", + "java.util.function.LongUnaryOperator", + "java.util.function.ObjDoubleConsumer", + "java.util.function.ObjIntConsumer", + "java.util.function.ObjLongConsumer", + "java.util.function.Predicate", + "java.util.function.Supplier", + "java.util.function.ToDoubleFunction", + "java.util.function.ToIntFunction", + "java.util.function.ToLongFunction", + "java.util.function.UnaryOperator", + "java.util.stream.BaseStream", + "java.util.stream.Collector", + "java.util.stream.DoubleStream", + "java.util.stream.IntStream", + "java.util.stream.LongStream", + "java.util.stream.Stream" + ] + } + ], + "library_flags": [ + { + "api_level_below_or_equal": 25, + "rewrite_prefix": { + "j$.time.": "java.time.", + "java.time.": "j$.time.", + "java.util.Desugar": "j$.util.Desugar" + }, + "backport": { + "java.lang.Double8": "java.lang.Double", + "java.lang.Integer8": "java.lang.Integer", + "java.lang.Long8": "java.lang.Long", + "java.lang.Math8": "java.lang.Math" + }, + "retarget_lib_member": { + "java.util.Date#toInstant": "java.util.DesugarDate", + "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar", + "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone" + }, + "custom_conversion": { + "java.time.ZonedDateTime": "java.time.TimeConversions", + "java.time.LocalDate": "java.time.TimeConversions", + "java.time.Duration": "java.time.TimeConversions", + "java.time.ZoneId": "java.time.TimeConversions", + "java.time.MonthDay": "java.time.TimeConversions", + "java.time.Instant": "java.time.TimeConversions" + } + }, + { + "api_level_below_or_equal": 23, + "rewrite_prefix": { + "j$.util.Optional": "java.util.Optional", + "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics", + "j$.util.IntSummaryStatistics": "java.util.IntSummaryStatistics", + "j$.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatistics", + "java.util.stream.": "j$.util.stream.", + "java.util.function.": "j$.util.function.", + "java.util.Comparators": "j$.util.Comparators", + "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics", + "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics", + "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics", + "java.util.Objects": "j$.util.Objects", + "java.util.Optional": "j$.util.Optional", + "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator", + "java.util.SortedSet$1": "j$.util.SortedSet$1", + "java.util.Spliterator": "j$.util.Spliterator", + "java.util.StringJoiner": "j$.util.StringJoiner", + "java.util.Tripwire": "j$.util.Tripwire", + "java.util.concurrent.DesugarUnsafe": "j$.util.concurrent.DesugarUnsafe", + "java.util.concurrent.ThreadLocalRandom": "j$.util.concurrent.ThreadLocalRandom", + "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic", + "java.util.concurrent.ConcurrentHashMap": "j$.util.concurrent.ConcurrentHashMap", + "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader", + "java.io.UncheckedIOException": "j$.io.UncheckedIOException" + }, + "retarget_lib_member": { + "java.util.Arrays#stream": "java.util.DesugarArrays", + "java.util.Arrays#spliterator": "java.util.DesugarArrays", + "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet", + "java.io.BufferedReader#lines": "java.io.DesugarBufferedReader" + }, + "dont_rewrite": [ + "java.util.Iterator#remove" + ], + "emulate_interface": { + "java.util.Map$Entry": "j$.util.Map$Entry", + "java.util.Collection": "j$.util.Collection", + "java.util.Map": "j$.util.Map", + "java.util.Iterator": "j$.util.Iterator", + "java.util.Comparator": "j$.util.Comparator", + "java.util.List": "j$.util.List", + "java.util.SortedSet": "j$.util.SortedSet", + "java.util.Set": "j$.util.Set", + "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap" + }, + "custom_conversion": { + "java.util.Optional": "java.util.OptionalConversions", + "java.util.OptionalDouble": "java.util.OptionalConversions", + "java.util.OptionalInt": "java.util.OptionalConversions", + "java.util.OptionalLong": "java.util.OptionalConversions", + "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions", + "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions", + "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions" + } + } + ], + "program_flags": [ + { + "api_level_below_or_equal": 25, + "rewrite_prefix": { + "java.time.": "j$.time.", + "java.util.Desugar": "j$.util.Desugar" + }, + "retarget_lib_member": { + "java.util.Calendar#toInstant": "java.util.DesugarCalendar", + "java.util.Date#from": "java.util.DesugarDate", + "java.util.Date#toInstant": "java.util.DesugarDate", + "java.util.GregorianCalendar#from": "java.util.DesugarGregorianCalendar", + "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar", + "java.util.TimeZone#getTimeZone": "java.util.DesugarTimeZone", + "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone" + }, + "custom_conversion": { + "java.time.ZonedDateTime": "java.time.TimeConversions", + "java.time.LocalDate": "java.time.TimeConversions", + "java.time.Duration": "java.time.TimeConversions", + "java.time.ZoneId": "java.time.TimeConversions", + "java.time.MonthDay": "java.time.TimeConversions", + "java.time.Instant": "java.time.TimeConversions" + } + }, + { + "api_level_below_or_equal": 23, + "rewrite_prefix": { + "java.util.stream.": "j$.util.stream.", + "java.util.function.": "j$.util.function.", + "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics", + "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics", + "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics", + "java.util.Optional": "j$.util.Optional", + "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator", + "java.util.Spliterator": "j$.util.Spliterator", + "java.util.StringJoiner": "j$.util.StringJoiner", + "java.util.concurrent.ThreadLocalRandom": "j$.util.concurrent.ThreadLocalRandom", + "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic", + "java.util.concurrent.ConcurrentHashMap": "j$.util.concurrent.ConcurrentHashMap", + "java.io.UncheckedIOException": "j$.io.UncheckedIOException" + }, + "retarget_lib_member": { + "java.util.Arrays#stream": "java.util.DesugarArrays", + "java.util.Arrays#spliterator": "java.util.DesugarArrays", + "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet", + "java.util.concurrent.atomic.AtomicInteger#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicInteger", + "java.util.concurrent.atomic.AtomicInteger#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicInteger", + "java.util.concurrent.atomic.AtomicInteger#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicInteger", + "java.util.concurrent.atomic.AtomicInteger#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicInteger", + "java.util.concurrent.atomic.AtomicLong#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicLong", + "java.util.concurrent.atomic.AtomicLong#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicLong", + "java.util.concurrent.atomic.AtomicLong#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicLong", + "java.util.concurrent.atomic.AtomicLong#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicLong", + "java.util.concurrent.atomic.AtomicReference#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicReference", + "java.util.concurrent.atomic.AtomicReference#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference", + "java.util.concurrent.atomic.AtomicReference#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicReference", + "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference", + "java.util.Collections#synchronizedMap": "java.util.DesugarCollections", + "java.util.Collections#synchronizedSortedMap": "java.util.DesugarCollections", + "java.io.BufferedReader#lines": "java.io.DesugarBufferedReader" + }, + "dont_rewrite": [ + "java.util.Iterator#remove" + ], + "emulate_interface": { + "java.util.Map$Entry": "j$.util.Map$Entry", + "java.util.Collection": "j$.util.Collection", + "java.util.Map": "j$.util.Map", + "java.util.Iterator": "j$.util.Iterator", + "java.util.Comparator": "j$.util.Comparator", + "java.util.List": "j$.util.List", + "java.util.SortedSet": "j$.util.SortedSet", + "java.util.Set": "j$.util.Set", + "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap" + }, + "custom_conversion": { + "java.util.Optional": "java.util.OptionalConversions", + "java.util.OptionalDouble": "java.util.OptionalConversions", + "java.util.OptionalInt": "java.util.OptionalConversions", + "java.util.OptionalLong": "java.util.OptionalConversions", + "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions", + "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions", + "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions" + } + } + ], + "shrinker_config": [ + "-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); }", + "-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; }", + "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }", + "-keepattributes Signature", + "-keepattributes EnclosingMethod", + "-keepattributes InnerClasses" + ] +}
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java index 6b8f9c5..e20e5b2 100644 --- a/src/main/java/com/android/tools/r8/DataEntryResource.java +++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -26,6 +26,15 @@ return new ByteDataEntryResource(bytes, name, origin); } + static DataEntryResource fromString(String name, Origin origin, String... lines) { + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line); + sb.append(System.lineSeparator()); + } + return new ByteDataEntryResource(sb.toString().getBytes(), name, origin); + } + static DataEntryResource fromFile(Path dir, Path file) { return new LocalDataEntryResource(dir.resolve(file).toFile(), file.toString().replace(File.separatorChar, SEPARATOR));
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index dad6c63..ebc0bc6 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -532,7 +532,7 @@ mainDexTracingResult); VerticalClassMergerGraphLens lens = verticalClassMerger.run(); if (lens != null) { - appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses()); + appView.setVerticallyMergedClasses(lens.getMergedClasses()); appView.rewriteWithLens(lens); runtimeTypeCheckInfo = runtimeTypeCheckInfo.rewriteWithLens(lens); }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index f9c2050..bbe9387 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -861,11 +861,16 @@ internal.enableClassStaticizer = false; internal.outline.enabled = false; internal.enableEnumUnboxing = false; + internal.enableLambdaMerging = false; } if (!internal.isShrinking()) { - // If R8 is not shrinking, there is no point in unboxing enums since the unboxed enums - // will still remain in the program (The application size would actually increase). + // If R8 is not shrinking, there is no point in running various optimizations since the + // optimized classes will still remain in the program (the application size could increase). internal.enableEnumUnboxing = false; + internal.enableHorizontalClassMerging = false; + internal.enableLambdaMerging = false; + internal.enableStaticClassMerging = false; + internal.enableVerticalClassMerging = false; } if (!internal.enableInlining) { @@ -929,6 +934,7 @@ if (internal.isGeneratingClassFiles()) { internal.outline.enabled = false; internal.enableEnumUnboxing = false; + internal.enableHorizontalClassMerging = false; } // EXPERIMENTAL flags.
diff --git a/src/main/java/com/android/tools/r8/errors/AssumeNoSideEffectsRuleForObjectMembersDiagnostic.java b/src/main/java/com/android/tools/r8/errors/AssumeNoSideEffectsRuleForObjectMembersDiagnostic.java new file mode 100644 index 0000000..cb4fafb --- /dev/null +++ b/src/main/java/com/android/tools/r8/errors/AssumeNoSideEffectsRuleForObjectMembersDiagnostic.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.errors; + +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.Keep; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.utils.MethodReferenceUtils; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +@Keep +public class AssumeNoSideEffectsRuleForObjectMembersDiagnostic implements Diagnostic { + + private final List<MethodReference> methods; + private final Origin origin; + private final Position position; + + private AssumeNoSideEffectsRuleForObjectMembersDiagnostic( + List<MethodReference> methods, Origin origin, Position position) { + this.methods = methods; + this.origin = origin; + this.position = position; + } + + @Override + public Origin getOrigin() { + return origin; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public String getDiagnosticMessage() { + Iterator<MethodReference> iterator = methods.iterator(); + StringBuilder message = + new StringBuilder("The -assumenosideeffects rule matches the following method(s) ") + .append("on java.lang.Object: ") + .append(MethodReferenceUtils.toSourceStringWithoutHolderAndReturnType(iterator.next())); + while (iterator.hasNext()) { + MethodReference method = iterator.next(); + message + .append(iterator.hasNext() ? ", " : " and ") + .append(MethodReferenceUtils.toSourceStringWithoutHolderAndReturnType(method)); + } + return message + .append(". ") + .append("This is most likely not intended. ") + .append("Consider specifying the methods more precisely.") + .toString(); + } + + public static class Builder { + + private final List<MethodReference> methods = new ArrayList<>(); + private Origin origin; + private Position position; + + public Builder() {} + + public Builder addMatchedMethods(Set<DexMethod> methods) { + for (DexMethod method : methods) { + this.methods.add(method.asMethodReference()); + } + return this; + } + + public Builder setOrigin(Origin origin) { + this.origin = origin; + return this; + } + + public Builder setPosition(Position position) { + this.position = position; + return this; + } + + public AssumeNoSideEffectsRuleForObjectMembersDiagnostic build() { + return new AssumeNoSideEffectsRuleForObjectMembersDiagnostic(methods, origin, position); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java index 114bc8a..3c5f4b7 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.utils.BooleanBox; import com.android.tools.r8.utils.InternalOptions; import java.util.Collection; +import java.util.List; import java.util.function.Consumer; public class AppInfo implements DexDefinitionSupplier { @@ -120,7 +121,7 @@ return app.classes(); } - public Iterable<DexProgramClass> classesWithDeterministicOrder() { + public List<DexProgramClass> classesWithDeterministicOrder() { assert checkIfObsolete(); return app.classesWithDeterministicOrder(); }
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 877f2ba..37f45c6 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator; import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory; import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer; +import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection; import com.android.tools.r8.optimize.MemberRebindingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepInfoCollection; @@ -69,6 +70,9 @@ // Desugared library prefix rewriter. public final PrefixRewritingMapper rewritePrefix; + // Modeling. + private final LibraryMethodSideEffectModelCollection libraryMethodSideEffectModelCollection; + // Optimizations. private final CallSiteOptimizationInfoPropagator callSiteOptimizationInfoPropagator; private final LibraryMemberOptimizer libraryMemberOptimizer; @@ -108,6 +112,8 @@ } this.libraryMemberOptimizer = new LibraryMemberOptimizer(this); + this.libraryMethodSideEffectModelCollection = + new LibraryMethodSideEffectModelCollection(dexItemFactory()); if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) { this.protoShrinker = new ProtoShrinker(withLiveness()); @@ -287,6 +293,10 @@ return libraryMemberOptimizer; } + public LibraryMethodSideEffectModelCollection getLibraryMethodSideEffectModelCollection() { + return libraryMethodSideEffectModelCollection; + } + public ProtoShrinker protoShrinker() { return protoShrinker; }
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 ae54120..bae65e8 100644 --- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -5,11 +5,12 @@ package com.android.tools.r8.graph; import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; -import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.utils.MapUtils; import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -77,23 +78,11 @@ private void recordOriginalTypeNames( DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) { DexType type = clazz.getType(); - VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses(); - if (verticallyMergedClasses != null && verticallyMergedClasses.hasBeenMergedIntoSubtype(type)) { - return; - } - DexType original = appView.graphLens().getOriginalType(type); - if (verticallyMergedClasses != null) { - List<DexType> sources = verticallyMergedClasses.getSourcesFor(type); - if (!sources.isEmpty()) { - renamedTypeNames.put(original, type); - sources.forEach(source -> renamedTypeNames.put(source, type)); - return; - } - } - - if (original != type) { - renamedTypeNames.put(original, type); + List<DexType> originalTypes = Lists.newArrayList(appView.graphLens().getOriginalTypes(type)); + boolean isIdentity = originalTypes.size() == 1 && originalTypes.get(0) == type; + if (!isIdentity) { + originalTypes.forEach(originalType -> renamedTypeNames.put(originalType, type)); } } @@ -112,6 +101,15 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + Set<DexType> originalTypes = renamedTypeNames.getKeys(type); + if (originalTypes == null) { + return ImmutableList.of(type); + } + return originalTypes; + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return originalFieldSignatures.getOrDefault(field, field); }
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 516edc0..abb91ed 100644 --- a/src/main/java/com/android/tools/r8/graph/DexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -103,7 +103,7 @@ return box.getClasses(); } - public Iterable<DexProgramClass> classesWithDeterministicOrder() { + public List<DexProgramClass> classesWithDeterministicOrder() { List<DexProgramClass> classes = new ArrayList<>(programClasses()); // We never actually sort by anything but the DexType, this is just here in case we ever change // that.
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java index 115bf84..0a17134 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -12,11 +12,15 @@ assert holder.isProgramClass() == (this instanceof ProgramMethod); } + public static ProgramMethod asProgramMethodOrNull(DexClassAndMethod method) { + return method != null ? method.asProgramMethod() : null; + } + public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) { if (holder.isProgramClass()) { return new ProgramMethod(holder.asProgramClass(), method); } else if (holder.isLibraryClass()) { - return new DexClassAndMethod(holder, method); + return new LibraryMethod(holder.asLibraryClass(), method); } else { assert holder.isClasspathClass(); return new ClasspathMethod(holder.asClasspathClass(), method); @@ -46,6 +50,14 @@ return null; } + public boolean isLibraryMethod() { + return false; + } + + public LibraryMethod asLibraryMethod() { + return null; + } + public boolean isProgramMethod() { return false; }
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 ea4103f..5a1e76d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -425,6 +425,15 @@ return asProgramMethod(definitions); } + public DexClassAndMethod asDexClassAndMethod(DexDefinitionSupplier definitions) { + assert method.holder.isClassType(); + DexClass clazz = definitions.definitionForHolder(method); + if (clazz != null) { + return DexClassAndMethod.create(clazz, this); + } + return null; + } + public ProgramMethod asProgramMethod(DexDefinitionSupplier definitions) { assert method.holder.isClassType(); DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(method)); @@ -434,6 +443,11 @@ return null; } + public static DexClassAndMethod asDexClassAndMethodOrNull( + DexEncodedMethod method, DexDefinitionSupplier definitions) { + return method != null ? method.asDexClassAndMethod(definitions) : null; + } + public static ProgramMethod asProgramMethodOrNull( DexEncodedMethod method, DexDefinitionSupplier definitions) { return method != null ? method.asProgramMethod(definitions) : null;
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 cf30eed..b192017 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -5,7 +5,6 @@ import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces; import static com.android.tools.r8.ir.optimize.ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME; -import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.dex.Constants; import com.android.tools.r8.dex.Marker; @@ -31,7 +30,6 @@ import com.android.tools.r8.kotlin.Kotlin; import com.android.tools.r8.utils.ArrayUtils; import com.android.tools.r8.utils.LRUCacheTable; -import com.android.tools.r8.utils.Pair; import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -39,7 +37,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; @@ -57,7 +54,6 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; public class DexItemFactory { @@ -109,8 +105,6 @@ public DexItemFactory() { this.kotlin = new Kotlin(this); - assert libraryMethodsWithReturnValueDependingOnlyOnArguments.stream() - .allMatch(libraryMethodsWithoutSideEffects::containsKey); } public static boolean isInternalSentinel(DexItem item) { @@ -604,7 +598,7 @@ } // Boxed Boxed#valueOf(Primitive), e.g., Boolean Boolean#valueOf(B) - private Set<DexMethod> boxedValueOfMethods() { + public Set<DexMethod> boxedValueOfMethods() { return primitiveToBoxed.entrySet().stream() .map( entry -> { @@ -686,31 +680,6 @@ objectsMethods.requireNonNullWithMessageSupplier, stringMembers.valueOf); - // We assume library methods listed here are `public`, i.e., free from visibility side effects. - // If not, that library method should not be added here because it literally has side effects. - public Map<DexMethod, Predicate<InvokeMethod>> libraryMethodsWithoutSideEffects = - Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat( - Stream.of(new Pair<>(enumMembers.constructor, alwaysTrue())), - Stream.of(new Pair<>(npeMethods.init, alwaysTrue())), - Stream.of(new Pair<>(npeMethods.initWithMessage, alwaysTrue())), - Stream.of(new Pair<>(objectMembers.constructor, alwaysTrue())), - Stream.of(new Pair<>(objectMembers.getClass, alwaysTrue())), - Stream.of(new Pair<>(stringMembers.hashCode, alwaysTrue())), - mapToPredicate(classMethods.getNames, alwaysTrue()), - mapToPredicate( - stringBufferMethods.constructorMethods, - stringBufferMethods::constructorInvokeIsSideEffectFree), - mapToPredicate( - stringBuilderMethods.constructorMethods, - stringBuilderMethods::constructorInvokeIsSideEffectFree), - mapToPredicate(boxedValueOfMethods(), alwaysTrue())) - .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); - - private static Stream<Pair<DexMethod, Predicate<InvokeMethod>>> mapToPredicate( - Set<DexMethod> methods, Predicate<InvokeMethod> predicate) { - return methods.stream().map(method -> new Pair<>(method, predicate)); - } - // TODO(b/119596718): More idempotent methods? Any singleton accessors? E.g., // java.util.Calendar#getInstance(...) // 4 variants // java.util.Locale#getDefault() // returns JVM default locale. @@ -1130,7 +1099,10 @@ public final DexField clinitField = createField(objectType, intType, "$r8$clinit"); public final DexMethod clone; + public final DexMethod equals = + createMethod(objectType, createProto(booleanType, objectType), "equals"); public final DexMethod getClass; + public final DexMethod hashCode = createMethod(objectType, createProto(intType), "hashCode"); public final DexMethod constructor; public final DexMethod finalize; public final DexMethod toString; @@ -1199,7 +1171,7 @@ public final DexMethod getDeclaredMethod; public final DexMethod newInstance; private final Set<DexMethod> getMembers; - private final Set<DexMethod> getNames; + public final Set<DexMethod> getNames; private ClassMethods() { desiredAssertionStatus = createMethod(classDescriptor, @@ -1561,8 +1533,6 @@ public final DexMethod toString; private final Set<DexMethod> appendMethods; - private final Set<DexMethod> constructorMethods; - private StringBuildingMethods(DexType receiver) { DexString append = createString("append"); @@ -1611,6 +1581,8 @@ charSequenceConstructor, defaultConstructor, intConstructor, stringConstructor); } + public final Set<DexMethod> constructorMethods; + public boolean isAppendMethod(DexMethod method) { return appendMethods.contains(method); }
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 a0d4cbe..74b0fff 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -37,6 +37,10 @@ return name; } + public DexType getParameter(int index) { + return proto.getParameter(index); + } + public DexTypeList getParameters() { return proto.parameters; } @@ -219,15 +223,22 @@ @Override public String toSourceString() { - return toSourceString(true); + return toSourceString(true, true); } public String toSourceStringWithoutHolder() { - return toSourceString(false); + return toSourceString(false, true); } - private String toSourceString(boolean includeHolder) { - StringBuilder builder = new StringBuilder(proto.returnType.toSourceString()).append(" "); + public String toSourceStringWithoutHolderAndReturnType() { + return toSourceString(false, false); + } + + private String toSourceString(boolean includeHolder, boolean includeReturnType) { + StringBuilder builder = new StringBuilder(); + if (includeReturnType) { + builder.append(getReturnType().toSourceString()).append(" "); + } if (includeHolder) { builder.append(holder.toSourceString()).append("."); }
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 ac9b854..eec753e 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens; import com.android.tools.r8.shaking.KeepInfoCollection; import com.android.tools.r8.utils.Action; +import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.BiMap; @@ -273,6 +274,8 @@ public abstract DexType getOriginalType(DexType type); + public abstract Iterable<DexType> getOriginalTypes(DexType type); + public abstract DexField getOriginalFieldSignature(DexField field); public abstract DexMethod getOriginalMethodSignature(DexMethod method); @@ -499,15 +502,16 @@ return result; } - public DexReference rewriteReference(DexReference reference) { + @SuppressWarnings("unchecked") + public <T extends DexReference> T rewriteReference(T reference) { if (reference.isDexField()) { - return getRenamedFieldSignature(reference.asDexField()); + return (T) getRenamedFieldSignature(reference.asDexField()); } if (reference.isDexMethod()) { - return getRenamedMethodSignature(reference.asDexMethod()); + return (T) getRenamedMethodSignature(reference.asDexMethod()); } assert reference.isDexType(); - return lookupType(reference.asDexType()); + return (T) lookupType(reference.asDexType()); } public Set<DexReference> rewriteReferences(Set<DexReference> references) { @@ -518,8 +522,8 @@ return result; } - public <T> ImmutableMap<DexReference, T> rewriteReferenceKeys(Map<DexReference, T> map) { - ImmutableMap.Builder<DexReference, T> builder = ImmutableMap.builder(); + public <R extends DexReference, T> ImmutableMap<R, T> rewriteReferenceKeys(Map<R, T> map) { + ImmutableMap.Builder<R, T> builder = ImmutableMap.builder(); map.forEach((reference, value) -> builder.put(rewriteReference(reference), value)); return builder.build(); } @@ -761,6 +765,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return IterableUtils.singleton(type); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return field; } @@ -845,6 +854,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return getPrevious().getOriginalTypes(type); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return getPrevious().getOriginalFieldSignature(field); } @@ -965,9 +979,22 @@ return new Builder(); } + protected DexType internalGetOriginalType(DexType previous) { + return previous; + } + + protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { + return IterableUtils.singleton(internalGetOriginalType(previous)); + } + @Override public DexType getOriginalType(DexType type) { - return getPrevious().getOriginalType(type); + return getPrevious().getOriginalType(internalGetOriginalType(type)); + } + + @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java new file mode 100644 index 0000000..c06ebab --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
@@ -0,0 +1,29 @@ +// Copyright (c) 2019, 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; + +/** Type representing a method definition from the library and its holder. */ +public final class LibraryMethod extends DexClassAndMethod { + + public LibraryMethod(DexLibraryClass holder, DexEncodedMethod method) { + super(holder, method); + } + + @Override + public boolean isLibraryMethod() { + return true; + } + + @Override + public LibraryMethod asLibraryMethod() { + return this; + } + + @Override + public DexLibraryClass getHolder() { + DexClass holder = super.getHolder(); + assert holder.isLibraryClass(); + return holder.asLibraryClass(); + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java index 949fd47..7c38464 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -95,6 +95,11 @@ } @Override + void clearDirectMethods() { + directMethods = DexEncodedMethod.EMPTY_ARRAY; + } + + @Override DexEncodedMethod removeMethod(DexMethod method) { DexEncodedMethod removedDirectMethod = removeMethodHelper( @@ -175,6 +180,11 @@ } @Override + void clearVirtualMethods() { + virtualMethods = DexEncodedMethod.EMPTY_ARRAY; + } + + @Override void setVirtualMethods(DexEncodedMethod[] methods) { virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY); assert verifyNoDuplicateMethods(); @@ -344,4 +354,26 @@ } } } + + @Override + public void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + DexEncodedMethod[] oldMethods = directMethods; + clearDirectMethods(); + DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length]; + for (int i = 0; i < oldMethods.length; i++) { + newMethods[i] = replacement.apply(oldMethods[i]); + } + directMethods = newMethods; + } + + @Override + public void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + DexEncodedMethod[] oldMethods = virtualMethods; + clearVirtualMethods(); + DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length]; + for (int i = 0; i < oldMethods.length; i++) { + newMethods[i] = replacement.apply(oldMethods[i]); + } + virtualMethods = newMethods; + } }
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 312cec2..b14ff47 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java +++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -223,14 +223,24 @@ backing.replaceMethods(replacement); } + public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + resetDirectMethodCaches(); + backing.replaceDirectMethods(replacement); + } + public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { resetVirtualMethodCaches(); backing.replaceVirtualMethods(replacement); } - public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + public void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { resetDirectMethodCaches(); - backing.replaceDirectMethods(replacement); + backing.replaceAllDirectMethods(replacement); + } + + public void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + resetVirtualMethodCaches(); + backing.replaceAllVirtualMethods(replacement); } /** @@ -252,6 +262,11 @@ backing.addDirectMethods(methods); } + public void clearDirectMethods() { + resetDirectMethodCaches(); + backing.clearDirectMethods(); + } + public DexEncodedMethod removeMethod(DexMethod method) { DexEncodedMethod removed = backing.removeMethod(method); if (removed != null) { @@ -283,6 +298,11 @@ backing.addVirtualMethods(methods); } + public void clearVirtualMethods() { + resetVirtualMethodCaches(); + backing.clearVirtualMethods(); + } + public void setVirtualMethods(DexEncodedMethod[] methods) { assert verifyCorrectnessOfMethodHolders(methods); resetVirtualMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java index 601306c..37a46f0 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -94,6 +94,10 @@ // Removal methods. + abstract void clearDirectMethods(); + + abstract void clearVirtualMethods(); + abstract DexEncodedMethod removeMethod(DexMethod method); abstract void removeMethods(Set<DexEncodedMethod> method); @@ -108,8 +112,12 @@ abstract void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement); + abstract void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement); + abstract void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement); + abstract void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement); + abstract DexEncodedMethod replaceDirectMethod( DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
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 d877896..2874e03 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java +++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -8,12 +8,14 @@ import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.android.tools.r8.utils.TraversalContinuation; import com.google.common.base.Equivalence.Wrapper; +import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceRBTreeMap; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; @@ -195,6 +197,16 @@ } @Override + void clearDirectMethods() { + methodMap.values().removeIf(this::belongsToDirectPool); + } + + @Override + void clearVirtualMethods() { + methodMap.values().removeIf(this::belongsToVirtualPool); + } + + @Override DexEncodedMethod removeMethod(DexMethod method) { return methodMap.remove(wrap(method)); } @@ -276,6 +288,28 @@ } @Override + void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + List<DexEncodedMethod> oldMethods = Lists.newArrayList(directMethods()); + clearDirectMethods(); + List<DexEncodedMethod> newMethods = new ArrayList<>(oldMethods.size()); + for (DexEncodedMethod method : oldMethods) { + newMethods.add(replacement.apply(method)); + } + addDirectMethods(newMethods); + } + + @Override + void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { + List<DexEncodedMethod> oldMethods = Lists.newArrayList(virtualMethods()); + clearVirtualMethods(); + List<DexEncodedMethod> newMethods = new ArrayList<>(oldMethods.size()); + for (DexEncodedMethod method : oldMethods) { + newMethods.add(replacement.apply(method)); + } + addVirtualMethods(newMethods); + } + + @Override DexEncodedMethod replaceDirectMethod( DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { return replaceMethod(method, replacement, this::belongsToDirectPool);
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 f76f9e7..ed69ace 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,27 +7,28 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; +import java.util.Collections; import java.util.Map; +import java.util.Set; public class VerticallyMergedClasses implements MergedClasses { private final Map<DexType, DexType> mergedClasses; - private final Map<DexType, List<DexType>> sources; + private final Map<DexType, Set<DexType>> mergedClassesInverse; - public VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) { - Map<DexType, List<DexType>> sources = Maps.newIdentityHashMap(); - mergedClasses.forEach( - (source, target) -> sources.computeIfAbsent(target, key -> new ArrayList<>()).add(source)); + public VerticallyMergedClasses( + Map<DexType, DexType> mergedClasses, Map<DexType, Set<DexType>> mergedClassesInverse) { this.mergedClasses = mergedClasses; - this.sources = sources; + this.mergedClassesInverse = mergedClassesInverse; } - public List<DexType> getSourcesFor(DexType type) { - return sources.getOrDefault(type, ImmutableList.of()); + public Map<DexType, DexType> getForwardMap() { + return mergedClasses; + } + + public Collection<DexType> getSourcesFor(DexType type) { + return mergedClassesInverse.getOrDefault(type, Collections.emptySet()); } public DexType getTargetFor(DexType type) { @@ -45,7 +46,7 @@ @Override public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) { - for (List<DexType> sourcesForTarget : sources.values()) { + for (Collection<DexType> sourcesForTarget : mergedClassesInverse.values()) { for (DexType source : sourcesForTarget) { assert appView.appInfo().wasPruned(source) : "Expected vertically merged class `" + source.toSourceString() + "` to be absent";
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 cfd5182..a6003c4 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -28,8 +28,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.IdentityHashMap; +import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -41,7 +42,7 @@ public static final String CLASS_ID_FIELD_NAME = "$r8$classId"; private final DexProgramClass target; - private final Collection<DexProgramClass> toMergeGroup; + private final List<DexProgramClass> toMergeGroup; private final DexItemFactory dexItemFactory; private final HorizontalClassMergerGraphLens.Builder lensBuilder; private final HorizontallyMergedClasses.Builder mergedClassesBuilder; @@ -58,7 +59,7 @@ HorizontallyMergedClasses.Builder mergedClassesBuilder, FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder, DexProgramClass target, - Collection<DexProgramClass> toMergeGroup, + List<DexProgramClass> toMergeGroup, DexField classIdField, Collection<VirtualMethodMerger> virtualMethodMergers, Collection<ConstructorMerger> constructorMergers) { @@ -174,9 +175,9 @@ public static class Builder { private final DexProgramClass target; - private final Collection<DexProgramClass> toMergeGroup = new ArrayList<>(); + private final List<DexProgramClass> toMergeGroup = new ArrayList<>(); private final Map<DexProto, ConstructorMerger.Builder> constructorMergerBuilders = - new IdentityHashMap<>(); + new LinkedHashMap<>(); private final Map<Wrapper<DexMethod>, VirtualMethodMerger.Builder> virtualMethodMergerBuilders = new LinkedHashMap<>(); @@ -191,7 +192,7 @@ return this; } - public Builder addClassesToMerge(Collection<DexProgramClass> toMerge) { + public Builder addClassesToMerge(List<DexProgramClass> toMerge) { toMerge.forEach(this::mergeClass); return this; } @@ -237,18 +238,26 @@ DexField classIdField = dexItemFactory.createField(target.type, dexItemFactory.intType, CLASS_ID_FIELD_NAME); - Collection<VirtualMethodMerger> virtualMethodMergers = + List<VirtualMethodMerger> virtualMethodMergers = new ArrayList<>(virtualMethodMergerBuilders.size()); for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) { virtualMethodMergers.add(builder.build(appView, target, classIdField)); } + // Try and merge the functions with the most arguments first, to avoid using synthetic + // arguments if possible. + virtualMethodMergers.sort(Comparator.comparing(VirtualMethodMerger::getArity).reversed()); - Collection<ConstructorMerger> constructorMergers = + List<ConstructorMerger> constructorMergers = new ArrayList<>(constructorMergerBuilders.size()); for (ConstructorMerger.Builder builder : constructorMergerBuilders.values()) { constructorMergers.add(builder.build(appView, target, classIdField)); } + // Try and merge the functions with the most arguments first, to avoid using synthetic + // arguments if possible. + virtualMethodMergers.sort(Comparator.comparing(VirtualMethodMerger::getArity).reversed()); + constructorMergers.sort(Comparator.comparing(ConstructorMerger::getArity).reversed()); + return new ClassMerger( appView, lensBuilder,
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 9f201bf..60ff636b 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -55,6 +55,10 @@ this.dexItemFactory = appView.dexItemFactory(); } + public int getArity() { + return constructors.iterator().next().getReference().getArity(); + } + public static class Builder { private final Collection<DexEncodedMethod> constructors;
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 c9a40d7..c8a1c16 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.AppView; 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.DontInlinePolicy; import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible; import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses; @@ -40,7 +41,7 @@ import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -76,6 +77,7 @@ new NotEntryPoint(appView.dexItemFactory()), new DontInlinePolicy(appView, mainDexTracingResult), new PreventMergeIntoMainDex(appView, mainDexTracingResult), + new AllInstantiatedOrUninstantiated(appView), new SameParentClass(), new SameNestHost(), new PreventChangingVisibility(), @@ -93,7 +95,7 @@ // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup public HorizontalClassMergerGraphLens run(DirectMappedDexApplication.Builder appBuilder) { - Map<FieldMultiset, Collection<DexProgramClass>> classes = new HashMap<>(); + Map<FieldMultiset, List<DexProgramClass>> classes = new LinkedHashMap<>(); // Group classes by same field signature using the hash map. for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) { @@ -101,7 +103,7 @@ } // Run the policies on all collected classes to produce a final grouping. - Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values()); + Collection<List<DexProgramClass>> groups = policyExecutor.run(classes.values()); // If there are no groups, then end horizontal class merging. if (groups.isEmpty()) { appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty()); @@ -116,7 +118,7 @@ new FieldAccessInfoCollectionModifier.Builder(); // Set up a class merger for each group. - Collection<ClassMerger> classMergers = + List<ClassMerger> classMergers = initializeClassMergers( mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder, groups); Iterable<DexProgramClass> allMergeClasses = @@ -138,15 +140,15 @@ * Prepare horizontal class merging by determining which virtual methods and constructors need to * be merged and how the merging should be performed. */ - private Collection<ClassMerger> initializeClassMergers( + private List<ClassMerger> initializeClassMergers( HorizontallyMergedClasses.Builder mergedClassesBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder, FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder, - Collection<Collection<DexProgramClass>> groups) { - Collection<ClassMerger> classMergers = new ArrayList<>(); + Collection<List<DexProgramClass>> groups) { + List<ClassMerger> classMergers = new ArrayList<>(); // TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup - for (Collection<DexProgramClass> group : groups) { + for (List<DexProgramClass> group : groups) { assert !group.isEmpty(); DexProgramClass target = group.stream().findFirst().get();
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 5f5a62b..867725b 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -7,9 +7,11 @@ import com.android.tools.r8.graph.AppView; 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.graph.GraphLens; import com.android.tools.r8.graph.GraphLens.NestedGraphLens; 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; @@ -24,10 +26,11 @@ private final AppView<?> appView; private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters; private final Map<DexMethod, DexMethod> originalConstructorSignatures; + private final HorizontallyMergedClasses mergedClasses; private HorizontalClassMergerGraphLens( AppView<?> appView, - HorizontallyMergedClasses horizontallyMergedClasses, + HorizontallyMergedClasses mergedClasses, Map<DexMethod, List<ExtraParameter>> methodExtraParameters, Map<DexField, DexField> fieldMap, Map<DexMethod, DexMethod> methodMap, @@ -36,7 +39,7 @@ Map<DexMethod, DexMethod> originalConstructorSignatures, GraphLens previousLens) { super( - horizontallyMergedClasses.getForwardMap(), + mergedClasses.getForwardMap(), methodMap, fieldMap, originalFieldSignatures, @@ -46,6 +49,12 @@ this.appView = appView; this.methodExtraParameters = methodExtraParameters; this.originalConstructorSignatures = originalConstructorSignatures; + this.mergedClasses = mergedClasses; + } + + @Override + protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { + return IterableUtils.prependSingleton(previous, mergedClasses.getSourcesFor(previous)); } @Override
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 653e31a..080102a 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -34,6 +34,10 @@ return mergedClasses.keySet(); } + public Set<DexType> getSourcesFor(DexType type) { + return mergedClasses.getKeys(type); + } + public boolean hasBeenMergedIntoDifferentType(DexType type) { return mergedClasses.hasKey(type); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java index 829c1f3..2c8f10a 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.DexProgramClass; import java.util.ArrayList; import java.util.Collection; +import java.util.List; public abstract class MultiClassPolicy extends Policy { @@ -18,7 +19,7 @@ /** * Remove all groups containing no or only a single class, as there is no point in merging these. */ - protected void removeTrivialGroups(Collection<Collection<DexProgramClass>> groups) { + protected void removeTrivialGroups(Collection<List<DexProgramClass>> groups) { assert !(groups instanceof ArrayList); groups.removeIf(this::isTrivial); } @@ -31,5 +32,5 @@ * merged. If the policy detects no issues then `group` will be returned unchanged. If classes * cannot be merged with any other classes they are returned as singleton lists. */ - public abstract Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group); + public abstract Collection<List<DexProgramClass>> apply(List<DexProgramClass> group); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java index 0a7e988..bf460d9 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -6,14 +6,15 @@ import com.android.tools.r8.graph.DexProgramClass; 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 abstract class MultiClassSameReferencePolicy<T> extends MultiClassPolicy { @Override - public final Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { - Map<T, Collection<DexProgramClass>> groups = new IdentityHashMap<>(); + public final Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { + Map<T, List<DexProgramClass>> groups = new LinkedHashMap<>(); for (DexProgramClass clazz : group) { groups.computeIfAbsent(getMergeKey(clazz), ignore -> new LinkedList<>()).add(clazz); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java index 7ffaa1b..cddca41 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.DexProgramClass; import java.util.Collection; +import java.util.List; public abstract class PolicyExecutor { protected final Collection<Policy> policies; @@ -19,6 +20,5 @@ * policies registered to this policy executor on the class groups yielding a new collection of * class groups. */ - public abstract Collection<Collection<DexProgramClass>> run( - Collection<Collection<DexProgramClass>> classes); + public abstract Collection<List<DexProgramClass>> run(Collection<List<DexProgramClass>> classes); }
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 6192da8..a5f26d8 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -8,6 +8,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.stream.Collectors; /** @@ -21,9 +22,9 @@ } // TODO(b/165506334): if performing mutable operation ensure that linked lists are used - private LinkedList<Collection<DexProgramClass>> applySingleClassPolicy( - SingleClassPolicy policy, LinkedList<Collection<DexProgramClass>> groups) { - Iterator<Collection<DexProgramClass>> i = groups.iterator(); + private LinkedList<List<DexProgramClass>> applySingleClassPolicy( + SingleClassPolicy policy, LinkedList<List<DexProgramClass>> groups) { + Iterator<List<DexProgramClass>> i = groups.iterator(); while (i.hasNext()) { Collection<DexProgramClass> group = i.next(); int previousNumberOfClasses = group.size(); @@ -36,8 +37,8 @@ return groups; } - private LinkedList<Collection<DexProgramClass>> applyMultiClassPolicy( - MultiClassPolicy policy, LinkedList<Collection<DexProgramClass>> groups) { + private LinkedList<List<DexProgramClass>> applyMultiClassPolicy( + 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()) @@ -45,12 +46,11 @@ } @Override - public Collection<Collection<DexProgramClass>> run( - Collection<Collection<DexProgramClass>> inputGroups) { - LinkedList<Collection<DexProgramClass>> linkedGroups; + public Collection<List<DexProgramClass>> run(Collection<List<DexProgramClass>> inputGroups) { + LinkedList<List<DexProgramClass>> linkedGroups; if (inputGroups instanceof LinkedList) { - linkedGroups = (LinkedList<Collection<DexProgramClass>>) inputGroups; + linkedGroups = (LinkedList<List<DexProgramClass>>) inputGroups; } else { linkedGroups = new LinkedList<>(inputGroups); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java index 402ddb1..b643225 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
@@ -11,6 +11,7 @@ import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.function.BiFunction; @@ -32,13 +33,12 @@ private final AppView<AppInfoWithLiveness> appView; private final Collection<DexProgramClass> roots = new ArrayList<>(); - private final Map<DexProgramClass, Collection<DexProgramClass>> subtypeMap = - new IdentityHashMap<>(); + private final Map<DexProgramClass, List<DexProgramClass>> subtypeMap = new IdentityHashMap<>(); - public SubtypingForrestForClasses(AppView<AppInfoWithLiveness> appView) { + public SubtypingForrestForClasses( + AppView<AppInfoWithLiveness> appView, List<DexProgramClass> classesWithDeterministicOrder) { this.appView = appView; - - calculateSubtyping(appView.appInfo().classes()); + calculateSubtyping(classesWithDeterministicOrder); } private DexProgramClass superClass(DexProgramClass clazz) {
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 b102b69..8cf6265 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -120,11 +120,11 @@ * </ul> */ public HorizontalClassMergerGraphLens fixupTypeReferences() { - Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder(); + List<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder(); Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass); classes.forEach(this::fixupProgramClassSuperType); - SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView); + SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView, classes); // TODO(b/170078037): parallelize this code segment. for (DexProgramClass root : subtypingForrest.getProgramRoots()) { subtypingForrest.traverseNodeDepthFirst( @@ -153,18 +153,18 @@ Map<Wrapper<DexMethod>, DexString> remappedClassVirtualMethods = new HashMap<>(remappedVirtualMethods); - Set<DexMethod> newDirectMethodReferences = new LinkedHashSet<>(); - Set<DexMethod> newVirtualMethodReferences = new LinkedHashSet<>(); - + Set<DexMethod> newVirtualMethodReferences = Sets.newIdentityHashSet(); clazz .getMethodCollection() - .replaceVirtualMethods( + .replaceAllVirtualMethods( method -> fixupVirtualMethod( remappedClassVirtualMethods, newVirtualMethodReferences, method)); + + Set<DexMethod> newDirectMethodReferences = Sets.newIdentityHashSet(); clazz .getMethodCollection() - .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethodReferences, method)); + .replaceAllDirectMethods(method -> fixupDirectMethod(newDirectMethodReferences, method)); fixupFields(clazz.staticFields(), clazz::setStaticField); fixupFields(clazz.instanceFields(), clazz::setInstanceField);
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 ec5cb59..14488b6 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -90,6 +90,10 @@ } } + public int getArity() { + return methods.iterator().next().getReference().getArity(); + } + private DexMethod moveMethod(ProgramMethod oldMethod) { DexMethod oldMethodReference = oldMethod.getReference(); DexMethod method = @@ -171,6 +175,13 @@ DexMethod templateReference = methods.iterator().next().getReference(); DexMethod originalMethodReference = appView.graphLens().getOriginalMethodSignature(templateReference); + DexMethod bridgeMethodReference = + dexItemFactory.createFreshMethodName( + originalMethodReference.getName().toSourceString() + "$bridge", + null, + originalMethodReference.proto, + originalMethodReference.getHolderType(), + tryMethod -> target.lookupMethod(tryMethod) == null); DexMethod newMethodReference = dexItemFactory.createMethod(target.type, templateReference.proto, templateReference.name); @@ -180,7 +191,7 @@ classIdField, superMethod, newMethodReference, - originalMethodReference); + bridgeMethodReference); DexEncodedMethod newMethod = new DexEncodedMethod( newMethodReference, @@ -196,7 +207,7 @@ for (ProgramMethod oldMethod : methods) { lensBuilder.moveMethod(oldMethod.getReference(), newMethodReference); } - lensBuilder.recordExtraOriginalSignature(originalMethodReference, newMethodReference); + lensBuilder.recordExtraOriginalSignature(bridgeMethodReference, newMethodReference); target.addVirtualMethod(newMethod);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java new file mode 100644 index 0000000..75d61d9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.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.horizontalclassmerging.policies; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy; +import com.android.tools.r8.shaking.AppInfoWithLiveness; + +public class AllInstantiatedOrUninstantiated extends MultiClassSameReferencePolicy<Boolean> { + + private final AppView<AppInfoWithLiveness> appView; + + public AllInstantiatedOrUninstantiated(AppView<AppInfoWithLiveness> appView) { + this.appView = appView; + } + + @Override + public Boolean getMergeKey(DexProgramClass clazz) { + return appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz); + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java index e579310..4e9b69e 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java
@@ -14,7 +14,7 @@ public class DontMergeIntoLessVisible extends MultiClassPolicy { @Override - public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { Iterator<DexProgramClass> iterator = group.iterator(); while (iterator.hasNext()) { DexProgramClass clazz = iterator.next();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java index 958e660..d8bebf9 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
@@ -12,6 +12,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; public class DontMergeSynchronizedClasses extends MultiClassPolicy { private final AppView<AppInfoWithLiveness> appView; @@ -25,14 +26,14 @@ } @Override - public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { // Gather all synchronized classes. - Collection<Collection<DexProgramClass>> synchronizedGroups = new LinkedList<>(); + Collection<List<DexProgramClass>> synchronizedGroups = new LinkedList<>(); group.removeIf( clazz -> { boolean synchronizationClass = isSynchronizationClass(clazz); if (synchronizationClass) { - Collection<DexProgramClass> synchronizedGroup = new LinkedList<>(); + List<DexProgramClass> synchronizedGroup = new LinkedList<>(); synchronizedGroup.add(clazz); synchronizedGroups.add(synchronizedGroup); } @@ -43,7 +44,7 @@ return Collections.singletonList(group); } - Iterator<Collection<DexProgramClass>> synchronizedGroupIterator = synchronizedGroups.iterator(); + Iterator<List<DexProgramClass>> synchronizedGroupIterator = synchronizedGroups.iterator(); for (DexProgramClass clazz : group) { if (!synchronizedGroupIterator.hasNext()) { synchronizedGroupIterator = synchronizedGroups.iterator();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java index 969f43a..4d90a1e 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.horizontalclassmerging.policies; - import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexMember;
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 112580c..474fcea 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
@@ -42,7 +42,7 @@ } @Override - public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { Map<DexProto, Set<DexProgramClass>> overlappingConstructors = new IdentityHashMap<>(); for (DexProgramClass clazz : group) { @@ -106,12 +106,10 @@ } // Map to collection - Collection<Collection<DexProgramClass>> newGroups = new ArrayList<>(); + Collection<List<DexProgramClass>> newGroups = new ArrayList<>(); for (Set<DexProgramClass> newGroup : groups) { - List<DexProgramClass> newGroupList = new ArrayList<>(newGroup); newGroups.add(new ArrayList<>(newGroup)); } - return newGroups; } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java index 3c317eb..8b35a5c 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java
@@ -23,10 +23,10 @@ public PreventChangingVisibility() {} public static class TargetGroup { - private final Collection<DexProgramClass> group = new LinkedList<>(); + private final List<DexProgramClass> group = new LinkedList<>(); private final Map<Wrapper<DexMethod>, MethodAccessFlags> methodMap = new HashMap<>(); - public Collection<DexProgramClass> getGroup() { + public List<DexProgramClass> getGroup() { return group; } @@ -53,7 +53,7 @@ } @Override - public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { List<TargetGroup> groups = new ArrayList<>(); for (DexProgramClass clazz : group) { @@ -66,7 +66,7 @@ } } - Collection<Collection<DexProgramClass>> newGroups = new ArrayList<>(); + Collection<List<DexProgramClass>> newGroups = new ArrayList<>(); for (TargetGroup newGroup : groups) { if (!isTrivial(newGroup.getGroup())) { newGroups.add(newGroup.getGroup());
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 f17e923..f8f0a3f 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
@@ -30,7 +30,7 @@ } @Override - public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { List<DexProgramClass> mainDexMembers = new LinkedList<>(); Iterator<DexProgramClass> iterator = group.iterator(); while (iterator.hasNext()) { @@ -41,14 +41,13 @@ } } - Collection<Collection<DexProgramClass>> newGroups = new LinkedList<>(); + Collection<List<DexProgramClass>> newGroups = new LinkedList<>(); if (!isTrivial(mainDexMembers)) { newGroups.add(mainDexMembers); } if (!isTrivial(group)) { newGroups.add(group); } - return newGroups; } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java index 4b112b8..bd59a0e 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -16,6 +16,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; +import java.util.List; import java.util.Map; public class RespectPackageBoundaries extends MultiClassPolicy { @@ -58,7 +59,7 @@ /** Sort unrestricted classes into restricted classes if they are in the same package. */ void tryFindRestrictedPackage( LinkedList<DexProgramClass> unrestrictedClasses, - Map<String, Collection<DexProgramClass>> restrictedClasses) { + Map<String, List<DexProgramClass>> restrictedClasses) { Iterator<DexProgramClass> i = unrestrictedClasses.iterator(); while (i.hasNext()) { DexProgramClass clazz = i.next(); @@ -72,8 +73,8 @@ } @Override - public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) { - Map<String, Collection<DexProgramClass>> restrictedClasses = new LinkedHashMap<>(); + public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) { + Map<String, List<DexProgramClass>> restrictedClasses = new LinkedHashMap<>(); LinkedList<DexProgramClass> unrestrictedClasses = new LinkedList<>(); // Sort all restricted classes into packages. @@ -92,7 +93,7 @@ // TODO(b/166577694): Add the unrestricted classes to restricted groups, but ensure they aren't // the merge target. - Collection<Collection<DexProgramClass>> groups = new ArrayList<>(restrictedClasses.size() + 1); + Collection<List<DexProgramClass>> groups = new ArrayList<>(restrictedClasses.size() + 1); if (unrestrictedClasses.size() > 1) { groups.add(unrestrictedClasses); }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java index 416f064..3ee0de7 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexDefinition; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -323,10 +324,11 @@ return false; } if (appView.appInfo().hasLiveness()) { - DexEncodedMethod singleTarget = + DexClassAndMethod singleTarget = instruction.lookupSingleTarget(appView.withLiveness(), context); if (singleTarget != null) { - return isTypeInitializedBy(instruction, type, singleTarget, appView, mode); + return isTypeInitializedBy( + instruction, type, singleTarget.getDefinition(), appView, mode); } } DexMethod method = instruction.getInvokedMethod(); @@ -350,8 +352,9 @@ // Class initialization may fail with ExceptionInInitializerError. return false; } - DexEncodedMethod method = instruction.lookupSingleTarget(appView, context); - return method != null && isTypeInitializedBy(instruction, type, method, appView, mode); + DexClassAndMethod method = instruction.lookupSingleTarget(appView, context); + return method != null + && isTypeInitializedBy(instruction, type, method.getDefinition(), appView, mode); } public static boolean forInvokeSuper( @@ -374,10 +377,11 @@ return false; } if (appView.appInfo().hasLiveness()) { - DexEncodedMethod singleTarget = + DexClassAndMethod singleTarget = instruction.lookupSingleTarget(appView.withLiveness(), context); if (singleTarget != null) { - return isTypeInitializedBy(instruction, type, singleTarget, appView, mode); + return isTypeInitializedBy( + instruction, type, singleTarget.getDefinition(), appView, mode); } } DexMethod method = instruction.getInvokedMethod(); @@ -418,10 +422,11 @@ return false; } if (appView.appInfo().hasLiveness()) { - DexEncodedMethod singleTarget = + DexClassAndMethod singleTarget = instruction.lookupSingleTarget(appView.withLiveness(), context); if (singleTarget != null) { - return isTypeInitializedBy(instruction, type, singleTarget, appView, mode); + return isTypeInitializedBy( + instruction, type, singleTarget.getDefinition(), appView, mode); } } DexMethod method = instruction.getInvokedMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java index 32c1bb1..c2a5b53 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.ir.analysis; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -36,9 +36,10 @@ return false; } if (instr.isInvokeMethod()) { - DexEncodedMethod target = + DexClassAndMethod target = instr.asInvokeMethod().lookupSingleTarget(appView, code.context()); - if (target != null && target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) { + if (target != null + && target.getDefinition().getOptimizationInfo().returnValueOnlyDependsOnArguments()) { continue; } return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java index b73a496..01ea16b 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
@@ -6,8 +6,8 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; @@ -132,13 +132,16 @@ InvokeMethod invoke = instruction.asInvokeMethod(); DexMethod method = invoke.getInvokedMethod(); if (method.holder.isClassType()) { - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); if (singleTarget != null) { - markInitializedOnNormalExit(singleTarget.holder()); + markInitializedOnNormalExit(singleTarget.getHolderType()); markInitializedOnNormalExit( - singleTarget.getOptimizationInfo().getInitializedClassesOnNormalExit()); + singleTarget + .getDefinition() + .getOptimizationInfo() + .getInitializedClassesOnNormalExit()); } else { - markInitializedOnNormalExit(method.holder); + markInitializedOnNormalExit(method.getHolderType()); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java index ea4040c..2fb3ef7 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -8,7 +8,7 @@ import static com.google.common.base.Predicates.or; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.ConstClass; @@ -160,9 +160,10 @@ } // For constructor calls include field initialization side effects. if (instruction.isInvokeConstructor(appView.dexItemFactory())) { - DexEncodedMethod singleTarget = + DexClassAndMethod singleTarget = instruction.asInvokeDirect().lookupSingleTarget(appView, context); - return singleTarget != null && !singleTarget.getOptimizationInfo().mayHaveSideEffects(); + return singleTarget != null + && !singleTarget.getDefinition().getOptimizationInfo().mayHaveSideEffects(); } return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java index 3bc0ebe..d0fd119 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -150,7 +151,7 @@ return; } - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); if (singleTarget == null) { // We just lost track. abstractInstanceFieldValues.remove(clazz); @@ -158,7 +159,11 @@ } InstanceFieldInitializationInfoCollection initializationInfoCollection = - singleTarget.getOptimizationInfo().getInstanceInitializerInfo().fieldInitializationInfos(); + singleTarget + .getDefinition() + .getOptimizationInfo() + .getInstanceInitializerInfo() + .fieldInitializationInfos(); // Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process // another allocation site of `clazz` concurrently.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java index f508479..d42d5d1 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -8,8 +8,8 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; @@ -43,14 +43,14 @@ private final InstanceFieldInitializationInfoFactory factory; - private final DexEncodedMethod parentConstructor; + private final DexClassAndMethod parentConstructor; private final InvokeDirect parentConstructorCall; private InstanceFieldValueAnalysis( AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback, - DexEncodedMethod parentConstructor, + DexClassAndMethod parentConstructor, InvokeDirect parentConstructorCall) { super(appView, code, feedback); this.factory = appView.instanceFieldInitializationInfoFactory(); @@ -90,7 +90,7 @@ return EmptyInstanceFieldInitializationInfoCollection.getInstance(); } - DexEncodedMethod parentConstructor = + DexClassAndMethod parentConstructor = parentConstructorCall.lookupSingleTarget(appView, code.context()); if (parentConstructor == null) { return EmptyInstanceFieldInitializationInfoCollection.getInstance(); @@ -167,6 +167,7 @@ } InstanceFieldInitializationInfoCollection infos = parentConstructor + .getDefinition() .getOptimizationInfo() .getInstanceInitializerInfo() .fieldInitializationInfos(); @@ -271,7 +272,7 @@ if (field.isFinal()) { return true; } - if (appView.appInfo().isFieldOnlyWrittenInMethod(field, parentConstructor)) { + if (appView.appInfo().isFieldOnlyWrittenInMethod(field, parentConstructor.getDefinition())) { return true; } // Otherwise, conservatively return false.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java index ebcdfff..1b4e723 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -9,8 +9,8 @@ import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; -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.ir.analysis.type.ClassTypeElement; @@ -339,13 +339,17 @@ return ObjectState.empty(); } - DexEncodedMethod singleTarget = uniqueConstructorInvoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = uniqueConstructorInvoke.lookupSingleTarget(appView, context); if (singleTarget == null) { return ObjectState.empty(); } InstanceFieldInitializationInfoCollection initializationInfos = - singleTarget.getOptimizationInfo().getInstanceInitializerInfo().fieldInitializationInfos(); + singleTarget + .getDefinition() + .getOptimizationInfo() + .getInstanceInitializerInfo() + .fieldInitializationInfos(); if (initializationInfos.isEmpty()) { return ObjectState.empty(); }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java index 34ab168..f9a2e25 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -4,41 +4,39 @@ package com.android.tools.r8.ir.analysis.modeling; -import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet; import com.android.tools.r8.ir.code.InvokeMethod; -import java.util.function.Predicate; /** Models if a given library method may cause a program field to be read. */ public class LibraryMethodReadSetModeling { public static AbstractFieldSet getModeledReadSetOrUnknown( - InvokeMethod invoke, DexItemFactory dexItemFactory) { + AppView<?> appView, InvokeMethod invoke) { DexMethod invokedMethod = invoke.getInvokedMethod(); // Check if it is a library method that does not have side effects. In that case it is safe to // assume that the method does not read any fields, since even if it did, it would not be able // to do anything with the values it read (since we will remove such invocations without side // effects). - Predicate<InvokeMethod> noSideEffectsPredicate = - dexItemFactory.libraryMethodsWithoutSideEffects.get(invokedMethod); - if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(invoke)) { + if (appView + .getLibraryMethodSideEffectModelCollection() + .isCallToSideEffectFreeFinalMethod(invoke)) { return EmptyFieldSet.getInstance(); } // Already handled above. - assert !dexItemFactory.classMethods.isReflectiveNameLookup(invokedMethod); + assert !appView.dexItemFactory().classMethods.isReflectiveNameLookup(invokedMethod); // Modeling of other library methods. DexType holder = invokedMethod.holder; - if (holder == dexItemFactory.objectType) { - if (invokedMethod == dexItemFactory.objectMembers.constructor) { - return EmptyFieldSet.getInstance(); - } + if (holder == appView.dexItemFactory().objectType + && invokedMethod == appView.dexItemFactory().objectMembers.constructor) { + return EmptyFieldSet.getInstance(); } return UnknownFieldSet.getInstance(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java index 2a81647..c61ca0a 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -3,10 +3,13 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull; + import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.InvokeDirectRange; import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -125,22 +128,24 @@ } @Override - public DexEncodedMethod lookupSingleTarget( + public DexClassAndMethod lookupSingleTarget( AppView<?> appView, ProgramMethod context, TypeElement receiverUpperBoundType, ClassTypeElement receiverLowerBoundType) { DexMethod invokedMethod = getInvokedMethod(); + DexEncodedMethod result; if (appView.appInfo().hasLiveness()) { AppInfoWithLiveness appInfo = appView.appInfo().withLiveness(); - DexEncodedMethod result = appInfo.lookupDirectTarget(invokedMethod, context); + result = appInfo.lookupDirectTarget(invokedMethod, context); assert verifyD8LookupResult( result, appView.appInfo().lookupDirectTargetOnItself(invokedMethod, context)); - return result; + } else { + // In D8, we can treat invoke-direct instructions as having a single target if the invoke is + // targeting a method in the enclosing class. + result = appView.appInfo().lookupDirectTargetOnItself(invokedMethod, context); } - // In D8, we can treat invoke-direct instructions as having a single target if the invoke is - // targeting a method in the enclosing class. - return appView.appInfo().lookupDirectTargetOnItself(invokedMethod, context); + return asDexClassAndMethodOrNull(result, appView); } @Override @@ -189,15 +194,19 @@ // Trivial instance initializers do not read any fields. if (appView.dexItemFactory().isConstructor(invokedMethod)) { - DexEncodedMethod singleTarget = lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = lookupSingleTarget(appView, context); // If we have a single target in the program, then use the computed initializer info. // If we have a single target in the library, then fallthrough to the library modeling below. - if (singleTarget != null && singleTarget.isProgramMethod(appView)) { - return singleTarget.getOptimizationInfo().getInstanceInitializerInfo().readSet(); + if (singleTarget != null && singleTarget.isProgramMethod()) { + return singleTarget + .getDefinition() + .getOptimizationInfo() + .getInstanceInitializerInfo() + .readSet(); } } - return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory()); + return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(appView, this); } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java index 83acc1d..6412e17 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -3,11 +3,13 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull; import static com.android.tools.r8.ir.analysis.type.TypeAnalysis.toRefinedReceiverType; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.InvokeInterfaceRange; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -95,25 +97,27 @@ } @Override - public DexEncodedMethod lookupSingleTarget( + public DexClassAndMethod lookupSingleTarget( AppView<?> appView, ProgramMethod context, TypeElement receiverUpperBoundType, ClassTypeElement receiverLowerBoundType) { - if (appView.appInfo().hasLiveness()) { - AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); - return appViewWithLiveness - .appInfo() - .lookupSingleVirtualTarget( - getInvokedMethod(), - context, - true, - appView, - toRefinedReceiverType( - receiverUpperBoundType, getInvokedMethod(), appViewWithLiveness), - receiverLowerBoundType); + if (!appView.appInfo().hasLiveness()) { + return null; } - return null; + AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); + DexEncodedMethod result = + appViewWithLiveness + .appInfo() + .lookupSingleVirtualTarget( + getInvokedMethod(), + context, + true, + appView, + toRefinedReceiverType( + receiverUpperBoundType, getInvokedMethod(), appViewWithLiveness), + receiverLowerBoundType); + return asDexClassAndMethodOrNull(result, appView); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java index c8dd6ec..6daa9a4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.cf.LoadStoreHelper; import com.android.tools.r8.cf.TypeVerificationHelper; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -75,11 +76,10 @@ // In subclasses, e.g., invoke-virtual or invoke-super, use a narrower receiver type by using // receiver type and calling context---the holder of the method where the current invocation is. // TODO(b/140204899): Refactor lookup methods to be defined in a single place. - public abstract DexEncodedMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context); + public abstract DexClassAndMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context); public final ProgramMethod lookupSingleProgramTarget(AppView<?> appView, ProgramMethod context) { - DexEncodedMethod singleTarget = lookupSingleTarget(appView, context); - return singleTarget != null ? singleTarget.asProgramMethod(appView) : null; + return DexClassAndMethod.asProgramMethodOrNull(lookupSingleTarget(appView, context)); } // TODO(b/140204899): Refactor lookup methods to be defined in a single place. @@ -202,16 +202,16 @@ @Override public AbstractFieldSet readSet(AppView<AppInfoWithLiveness> appView, ProgramMethod context) { - return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory()); + return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(appView, this); } @Override public AbstractValue getAbstractValue( AppView<AppInfoWithLiveness> appView, ProgramMethod context) { assert hasOutValue(); - DexEncodedMethod method = lookupSingleTarget(appView, context); + DexClassAndMethod method = lookupSingleTarget(appView, context); if (method != null) { - return method.getOptimizationInfo().getAbstractReturnValue(); + return method.getDefinition().getOptimizationInfo().getAbstractReturnValue(); } return UnknownValue.getInstance(); } @@ -227,9 +227,10 @@ @Override public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, ProgramMethod context) { - DexEncodedMethod singleTarget = lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = lookupSingleTarget(appView, context); if (singleTarget != null) { - BitSet nonNullParamOrThrow = singleTarget.getOptimizationInfo().getNonNullParamOrThrow(); + BitSet nonNullParamOrThrow = + singleTarget.getDefinition().getOptimizationInfo().getNonNullParamOrThrow(); if (nonNullParamOrThrow != null) { int argumentIndex = inValues.indexOf(value); return argumentIndex >= 0 && nonNullParamOrThrow.get(argumentIndex);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java index b006d6a..2aeb9b5 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -3,10 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; -import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull; - import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -26,7 +25,6 @@ import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.List; -import java.util.function.Predicate; public abstract class InvokeMethodWithReceiver extends InvokeMethod { @@ -73,7 +71,7 @@ } @Override - public final DexEncodedMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) { + public final DexClassAndMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) { TypeElement receiverUpperBoundType = null; ClassTypeElement receiverLowerBoundType = null; if (appView.enableWholeProgramOptimizations()) { @@ -84,7 +82,7 @@ return lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType); } - public abstract DexEncodedMethod lookupSingleTarget( + public abstract DexClassAndMethod lookupSingleTarget( AppView<?> appView, ProgramMethod context, TypeElement receiverUpperBoundType, @@ -95,9 +93,8 @@ ProgramMethod context, TypeElement receiverUpperBoundType, ClassTypeElement receiverLowerBoundType) { - return asProgramMethodOrNull( - lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType), - appView); + return DexClassAndMethod.asProgramMethodOrNull( + lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType)); } @Override @@ -197,9 +194,9 @@ } // Check if it is a call to one of library methods that are known to be side-effect free. - Predicate<InvokeMethod> noSideEffectsPredicate = - appView.dexItemFactory().libraryMethodsWithoutSideEffects.get(getInvokedMethod()); - if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(this)) { + if (appView + .getLibraryMethodSideEffectModelCollection() + .isCallToSideEffectFreeFinalMethod(this)) { return false; } @@ -237,18 +234,26 @@ } // Find the target and check if the invoke may have side effects. - DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context); - if (target == null) { + DexClassAndMethod singleTarget = lookupSingleTarget(appViewWithLiveness, context); + if (singleTarget == null) { return true; } - // Verify that the target method does not have side-effects. - if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) { + if (singleTarget.isLibraryMethod() + && appView + .getLibraryMethodSideEffectModelCollection() + .isSideEffectFree(this, singleTarget.asLibraryMethod())) { return false; } - MethodOptimizationInfo optimizationInfo = target.getOptimizationInfo(); - if (target.isInstanceInitializer()) { + // Verify that the target method does not have side-effects. + if (appViewWithLiveness.appInfo().noSideEffects.containsKey(singleTarget.getReference())) { + return false; + } + + DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition(); + MethodOptimizationInfo optimizationInfo = singleTargetDefinition.getOptimizationInfo(); + if (singleTargetDefinition.isInstanceInitializer()) { InstanceInitializerInfo initializerInfo = optimizationInfo.getInstanceInitializerInfo(); if (!initializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) { return !isInvokeDirect();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java index dbcafdc..425262b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -7,7 +7,7 @@ import com.android.tools.r8.code.InvokePolymorphicRange; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProto; @@ -125,7 +125,7 @@ } @Override - public DexEncodedMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) { + public DexClassAndMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) { // TODO(herhut): Implement lookup target for invokePolymorphic. return null; }
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 696d674..bf3a599 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
@@ -3,10 +3,13 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull; + import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.InvokeStaticRange; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -26,7 +29,6 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.Sets; import java.util.List; -import java.util.function.Predicate; public class InvokeStatic extends InvokeMethod { @@ -107,24 +109,27 @@ } @Override - public DexEncodedMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) { + public DexClassAndMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) { DexMethod invokedMethod = getInvokedMethod(); + DexEncodedMethod result; if (appView.appInfo().hasLiveness()) { AppInfoWithLiveness appInfo = appView.appInfo().withLiveness(); - DexEncodedMethod result = appInfo.lookupStaticTarget(invokedMethod, context); + result = appInfo.lookupStaticTarget(invokedMethod, context); assert verifyD8LookupResult( result, appView.appInfo().lookupStaticTargetOnItself(invokedMethod, context)); - return result; + } else { + // Allow optimizing static library invokes in D8. + DexClass clazz = appView.definitionForHolder(getInvokedMethod()); + if (clazz != null + && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) { + result = clazz.lookupMethod(getInvokedMethod()); + } else { + // In D8, we can treat invoke-static instructions as having a single target if the invoke is + // targeting a method in the enclosing class. + result = appView.appInfo().lookupStaticTargetOnItself(invokedMethod, context); + } } - // Allow optimizing static library invokes in D8. - DexClass clazz = appView.definitionForHolder(getInvokedMethod()); - if (clazz != null - && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) { - return clazz.lookupMethod(getInvokedMethod()); - } - // In D8, we can treat invoke-static instructions as having a single target if the invoke is - // targeting a method in the enclosing class. - return appView.appInfo().lookupStaticTargetOnItself(invokedMethod, context); + return asDexClassAndMethodOrNull(result, appView); } @Override @@ -173,9 +178,9 @@ } // Check if it is a call to one of library methods that are known to be side-effect free. - Predicate<InvokeMethod> noSideEffectsPredicate = - appView.dexItemFactory().libraryMethodsWithoutSideEffects.get(getInvokedMethod()); - if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(this)) { + if (appView + .getLibraryMethodSideEffectModelCollection() + .isCallToSideEffectFreeFinalMethod(this)) { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java index 34fa59c..8ce0b70 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -3,9 +3,12 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull; + import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.InvokeSuperRange; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -102,7 +105,7 @@ } @Override - public DexEncodedMethod lookupSingleTarget( + public DexClassAndMethod lookupSingleTarget( AppView<?> appView, ProgramMethod context, TypeElement receiverUpperBoundType, @@ -111,7 +114,8 @@ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); AppInfoWithLiveness appInfo = appViewWithLiveness.appInfo(); if (appInfo.isSubtype(context.getHolderType(), getInvokedMethod().holder)) { - return appInfo.lookupSuperTarget(getInvokedMethod(), context); + DexEncodedMethod result = appInfo.lookupSuperTarget(getInvokedMethod(), context); + return asDexClassAndMethodOrNull(result, appView); } } return null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java index e7fb163..d4f737e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -3,12 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull; import static com.android.tools.r8.ir.analysis.type.TypeAnalysis.toRefinedReceiverType; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.InvokeVirtualRange; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -96,7 +98,7 @@ } @Override - public DexEncodedMethod lookupSingleTarget( + public DexClassAndMethod lookupSingleTarget( AppView<?> appView, ProgramMethod context, TypeElement receiverUpperBoundType, @@ -105,38 +107,43 @@ appView, context, receiverUpperBoundType, receiverLowerBoundType, getInvokedMethod()); } - public static DexEncodedMethod lookupSingleTarget( + public static DexClassAndMethod lookupSingleTarget( AppView<?> appView, ProgramMethod context, TypeElement receiverUpperBoundType, ClassTypeElement receiverLowerBoundType, DexMethod method) { + DexEncodedMethod result = null; if (appView.appInfo().hasLiveness()) { AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); - return appViewWithLiveness - .appInfo() - .lookupSingleVirtualTarget( - method, - context, - false, - appView, - toRefinedReceiverType(receiverUpperBoundType, method, appViewWithLiveness), - receiverLowerBoundType); - } - // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is used - // for library modeling. - DexType holder = method.holder; - if (holder.isClassType()) { - DexClass clazz = appView.definitionFor(holder); - if (clazz != null - && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) { - DexEncodedMethod singleTargetCandidate = clazz.lookupMethod(method); - if (singleTargetCandidate != null && (clazz.isFinal() || singleTargetCandidate.isFinal())) { - return singleTargetCandidate; + result = + appViewWithLiveness + .appInfo() + .lookupSingleVirtualTarget( + method, + context, + false, + appView, + toRefinedReceiverType(receiverUpperBoundType, method, appViewWithLiveness), + receiverLowerBoundType); + } else { + // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is + // used + // for library modeling. + DexType holder = method.holder; + if (holder.isClassType()) { + DexClass clazz = appView.definitionFor(holder); + if (clazz != null + && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) { + DexEncodedMethod singleTargetCandidate = clazz.lookupMethod(method); + if (singleTargetCandidate != null + && (clazz.isFinal() || singleTargetCandidate.isFinal())) { + result = singleTargetCandidate; + } } } } - return null; + return asDexClassAndMethodOrNull(result, appView); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java index c337afe..9356caa 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -186,6 +186,10 @@ .containsKey(method.getHolderType())) { return false; } + if (!appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary + && appView.options().isDesugaredLibraryCompilation()) { + return false; + } return overridesLibraryMethod(method); } @@ -212,6 +216,9 @@ if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) { continue; } + if (!shouldGenerateCallbacksForEmulateInterfaceAPIs(dexClass)) { + continue; + } DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method.getReference()); if (dexEncodedMethod != null) { // In this case, the object will be wrapped. @@ -224,6 +231,16 @@ return foundOverrideToRewrite; } + private boolean shouldGenerateCallbacksForEmulateInterfaceAPIs(DexClass dexClass) { + if (appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary) { + return true; + } + Map<DexType, DexType> emulateLibraryInterfaces = + appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface(); + return !(emulateLibraryInterfaces.containsKey(dexClass.type) + || emulateLibraryInterfaces.containsValue(dexClass.type)); + } + private synchronized void registerCallback(ProgramMethod method) { // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both // methods will be desugared.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java index 542c5d7..9896987 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -36,12 +36,15 @@ public class DesugaredLibraryConfiguration { public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/"; + public static final boolean FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY = true; + public static final DesugaredLibraryConfiguration EMPTY_DESUGARED_LIBRARY_CONFIGURATION = new DesugaredLibraryConfiguration( AndroidApiLevel.B, false, FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX, null, + FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY, ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), @@ -51,11 +54,18 @@ ImmutableList.of(), ImmutableList.of()); - // TODO(b/158632510): should use DexString, DexType, DexMethod or so on when possible. private final AndroidApiLevel requiredCompilationAPILevel; private final boolean libraryCompilation; private final String synthesizedLibraryClassesPackagePrefix; private final String identifier; + // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs, + // more specifically: + // - no call-back is generated for emulated interface method overrides (forEach, etc.) + // - no call-back is generated inside the desugared library itself. + // Such setting decreases significantly the desugared library dex file, but virtual calls from + // within the library to desugared library classes instances as receiver may be incorrect, for + // example the method forEach in Iterable may be executed over a concrete implementation. + public final boolean supportAllCallbacksFromLibrary; private final Map<String, String> rewritePrefix; private final Map<DexType, DexType> emulateLibraryInterface; private final Map<DexString, Map<DexType, DexType>> retargetCoreLibMember; @@ -76,6 +86,7 @@ true, FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX, "testingOnlyVersion", + FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY, prefix, ImmutableMap.of(), ImmutableMap.of(), @@ -95,6 +106,7 @@ boolean libraryCompilation, String packagePrefix, String identifier, + boolean supportAllCallbacksFromLibrary, Map<String, String> rewritePrefix, Map<DexType, DexType> emulateLibraryInterface, Map<DexString, Map<DexType, DexType>> retargetCoreLibMember, @@ -107,6 +119,7 @@ this.libraryCompilation = libraryCompilation; this.synthesizedLibraryClassesPackagePrefix = packagePrefix; this.identifier = identifier; + this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary; this.rewritePrefix = rewritePrefix; this.emulateLibraryInterface = emulateLibraryInterface; this.retargetCoreLibMember = retargetCoreLibMember; @@ -204,6 +217,7 @@ private Set<DexType> wrapperConversions = Sets.newIdentityHashSet(); private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>(); private List<String> extraKeepRules = Collections.emptyList(); + private boolean supportAllCallbacksFromLibrary = FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY; private Builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) { this.factory = dexItemFactory; @@ -344,6 +358,10 @@ return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass)); } + public void setSupportAllCallbacksFromLibrary(boolean supportAllCallbacksFromLibrary) { + this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary; + } + public DesugaredLibraryConfiguration build() { validate(); return new DesugaredLibraryConfiguration( @@ -351,6 +369,7 @@ libraryCompilation, synthesizedLibraryClassesPackagePrefix, identifier, + supportAllCallbacksFromLibrary, ImmutableMap.copyOf(rewritePrefix), ImmutableMap.copyOf(emulateLibraryInterface), ImmutableMap.copyOf(retargetCoreLibMember), @@ -373,5 +392,6 @@ origin)); } } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java index 7586210..a20c8e0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -47,6 +47,7 @@ static final String DONT_REWRITE_KEY = "dont_rewrite"; static final String BACKPORT_KEY = "backport"; static final String SHRINKER_CONFIG_KEY = "shrinker_config"; + static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library"; private final DexItemFactory dexItemFactory; private final Reporter reporter; @@ -149,6 +150,12 @@ } configurationBuilder.setExtraKeepRules(extraKeepRules); } + + if (jsonConfig.has(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY)) { + boolean supportAllCallbacksFromLibrary = + jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean(); + configurationBuilder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary); + } configurationAmender.accept(configurationBuilder); DesugaredLibraryConfiguration config = configurationBuilder.build(); configurationBuilder = null;
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 4a3ee89..f9985d5 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
@@ -48,6 +48,7 @@ import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IterableUtils; +import com.android.tools.r8.utils.ObjectUtils; import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.collections.SortedProgramMethodSet; @@ -1004,18 +1005,19 @@ GenericSignature.ClassSignature classSignature = clazz.getClassSignature(); for (int i = 0; i < clazz.interfaces.size(); i++) { DexType itf = clazz.interfaces.values[i]; - assert emulatedInterfaces.containsKey(itf); - List<GenericSignature.FieldTypeSignature> typeArguments; - if (classSignature == null) { - typeArguments = Collections.emptyList(); - } else { - GenericSignature.ClassTypeSignature classTypeSignature = - classSignature.superInterfaceSignatures().get(i); - assert itf == classTypeSignature.type(); - typeArguments = classTypeSignature.typeArguments(); + if (emulatedInterfaces.containsKey(itf)) { + List<GenericSignature.FieldTypeSignature> typeArguments; + if (classSignature == null) { + typeArguments = Collections.emptyList(); + } else { + GenericSignature.ClassTypeSignature classTypeSignature = + classSignature.superInterfaceSignatures().get(i); + assert itf == classTypeSignature.type(); + typeArguments = classTypeSignature.typeArguments(); + } + newInterfaces.add( + new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments)); } - newInterfaces.add( - new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments)); } clazz.replaceInterfaces(newInterfaces); } @@ -1118,6 +1120,10 @@ || missing.isD8R8SynthesizedClassType() || isCompanionClassType(missing) || emulatedInterfaces.containsValue(missing) + || ObjectUtils.getBooleanOrElse( + options.getProguardConfiguration(), + configuration -> configuration.getDontWarnPatterns().matches(missing), + false) || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java index 9b8f930..c9e47c2 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -233,7 +233,7 @@ new DexEncodedMethod( mainMethod, MethodAccessFlags.fromSharedAccessFlags( - Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC, false), + Constants.ACC_PUBLIC | Constants.ACC_FINAL, false), MethodTypeSignature.noSignature(), DexAnnotationSet.empty(), ParameterAnnotationsList.empty(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java index 0008896..d6eae9a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -8,8 +8,8 @@ import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -225,13 +225,13 @@ private boolean computeAssumedValuesFromSingleTarget( IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) { - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); if (singleTarget == null) { return false; } boolean needsAssumeInstruction = false; - MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); + MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo(); // Case (2), invocations that are guaranteed to return a non-null value. Value outValue = invoke.outValue();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index 4c61a1c..61b13cb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -1253,9 +1254,14 @@ } // Check if the invoked method is known to return one of its arguments. - DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context()); - if (target != null && target.getOptimizationInfo().returnsArgument()) { - int argumentIndex = target.getOptimizationInfo().getReturnedArgument(); + DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context()); + if (target == null) { + continue; + } + + MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo(); + if (optimizationInfo.returnsArgument()) { + int argumentIndex = optimizationInfo.getReturnedArgument(); // Replace the out value of the invoke with the argument and ignore the out value. if (argumentIndex >= 0 && checkArgumentType(invoke, argumentIndex)) { Value argument = invoke.arguments().get(argumentIndex); @@ -2878,13 +2884,14 @@ } InvokeMethod invoke = instruction.asInvokeMethod(); - DexEncodedMethod singleTarget = + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView.withLiveness(), code.context()); if (singleTarget == null) { continue; } - MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); + MethodOptimizationInfo optimizationInfo = + singleTarget.getDefinition().getOptimizationInfo(); // If the invoke instruction is a null check, we can remove it. boolean isNullCheck = false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 36fba35..912a030 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -11,6 +11,9 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.SingleFieldValue; @@ -147,13 +150,19 @@ continue; } SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue(); + DexType fieldHolderType = singleFieldValue.getField().getHolderType(); if (context.getDefinition().isClassInitializer() - && context.getHolderType() == singleFieldValue.getField().holder) { + && context.getHolderType() == fieldHolderType) { // Avoid that canonicalization inserts a read before the unique write in the class // initializer, as that would change the program behavior. continue; } - if (current.instructionMayHaveSideEffects(appView, context)) { + DexClass fieldHolder = appView.definitionFor(fieldHolderType); + DexEncodedField field = singleFieldValue.getField().lookupOnClass(fieldHolder); + if (field == null + || !field.isEnum() + || current.instructionMayHaveSideEffects(appView, context)) { + // Only allow canonicalization of enums. continue; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index 4c7570c..d648bc1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -123,19 +123,18 @@ // Check if the instruction can be rewritten to invoke-super. This allows inlining of the // enclosing method into contexts outside the current class. if (appView.options().testing.enableInvokeSuperToInvokeVirtualRewriting) { - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); if (singleTarget != null) { - DexClass holder = appView.definitionForHolder(singleTarget, context); - assert holder != null; DexMethod invokedMethod = invoke.getInvokedMethod(); - DexEncodedMethod newSingleTarget = + DexClassAndMethod newSingleTarget = InvokeVirtual.lookupSingleTarget( appView, context, invoke.getReceiver().getDynamicUpperBoundType(appView), invoke.getReceiver().getDynamicLowerBoundType(appView), invokedMethod); - if (newSingleTarget == singleTarget) { + if (newSingleTarget != null + && newSingleTarget.getReference() == singleTarget.getReference()) { it.replaceCurrentInstruction( new InvokeVirtual(invokedMethod, invoke.outValue(), invoke.arguments())); continue; @@ -173,12 +172,11 @@ continue; } InvokeInterface invoke = current.asInvokeInterface(); - DexEncodedMethod target = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod target = invoke.lookupSingleTarget(appView, context); if (target == null) { continue; } - DexType holderType = target.holder(); - DexClass holderClass = appView.definitionFor(holderType); + DexClass holderClass = target.getHolder(); // Make sure we are not landing on another interface, e.g., interface's default method. if (holderClass == null || holderClass.isInterface()) { continue; @@ -190,7 +188,7 @@ } InvokeVirtual devirtualizedInvoke = - new InvokeVirtual(target.method, invoke.outValue(), invoke.inValues()); + new InvokeVirtual(target.getReference(), invoke.outValue(), invoke.inValues()); it.replaceCurrentInstruction(devirtualizedInvoke); devirtualizedCall.put(invoke, devirtualizedInvoke); @@ -204,11 +202,12 @@ // CodeRewriter#removeTrivialCheckCastAndInstanceOfInstructions}. // a <- check-cast A i // Otherwise ART verification error. // (out <-) invoke-virtual a, ... A#foo - if (holderType != invoke.getInvokedMethod().holder) { + if (holderClass.getType() != invoke.getInvokedMethod().holder) { Value receiver = invoke.getReceiver(); TypeElement receiverTypeLattice = receiver.getType(); TypeElement castTypeLattice = - TypeElement.fromDexType(holderType, receiverTypeLattice.nullability(), appView); + TypeElement.fromDexType( + holderClass.getType(), receiverTypeLattice.nullability(), appView); // Avoid adding trivial cast and up-cast. // We should not use strictlyLessThan(castType, receiverType), which detects downcast, // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B. @@ -224,8 +223,8 @@ // a2 <- check-cast A i // We should be able to reuse a1 here! // invoke-virtual a2, ... A#m2 (from I#m2) if (castedReceiverCache.containsKey(receiver) - && castedReceiverCache.get(receiver).containsKey(holderType)) { - Value cachedReceiver = castedReceiverCache.get(receiver).get(holderType); + && castedReceiverCache.get(receiver).containsKey(holderClass.getType())) { + Value cachedReceiver = castedReceiverCache.get(receiver).get(holderClass.getType()); if (dominatorTree.dominatedBy(block, cachedReceiver.definition.getBlock())) { newReceiver = cachedReceiver; } @@ -237,9 +236,9 @@ // Cache the new receiver with a narrower type to avoid redundant checkcast. if (!receiver.hasLocalInfo()) { castedReceiverCache.putIfAbsent(receiver, new IdentityHashMap<>()); - castedReceiverCache.get(receiver).put(holderType, newReceiver); + castedReceiverCache.get(receiver).put(holderClass.getType(), newReceiver); } - CheckCast checkCast = new CheckCast(newReceiver, receiver, holderType); + CheckCast checkCast = new CheckCast(newReceiver, receiver, holderClass.getType()); checkCast.setPosition(invoke.getPosition()); newCheckCastInstructions.add(checkCast);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java index 10d17e7..50443c6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.ir.optimize; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; @@ -17,6 +17,7 @@ import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.logging.Log; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.StringUtils; @@ -29,7 +30,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.function.Predicate; /** * Canonicalize idempotent function calls. @@ -154,10 +154,14 @@ // return the resolution result such that the call site can perform the accessibility // check, or (iii) always perform the accessibility check such that it can be skipped // at the call site. - DexEncodedMethod target = invoke.lookupSingleTarget(appViewWithLiveness, context); - if (target == null - || target.getOptimizationInfo().mayHaveSideEffects() - || !target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) { + DexClassAndMethod target = invoke.lookupSingleTarget(appViewWithLiveness, context); + if (target == null) { + continue; + } + + MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo(); + if (optimizationInfo.mayHaveSideEffects() + || !optimizationInfo.returnValueOnlyDependsOnArguments()) { continue; } @@ -272,12 +276,10 @@ private boolean isIdempotentLibraryMethodInvoke(InvokeMethod invoke) { DexMethod invokedMethod = invoke.getInvokedMethod(); - Predicate<InvokeMethod> noSideEffectPredicate = - factory.libraryMethodsWithoutSideEffects.get(invokedMethod); - if (noSideEffectPredicate == null || !noSideEffectPredicate.test(invoke)) { - return false; - } - return factory.libraryMethodsWithReturnValueDependingOnlyOnArguments.contains(invokedMethod); + return appView + .getLibraryMethodSideEffectModelCollection() + .isCallToSideEffectFreeFinalMethod(invoke) + && factory.libraryMethodsWithReturnValueDependingOnlyOnArguments.contains(invokedMethod); } private static void insertCanonicalizedInvokeWithInValues(
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 96fd58c..621f3b9 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
@@ -8,6 +8,7 @@ import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexDefinition; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -103,12 +104,16 @@ || appView.appInfo().noSideEffects.containsKey(field.field); } - private boolean mayPropagateValueFor(DexEncodedMethod method) { - if (method.isProgramMethod(appView)) { - return appView.appInfo().mayPropagateValueFor(method.method); + private boolean mayPropagateValueFor(DexClassAndMethod method) { + if (method.isProgramMethod()) { + return appView.appInfo().mayPropagateValueFor(method.getReference()); } - return appView.appInfo().assumedValues.containsKey(method.method) - || appView.appInfo().noSideEffects.containsKey(method.method); + return appView.appInfo().assumedValues.containsKey(method.getReference()) + || appView.appInfo().noSideEffects.containsKey(method.getReference()); + } + + private ProguardMemberRuleLookup lookupMemberRule(DexClassAndMethod method) { + return method != null ? lookupMemberRule(method.getDefinition()) : null; } private ProguardMemberRuleLookup lookupMemberRule(DexDefinition definition) { @@ -245,7 +250,7 @@ return; } - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget); if (lookup == null) { // -assumenosideeffects rules are applied to upward visible and overriding methods, but only @@ -264,7 +269,7 @@ if (singleTarget != null && lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS && !lookup.rule.hasReturnValue()) { - ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.toReference()); + ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.getReference()); if (rule != null) { lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule); } @@ -280,7 +285,8 @@ return; } - AbstractValue abstractReturnValue = singleTarget.getOptimizationInfo().getAbstractReturnValue(); + AbstractValue abstractReturnValue = + singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue(); if (abstractReturnValue.isSingleValue()) { SingleValue singleReturnValue = abstractReturnValue.asSingleValue(); @@ -299,7 +305,7 @@ replaceInstructionByNullCheckIfPossible(invoke, iterator, context); } else if (invoke.isInvokeStatic()) { replaceInstructionByInitClassIfPossible( - invoke, singleTarget.holder(), code, iterator, context); + invoke, singleTarget.getHolderType(), code, iterator, context); } // Insert the definition of the replacement. @@ -309,7 +315,7 @@ } else { iterator.add(replacement); } - singleTarget.getMutableOptimizationInfo().markAsPropagated(); + singleTarget.getDefinition().getMutableOptimizationInfo().markAsPropagated(); } } }
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 b6bd724..b2ab54f 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
@@ -8,10 +8,11 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndField; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; +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.ir.analysis.type.TypeAnalysis; @@ -148,19 +149,24 @@ } } - public boolean isFinal(DexEncodedField field) { - if (field.isProgramField(appView)) { - return field.isFinal(); + public boolean isFinal(DexClassAndField field) { + if (field.isProgramField()) { + // Treat this field as being final if it is declared final or we have determined a constant + // value for it. + return field.getDefinition().isFinal() + || field.getDefinition().getOptimizationInfo().getAbstractValue().isSingleValue(); } - return appView.libraryMethodOptimizer().isFinalLibraryField(field); + return appView.libraryMethodOptimizer().isFinalLibraryField(field.getDefinition()); } - private DexEncodedField resolveField(DexField field) { + private DexClassAndField resolveField(DexField field) { if (appView.enableWholeProgramOptimizations()) { - return appView.appInfo().withLiveness().resolveField(field).getResolvedField(); + SuccessfulFieldResolutionResult resolutionResult = + appView.appInfo().withLiveness().resolveField(field).asSuccessfulResolution(); + return resolutionResult != null ? resolutionResult.getResolutionPair() : null; } - if (field.holder == method.getHolderType()) { - return method.getHolder().lookupField(field); + if (field.getHolderType() == method.getHolderType()) { + return method.getHolder().lookupProgramField(field); } return null; } @@ -187,9 +193,9 @@ while (it.hasNext()) { Instruction instruction = it.next(); if (instruction.isFieldInstruction()) { - DexField field = instruction.asFieldInstruction().getField(); - DexEncodedField definition = resolveField(field); - if (definition == null || definition.isVolatile()) { + DexField reference = instruction.asFieldInstruction().getField(); + DexClassAndField field = resolveField(reference); + if (field == null || field.getDefinition().isVolatile()) { killAllNonFinalActiveFields(); continue; } @@ -200,7 +206,7 @@ continue; } Value object = instanceGet.object().getAliasedValue(); - FieldAndObject fieldAndObject = new FieldAndObject(field, object); + FieldAndObject fieldAndObject = new FieldAndObject(reference, object); FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject); if (replacement != null) { replacement.eliminateRedundantRead(it, instanceGet); @@ -215,10 +221,11 @@ killNonFinalActiveFields(instancePut); // ... but at least we know the field value for this particular object. Value object = instancePut.object().getAliasedValue(); - FieldAndObject fieldAndObject = new FieldAndObject(field, object); + FieldAndObject fieldAndObject = new FieldAndObject(reference, object); ExistingValue value = new ExistingValue(instancePut.value()); - if (isFinal(definition)) { - assert method.getDefinition().isInstanceInitializer() + if (isFinal(field)) { + assert !field.getDefinition().isFinal() + || method.getDefinition().isInstanceInitializer() || verifyWasInstanceInitializer(); activeState.putFinalInstanceField(fieldAndObject, value); } else { @@ -229,7 +236,7 @@ if (staticGet.outValue().hasLocalInfo()) { continue; } - FieldValue replacement = activeState.getStaticFieldValue(field); + FieldValue replacement = activeState.getStaticFieldValue(reference); if (replacement != null) { replacement.eliminateRedundantRead(it, staticGet); } else { @@ -237,10 +244,10 @@ // field values. killNonFinalActiveFields(staticGet); FieldValue value = new ExistingValue(staticGet.value()); - if (isFinal(definition)) { - activeState.putFinalStaticField(field, value); + if (isFinal(field)) { + activeState.putFinalStaticField(reference, value); } else { - activeState.putNonFinalStaticField(field, value); + activeState.putNonFinalStaticField(reference, value); } } } else if (instruction.isStaticPut()) { @@ -249,11 +256,12 @@ // field values. killNonFinalActiveFields(staticPut); ExistingValue value = new ExistingValue(staticPut.value()); - if (definition.isFinal()) { - assert method.getDefinition().isClassInitializer(); - activeState.putFinalStaticField(field, value); + if (isFinal(field)) { + assert !field.getDefinition().isFinal() + || method.getDefinition().isClassInitializer(); + activeState.putFinalStaticField(reference, value); } else { - activeState.putNonFinalStaticField(field, value); + activeState.putNonFinalStaticField(reference, value); } } } else if (instruction.isInitClass()) { @@ -352,14 +360,14 @@ return; } - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method); - if (singleTarget == null || !singleTarget.isInstanceInitializer()) { + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method); + if (singleTarget == null || !singleTarget.getDefinition().isInstanceInitializer()) { killAllNonFinalActiveFields(); return; } InstanceInitializerInfo instanceInitializerInfo = - singleTarget.getOptimizationInfo().getInstanceInitializerInfo(); + singleTarget.getDefinition().getOptimizationInfo().getInstanceInitializerInfo(); if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) { killAllNonFinalActiveFields(); }
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 c8cc00e..6b0b61d 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
@@ -6,18 +6,19 @@ import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull; import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; -import static com.google.common.base.Predicates.alwaysFalse; import com.android.tools.r8.errors.InternalCompilerError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AccessControl; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; 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.DexMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LibraryMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; @@ -296,21 +297,22 @@ } // TODO(b/156853206): Avoid duplicating resolution. - DexEncodedMethod singleTargetMethod = invokeMethod.lookupSingleTarget(appView, method); - if (singleTargetMethod == null) { + DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method); + if (singleTarget == null) { return user; // Not eligible. } - if (isEligibleLibraryMethodCall(invokeMethod, singleTargetMethod)) { + if (singleTarget.isLibraryMethod() + && isEligibleLibraryMethodCall(invokeMethod, singleTarget.asLibraryMethod())) { continue; } - ProgramMethod singleTarget = singleTargetMethod.asProgramMethod(appView); - if (!isEligibleSingleTarget(singleTarget)) { + ProgramMethod singleProgramTarget = singleTarget.asProgramMethod(); + if (!isEligibleSingleTarget(singleProgramTarget)) { return user; // Not eligible. } - if (AccessControl.isClassAccessible(singleTarget.getHolder(), method, appView) + if (AccessControl.isClassAccessible(singleProgramTarget.getHolder(), method, appView) .isPossiblyFalse()) { return user; // Not eligible. } @@ -324,7 +326,7 @@ && !invoke.inValues().isEmpty() && root.outValue() == invoke.getReceiver(); if (isCorrespondingConstructorCall) { - InliningInfo inliningInfo = isEligibleConstructorCall(invoke, singleTarget); + InliningInfo inliningInfo = isEligibleConstructorCall(invoke, singleProgramTarget); if (inliningInfo != null) { methodCallsOnInstance.put(invoke, inliningInfo); continue; @@ -340,7 +342,7 @@ InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver(); InliningInfo inliningInfo = isEligibleDirectVirtualMethodCall( - invoke, resolutionResult, singleTarget, indirectUsers, defaultOracle); + invoke, resolutionResult, singleProgramTarget, indirectUsers, defaultOracle); if (inliningInfo != null) { methodCallsOnInstance.put(invoke, inliningInfo); continue; @@ -351,7 +353,7 @@ if (isExtraMethodCall(invokeMethod)) { assert !invokeMethod.isInvokeSuper(); assert !invokeMethod.isInvokePolymorphic(); - if (isExtraMethodCallEligible(invokeMethod, singleTarget, defaultOracle)) { + if (isExtraMethodCallEligible(invokeMethod, singleProgramTarget, defaultOracle)) { continue; } } @@ -646,12 +648,13 @@ continue; } - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method); - if (singleTarget != null) { - Predicate<InvokeMethod> noSideEffectsPredicate = - dexItemFactory.libraryMethodsWithoutSideEffects.getOrDefault( - singleTarget.method, alwaysFalse()); - if (noSideEffectsPredicate.test(invoke)) { + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method); + if (singleTarget != null && singleTarget.isLibraryMethod()) { + boolean isSideEffectFree = + appView + .getLibraryMethodSideEffectModelCollection() + .isSideEffectFree(invoke, singleTarget.asLibraryMethod()); + if (isSideEffectFree) { if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) { removeInstruction(invoke); continue; @@ -1169,13 +1172,13 @@ return true; } - private boolean isEligibleLibraryMethodCall(InvokeMethod invoke, DexEncodedMethod singleTarget) { - Predicate<InvokeMethod> noSideEffectsPredicate = - dexItemFactory.libraryMethodsWithoutSideEffects.get(singleTarget.method); - if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(invoke)) { + private boolean isEligibleLibraryMethodCall(InvokeMethod invoke, LibraryMethod singleTarget) { + boolean isSideEffectFree = + appView.getLibraryMethodSideEffectModelCollection().isSideEffectFree(invoke, singleTarget); + if (isSideEffectFree) { return !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers(); } - if (singleTarget.method == dexItemFactory.objectsMethods.requireNonNull) { + if (singleTarget.getReference() == dexItemFactory.objectsMethods.requireNonNull) { return !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers(); } return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java index c766569..334e4fa 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexCallSite; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexEncodedMethod; @@ -211,7 +212,7 @@ DexMethod invokedMethod = invokeStatic.getInvokedMethod(); DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder); if (enumClass != null) { - DexEncodedMethod method = invokeStatic.lookupSingleTarget(appView, context); + DexClassAndMethod method = invokeStatic.lookupSingleTarget(appView, context); if (method != null) { eligibleEnums.add(enumClass.type); } else { @@ -726,19 +727,13 @@ } return Reason.INVALID_INVOKE_ON_ARRAY; } - DexEncodedMethod encodedSingleTarget = - invokeMethod.lookupSingleTarget(appView, code.context()); - if (encodedSingleTarget == null) { + DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, code.context()); + if (singleTarget == null) { return Reason.INVALID_INVOKE; } - DexMethod singleTarget = encodedSingleTarget.method; - DexClass dexClass = appView.definitionFor(singleTarget.holder, code.context()); - if (dexClass == null) { - assert false; - return Reason.INVALID_INVOKE; - } + DexClass dexClass = singleTarget.getHolder(); if (dexClass.isProgramClass()) { - if (dexClass.isEnum() && encodedSingleTarget.isInstanceInitializer()) { + if (dexClass.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) { if (code.method().holder() == dexClass.type && code.method().isClassInitializer()) { // The enum instance initializer is allowed to be called only from the enum clinit. return Reason.ELIGIBLE; @@ -748,10 +743,10 @@ } // Check that the enum-value only flows into parameters whose type exactly matches the // enum's type. - int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic()); - for (int i = 0; i < singleTarget.proto.parameters.size(); i++) { + int offset = BooleanUtils.intValue(!singleTarget.getDefinition().isStatic()); + for (int i = 0; i < singleTarget.getReference().getParameters().size(); i++) { if (invokeMethod.getArgument(offset + i) == enumValue) { - if (singleTarget.proto.parameters.values[i].toBaseType(factory) != enumClass.type) { + if (singleTarget.getReference().getParameter(i).toBaseType(factory) != enumClass.type) { return Reason.GENERIC_INVOKE; } } @@ -768,43 +763,44 @@ return Reason.INVALID_INVOKE; } assert dexClass.isLibraryClass(); + DexMethod singleTargetReference = singleTarget.getReference(); if (dexClass.type != factory.enumType) { // System.identityHashCode(Object) is supported for proto enums. // Object#getClass without outValue and Objects.requireNonNull are supported since R8 // rewrites explicit null checks to such instructions. - if (singleTarget == factory.javaLangSystemMethods.identityHashCode) { + if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) { return Reason.ELIGIBLE; } - if (singleTarget == factory.stringMembers.valueOf) { + if (singleTargetReference == factory.stringMembers.valueOf) { addRequiredNameData(enumClass.type); return Reason.ELIGIBLE; } - if (singleTarget == factory.objectMembers.getClass + if (singleTargetReference == factory.objectMembers.getClass && (!invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers())) { // This is a hidden null check. return Reason.ELIGIBLE; } - if (singleTarget == factory.objectsMethods.requireNonNull - || singleTarget == factory.objectsMethods.requireNonNullWithMessage) { + if (singleTargetReference == factory.objectsMethods.requireNonNull + || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) { return Reason.ELIGIBLE; } return Reason.UNSUPPORTED_LIBRARY_CALL; } // TODO(b/147860220): EnumSet and EnumMap may be interesting to model. - if (singleTarget == factory.enumMembers.compareTo) { + if (singleTargetReference == factory.enumMembers.compareTo) { return Reason.ELIGIBLE; - } else if (singleTarget == factory.enumMembers.equals) { + } else if (singleTargetReference == factory.enumMembers.equals) { return Reason.ELIGIBLE; - } else if (singleTarget == factory.enumMembers.nameMethod - || singleTarget == factory.enumMembers.toString) { + } else if (singleTargetReference == factory.enumMembers.nameMethod + || singleTargetReference == factory.enumMembers.toString) { assert invokeMethod.asInvokeMethodWithReceiver().getReceiver() == enumValue; addRequiredNameData(enumClass.type); return Reason.ELIGIBLE; - } else if (singleTarget == factory.enumMembers.ordinalMethod) { + } else if (singleTargetReference == factory.enumMembers.ordinalMethod) { return Reason.ELIGIBLE; - } else if (singleTarget == factory.enumMembers.hashCode) { + } else if (singleTargetReference == factory.enumMembers.hashCode) { return Reason.ELIGIBLE; - } else if (singleTarget == factory.enumMembers.constructor) { + } else if (singleTargetReference == factory.enumMembers.constructor) { // Enum constructor call is allowed only if called from an enum initializer. if (code.method().isInstanceInitializer() && code.method().holder() == enumClass.type) { return Reason.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java index 9125b1c..cb6a6fa 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -45,6 +45,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; @@ -260,11 +261,11 @@ case INVOKE_STATIC: { InvokeStatic invoke = insn.asInvokeStatic(); - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); if (singleTarget == null) { return; // Not allowed. } - if (singleTarget.method == dexItemFactory.objectsMethods.requireNonNull) { + if (singleTarget.getReference() == dexItemFactory.objectsMethods.requireNonNull) { if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) { continue; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java index 92635f2..1da512d 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstClass; @@ -65,6 +66,8 @@ boolean isValidInitClass(CodeProcessor context, DexType clazz); + boolean isValidHolder(CodeProcessor context, DexType holder); + void patch(ApplyStrategy context, NewInstance newInstance); void patch(ApplyStrategy context, InvokeMethod invoke); @@ -74,6 +77,8 @@ void patch(ApplyStrategy context, StaticGet staticGet); void patch(ApplyStrategy context, InitClass initClass); + + void patch(ApplyStrategy context, Argument argument); } // No-op strategy. @@ -120,6 +125,11 @@ } @Override + public boolean isValidHolder(CodeProcessor context, DexType holder) { + return false; + } + + @Override public void patch(ApplyStrategy context, NewInstance newInstance) { throw new Unreachable(); } @@ -143,6 +153,11 @@ public void patch(ApplyStrategy context, InitClass initClass) { throw new Unreachable(); } + + @Override + public void patch(ApplyStrategy context, Argument argument) { + throw new Unreachable(); + } }; public final AppView<AppInfoWithLiveness> appView; @@ -383,6 +398,20 @@ return null; } + @Override + public Void visit(Argument instruction) { + if (instruction.outValue() != code.getThis()) { + return null; + } + Strategy strategy = strategyProvider.apply(method.getHolderType()); + if (strategy.isValidHolder(this, method.getHolderType())) { + if (shouldRewrite(method.getHolderType())) { + process(strategy, instruction); + } + } + return null; + } + abstract void process(Strategy strategy, InvokeMethod invokeMethod); abstract void process(Strategy strategy, NewInstance newInstance); @@ -396,4 +425,6 @@ abstract void process(Strategy strategy, StaticGet staticGet); abstract void process(Strategy strategy, InitClass initClass); + + abstract void process(Strategy strategy, Argument argument); }
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 9ccbbbc..8aacbde 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
@@ -22,6 +22,7 @@ import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses; import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater; +import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InitClass; import com.android.tools.r8.ir.code.InstanceGet; @@ -542,6 +543,11 @@ void process(Strategy strategy, InitClass initClass) { queueForProcessing(method); } + + @Override + void process(Strategy strategy, Argument argument) { + throw new Unreachable(); + } } public final class ApplyStrategy extends CodeProcessor { @@ -661,6 +667,11 @@ void process(Strategy strategy, InitClass initClass) { strategy.patch(this, initClass); } + + @Override + void process(Strategy strategy, Argument argument) { + strategy.patch(this, argument); + } } private final class LambdaMergerOptimizationInfoFixer
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java index 112b37c..2c88700 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.InitClass; @@ -120,6 +121,12 @@ } @Override + public boolean isValidHolder(CodeProcessor context, DexType holder) { + assert group.containsLambda(holder); + return true; + } + + @Override public void patch(ApplyStrategy context, NewInstance newInstance) { DexType oldType = newInstance.clazz; DexType newType = group.getGroupClassType(); @@ -217,6 +224,17 @@ context.instructions().replaceCurrentInstruction(pachedInitClass); } + @Override + public void patch(ApplyStrategy context, Argument argument) { + // An argument can be a direct operand to a phi that we potentially could not remove. + assert argument.getIndex() == 0; + // The argument value will be replaced by the invoke value. + argument + .outValue() + .setType(TypeElement.fromDexType(group.getGroupClassType(), maybeNull(), context.appView)); + context.recordTypeHasChanged(argument.outValue()); + } + private void patchInitializer(CodeProcessor context, InvokeDirect invoke) { // Patching includes: // - change of methods
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java index dcb128a..69bdef8 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; @@ -39,13 +39,13 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { - if (singleTarget.method == dexItemFactory.booleanMembers.booleanValue) { + if (singleTarget.getReference() == dexItemFactory.booleanMembers.booleanValue) { optimizeBooleanValue(code, instructionIterator, invoke); - } else if (singleTarget.method == dexItemFactory.booleanMembers.parseBoolean) { + } else if (singleTarget.getReference() == dexItemFactory.booleanMembers.parseBoolean) { optimizeParseBoolean(code, instructionIterator, invoke); - } else if (singleTarget.method == dexItemFactory.booleanMembers.valueOf) { + } else if (singleTarget.getReference() == dexItemFactory.booleanMembers.valueOf) { optimizeValueOf(code, instructionIterator, invoke, affectedValues); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java index 658fcb5..4323220 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -7,7 +7,7 @@ import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -36,9 +36,9 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { - if (singleTarget.method == appView.dexItemFactory().enumMembers.valueOf + if (singleTarget.getReference() == appView.dexItemFactory().enumMembers.valueOf && invoke.inValues().get(0).isConstClass()) { insertAssumeDynamicType(code, instructionIterator, invoke); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java index bcea380..d6c8ada 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -5,8 +5,8 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory.LibraryMembers; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; @@ -118,7 +118,7 @@ Instruction instruction = instructionIterator.next(); if (instruction.isInvokeMethod()) { InvokeMethod invoke = instruction.asInvokeMethod(); - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); if (singleTarget != null) { optimizeInvoke(code, instructionIterator, invoke, singleTarget, affectedValues); } @@ -133,11 +133,11 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { LibraryMethodModelCollection optimizer = libraryMethodModelCollections.getOrDefault( - singleTarget.holder(), NopLibraryMethodModelCollection.getInstance()); + singleTarget.getHolderType(), NopLibraryMethodModelCollection.getInstance()); optimizer.optimize(code, instructionIterator, invoke, singleTarget, affectedValues); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java index 5608b94..843e9ab 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.ir.optimize.library; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -29,6 +29,6 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java new file mode 100644 index 0000000..55d1dc3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -0,0 +1,82 @@ +// 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.ir.optimize.library; + +import static com.google.common.base.Predicates.alwaysFalse; +import static com.google.common.base.Predicates.alwaysTrue; + +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.LibraryMethod; +import com.android.tools.r8.ir.code.InvokeMethod; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +public class LibraryMethodSideEffectModelCollection { + + private final Map<DexMethod, Predicate<InvokeMethod>> finalMethodsWithoutSideEffects; + private final Set<DexMethod> nonFinalMethodsWithoutSideEffects; + + public LibraryMethodSideEffectModelCollection(DexItemFactory dexItemFactory) { + finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(dexItemFactory); + nonFinalMethodsWithoutSideEffects = buildNonFinalMethodsWithoutSideEffects(dexItemFactory); + } + + private static Map<DexMethod, Predicate<InvokeMethod>> buildFinalMethodsWithoutSideEffects( + DexItemFactory dexItemFactory) { + ImmutableMap.Builder<DexMethod, Predicate<InvokeMethod>> builder = + ImmutableMap.<DexMethod, Predicate<InvokeMethod>>builder() + .put(dexItemFactory.enumMembers.constructor, alwaysTrue()) + .put(dexItemFactory.npeMethods.init, alwaysTrue()) + .put(dexItemFactory.npeMethods.initWithMessage, alwaysTrue()) + .put(dexItemFactory.objectMembers.constructor, alwaysTrue()) + .put(dexItemFactory.objectMembers.getClass, alwaysTrue()) + .put(dexItemFactory.stringMembers.hashCode, alwaysTrue()); + putAll(builder, dexItemFactory.classMethods.getNames, alwaysTrue()); + putAll( + builder, + dexItemFactory.stringBufferMethods.constructorMethods, + dexItemFactory.stringBufferMethods::constructorInvokeIsSideEffectFree); + putAll( + builder, + dexItemFactory.stringBuilderMethods.constructorMethods, + dexItemFactory.stringBuilderMethods::constructorInvokeIsSideEffectFree); + putAll(builder, dexItemFactory.boxedValueOfMethods(), alwaysTrue()); + return builder.build(); + } + + private static Set<DexMethod> buildNonFinalMethodsWithoutSideEffects( + DexItemFactory dexItemFactory) { + return ImmutableSet.of( + dexItemFactory.objectMembers.equals, + dexItemFactory.objectMembers.hashCode, + dexItemFactory.objectMembers.toString); + } + + private static void putAll( + ImmutableMap.Builder<DexMethod, Predicate<InvokeMethod>> builder, + Iterable<DexMethod> methods, + Predicate<InvokeMethod> predicate) { + for (DexMethod method : methods) { + builder.put(method, predicate); + } + } + + public boolean isCallToSideEffectFreeFinalMethod(InvokeMethod invoke) { + return finalMethodsWithoutSideEffects + .getOrDefault(invoke.getInvokedMethod(), alwaysFalse()) + .test(invoke); + } + + // This intentionally takes the invoke instruction since the determination of whether a library + // method has side effects may depend on the arguments. + public boolean isSideEffectFree(InvokeMethod invoke, LibraryMethod singleTarget) { + return isCallToSideEffectFreeFinalMethod(invoke) + || nonFinalMethodsWithoutSideEffects.contains(singleTarget.getReference()); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java index 35220c7..f8847d3 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -104,11 +104,11 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { int maxRemovedAndroidLogLevel = appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel(); - if (singleTarget.method == isLoggableMethod) { + if (singleTarget.getReference() == isLoggableMethod) { Value logLevelValue = invoke.arguments().get(1).getAliasedValue(); if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) { Instruction definition = logLevelValue.definition; @@ -118,27 +118,27 @@ code, instructionIterator, invoke, maxRemovedAndroidLogLevel >= logLevel ? 0 : 1); } } - } else if (singleTarget.method == vMethod) { + } else if (singleTarget.getReference() == vMethod) { if (maxRemovedAndroidLogLevel >= VERBOSE) { replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0); } - } else if (singleTarget.method == dMethod) { + } else if (singleTarget.getReference() == dMethod) { if (maxRemovedAndroidLogLevel >= DEBUG) { replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0); } - } else if (singleTarget.method == iMethod) { + } else if (singleTarget.getReference() == iMethod) { if (maxRemovedAndroidLogLevel >= INFO) { replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0); } - } else if (singleTarget.method == wMethod) { + } else if (singleTarget.getReference() == wMethod) { if (maxRemovedAndroidLogLevel >= WARN) { replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0); } - } else if (singleTarget.method == eMethod) { + } else if (singleTarget.getReference() == eMethod) { if (maxRemovedAndroidLogLevel >= ERROR) { replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0); } - } else if (singleTarget.method == wtfMethod) { + } else if (singleTarget.getReference() == wtfMethod) { if (maxRemovedAndroidLogLevel >= ASSERT) { replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java index 9a0980f..f852393 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -34,6 +34,6 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) {} }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java index 1674baf..f16af45 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.IRCode; @@ -32,9 +32,9 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { - if (singleTarget.method == dexItemFactory.objectMembers.getClass) { + if (singleTarget.getReference() == dexItemFactory.objectMembers.getClass) { optimizeGetClass(instructionIterator, invoke); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java index c78a123..8fcc1ac 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.IRCode; @@ -32,9 +32,9 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { - if (dexItemFactory.objectsMethods.isRequireNonNullMethod(singleTarget.method)) { + if (dexItemFactory.objectsMethods.isRequireNonNullMethod(singleTarget.getReference())) { optimizeRequireNonNull(instructionIterator, invoke, affectedValues); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java index 64dce88..18ad3e6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.ir.optimize.library; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexType; @@ -38,9 +38,9 @@ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, - DexEncodedMethod singleTarget, + DexClassAndMethod singleTarget, Set<Value> affectedValues) { - if (singleTarget.method == dexItemFactory.stringMembers.equals) { + if (singleTarget.getReference() == dexItemFactory.stringMembers.equals) { optimizeEquals(code, instructionIterator, invoke); } } @@ -74,9 +74,10 @@ return false; } - DexEncodedMethod singleTarget = + DexClassAndMethod singleTarget = classNameDefinition.asInvokeVirtual().lookupSingleTarget(appView, context); - if (singleTarget == null || singleTarget.method != dexItemFactory.classMethods.getName) { + if (singleTarget == null + || singleTarget.getReference() != dexItemFactory.classMethods.getName) { return false; }
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 7e66078..9b12469 100644 --- a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java +++ b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
@@ -60,6 +60,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return getPrevious().getOriginalTypes(type); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return getPrevious().getOriginalFieldSignature(field); }
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 3f625f2..02b56e3 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -84,6 +84,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return getPrevious().getOriginalTypes(type); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return getPrevious().getOriginalFieldSignature(field); }
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 9e23dd0..867ec0b 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -52,6 +52,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return getPrevious().getOriginalTypes(type); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return getPrevious().getOriginalFieldSignature(field); }
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java index 9fe3e29..6a6c3bb 100644 --- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java +++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
@@ -49,6 +49,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + return getPrevious().getOriginalTypes(type); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { return getPrevious().getOriginalFieldSignature(field); }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java index 9b838df..bd537a6 100644 --- a/src/main/java/com/android/tools/r8/retrace/Retrace.java +++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -18,7 +18,10 @@ import com.android.tools.r8.retrace.internal.RetraceRegularExpression; import com.android.tools.r8.retrace.internal.RetracerImpl; import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl; +import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl; import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy; +import com.android.tools.r8.retrace.internal.StackTraceVisitor; +import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.OptionsParsing; import com.android.tools.r8.utils.OptionsParsing.ParseContext; @@ -35,7 +38,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Scanner; /** @@ -169,38 +174,53 @@ timing.end(); RetraceCommandLineResult result; timing.begin("Parse and Retrace"); - if (command.regularExpression != null) { - result = - new RetraceRegularExpression( - retracer, - command.stackTrace, - command.diagnosticsHandler, - command.regularExpression, - command.isVerbose) - .retrace(); - } else { - PlainStackTraceVisitor plainStackTraceVisitor = - new PlainStackTraceVisitor(command.stackTrace, command.diagnosticsHandler); + StackTraceVisitor<StackTraceElementStringProxy> stackTraceVisitor = + command.regularExpression != null + ? new RetraceRegularExpression( + retracer, command.stackTrace, command.regularExpression) + : new PlainStackTraceVisitor(command.stackTrace, command.diagnosticsHandler); StackTraceElementProxyRetracer<StackTraceElementStringProxy> proxyRetracer = new StackTraceElementProxyRetracerImpl<>(retracer); List<String> retracedStrings = new ArrayList<>(); - plainStackTraceVisitor.forEach( - stackTraceElement -> { - List<String> retracedStringsForElement = new ArrayList<>(); - proxyRetracer - .retrace(stackTraceElement) - .forEach( - retracedElement -> { - StackTraceElementStringProxy originalItem = - retracedElement.getOriginalItem(); - retracedStringsForElement.add( - originalItem.toRetracedItem( - retracedElement, !retracedStringsForElement.isEmpty())); - }); - retracedStrings.addAll(retracedStringsForElement); - }); + stackTraceVisitor.forEach( + stackTraceElement -> { + Box<List<RetraceStackTraceProxyImpl<StackTraceElementStringProxy>>> currentList = + new Box<>(); + Map< + RetraceStackTraceProxyImpl<StackTraceElementStringProxy>, + List<RetraceStackTraceProxyImpl<StackTraceElementStringProxy>>> + ambiguousBlocks = new HashMap<>(); + proxyRetracer + .retrace(stackTraceElement) + .forEach( + retracedElement -> { + if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) { + List<RetraceStackTraceProxyImpl<StackTraceElementStringProxy>> block = + new ArrayList<>(); + ambiguousBlocks.put(retracedElement, block); + currentList.set(block); + } + currentList.get().add(retracedElement); + }); + ambiguousBlocks.keySet().stream() + .sorted() + .forEach( + topFrame -> { + ambiguousBlocks + .get(topFrame) + .forEach( + frame -> { + StackTraceElementStringProxy originalItem = frame.getOriginalItem(); + retracedStrings.add( + originalItem.toRetracedItem( + frame, !currentList.isSet(), command.isVerbose)); + // Use the current list as indicator for us seeing the first + // sorted element. + currentList.set(null); + }); + }); + }); result = new RetraceCommandLineResult(retracedStrings); - } timing.end(); timing.begin("Report result"); command.retracedStackTraceConsumer.accept(result.getNodes());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java index f141080..d3b6014 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -27,5 +27,7 @@ RetraceFieldResult getRetraceFieldResult(); RetraceClassResult.Element getClassElement(); + + RetraceSourceFileResult retraceSourceFile(String sourceFile); } }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java index b69c237..d9d1375 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
@@ -5,26 +5,42 @@ package com.android.tools.r8.retrace; import com.android.tools.r8.Keep; +import java.util.List; @Keep -public interface RetraceStackTraceProxy<T extends StackTraceElementProxy<?>> { +public interface RetraceStackTraceProxy<T extends StackTraceElementProxy<?>> + extends Comparable<RetraceStackTraceProxy<T>> { boolean isAmbiguous(); + boolean isTopFrame(); + boolean hasRetracedClass(); boolean hasRetracedMethod(); + boolean hasRetracedField(); + boolean hasSourceFile(); boolean hasLineNumber(); + boolean hasFieldOrReturnType(); + + boolean hasMethodArguments(); + T getOriginalItem(); RetracedClass getRetracedClass(); RetracedMethod getRetracedMethod(); + RetracedField getRetracedField(); + + RetracedType getRetracedFieldOrReturnType(); + + List<RetracedType> getMethodArguments(); + String getSourceFile(); int getLineNumber();
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java index b9fa2d3..2c0c1fa 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java +++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.retrace; import com.android.tools.r8.Keep; -import com.android.tools.r8.retrace.internal.RetracedTypeImpl; import java.util.function.Consumer; import java.util.stream.Stream; @@ -21,6 +20,6 @@ @Keep interface Element { - RetracedTypeImpl getType(); + RetracedType getType(); } }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java index 10aa77c..f28b87e 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java +++ b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
@@ -5,10 +5,9 @@ package com.android.tools.r8.retrace; import com.android.tools.r8.Keep; -import com.android.tools.r8.retrace.internal.RetracedClassImpl; @Keep public interface RetracedClassMember { - RetracedClassImpl getHolderClass(); + RetracedClass getHolderClass(); }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java index bfc95e9..936ea3e 100644 --- a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java +++ b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
@@ -10,7 +10,7 @@ import java.util.List; @Keep -public interface RetracedMethod extends RetracedClassMember { +public interface RetracedMethod extends RetracedClassMember, Comparable<RetracedMethod> { boolean isUnknown();
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java index efaab05..09c1a8f 100644 --- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java +++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.retrace; import com.android.tools.r8.Keep; +import com.android.tools.r8.references.ClassReference; @Keep public abstract class StackTraceElementProxy<T> { @@ -17,11 +18,23 @@ public abstract boolean hasLineNumber(); - public abstract String className(); + public abstract boolean hasFieldName(); - public abstract String methodName(); + public abstract boolean hasFieldOrReturnType(); - public abstract String fileName(); + public abstract boolean hasMethodArguments(); - public abstract int lineNumber(); + public abstract ClassReference getClassReference(); + + public abstract String getMethodName(); + + public abstract String getFileName(); + + public abstract int getLineNumber(); + + public abstract String getFieldName(); + + public abstract String getFieldOrReturnType(); + + public abstract String getMethodArguments(); }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java b/src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java index 4a3b93c..27d9a54 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java
@@ -43,7 +43,7 @@ @Override FieldDefinition substituteHolder(ClassReference newHolder) { - return FieldDefinition.create(classReference, name); + return FieldDefinition.create(newHolder, name); } @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java index 0aac05f..7f7d7fc 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
@@ -7,6 +7,7 @@ import static com.google.common.base.Predicates.not; import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType; import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder; import com.android.tools.r8.utils.DescriptorUtils; import java.util.List; @@ -92,7 +93,7 @@ return null; } return StackTraceElementStringProxy.builder(line) - .registerClassName(exceptionStartIndex, messageStartIndex) + .registerClassName(exceptionStartIndex, messageStartIndex, ClassNameType.TYPENAME) .build(); } } @@ -161,7 +162,7 @@ } StackTraceElementStringProxyBuilder builder = StackTraceElementStringProxy.builder(line) - .registerClassName(classStartIndex, methodSeparator) + .registerClassName(classStartIndex, methodSeparator, ClassNameType.TYPENAME) .registerMethodName(methodSeparator + 1, parensStart); // Check if we have a filename and position. int separatorIndex = firstCharFromIndex(line, parensStart, ':'); @@ -201,7 +202,7 @@ return null; } return StackTraceElementStringProxy.builder(line) - .registerClassName(exceptionStartIndex, lastBracketPosition) + .registerClassName(exceptionStartIndex, lastBracketPosition, ClassNameType.TYPENAME) .build(); } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java index 066b979..3a65b0a 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -9,7 +9,7 @@ import com.android.tools.r8.naming.MemberNaming.FieldSignature; import com.android.tools.r8.references.Reference; import com.android.tools.r8.retrace.RetraceFieldResult; -import com.android.tools.r8.retrace.Retracer; +import com.android.tools.r8.retrace.RetraceSourceFileResult; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.Pair; import java.util.List; @@ -22,13 +22,13 @@ private final RetraceClassResultImpl classResult; private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MemberNaming>>> memberNamings; private final FieldDefinition fieldDefinition; - private final Retracer retracer; + private final RetracerImpl retracer; RetraceFieldResultImpl( RetraceClassResultImpl classResult, List<Pair<RetraceClassResultImpl.ElementImpl, List<MemberNaming>>> memberNamings, FieldDefinition fieldDefinition, - Retracer retracer) { + RetracerImpl retracer) { this.classResult = classResult; this.memberNamings = memberNamings; this.fieldDefinition = fieldDefinition; @@ -131,5 +131,11 @@ public RetraceClassResultImpl.ElementImpl getClassElement() { return classElement; } + + @Override + public RetraceSourceFileResult retraceSourceFile(String sourceFile) { + return RetraceUtils.getSourceFile( + classElement, fieldReference.getHolderClass(), sourceFile, retraceFieldResult.retracer); + } } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java index 6ba4d1a..ca9a2f8 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
@@ -4,42 +4,19 @@ package com.android.tools.r8.retrace.internal; -import static com.android.tools.r8.retrace.internal.RetraceUtils.methodDescriptionFromRetraceMethod; - -import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.references.ClassReference; -import com.android.tools.r8.references.Reference; -import com.android.tools.r8.references.TypeReference; -import com.android.tools.r8.retrace.RetraceClassResult; -import com.android.tools.r8.retrace.RetraceFieldResult; -import com.android.tools.r8.retrace.RetraceFrameResult; -import com.android.tools.r8.retrace.RetraceFrameResult.Element; -import com.android.tools.r8.retrace.RetraceSourceFileResult; -import com.android.tools.r8.retrace.RetracedClass; -import com.android.tools.r8.retrace.RetracedField; -import com.android.tools.r8.retrace.RetracedField.KnownRetracedField; -import com.android.tools.r8.retrace.RetracedMethod; -import com.android.tools.r8.retrace.internal.RetraceRegularExpression.ClassNameGroup.ClassNameGroupHandler; -import com.android.tools.r8.utils.Box; -import com.android.tools.r8.utils.DescriptorUtils; -import com.android.tools.r8.utils.StringUtils; -import com.google.common.collect.Lists; +import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType; +import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.function.Function; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -public class RetraceRegularExpression { +public class RetraceRegularExpression implements StackTraceVisitor<StackTraceElementStringProxy> { private final RetracerImpl retracer; private final List<String> stackTrace; - private final DiagnosticsHandler diagnosticsHandler; private final String regularExpression; private static final int NO_MATCH = -1; @@ -52,28 +29,21 @@ private final LineNumberGroup lineNumberGroup = new LineNumberGroup(); private final FieldOrReturnTypeGroup fieldOrReturnTypeGroup = new FieldOrReturnTypeGroup(); private final MethodArgumentsGroup methodArgumentsGroup = new MethodArgumentsGroup(); - - private final MethodNameGroup methodNameGroup; - private final FieldNameGroup fieldNameGroup; + private final MethodNameGroup methodNameGroup = new MethodNameGroup(); + private final FieldNameGroup fieldNameGroup = new FieldNameGroup(); private static final String CAPTURE_GROUP_PREFIX = "captureGroup"; private static final int FIRST_CAPTURE_GROUP_INDEX = 0; public RetraceRegularExpression( - RetracerImpl retracer, - List<String> stackTrace, - DiagnosticsHandler diagnosticsHandler, - String regularExpression, - boolean isVerbose) { + RetracerImpl retracer, List<String> stackTrace, String regularExpression) { this.retracer = retracer; this.stackTrace = stackTrace; - this.diagnosticsHandler = diagnosticsHandler; this.regularExpression = regularExpression; - methodNameGroup = new MethodNameGroup(isVerbose); - fieldNameGroup = new FieldNameGroup(isVerbose); } - public RetraceCommandLineResult retrace() { + @Override + public void forEach(Consumer<StackTraceElementStringProxy> consumer) { List<RegularExpressionGroupHandler> handlers = new ArrayList<>(); StringBuilder refinedRegularExpressionBuilder = new StringBuilder(); registerGroups( @@ -83,74 +53,22 @@ FIRST_CAPTURE_GROUP_INDEX); String refinedRegularExpression = refinedRegularExpressionBuilder.toString(); Pattern compiledPattern = Pattern.compile(refinedRegularExpression); - List<String> result = new ArrayList<>(); for (String string : stackTrace) { + StackTraceElementStringProxyBuilder proxyBuilder = + StackTraceElementStringProxy.builder(string); Matcher matcher = compiledPattern.matcher(string); - if (!matcher.matches()) { - result.add(string); - continue; - } - // Iterate through handlers to set contexts. That will allow us to process all handlers from - // left to right. - RetraceStringContext initialContext = RetraceStringContext.empty(); - for (RegularExpressionGroupHandler handler : handlers) { - initialContext = handler.buildInitial(initialContext, matcher, retracer); - } - final RetraceString initialRetraceString = RetraceString.start(initialContext); - List<RetraceString> retracedStrings = Lists.newArrayList(initialRetraceString); - for (RegularExpressionGroupHandler handler : handlers) { - retracedStrings = handler.handleMatch(string, retracedStrings, matcher, retracer); - } - if (retracedStrings.isEmpty()) { - // We could not find a match. Output the identity. - result.add(string); - } - boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous(); - if (isAmbiguous) { - retracedStrings.sort(new RetraceLineComparator()); - } - RetracedClass previousContext = null; - for (RetraceString retracedString : retracedStrings) { - String finalString = retracedString.builder.build(string); - if (!isAmbiguous) { - result.add(finalString); - continue; + if (matcher.matches()) { + boolean seenMatchedClassHandler = false; + for (RegularExpressionGroupHandler handler : handlers) { + if (seenMatchedClassHandler && handler.isClassHandler()) { + continue; + } + if (handler.matchHandler(proxyBuilder, matcher)) { + seenMatchedClassHandler |= handler.isClassHandler(); + } } - assert retracedString.getClassContext() != null; - RetracedClass currentContext = retracedString.getClassContext().getRetracedClass(); - if (currentContext.equals(previousContext)) { - int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString); - finalString = - finalString.substring(0, firstNonWhitespaceCharacter) - + "<OR> " - + finalString.substring(firstNonWhitespaceCharacter); - } - previousContext = currentContext; - result.add(finalString); } - } - return new RetraceCommandLineResult(result); - } - - static class RetraceLineComparator extends AmbiguousComparator<RetraceString> { - - RetraceLineComparator() { - super( - (line, t) -> { - switch (t) { - case CLASS: - return line.getClassContext().getRetracedClass().getTypeName(); - case METHOD: - return line.getMethodContext().getTopFrame().getMethodName(); - case SOURCE: - return line.getSource(); - case LINE: - return line.getLineNumber() + ""; - default: - assert false; - } - throw new RuntimeException("Comparator key is unknown"); - }); + consumer.accept(proxyBuilder.build()); } } @@ -160,7 +78,6 @@ List<RegularExpressionGroupHandler> handlers, int captureGroupIndex) { int lastCommittedIndex = 0; - RegularExpressionGroupHandler lastHandler = null; boolean seenPercentage = false; boolean escaped = false; for (int i = 0; i < regularExpression.length(); i++) { @@ -180,23 +97,7 @@ .append(">") .append(group.subExpression()) .append(")"); - final RegularExpressionGroupHandler handler = group.createHandler(captureGroupName); - // If we see a pattern as %c.%m or %C/%m, then register the groups to allow delaying - // writing of the class string until we have the fully qualified member. - if (lastHandler != null - && handler.isQualifiedHandler() - && lastHandler.isClassNameGroupHandler() - && lastCommittedIndex == i - 3 - && isTypeOrBinarySeparator(regularExpression, lastCommittedIndex, i - 2)) { - final ClassNameGroupHandler classNameGroupHandler = - lastHandler.asClassNameGroupHandler(); - final QualifiedRegularExpressionGroupHandler qualifiedHandler = - handler.asQualifiedHandler(); - classNameGroupHandler.setQualifiedHandler(qualifiedHandler); - qualifiedHandler.setClassNameGroupHandler(classNameGroupHandler); - } - lastHandler = handler; - handlers.add(handler); + handlers.add(group.createHandler(captureGroupName)); } lastCommittedIndex = i + 1; seenPercentage = false; @@ -247,250 +148,13 @@ } } - static class RetraceStringContext { - private final RetraceClassResult.Element classContext; - private final RetraceFrameResult.Element methodContext; - private final RetracedClass qualifiedClassContext; - private final RetracedMethod qualifiedMethodContext; - private final String methodName; - private final int minifiedLineNumber; - private final int originalLineNumber; - private final String source; - private final boolean isAmbiguous; - - private RetraceStringContext( - RetraceClassResult.Element classContext, - RetraceFrameResult.Element methodContext, - RetracedClass qualifiedClassContext, - RetracedMethod qualifiedMethodContext, - String methodName, - int minifiedLineNumber, - int originalLineNumber, - String source, - boolean isAmbiguous) { - this.classContext = classContext; - this.methodContext = methodContext; - this.qualifiedClassContext = qualifiedClassContext; - this.qualifiedMethodContext = qualifiedMethodContext; - this.methodName = methodName; - this.minifiedLineNumber = minifiedLineNumber; - this.originalLineNumber = originalLineNumber; - this.source = source; - this.isAmbiguous = isAmbiguous; - } - - private static RetraceStringContext empty() { - return new RetraceStringContext( - null, null, null, null, null, NO_MATCH, NO_MATCH, null, false); - } - - private RetraceStringContext withClassContext( - RetraceClassResult.Element classContext, RetracedClass qualifiedContext) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedContext, - qualifiedMethodContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - - private RetraceStringContext withMethodName(String methodName) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedClassContext, - qualifiedMethodContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - - private RetraceStringContext withMethodContext(Element methodContext, boolean isAmbiguous) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedClassContext, - qualifiedMethodContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - - private RetraceStringContext withQualifiedClassContext(RetracedClass qualifiedContext) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedContext, - qualifiedMethodContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - - private RetraceStringContext withQualifiedMethodContext(RetracedMethod qualifiedContext) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedContext.getHolderClass(), - qualifiedContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - - public RetraceStringContext withSource(String source) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedClassContext, - qualifiedMethodContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - - public RetraceStringContext withLineNumbers(int minifiedLineNumber, int originalLineNumber) { - return new RetraceStringContext( - classContext, - methodContext, - qualifiedClassContext, - qualifiedMethodContext, - methodName, - minifiedLineNumber, - originalLineNumber, - source, - isAmbiguous); - } - } - - static class RetraceStringBuilder { - - private final StringBuilder retracedString; - private int lastCommittedIndex; - - private RetraceStringBuilder(String retracedString, int lastCommittedIndex) { - this.retracedString = new StringBuilder(retracedString); - this.lastCommittedIndex = lastCommittedIndex; - } - - private void appendRetracedString( - String source, String stringToAppend, int originalFromIndex, int originalToIndex) { - retracedString.append(source, lastCommittedIndex, originalFromIndex); - retracedString.append(stringToAppend); - lastCommittedIndex = originalToIndex; - } - - private String build(String source) { - return retracedString.append(source, lastCommittedIndex, source.length()).toString(); - } - } - - private static class RetraceString { - - private final RetraceStringBuilder builder; - private final RetraceStringContext context; - - private RetraceString(RetraceStringBuilder builder, RetraceStringContext context) { - this.builder = builder; - this.context = context; - } - - private RetraceClassResult.Element getClassContext() { - return context.classContext; - } - - private RetraceFrameResult.Element getMethodContext() { - return context.methodContext; - } - - private String getSource() { - return context.source; - } - - private int getLineNumber() { - return context.originalLineNumber; - } - - private boolean isAmbiguous() { - return context.isAmbiguous; - } - - private static RetraceString start(RetraceStringContext initialContext) { - return new RetraceString(new RetraceStringBuilder("", 0), initialContext); - } - - private RetraceString updateContext( - Function<RetraceStringContext, RetraceStringContext> update) { - return new RetraceString(builder, update.apply(context)); - } - - private RetraceString duplicate(RetraceStringContext newContext) { - return new RetraceString( - new RetraceStringBuilder(builder.retracedString.toString(), builder.lastCommittedIndex), - newContext); - } - - private RetraceString appendRetracedString( - String source, String stringToAppend, int originalFromIndex, int originalToIndex) { - builder.appendRetracedString(source, stringToAppend, originalFromIndex, originalToIndex); - return this; - } - } - private interface RegularExpressionGroupHandler { - List<RetraceString> handleMatch( - String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer); + boolean matchHandler(StackTraceElementStringProxyBuilder builder, Matcher matcher); - default RetraceStringContext buildInitial( - RetraceStringContext context, Matcher matcher, RetracerImpl retracer) { - return context; - } - - default boolean isClassNameGroupHandler() { + default boolean isClassHandler() { return false; } - - default ClassNameGroupHandler asClassNameGroupHandler() { - return null; - } - - default boolean isQualifiedHandler() { - return false; - } - - default QualifiedRegularExpressionGroupHandler asQualifiedHandler() { - return null; - } - } - - private interface QualifiedRegularExpressionGroupHandler extends RegularExpressionGroupHandler { - - @Override - default boolean isQualifiedHandler() { - return true; - } - - @Override - default QualifiedRegularExpressionGroupHandler asQualifiedHandler() { - return this; - } - - void setClassNameGroupHandler(ClassNameGroupHandler handler); } private abstract static class RegularExpressionGroup { @@ -513,107 +177,31 @@ abstract static class ClassNameGroup extends RegularExpressionGroup { - abstract String getClassName(RetracedClass classReference); - - abstract ClassReference classFromMatch(String match); + abstract ClassNameType getClassNameType(); @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return new ClassNameGroupHandler(this, captureGroup); - } - - static class ClassNameGroupHandler implements RegularExpressionGroupHandler { - - private RetraceClassResultImpl retraceClassResult = null; - private final ClassNameGroup classNameGroup; - private final String captureGroup; - private RegularExpressionGroupHandler qualifiedHandler; - - public ClassNameGroupHandler(ClassNameGroup classNameGroup, String captureGroup) { - this.classNameGroup = classNameGroup; - this.captureGroup = captureGroup; - } - - @Override - public List<RetraceString> handleMatch( - String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) { - final int startOfGroup = matcher.start(captureGroup); - if (startOfGroup == NO_MATCH) { - return strings; + return new RegularExpressionGroupHandler() { + @Override + public boolean matchHandler(StackTraceElementStringProxyBuilder builder, Matcher matcher) { + int startOfGroup = matcher.start(captureGroup); + if (startOfGroup == NO_MATCH) { + return false; + } + String typeName = matcher.group(captureGroup); + if (typeName.equals("Suppressed")) { + // Ensure we do not map supressed. + return false; + } + builder.registerClassName(startOfGroup, matcher.end(captureGroup), getClassNameType()); + return true; } - String typeName = matcher.group(captureGroup); - RetraceClassResultImpl retraceResult = - retraceClassResult == null - ? retracer.retraceClass(classNameGroup.classFromMatch(typeName)) - : retraceClassResult; - assert !retraceResult.isAmbiguous(); - List<RetraceString> retraceStrings = new ArrayList<>(strings.size()); - for (RetraceString retraceString : strings) { - retraceResult.forEach( - element -> { - RetraceString newRetraceString = - retraceString.updateContext( - context -> context.withClassContext(element, element.getRetracedClass())); - retraceStrings.add(newRetraceString); - if (qualifiedHandler == null) { - // If there is no qualified handler, commit right away. - newRetraceString.builder.appendRetracedString( - original, - classNameGroup.getClassName(element.getRetracedClass()), - startOfGroup, - matcher.end(captureGroup)); - } - }); + + @Override + public boolean isClassHandler() { + return true; } - return retraceStrings; - } - - void commitClassName( - String original, - RetraceString retraceString, - RetracedClass qualifiedContext, - Matcher matcher) { - if (matcher.start(captureGroup) == NO_MATCH) { - return; - } - retraceString.builder.appendRetracedString( - original, - classNameGroup.getClassName(qualifiedContext), - matcher.start(captureGroup), - matcher.end(captureGroup)); - } - - @Override - public RetraceStringContext buildInitial( - RetraceStringContext context, Matcher matcher, RetracerImpl retracer) { - // Reset the local class context since this the same handler is used for multiple lines. - retraceClassResult = null; - if (matcher.start(captureGroup) == NO_MATCH || context.classContext != null) { - return context; - } - String typeName = matcher.group(captureGroup); - retraceClassResult = retracer.retraceClass(classNameGroup.classFromMatch(typeName)); - assert !retraceClassResult.isAmbiguous(); - Box<RetraceStringContext> box = new Box<>(); - retraceClassResult.forEach( - element -> box.set(context.withClassContext(element, element.getRetracedClass()))); - return box.get(); - } - - public void setQualifiedHandler(RegularExpressionGroupHandler handler) { - assert handler.isQualifiedHandler(); - this.qualifiedHandler = handler; - } - - @Override - public boolean isClassNameGroupHandler() { - return true; - } - - @Override - public ClassNameGroupHandler asClassNameGroupHandler() { - return this; - } + }; } } @@ -625,13 +213,8 @@ } @Override - String getClassName(RetracedClass classReference) { - return classReference.getTypeName(); - } - - @Override - ClassReference classFromMatch(String match) { - return Reference.classFromTypeName(match); + ClassNameType getClassNameType() { + return ClassNameType.TYPENAME; } } @@ -643,24 +226,13 @@ } @Override - String getClassName(RetracedClass classReference) { - return classReference.getBinaryName(); - } - - @Override - ClassReference classFromMatch(String match) { - return Reference.classFromBinaryName(match); + ClassNameType getClassNameType() { + return ClassNameType.BINARY; } } private static class MethodNameGroup extends RegularExpressionGroup { - private final boolean printVerbose; - - public MethodNameGroup(boolean printVerbose) { - this.printVerbose = printVerbose; - } - @Override String subExpression() { return METHOD_NAME_REGULAR_EXPRESSION; @@ -668,98 +240,19 @@ @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return new QualifiedRegularExpressionGroupHandler() { - - private ClassNameGroupHandler classNameGroupHandler; - - @Override - public void setClassNameGroupHandler(ClassNameGroupHandler handler) { - classNameGroupHandler = handler; + return (builder, matcher) -> { + int startOfGroup = matcher.start(captureGroup); + if (startOfGroup == NO_MATCH) { + return false; } - - @Override - public List<RetraceString> handleMatch( - String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) { - final int startOfGroup = matcher.start(captureGroup); - if (startOfGroup == NO_MATCH) { - if (classNameGroupHandler != null) { - for (RetraceString string : strings) { - classNameGroupHandler.commitClassName( - original, string, string.context.qualifiedClassContext, matcher); - } - } - return strings; - } - String methodName = matcher.group(captureGroup); - List<RetraceString> retracedStrings = new ArrayList<>(); - for (RetraceString retraceString : strings) { - retraceMethodForString( - retraceString, - methodName, - (element, newContext) -> { - element.visitFrames( - (method, ignoredPosition) -> { - RetraceString newRetraceString = - retraceString.duplicate(newContext.withQualifiedMethodContext(method)); - if (classNameGroupHandler != null) { - classNameGroupHandler.commitClassName( - original, newRetraceString, method.getHolderClass(), matcher); - } - retracedStrings.add( - newRetraceString.appendRetracedString( - original, - printVerbose - ? methodDescriptionFromRetraceMethod(method, false, true) - : method.getMethodName(), - startOfGroup, - matcher.end(captureGroup))); - }); - }); - } - return retracedStrings; - } - - @Override - public RetraceStringContext buildInitial( - RetraceStringContext context, Matcher matcher, RetracerImpl retracer) { - final int startOfGroup = matcher.start(captureGroup); - if (startOfGroup == NO_MATCH || context.methodName != null) { - return context; - } - return context.withMethodName(matcher.group(captureGroup)); - } + builder.registerMethodName(startOfGroup, matcher.end(captureGroup)); + return true; }; } - - private static void retraceMethodForString( - RetraceString retraceString, - String methodName, - BiConsumer<Element, RetraceStringContext> process) { - if (retraceString.context.classContext == null) { - return; - } - RetraceClassResult.Element classContext = retraceString.getClassContext(); - RetraceFrameResult retraceFrameResult = - retraceString.context.minifiedLineNumber > NO_MATCH - ? classContext.lookupFrame(methodName, retraceString.context.minifiedLineNumber) - : classContext.lookupFrame(methodName); - retraceFrameResult.forEach( - element -> { - process.accept( - element, - retraceString.context.withMethodContext(element, retraceFrameResult.isAmbiguous())); - }); - } } private static class FieldNameGroup extends RegularExpressionGroup { - private final boolean printVerbose; - - public FieldNameGroup(boolean printVerbose) { - this.printVerbose = printVerbose; - } - @Override String subExpression() { return javaIdentifierSegment; @@ -767,70 +260,15 @@ @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return new QualifiedRegularExpressionGroupHandler() { - - private ClassNameGroupHandler classNameGroupHandler; - - @Override - public void setClassNameGroupHandler(ClassNameGroupHandler handler) { - classNameGroupHandler = handler; + return (builder, matcher) -> { + int startOfGroup = matcher.start(captureGroup); + if (startOfGroup == NO_MATCH) { + return false; } - - @Override - public List<RetraceString> handleMatch( - String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) { - final int startOfGroup = matcher.start(captureGroup); - if (startOfGroup == NO_MATCH) { - if (classNameGroupHandler != null) { - for (RetraceString string : strings) { - classNameGroupHandler.commitClassName( - original, string, string.context.qualifiedClassContext, matcher); - } - } - return strings; - } - String fieldName = matcher.group(captureGroup); - List<RetraceString> retracedStrings = new ArrayList<>(); - for (RetraceString retraceString : strings) { - if (retraceString.getClassContext() == null) { - assert classNameGroupHandler == null; - return strings; - } - final RetraceFieldResult retraceFieldResult = - retraceString.getClassContext().lookupField(fieldName); - assert !retraceFieldResult.isAmbiguous(); - retraceFieldResult.forEach( - element -> { - if (classNameGroupHandler != null) { - classNameGroupHandler.commitClassName( - original, retraceString, element.getField().getHolderClass(), matcher); - } - retracedStrings.add( - retraceString - .updateContext( - context -> - context.withQualifiedClassContext( - element.getField().getHolderClass())) - .appendRetracedString( - original, - getFieldString(element.getField()), - startOfGroup, - matcher.end(captureGroup))); - }); - } - return retracedStrings; - } + builder.registerFieldName(startOfGroup, matcher.end(captureGroup)); + return true; }; } - - private String getFieldString(RetracedField fieldReference) { - if (!printVerbose || fieldReference.isUnknown()) { - return fieldReference.getFieldName(); - } - assert fieldReference.isKnown(); - KnownRetracedField knownRef = fieldReference.asKnown(); - return knownRef.getFieldType().getTypeName() + " " + fieldReference.getFieldName(); - } } private static class SourceFileGroup extends RegularExpressionGroup { @@ -842,40 +280,13 @@ @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return (original, strings, matcher, retracer) -> { - final int startOfGroup = matcher.start(captureGroup); + return (builder, matcher) -> { + int startOfGroup = matcher.start(captureGroup); if (startOfGroup == NO_MATCH) { - return strings; + return false; } - String fileName = matcher.group(captureGroup); - List<RetraceString> retracedStrings = null; - for (RetraceString retraceString : strings) { - if (retraceString.context.classContext == null) { - return strings; - } - if (retracedStrings == null) { - retracedStrings = new ArrayList<>(); - } - RetraceSourceFileResult sourceFileResult = - retraceString.getMethodContext() != null - ? retraceString - .getMethodContext() - .retraceSourceFile(retraceString.context.qualifiedMethodContext, fileName) - : RetraceUtils.getSourceFile( - retraceString.getClassContext(), - retraceString.context.qualifiedClassContext, - fileName, - retracer); - retracedStrings.add( - retraceString - .updateContext(context -> context.withSource(sourceFileResult.getFilename())) - .appendRetracedString( - original, - sourceFileResult.getFilename(), - startOfGroup, - matcher.end(captureGroup))); - } - return retracedStrings; + builder.registerSourceFile(startOfGroup, matcher.end(captureGroup)); + return true; }; } } @@ -889,88 +300,13 @@ @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return new RegularExpressionGroupHandler() { - @Override - public List<RetraceString> handleMatch( - String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) { - final int startOfGroup = matcher.start(captureGroup); - if (startOfGroup == NO_MATCH) { - return strings; - } - String lineNumberAsString = matcher.group(captureGroup); - if (lineNumberAsString.isEmpty()) { - return strings; - } - int lineNumber = Integer.parseInt(lineNumberAsString); - List<RetraceString> retracedStrings = new ArrayList<>(); - for (RetraceString retraceString : strings) { - Element methodContext = retraceString.context.methodContext; - if (methodContext == null) { - if (retraceString.context.classContext == null - || retraceString.context.methodName == null) { - // We have no way of retracing the line number. - retracedStrings.add(retraceString); - continue; - } - // This situation arises when we have a matched pattern as %l..%c.%m where the - // line number handler is defined before the methodname handler. - MethodNameGroup.retraceMethodForString( - retraceString, - retraceString.context.methodName, - (element, newContext) -> { - // The same method can be represented multiple times if it has multiple - // mappings. - element.visitFrames( - (method, ignoredPosition) -> { - int originalPosition = method.getOriginalPositionOrDefault(lineNumber); - retracedStrings.add( - retraceString - .duplicate( - retraceString - .context - .withQualifiedMethodContext(method) - .withLineNumbers(lineNumber, originalPosition)) - .appendRetracedString( - original, - originalPosition + "", - startOfGroup, - matcher.end(captureGroup))); - }); - }); - continue; - } - // If the method context is unknown, do nothing. - if (methodContext.isUnknown()) { - retracedStrings.add(retraceString); - continue; - } - int originalLineNumber = - retraceString.context.qualifiedMethodContext.getOriginalPositionOrDefault( - lineNumber); - retracedStrings.add( - retraceString - .updateContext( - context -> context.withLineNumbers(lineNumber, originalLineNumber)) - .appendRetracedString( - original, - originalLineNumber + "", - startOfGroup, - matcher.end(captureGroup))); - } - return retracedStrings; + return (builder, matcher) -> { + int startOfGroup = matcher.start(captureGroup); + if (startOfGroup == NO_MATCH) { + return false; } - - @Override - public RetraceStringContext buildInitial( - RetraceStringContext context, Matcher matcher, RetracerImpl retracer) { - if (matcher.start(captureGroup) == NO_MATCH || context.minifiedLineNumber > NO_MATCH) { - return context; - } - String lineNumberAsString = matcher.group(captureGroup); - return context.withLineNumbers( - lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString), - NO_MATCH); - } + builder.registerLineNumber(startOfGroup, matcher.end(captureGroup)); + return true; }; } } @@ -1005,31 +341,13 @@ @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return (original, strings, matcher, retracer) -> { - final int startOfGroup = matcher.start(captureGroup); + return (builder, matcher) -> { + int startOfGroup = matcher.start(captureGroup); if (startOfGroup == NO_MATCH) { - return strings; + return false; } - String typeName = matcher.group(captureGroup); - String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName); - if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) { - return strings; - } - TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor); - RetraceTypeResultImpl retracedType = retracer.retraceType(typeReference); - assert !retracedType.isAmbiguous(); - for (RetraceString retraceString : strings) { - retracedType.forEach( - element -> { - RetracedTypeImpl retracedReference = element.getType(); - retraceString.appendRetracedString( - original, - retracedReference.isVoid() ? "void" : retracedReference.getTypeName(), - startOfGroup, - matcher.end(captureGroup)); - }); - } - return strings; + builder.registerFieldOrReturnType(startOfGroup, matcher.end(captureGroup)); + return true; }; } } @@ -1043,37 +361,15 @@ @Override RegularExpressionGroupHandler createHandler(String captureGroup) { - return (original, strings, matcher, retracer) -> { - final int startOfGroup = matcher.start(captureGroup); + return (builder, matcher) -> { + int startOfGroup = matcher.start(captureGroup); if (startOfGroup == NO_MATCH) { - return strings; + return false; } - final String formals = - Arrays.stream(matcher.group(captureGroup).split(",")) - .map( - typeName -> { - typeName = typeName.trim(); - if (typeName.isEmpty()) { - return null; - } - String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName); - if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) { - return typeName; - } - final RetraceTypeResultImpl retraceResult = - retracer.retraceType(Reference.returnTypeFromDescriptor(descriptor)); - assert !retraceResult.isAmbiguous(); - final Box<RetracedTypeImpl> elementBox = new Box<>(); - retraceResult.forEach(element -> elementBox.set(element.getType())); - return elementBox.get().getTypeName(); - }) - .filter(Objects::nonNull) - .collect(Collectors.joining(",")); - for (RetraceString string : strings) { - string.appendRetracedString(original, formals, startOfGroup, matcher.end(captureGroup)); - } - return strings; + builder.registerMethodArguments(startOfGroup, matcher.end(captureGroup)); + return true; }; } } } +
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java index 998fe7d..1f4ad68 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java
@@ -8,6 +8,8 @@ import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.TypeReference; import com.android.tools.r8.retrace.RetracedMethod; +import com.android.tools.r8.utils.ComparatorUtils; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -34,6 +36,27 @@ return null; } + @Override + public int compareTo(RetracedMethod other) { + return Comparator.comparing(RetracedMethod::getMethodName) + .thenComparing(RetracedMethod::isKnown) + .thenComparing( + RetracedMethod::asKnown, + Comparator.nullsFirst( + Comparator.comparing( + (KnownRetracedMethod m) -> { + if (m == null) { + return null; + } + return m.isVoid() ? "void" : m.getReturnType().getTypeName(); + })) + .thenComparing( + KnownRetracedMethod::getFormalTypes, + ComparatorUtils.listComparator( + Comparator.comparing(TypeReference::getTypeName)))) + .compare(this, other); + } + public static final class KnownRetracedMethodImpl extends RetracedMethodImpl implements KnownRetracedMethod {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java index 8162e05..d467cc1 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java
@@ -23,6 +23,10 @@ return new RetracedTypeImpl(typeReference); } + static RetracedType createVoid() { + return new RetracedTypeImpl(null); + } + @Override public boolean isVoid() { return typeReference == null;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java index c21b92d..a949fc1 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -5,13 +5,23 @@ package com.android.tools.r8.retrace.internal; import com.android.tools.r8.references.Reference; +import com.android.tools.r8.references.TypeReference; +import com.android.tools.r8.retrace.RetraceClassResult; +import com.android.tools.r8.retrace.RetraceFieldResult; import com.android.tools.r8.retrace.RetraceStackTraceProxy; import com.android.tools.r8.retrace.RetracedClass; +import com.android.tools.r8.retrace.RetracedField; import com.android.tools.r8.retrace.RetracedMethod; +import com.android.tools.r8.retrace.RetracedType; import com.android.tools.r8.retrace.StackTraceElementProxy; import com.android.tools.r8.retrace.StackTraceElementProxyRetracer; +import com.android.tools.r8.utils.Box; +import com.android.tools.r8.utils.ListUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; public class StackTraceElementProxyRetracerImpl<T extends StackTraceElementProxy<?>> @@ -28,47 +38,188 @@ if (!element.hasClassName()) { return Stream.of(RetraceStackTraceProxyImpl.builder(element).build()); } - RetraceClassResultImpl classResult = - retracer.retraceClass(Reference.classFromTypeName(element.className())); - List<RetraceStackTraceProxyImpl<T>> retracedProxies = new ArrayList<>(); - if (!element.hasMethodName()) { - classResult.forEach( - classElement -> { - RetraceStackTraceProxyImpl.Builder<T> proxy = - RetraceStackTraceProxyImpl.builder(element) - .setRetracedClassElement(classElement.getRetracedClass()) - .setAmbiguous(classResult.isAmbiguous()); - if (element.hasFileName()) { - proxy.setSourceFile(classElement.retraceSourceFile(element.fileName()).getFilename()); - } - retracedProxies.add(proxy.build()); - }); + RetraceClassResultImpl classResult = retracer.retraceClass(element.getClassReference()); + if (element.hasMethodName()) { + return retraceMethod(element, classResult); + } else if (element.hasFieldName()) { + return retraceField(element, classResult); } else { - RetraceFrameResultImpl frameResult = - element.hasLineNumber() - ? classResult.lookupFrame(element.methodName(), element.lineNumber()) - : classResult.lookupFrame(element.methodName()); - frameResult.forEach( - frameElement -> { - frameElement.visitFrames( - (frame, index) -> { - RetraceStackTraceProxyImpl.Builder<T> proxy = - RetraceStackTraceProxyImpl.builder(element) - .setRetracedClassElement(frame.getHolderClass()) - .setRetracedMethodElement(frame) - .setAmbiguous(frameResult.isAmbiguous() && index == 0); - if (element.hasLineNumber()) { - proxy.setLineNumber(frame.getOriginalPositionOrDefault(element.lineNumber())); - } - if (element.hasFileName()) { - proxy.setSourceFile( - frameElement.retraceSourceFile(frame, element.fileName()).getFilename()); - } - retracedProxies.add(proxy.build()); - }); - }); + return retraceClassOrType(element, classResult); } - return retracedProxies.stream(); + } + + private Stream<RetraceStackTraceProxyImpl<T>> retraceClassOrType( + T element, RetraceClassResult classResult) { + return classResult.stream() + .flatMap( + classElement -> + retraceFieldOrReturnType(element) + .flatMap( + fieldOrReturnTypeConsumer -> + retracedMethodArguments(element) + .map( + argumentsConsumer -> { + RetraceStackTraceProxyImpl.Builder<T> proxy = + RetraceStackTraceProxyImpl.builder(element) + .setRetracedClass(classElement.getRetracedClass()) + .setAmbiguous(classResult.isAmbiguous()) + .setTopFrame(true); + argumentsConsumer.accept(proxy); + fieldOrReturnTypeConsumer.accept(proxy); + if (element.hasFileName()) { + proxy.setSourceFile( + classElement + .retraceSourceFile(element.getFileName()) + .getFilename()); + } + return proxy.build(); + }))); + } + + private Stream<RetraceStackTraceProxyImpl<T>> retraceMethod( + T element, RetraceClassResultImpl classResult) { + return retraceFieldOrReturnType(element) + .flatMap( + fieldOrReturnTypeConsumer -> + retracedMethodArguments(element) + .flatMap( + argumentsConsumer -> { + RetraceFrameResultImpl frameResult = + element.hasLineNumber() + ? classResult.lookupFrame( + element.getMethodName(), element.getLineNumber()) + : classResult.lookupFrame(element.getMethodName()); + return frameResult.stream() + .flatMap( + frameElement -> { + List<RetraceStackTraceProxyImpl<T>> retracedProxies = + new ArrayList<>(); + frameElement.visitFrames( + (frame, index) -> { + RetraceStackTraceProxyImpl.Builder<T> proxy = + RetraceStackTraceProxyImpl.builder(element) + .setRetracedClass(frame.getHolderClass()) + .setRetracedMethod(frame) + .setAmbiguous( + frameResult.isAmbiguous() && index == 0) + .setTopFrame(index == 0); + if (element.hasLineNumber()) { + proxy.setLineNumber( + frame.getOriginalPositionOrDefault( + element.getLineNumber())); + } + if (element.hasFileName()) { + proxy.setSourceFile( + frameElement + .retraceSourceFile(frame, element.getFileName()) + .getFilename()); + } + fieldOrReturnTypeConsumer.accept(proxy); + argumentsConsumer.accept(proxy); + retracedProxies.add(proxy.build()); + }); + return retracedProxies.stream(); + }); + })); + } + + private Stream<RetraceStackTraceProxyImpl<T>> retraceField( + T element, RetraceClassResult classResult) { + return retraceFieldOrReturnType(element) + .flatMap( + fieldOrReturnTypeConsumer -> + retracedMethodArguments(element) + .flatMap( + argumentsConsumer -> { + RetraceFieldResult retraceFieldResult = + classResult.lookupField(element.getFieldName()); + return retraceFieldResult.stream() + .map( + fieldElement -> { + RetraceStackTraceProxyImpl.Builder<T> proxy = + RetraceStackTraceProxyImpl.builder(element) + .setRetracedClass( + fieldElement.getField().getHolderClass()) + .setRetracedField(fieldElement.getField()) + .setAmbiguous(retraceFieldResult.isAmbiguous()) + .setTopFrame(true); + if (element.hasFileName()) { + proxy.setSourceFile( + fieldElement + .retraceSourceFile(element.getFileName()) + .getFilename()); + } + fieldOrReturnTypeConsumer.accept(proxy); + argumentsConsumer.accept(proxy); + return proxy.build(); + }); + })); + } + + private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T>>> retraceFieldOrReturnType( + T element) { + if (!element.hasFieldOrReturnType()) { + return Stream.of(noEffect -> {}); + } + String elementOrReturnType = element.getFieldOrReturnType(); + if (elementOrReturnType.equals("void")) { + return Stream.of(proxy -> proxy.setRetracedFieldOrReturnType(RetracedTypeImpl.createVoid())); + } else { + TypeReference typeReference = Reference.typeFromTypeName(elementOrReturnType); + RetraceTypeResultImpl retraceTypeResult = retracer.retraceType(typeReference); + return retraceTypeResult.stream() + .map( + type -> + (proxy -> { + proxy.setRetracedFieldOrReturnType(type.getType()); + if (retraceTypeResult.isAmbiguous()) { + proxy.setAmbiguous(true); + } + })); + } + } + + private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T>>> retracedMethodArguments( + T element) { + if (!element.hasMethodArguments()) { + return Stream.of(noEffect -> {}); + } + List<RetraceTypeResultImpl> retracedResults = + Arrays.stream(element.getMethodArguments().split(",")) + .map(typeName -> retracer.retraceType(Reference.typeFromTypeName(typeName))) + .collect(Collectors.toList()); + List<List<RetracedType>> initial = new ArrayList<>(); + initial.add(new ArrayList<>()); + Box<Boolean> isAmbiguous = new Box<>(false); + List<List<RetracedType>> retracedArguments = + ListUtils.fold( + retracedResults, + initial, + (acc, retracedTypeResult) -> { + if (retracedTypeResult.isAmbiguous()) { + isAmbiguous.set(true); + } + List<List<RetracedType>> newResult = new ArrayList<>(); + retracedTypeResult.forEach( + retracedElement -> { + acc.forEach( + oldResult -> { + List<RetracedType> newList = new ArrayList<>(oldResult); + newList.add(retracedElement.getType()); + newResult.add(newList); + }); + }); + return newResult; + }); + return retracedArguments.stream() + .map( + arguments -> + proxy -> { + proxy.setRetracedMethodArguments(arguments); + if (isAmbiguous.get()) { + proxy.setAmbiguous(true); + } + }); } public static class RetraceStackTraceProxyImpl<T extends StackTraceElementProxy<?>> @@ -77,24 +228,36 @@ private final T originalItem; private final RetracedClass retracedClass; private final RetracedMethod retracedMethod; + private final RetracedField retracedField; + private final RetracedType fieldOrReturnType; + private final List<RetracedType> methodArguments; private final String sourceFile; private final int lineNumber; private final boolean isAmbiguous; + private final boolean isTopFrame; private RetraceStackTraceProxyImpl( T originalItem, RetracedClass retracedClass, RetracedMethod retracedMethod, + RetracedField retracedField, + RetracedType fieldOrReturnType, + List<RetracedType> methodArguments, String sourceFile, int lineNumber, - boolean isAmbiguous) { + boolean isAmbiguous, + boolean isTopFrame) { assert originalItem != null; this.originalItem = originalItem; this.retracedClass = retracedClass; this.retracedMethod = retracedMethod; + this.retracedField = retracedField; + this.fieldOrReturnType = fieldOrReturnType; + this.methodArguments = methodArguments; this.sourceFile = sourceFile; this.lineNumber = lineNumber; this.isAmbiguous = isAmbiguous; + this.isTopFrame = isTopFrame; } @Override @@ -103,6 +266,11 @@ } @Override + public boolean isTopFrame() { + return isTopFrame; + } + + @Override public boolean hasRetracedClass() { return retracedClass != null; } @@ -113,6 +281,11 @@ } @Override + public boolean hasRetracedField() { + return retracedField != null; + } + + @Override public boolean hasSourceFile() { return sourceFile != null; } @@ -123,6 +296,16 @@ } @Override + public boolean hasFieldOrReturnType() { + return fieldOrReturnType != null; + } + + @Override + public boolean hasMethodArguments() { + return methodArguments != null; + } + + @Override public T getOriginalItem() { return originalItem; } @@ -138,6 +321,21 @@ } @Override + public RetracedField getRetracedField() { + return retracedField; + } + + @Override + public RetracedType getRetracedFieldOrReturnType() { + return fieldOrReturnType; + } + + @Override + public List<RetracedType> getMethodArguments() { + return methodArguments; + } + + @Override public String getSourceFile() { return sourceFile; } @@ -151,29 +349,91 @@ return lineNumber; } + @Override + public int compareTo(RetraceStackTraceProxy<T> other) { + int classCompare = Boolean.compare(hasRetracedClass(), other.hasRetracedClass()); + if (classCompare != 0) { + return classCompare; + } + if (hasRetracedClass()) { + classCompare = + getRetracedClass().getTypeName().compareTo(other.getRetracedClass().getTypeName()); + if (classCompare != 0) { + return classCompare; + } + } + int methodCompare = Boolean.compare(hasRetracedMethod(), other.hasRetracedMethod()); + if (methodCompare != 0) { + return methodCompare; + } + if (hasRetracedMethod()) { + methodCompare = getRetracedMethod().compareTo(other.getRetracedMethod()); + if (methodCompare != 0) { + return methodCompare; + } + } + int sourceFileCompare = Boolean.compare(hasSourceFile(), other.hasSourceFile()); + if (sourceFileCompare != 0) { + return sourceFileCompare; + } + if (hasSourceFile()) { + sourceFileCompare = getSourceFile().compareTo(other.getSourceFile()); + if (sourceFileCompare != 0) { + return sourceFileCompare; + } + } + int lineNumberCompare = Boolean.compare(hasLineNumber(), other.hasLineNumber()); + if (lineNumberCompare != 0) { + return lineNumberCompare; + } + if (hasLineNumber()) { + return Integer.compare(lineNumber, other.getLineNumber()); + } + return 0; + } + private static class Builder<T extends StackTraceElementProxy<?>> { private final T originalElement; private RetracedClass classContext; private RetracedMethod methodContext; + private RetracedField retracedField; + private RetracedType fieldOrReturnType; + private List<RetracedType> methodArguments; private String sourceFile; private int lineNumber = -1; private boolean isAmbiguous; + private boolean isTopFrame; private Builder(T originalElement) { this.originalElement = originalElement; } - private Builder<T> setRetracedClassElement(RetracedClass retracedClass) { + private Builder<T> setRetracedClass(RetracedClass retracedClass) { this.classContext = retracedClass; return this; } - private Builder<T> setRetracedMethodElement(RetracedMethod methodElement) { + private Builder<T> setRetracedMethod(RetracedMethod methodElement) { this.methodContext = methodElement; return this; } + private Builder<T> setRetracedField(RetracedField retracedField) { + this.retracedField = retracedField; + return this; + } + + private Builder<T> setRetracedFieldOrReturnType(RetracedType retracedType) { + this.fieldOrReturnType = retracedType; + return this; + } + + private Builder<T> setRetracedMethodArguments(List<RetracedType> arguments) { + this.methodArguments = arguments; + return this; + } + private Builder<T> setSourceFile(String sourceFile) { this.sourceFile = sourceFile; return this; @@ -189,6 +449,11 @@ return this; } + private Builder<T> setTopFrame(boolean topFrame) { + isTopFrame = topFrame; + return this; + } + private RetraceStackTraceProxyImpl<T> build() { RetracedClass retracedClass = classContext; if (methodContext != null) { @@ -198,9 +463,13 @@ originalElement, retracedClass, methodContext, - sourceFile != null ? sourceFile : null, + retracedField, + fieldOrReturnType, + methodArguments, + sourceFile, lineNumber, - isAmbiguous); + isAmbiguous, + isTopFrame); } } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java index 202d297..f649598 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -5,36 +5,53 @@ package com.android.tools.r8.retrace.internal; import static com.android.tools.r8.retrace.internal.PlainStackTraceVisitor.firstNonWhiteSpaceCharacterFromIndex; +import static com.android.tools.r8.retrace.internal.RetraceUtils.methodDescriptionFromRetraceMethod; import static com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StringIndex.noIndex; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.retrace.RetracedClass; +import com.android.tools.r8.retrace.RetracedField; +import com.android.tools.r8.retrace.RetracedType; import com.android.tools.r8.retrace.StackTraceElementProxy; import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.StringUtils.BraceType; +import com.android.tools.r8.utils.TriFunction; import java.util.ArrayList; import java.util.List; -import java.util.function.BiFunction; public final class StackTraceElementStringProxy extends StackTraceElementProxy<String> { private final String line; private final List<StringIndex> orderedIndices; - private final StringIndex className; + private final ClassStringIndex className; private final StringIndex methodName; private final StringIndex sourceFile; private final StringIndex lineNumber; + private final StringIndex fieldName; + private final StringIndex fieldOrReturnType; + private final StringIndex methodArguments; private StackTraceElementStringProxy( String line, List<StringIndex> orderedIndices, - StringIndex className, + ClassStringIndex className, StringIndex methodName, StringIndex sourceFile, - StringIndex lineNumber) { + StringIndex lineNumber, + StringIndex fieldName, + StringIndex fieldOrReturnType, + StringIndex methodArguments) { this.line = line; this.orderedIndices = orderedIndices; this.className = className; this.methodName = methodName; this.sourceFile = sourceFile; this.lineNumber = lineNumber; + this.fieldName = fieldName; + this.fieldOrReturnType = fieldOrReturnType; + this.methodArguments = methodArguments; } static StackTraceElementStringProxyBuilder builder(String line) { @@ -62,22 +79,37 @@ } @Override - public String className() { - return hasClassName() ? getEntryInLine(className) : null; + public boolean hasFieldName() { + return fieldName.hasIndex(); } @Override - public String methodName() { + public boolean hasFieldOrReturnType() { + return fieldOrReturnType.hasIndex(); + } + + @Override + public boolean hasMethodArguments() { + return methodArguments.hasIndex(); + } + + @Override + public ClassReference getClassReference() { + return hasClassName() ? className.getReference(line) : null; + } + + @Override + public String getMethodName() { return hasMethodName() ? getEntryInLine(methodName) : null; } @Override - public String fileName() { + public String getFileName() { return hasFileName() ? getEntryInLine(sourceFile) : null; } @Override - public int lineNumber() { + public int getLineNumber() { if (!hasLineNumber()) { return -1; } @@ -88,9 +120,25 @@ } } + @Override + public String getFieldName() { + return hasFieldName() ? getEntryInLine(fieldName) : null; + } + + @Override + public String getFieldOrReturnType() { + return hasFieldOrReturnType() ? getEntryInLine(fieldOrReturnType) : null; + } + + @Override + public String getMethodArguments() { + return hasMethodArguments() ? getEntryInLine(methodArguments) : null; + } + public String toRetracedItem( RetraceStackTraceProxyImpl<StackTraceElementStringProxy> retracedProxy, - boolean printAmbiguous) { + boolean printAmbiguous, + boolean verbose) { StringBuilder sb = new StringBuilder(); int lastSeenIndex = 0; if (retracedProxy.isAmbiguous() && printAmbiguous) { @@ -100,7 +148,7 @@ } for (StringIndex index : orderedIndices) { sb.append(line, lastSeenIndex, index.startIndex); - sb.append(index.retracedString.apply(retracedProxy, this)); + sb.append(index.retracedString.apply(retracedProxy, this, verbose)); lastSeenIndex = index.endIndex; } sb.append(line, lastSeenIndex, line.length()); @@ -116,30 +164,43 @@ return line.substring(index.startIndex, index.endIndex); } + public enum ClassNameType { + BINARY, + TYPENAME + } + public static class StackTraceElementStringProxyBuilder { private final String line; private final List<StringIndex> orderedIndices = new ArrayList<>(); - private StringIndex className = noIndex(); + private ClassStringIndex className = noIndex(); private StringIndex methodName = noIndex(); private StringIndex sourceFile = noIndex(); private StringIndex lineNumber = noIndex(); + private StringIndex fieldName = noIndex(); + private StringIndex fieldOrReturnType = noIndex(); + private StringIndex methodArguments = noIndex(); private int lastSeenStartIndex = -1; private StackTraceElementStringProxyBuilder(String line) { this.line = line; } - public StackTraceElementStringProxyBuilder registerClassName(int startIndex, int endIndex) { + public StackTraceElementStringProxyBuilder registerClassName( + int startIndex, int endIndex, ClassNameType classNameType) { ensureLineIndexIncreases(startIndex); className = - new StringIndex( + new ClassStringIndex( startIndex, endIndex, - (retraced, original) -> { + (retraced, original, verbose) -> { assert retraced.hasRetracedClass(); - return retraced.getRetracedClass().getTypeName(); - }); + RetracedClass retracedClass = retraced.getRetracedClass(); + return classNameType == ClassNameType.BINARY + ? retracedClass.getBinaryName() + : retracedClass.getTypeName(); + }, + classNameType); orderedIndices.add(className); return this; } @@ -149,10 +210,13 @@ new StringIndex( startIndex, endIndex, - (retraced, original) -> - retraced.hasRetracedMethod() - ? retraced.getRetracedMethod().getMethodName() - : original.methodName()); + (retraced, original, verbose) -> { + if (!retraced.hasRetracedMethod()) { + return original.getMethodName(); + } + return methodDescriptionFromRetraceMethod( + retraced.getRetracedMethod(), false, verbose); + }); orderedIndices.add(methodName); return this; } @@ -162,8 +226,8 @@ new StringIndex( startIndex, endIndex, - (retraced, original) -> - retraced.hasSourceFile() ? retraced.getSourceFile() : original.fileName()); + (retraced, original, verbose) -> + retraced.hasSourceFile() ? retraced.getSourceFile() : original.getFileName()); orderedIndices.add(sourceFile); return this; } @@ -173,7 +237,7 @@ new StringIndex( startIndex, endIndex, - (retraced, original) -> + (retraced, original, verbose) -> retraced.hasLineNumber() ? retraced.getLineNumber() + "" : original.lineNumberAsString()); @@ -181,9 +245,73 @@ return this; } + public StackTraceElementStringProxyBuilder registerFieldName(int startIndex, int endIndex) { + fieldName = + new StringIndex( + startIndex, + endIndex, + (retraced, original, verbose) -> { + if (!retraced.hasRetracedField()) { + return original.getFieldName(); + } + RetracedField retracedField = retraced.getRetracedField(); + if (!verbose || retracedField.isUnknown()) { + return retracedField.getFieldName(); + } + return retracedField.asKnown().getFieldType().getTypeName() + + " " + + retracedField.getFieldName(); + }); + orderedIndices.add(fieldName); + return this; + } + + public StackTraceElementStringProxyBuilder registerFieldOrReturnType( + int startIndex, int endIndex) { + fieldOrReturnType = + new StringIndex( + startIndex, + endIndex, + (retraced, original, verbose) -> { + if (!retraced.hasFieldOrReturnType()) { + return original.getFieldOrReturnType(); + } + return retraced.getRetracedFieldOrReturnType().isVoid() + ? "void" + : retraced.getRetracedFieldOrReturnType().getTypeName(); + }); + orderedIndices.add(fieldOrReturnType); + return this; + } + + public StackTraceElementStringProxyBuilder registerMethodArguments( + int startIndex, int endIndex) { + methodArguments = + new StringIndex( + startIndex, + endIndex, + (retraced, original, verbose) -> { + if (!retraced.hasMethodArguments()) { + return original.getMethodArguments(); + } + return StringUtils.join( + retraced.getMethodArguments(), ",", BraceType.NONE, RetracedType::getTypeName); + }); + orderedIndices.add(methodArguments); + return this; + } + public StackTraceElementStringProxy build() { return new StackTraceElementStringProxy( - line, orderedIndices, className, methodName, sourceFile, lineNumber); + line, + orderedIndices, + className, + methodName, + sourceFile, + lineNumber, + fieldName, + fieldOrReturnType, + methodArguments); } private void ensureLineIndexIncreases(int newStartIndex) { @@ -194,28 +322,31 @@ } } - static final class StringIndex { + static class StringIndex { - private static final StringIndex NO_INDEX = new StringIndex(-1, -1, null); + private static final ClassStringIndex NO_INDEX = + new ClassStringIndex(-1, -1, null, ClassNameType.TYPENAME); - static StringIndex noIndex() { + static ClassStringIndex noIndex() { return NO_INDEX; } - private final int startIndex; - private final int endIndex; - private final BiFunction< + protected final int startIndex; + protected final int endIndex; + private final TriFunction< RetraceStackTraceProxyImpl<StackTraceElementStringProxy>, StackTraceElementStringProxy, + Boolean, String> retracedString; private StringIndex( int startIndex, int endIndex, - BiFunction< + TriFunction< RetraceStackTraceProxyImpl<StackTraceElementStringProxy>, StackTraceElementStringProxy, + Boolean, String> retracedString) { this.startIndex = startIndex; @@ -227,4 +358,30 @@ return this != NO_INDEX; } } + + static final class ClassStringIndex extends StringIndex { + + private final ClassNameType classNameType; + + private ClassStringIndex( + int startIndex, + int endIndex, + TriFunction< + RetraceStackTraceProxyImpl<StackTraceElementStringProxy>, + StackTraceElementStringProxy, + Boolean, + String> + retracedString, + ClassNameType classNameType) { + super(startIndex, endIndex, retracedString); + this.classNameType = classNameType; + } + + ClassReference getReference(String line) { + String className = line.substring(startIndex, endIndex); + return classNameType == ClassNameType.BINARY + ? Reference.classFromBinaryName(className) + : Reference.classFromTypeName(className); + } + } }
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 46e4629..fc6cb56 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GraphLens; import com.android.tools.r8.graph.InnerClassAttribute; +import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.util.IdentityHashMap; @@ -27,6 +28,7 @@ public class AnnotationRemover { private final AppView<AppInfoWithLiveness> appView; + private final InternalOptions options; private final Set<DexAnnotation> annotationsToRetain; private final Set<DexType> classesToRetainInnerClassAttributeFor; private final ProguardKeepAttributes keep; @@ -38,6 +40,7 @@ Set<DexAnnotation> annotationsToRetain, Set<DexType> removedClasses) { this.appView = appView; + this.options = appView.options(); this.annotationsToRetain = annotationsToRetain; this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor; this.keep = appView.options().getProguardConfiguration().getKeepAttributes(); @@ -187,25 +190,31 @@ stripAttributes(clazz); clazz.setAnnotations( clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation))); - clazz.forEachMethod(this::processMethod); - clazz.forEachField(this::processField); + clazz.forEachMethod(method -> processMethod(method, clazz)); + clazz.forEachField(field -> processField(field, clazz)); } } - private void processMethod(DexEncodedMethod method) { + private void processMethod(DexEncodedMethod method, DexProgramClass clazz) { method.setAnnotations( method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation))); method.parameterAnnotationsList = method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations); - if (!keep.signature) { + if (appView + .getKeepInfo() + .getMethodInfo(method, clazz) + .isAllowSignatureAttributeRemovalAllowed(options)) { method.clearGenericSignature(); } } - private void processField(DexEncodedField field) { + private void processField(DexEncodedField field, DexProgramClass clazz) { field.setAnnotations( field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation))); - if (!keep.signature) { + if (appView + .getKeepInfo() + .getFieldInfo(field, clazz) + .isAllowSignatureAttributeRemovalAllowed(options)) { field.clearGenericSignature(); } } @@ -321,8 +330,10 @@ clazz.clearEnclosingMethodAttribute(); clazz.clearInnerClasses(); } - // TODO(b/170077516): Prune attributes. - if (!keep.signature) { + if (appView + .getKeepInfo() + .getClassInfo(clazz) + .isAllowSignatureAttributeRemovalAllowed(options)) { clazz.clearClassSignature(); } }
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 7c6d911..b54a0db 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMember; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexReference; @@ -136,9 +137,9 @@ /** All items with assumemayhavesideeffects rule. */ public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects; /** All items with assumenosideeffects rule. */ - public final Map<DexReference, ProguardMemberRule> noSideEffects; + public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects; /** All items with assumevalues rule. */ - public final Map<DexReference, ProguardMemberRule> assumedValues; + public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues; /** All methods that should be inlined if possible due to a configuration directive. */ public final Set<DexMethod> alwaysInline; /** All methods that *must* be inlined due to a configuration directive (testing only). */ @@ -213,8 +214,8 @@ Map<DexCallSite, ProgramMethodSet> callSites, KeepInfoCollection keepInfo, Map<DexReference, ProguardMemberRule> mayHaveSideEffects, - Map<DexReference, ProguardMemberRule> noSideEffects, - Map<DexReference, ProguardMemberRule> assumedValues, + Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects, + Map<DexMember<?, ?>, ProguardMemberRule> assumedValues, Set<DexMethod> alwaysInline, Set<DexMethod> forceInline, Set<DexMethod> neverInline, @@ -294,8 +295,8 @@ Map<DexCallSite, ProgramMethodSet> callSites, KeepInfoCollection keepInfo, Map<DexReference, ProguardMemberRule> mayHaveSideEffects, - Map<DexReference, ProguardMemberRule> noSideEffects, - Map<DexReference, ProguardMemberRule> assumedValues, + Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects, + Map<DexMember<?, ?>, ProguardMemberRule> assumedValues, Set<DexMethod> alwaysInline, Set<DexMethod> forceInline, Set<DexMethod> neverInline,
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 a7fdc85..be79780 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -57,6 +57,7 @@ import com.android.tools.r8.graph.PresortedComparable; import com.android.tools.r8.graph.ProgramDefinition; import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.ProgramMember; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult; @@ -93,6 +94,7 @@ import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection; import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet; import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules; +import com.android.tools.r8.shaking.RootSetBuilder.MutableItemsWithRules; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult; import com.android.tools.r8.utils.Action; @@ -126,6 +128,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -2125,9 +2128,10 @@ worklist.addIfNotSeen(interfaces); // First we lookup and mark all targets on the instantiated class for each reachable method in // the super chain (inclusive). + DexClass initialClass = clazz; while (clazz != null) { if (clazz.isProgramClass()) { - markProgramMethodOverridesAsLive(instantiation, clazz.asProgramClass(), seen); + markProgramMethodOverridesAsLive(instantiation, initialClass, clazz.asProgramClass(), seen); } else { markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz); } @@ -2148,7 +2152,7 @@ if (iface.isNotProgramClass()) { markLibraryAndClasspathMethodOverridesAsLive(instantiation, iface); } else { - markProgramMethodOverridesAsLive(instantiation, iface.asProgramClass(), seen); + markProgramMethodOverridesAsLive(instantiation, initialClass, iface.asProgramClass(), seen); } worklist.addIfNotSeen(Arrays.asList(iface.interfaces.values)); } @@ -2160,18 +2164,28 @@ private void markProgramMethodOverridesAsLive( InstantiatedObject instantiation, + DexClass initialClass, DexProgramClass superClass, Set<Wrapper<DexMethod>> seenMethods) { for (DexMethod method : getReachableVirtualTargets(superClass)) { assert method.holder == superClass.type; - if (seenMethods.add(MethodSignatureEquivalence.get().wrap(method))) { + Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method); + if (!seenMethods.contains(signature)) { SingleResolutionResult resolution = appInfo.resolveMethodOn(superClass, method).asSingleResolution(); assert resolution != null; assert resolution.getResolvedHolder().isProgramClass(); - if (resolution != null && resolution.getResolvedHolder().isProgramClass()) { - markLiveOverrides( - instantiation, superClass, resolution.getResolutionPair().asProgramMethod()); + if (resolution != null) { + if (!initialClass.isProgramClass() + || resolution + .isAccessibleForVirtualDispatchFrom(initialClass.asProgramClass(), appInfo) + .isTrue()) { + seenMethods.add(signature); + } + if (resolution.getResolvedHolder().isProgramClass()) { + markLiveOverrides( + instantiation, superClass, resolution.getResolutionPair().asProgramMethod()); + } } } } @@ -2370,6 +2384,8 @@ // Add all dependent members to the workqueue. enqueueRootItems(rootSet.getDependentItems(field.getDefinition())); + checkMemberForSoftPinning(field); + // Notify analyses. analyses.forEach(analysis -> analysis.processNewlyLiveField(field)); } @@ -2386,6 +2402,8 @@ // Add all dependent members to the workqueue. enqueueRootItems(rootSet.getDependentItems(field.getDefinition())); + checkMemberForSoftPinning(field); + // Notify analyses. analyses.forEach(analysis -> analysis.processNewlyLiveField(field)); } @@ -3400,12 +3418,26 @@ enqueueRootItems(dependentItems); } }); + consequentRootSet.dependentSoftPinned.forEach( + (reference, dependentItems) -> { + if (isLiveProgramReference(reference)) { + dependentItems.forEachReference( + item -> { + if (isLiveProgramReference(item)) { + keepInfo.joinInfo(item, appView, Joiner::pin); + } + }); + } + }); + // TODO(b/132600955): This modifies the root set. Should the consequent be persistent? rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking); if (mode.isInitialTreeShaking()) { for (DexReference reference : consequentRootSet.noObfuscation) { keepInfo.evaluateRule(reference, appView, Joiner::disallowMinification); } + consequentRootSet.softPinned.forEachReference( + reference -> keepInfo.evaluateRule(reference, appView, Joiner::pin)); } enqueueRootItems(consequentRootSet.noShrinking); // Check for compatibility rules indicating that the holder must be implicitly kept. @@ -3419,6 +3451,18 @@ } } + private boolean isLiveProgramReference(DexReference reference) { + if (reference.isDexType()) { + DexProgramClass clazz = + DexProgramClass.asProgramClassOrNull(definitionFor(reference.asDexType())); + return clazz != null && isTypeLive(clazz); + } + DexMember<?, ?> member = reference.asDexMember(); + DexProgramClass holder = DexProgramClass.asProgramClassOrNull(definitionFor(member.holder)); + ProgramMember<?, ?> programMember = member.lookupOnProgramClass(holder); + return programMember != null && isMemberLive(programMember.getDefinition()); + } + private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() { RootSetBuilder builder = new RootSetBuilder(appView, subtypingInfo); for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) { @@ -3430,7 +3474,8 @@ return builder.buildConsequentRootSet(); } - private Map<DexMethod, ProgramMethod> syntheticInterfaceMethodBridges = new IdentityHashMap<>(); + private final Map<DexMethod, ProgramMethod> syntheticInterfaceMethodBridges = + new LinkedHashMap<>(); private void handleInterfaceMethodSyntheticBridgeAction( InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) { @@ -3608,10 +3653,28 @@ // Add all dependent members to the workqueue. enqueueRootItems(rootSet.getDependentItems(definition)); + checkMemberForSoftPinning(method); + // Notify analyses. analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method)); } + private void checkMemberForSoftPinning(ProgramMember<?, ?> member) { + DexMember<?, ?> reference = member.getDefinition().toReference(); + Set<ProguardKeepRuleBase> softPinRules = rootSet.softPinned.getRulesForReference(reference); + if (softPinRules != null) { + assert softPinRules.stream().noneMatch(r -> r.getModifiers().allowsOptimization); + keepInfo.joinInfo(reference, appInfo, Joiner::pin); + } + // Identify dependent soft pinning. + MutableItemsWithRules items = rootSet.dependentSoftPinned.get(member.getHolderType()); + if (items != null && items.containsReference(reference)) { + assert items.getRulesForReference(reference).stream() + .noneMatch(r -> r.getModifiers().allowsOptimization); + keepInfo.joinInfo(reference, appInfo, Joiner::pin); + } + } + private void markReferencedTypesAsLive(ProgramMethod method) { markTypeAsLive( method.getHolderType(), clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java index c6a1725..2de5c70 100644 --- a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java +++ b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; @@ -222,13 +223,13 @@ } } - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context); if (singleTarget == null) { return false; } InstanceInitializerInfo initializerInfo = - singleTarget.getOptimizationInfo().getInstanceInitializerInfo(); + singleTarget.getDefinition().getOptimizationInfo().getInstanceInitializerInfo(); return initializerInfo.receiverNeverEscapesOutsideConstructorChain(); } }
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 f8b5987..a5ee8b7 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -5,8 +5,8 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; -import com.android.tools.r8.Diagnostic; import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; @@ -67,6 +67,7 @@ import java.util.Queue; import java.util.Set; import java.util.Stack; +import java.util.TreeSet; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -83,6 +84,7 @@ private final DirectMappedDexApplication application; private final Iterable<? extends ProguardConfigurationRule> rules; private final MutableItemsWithRules noShrinking = new MutableItemsWithRules(); + private final MutableItemsWithRules softPinned = new MutableItemsWithRules(); private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet(); private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>(); private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>(); @@ -103,11 +105,13 @@ private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet(); private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking = new IdentityHashMap<>(); + private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned = + new IdentityHashMap<>(); private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule = new IdentityHashMap<>(); private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = new IdentityHashMap<>(); - private final Map<DexReference, ProguardMemberRule> noSideEffects = new IdentityHashMap<>(); - private final Map<DexReference, ProguardMemberRule> assumedValues = new IdentityHashMap<>(); + private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>(); + private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>(); private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet(); private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems = new ConcurrentLinkedQueue<>(); @@ -116,8 +120,8 @@ private final DexStringCache dexStringCache = new DexStringCache(); private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet(); - private final Map<OriginWithPosition, List<DexMethod>> assumeNoSideEffectsWarnings = - new HashMap<>(); + private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings = + new LinkedHashMap<>(); public RootSetBuilder( AppView<? extends AppInfoWithClassHierarchy> appView, @@ -334,6 +338,7 @@ assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty(); return new RootSet( noShrinking, + softPinned, noObfuscation, ImmutableList.copyOf(reasonAsked.values()), ImmutableList.copyOf(checkDiscarded.values()), @@ -356,6 +361,7 @@ noSideEffects, assumedValues, dependentNoShrinking, + dependentSoftPinned, dependentKeepClassCompatRule, identifierNameStrings, ifRules, @@ -382,7 +388,7 @@ DexType type, DexMethod reference, Set<DexType> subTypes, - Map<DexReference, ProguardMemberRule> assumeRulePool) { + Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) { ProguardMemberRule ruleToBePropagated = null; for (DexType subType : subTypes) { DexMethod referenceInSubType = @@ -424,8 +430,10 @@ neverInline, neverClassInline, noShrinking, + softPinned, noObfuscation, dependentNoShrinking, + dependentSoftPinned, dependentKeepClassCompatRule, Lists.newArrayList(delayedRootSetActionItems)); } @@ -1136,6 +1144,14 @@ noShrinking.addReferenceWithRule(item.toReference(), keepRule); } context.markAsUsed(); + } else if (!modifiers.allowsOptimization) { + if (precondition != null) { + dependentSoftPinned + .computeIfAbsent(precondition.toReference(), x -> new MutableItemsWithRules()) + .addReferenceWithRule(item.toReference(), keepRule); + } else { + softPinned.addReferenceWithRule(item.toReference(), keepRule); + } } if (!modifiers.allowsOptimization) { // The -dontoptimize flag has only effect through the keep all rule, but we still @@ -1155,15 +1171,25 @@ mayHaveSideEffects.put(item.toReference(), rule); context.markAsUsed(); } else if (context instanceof ProguardAssumeNoSideEffectRule) { - checkAssumeNoSideEffectsWarnings(item, (ProguardAssumeNoSideEffectRule) context, rule); - noSideEffects.put(item.toReference(), rule); - context.markAsUsed(); + if (item.isDexEncodedMember()) { + DexEncodedMember<?, ?> member = item.asDexEncodedMember(); + if (member.holder() == appView.dexItemFactory().objectType) { + assert member.isDexEncodedMethod(); + reportAssumeNoSideEffectsWarningForJavaLangClassMethod( + member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context); + } else { + noSideEffects.put(member.toReference(), rule); + } + context.markAsUsed(); + } } else if (context instanceof ProguardWhyAreYouKeepingRule) { reasonAsked.computeIfAbsent(item.toReference(), i -> i); context.markAsUsed(); } else if (context instanceof ProguardAssumeValuesRule) { - assumedValues.put(item.toReference(), rule); - context.markAsUsed(); + if (item.isDexEncodedMember()) { + assumedValues.put(item.asDexEncodedMember().toReference(), rule); + context.markAsUsed(); + } } else if (context instanceof ProguardCheckDiscardRule) { checkDiscarded.computeIfAbsent(item.toReference(), i -> i); context.markAsUsed(); @@ -1301,8 +1327,10 @@ final Set<DexMethod> neverInline; final Set<DexType> neverClassInline; final MutableItemsWithRules noShrinking; + final MutableItemsWithRules softPinned; final Set<DexReference> noObfuscation; final Map<DexReference, MutableItemsWithRules> dependentNoShrinking; + final Map<DexReference, MutableItemsWithRules> dependentSoftPinned; final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule; final List<DelayedRootSetActionItem> delayedRootSetActionItems; @@ -1310,15 +1338,19 @@ Set<DexMethod> neverInline, Set<DexType> neverClassInline, MutableItemsWithRules noShrinking, + MutableItemsWithRules softPinned, Set<DexReference> noObfuscation, Map<DexReference, MutableItemsWithRules> dependentNoShrinking, + Map<DexReference, MutableItemsWithRules> dependentSoftPinned, Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, List<DelayedRootSetActionItem> delayedRootSetActionItems) { this.neverInline = neverInline; this.neverClassInline = neverClassInline; this.noShrinking = noShrinking; + this.softPinned = softPinned; this.noObfuscation = noObfuscation; this.dependentNoShrinking = dependentNoShrinking; + this.dependentSoftPinned = dependentSoftPinned; this.dependentKeepClassCompatRule = dependentKeepClassCompatRule; this.delayedRootSetActionItems = delayedRootSetActionItems; } @@ -1445,19 +1477,20 @@ return reference.apply(this::containsClass, this::containsField, this::containsMethod); } - public abstract void forEachClass(Consumer<DexType> consumer); + public abstract void forEachClass(Consumer<? super DexType> consumer); - public abstract void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer); + public abstract void forEachClass( + BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer); public abstract void forEachField(Consumer<? super DexField> consumer); public abstract void forEachField( BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer); - public abstract void forEachMember(Consumer<DexMember<?, ?>> consumer); + public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer); public abstract void forEachMember( - BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer); + BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer); public abstract void forEachMethod(Consumer<? super DexMethod> consumer); @@ -1554,13 +1587,18 @@ return methodsWithRules.containsKey(method); } + public void forEachReference(Consumer<DexReference> consumer) { + forEachClass(consumer); + forEachMember(consumer); + } + @Override - public void forEachClass(Consumer<DexType> consumer) { + public void forEachClass(Consumer<? super DexType> consumer) { classesWithRules.keySet().forEach(consumer); } @Override - public void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer) { + public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) { classesWithRules.forEach(consumer); } @@ -1575,13 +1613,14 @@ } @Override - public void forEachMember(Consumer<DexMember<?, ?>> consumer) { + public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) { forEachField(consumer); forEachMethod(consumer); } @Override - public void forEachMember(BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) { + public void forEachMember( + BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) { forEachField(consumer); forEachMethod(consumer); } @@ -1655,18 +1694,13 @@ } } - private void checkAssumeNoSideEffectsWarnings( - DexDefinition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) { - if (rule.getRuleType() == ProguardMemberType.METHOD && rule.isSpecific()) { - return; - } - if (item.isDexEncodedMethod()) { - DexEncodedMethod method = item.asDexEncodedMethod(); - if (method.holder() == options.itemFactory.objectType) { - OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition()); - assumeNoSideEffectsWarnings.computeIfAbsent(key, k -> new ArrayList<>()).add(method.method); - } - } + private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod( + DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) { + assert method.getHolderType() == options.dexItemFactory().objectType; + OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition()); + assumeNoSideEffectsWarnings + .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::slowCompareTo)) + .add(method.getReference()); } private boolean isWaitOrNotifyMethod(DexMethod method) { @@ -1680,50 +1714,27 @@ options.getProguardConfiguration() != null ? options.getProguardConfiguration().getDontWarnPatterns() : ProguardClassFilter.empty(); + if (dontWarnPatterns.matches(options.itemFactory.objectType)) { + // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or + // wait() anyway. + return; + } assumeNoSideEffectsWarnings.forEach( (originWithPosition, methods) -> { - boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod); - boolean dontWarnObject = dontWarnPatterns.matches(options.itemFactory.objectType); - StringBuilder message = new StringBuilder(); - message.append( - "The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards"); - message.append(" including the method(s) "); - for (int i = 0; i < methods.size(); i++) { - if (i > 0) { - message.append(i < methods.size() - 1 ? ", " : " and "); - } - message.append("`"); - message.append(methods.get(i).toSourceStringWithoutHolder()); - message.append("`."); + boolean matchesWaitOrNotifyMethods = + methods.stream().anyMatch(this::isWaitOrNotifyMethod); + if (!matchesWaitOrNotifyMethods) { + // We model the remaining methods on java.lang.Object, and thus there should be no need + // to warn in this case. + return; } - if (waitOrNotifyMethods) { - message.append(" This will most likely cause problems."); - } else { - message.append(" This is most likely not intended."); - } - if (waitOrNotifyMethods && !dontWarnObject) { - message.append(" Specify the methods more precisely."); - } else { - message.append(" Consider specifying the methods more precisely."); - } - Diagnostic diagnostic = - new StringDiagnostic( - message.toString(), - originWithPosition.getOrigin(), - originWithPosition.getPosition()); - if (waitOrNotifyMethods) { - if (!dontWarnObject) { - options.reporter.error(diagnostic); - } else { - options.reporter.warning(diagnostic); - } - - } else { - if (!dontWarnObject) { - options.reporter.warning(diagnostic); - } - } + options.reporter.warning( + new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder() + .addMatchedMethods(methods) + .setOrigin(originWithPosition.getOrigin()) + .setPosition(originWithPosition.getPosition()) + .build()); }); } @@ -1745,13 +1756,14 @@ public final Set<DexType> noStaticClassMerging; public final Set<DexReference> neverPropagateValue; public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects; - public final Map<DexReference, ProguardMemberRule> noSideEffects; - public final Map<DexReference, ProguardMemberRule> assumedValues; + public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects; + public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues; public final Set<DexReference> identifierNameStrings; public final Set<ProguardIfRule> ifRules; private RootSet( MutableItemsWithRules noShrinking, + MutableItemsWithRules softPinned, Set<DexReference> noObfuscation, ImmutableList<DexReference> reasonAsked, ImmutableList<DexReference> checkDiscarded, @@ -1771,9 +1783,10 @@ Set<DexType> noStaticClassMerging, Set<DexReference> neverPropagateValue, Map<DexReference, ProguardMemberRule> mayHaveSideEffects, - Map<DexReference, ProguardMemberRule> noSideEffects, - Map<DexReference, ProguardMemberRule> assumedValues, + Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects, + Map<DexMember<?, ?>, ProguardMemberRule> assumedValues, Map<DexReference, MutableItemsWithRules> dependentNoShrinking, + Map<DexReference, MutableItemsWithRules> dependentSoftPinned, Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, Set<DexReference> identifierNameStrings, Set<ProguardIfRule> ifRules, @@ -1782,8 +1795,10 @@ neverInline, neverClassInline, noShrinking, + softPinned, noObfuscation, dependentNoShrinking, + dependentSoftPinned, dependentKeepClassCompatRule, delayedRootSetActionItems); this.reasonAsked = reasonAsked; @@ -1831,7 +1846,8 @@ if (addNoShrinking) { noShrinking.addAll(consequentRootSet.noShrinking); } - addDependentItems(consequentRootSet.dependentNoShrinking); + addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking); + addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned); consequentRootSet.dependentKeepClassCompatRule.forEach( (type, rules) -> dependentKeepClassCompatRule.computeIfAbsent( @@ -1840,10 +1856,12 @@ } // Add dependent items that depend on -if rules. - private void addDependentItems(Map<DexReference, ? extends ItemsWithRules> dependentItems) { - dependentItems.forEach( + private static void addDependentItems( + Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd, + Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) { + dependentItemsToAdd.forEach( (reference, dependence) -> - dependentNoShrinking + dependentItemsToAddTo .computeIfAbsent(reference, x -> new MutableItemsWithRules()) .putAll(dependence)); } @@ -1855,11 +1873,15 @@ if (noObfuscation.contains(original)) { noObfuscation.add(rewritten); } - if (noSideEffects.containsKey(original)) { - noSideEffects.put(rewritten, noSideEffects.get(original)); - } - if (assumedValues.containsKey(original)) { - assumedValues.put(rewritten, assumedValues.get(original)); + if (original.isDexMember()) { + assert rewritten.isDexMember(); + DexMember<?, ?> originalMember = original.asDexMember(); + if (noSideEffects.containsKey(originalMember)) { + noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember)); + } + if (assumedValues.containsKey(originalMember)) { + assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember)); + } } } @@ -2075,16 +2097,20 @@ Set<DexMethod> neverInline, Set<DexType> neverClassInline, MutableItemsWithRules noShrinking, + MutableItemsWithRules softPinned, Set<DexReference> noObfuscation, Map<DexReference, MutableItemsWithRules> dependentNoShrinking, + Map<DexReference, MutableItemsWithRules> dependentSoftPinned, Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, List<DelayedRootSetActionItem> delayedRootSetActionItems) { super( neverInline, neverClassInline, noShrinking, + softPinned, noObfuscation, dependentNoShrinking, + dependentSoftPinned, dependentKeepClassCompatRule, delayedRootSetActionItems); }
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 6100000..de95f92 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -241,8 +241,8 @@ initializeMergeCandidates(classes); } - public VerticallyMergedClasses getMergedClasses() { - return new VerticallyMergedClasses(mergedClasses); + private VerticallyMergedClasses getMergedClasses() { + return new VerticallyMergedClasses(mergedClasses, mergedClassesInverse); } private void initializeMergeCandidates(Iterable<DexProgramClass> classes) { @@ -1484,7 +1484,7 @@ for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) { synthesizedBridge.updateMethodSignatures(this::fixupMethod); } - VerticalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses); + VerticalClassMergerGraphLens lens = lensBuilder.build(appView, getMergedClasses()); if (lens != null) { new AnnotationFixer(lens).run(appView.appInfo().classes()); } @@ -1728,6 +1728,11 @@ } @Override + public Iterable<DexType> getOriginalTypes(DexType type) { + throw new Unreachable(); + } + + @Override public DexField getOriginalFieldSignature(DexField field) { throw new Unreachable(); }
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 a95cfb4..a5481fd 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -14,10 +14,14 @@ import com.android.tools.r8.graph.GraphLens; import com.android.tools.r8.graph.GraphLens.NestedGraphLens; import com.android.tools.r8.graph.RewrittenPrototypeDescription; +import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; import com.android.tools.r8.ir.code.Invoke.Type; +import com.android.tools.r8.utils.IterableUtils; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import java.util.Collection; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; @@ -55,6 +59,7 @@ private final AppView<?> appView; + private VerticallyMergedClasses mergedClasses; private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>> contextualVirtualToDirectMethodMaps; private Set<DexMethod> mergedMethods; @@ -62,7 +67,7 @@ private VerticalClassMergerGraphLens( AppView<?> appView, - Map<DexType, DexType> typeMap, + VerticallyMergedClasses mergedClasses, Map<DexField, DexField> fieldMap, Map<DexMethod, DexMethod> methodMap, Set<DexMethod> mergedMethods, @@ -73,7 +78,7 @@ Map<DexMethod, DexMethod> originalMethodSignaturesForBridges, GraphLens previousLens) { super( - typeMap, + mergedClasses.getForwardMap(), methodMap, fieldMap, originalFieldSignatures, @@ -81,14 +86,24 @@ previousLens, appView.dexItemFactory()); this.appView = appView; + this.mergedClasses = mergedClasses; this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps; this.mergedMethods = mergedMethods; this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges; } + public VerticallyMergedClasses getMergedClasses() { + return mergedClasses; + } + @Override - public DexType getOriginalType(DexType type) { - return getPrevious().getOriginalType(type); + protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { + Collection<DexType> originalTypes = mergedClasses.getSourcesFor(previous); + Iterable<DexType> currentType = IterableUtils.singleton(previous); + if (originalTypes == null) { + return currentType; + } + return Iterables.concat(currentType, originalTypes); } @Override @@ -219,8 +234,8 @@ } public VerticalClassMergerGraphLens build( - AppView<?> appView, Map<DexType, DexType> mergedClasses) { - if (mergedClasses.isEmpty()) { + AppView<?> appView, VerticallyMergedClasses mergedClasses) { + if (mergedClasses.getForwardMap().isEmpty()) { return null; } BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
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 f4c003e..ea902d5 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -166,6 +166,7 @@ itemFactory = proguardConfiguration.getDexItemFactory(); enableTreeShaking = proguardConfiguration.isShrinking(); enableMinification = proguardConfiguration.isObfuscating(); + // TODO(b/171457102): Avoid the need for this. // -dontoptimize disables optimizations by flipping related flags. if (!proguardConfiguration.isOptimizing()) { disableAllOptimizations(); @@ -234,8 +235,7 @@ public boolean enableFieldBitAccessAnalysis = System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null; public boolean enableStaticClassMerging = true; - public boolean enableHorizontalClassMerging = - System.getProperty("com.android.tools.r8.horizontalClassMerging") != null; + public boolean enableHorizontalClassMerging = true; public boolean enableVerticalClassMerging = true; public boolean enableArgumentRemoval = true; public boolean enableUnusedInterfaceRemoval = true;
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java index 15a8198..57cf52b 100644 --- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java +++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -4,9 +4,12 @@ package com.android.tools.r8.utils; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; public class IterableUtils { @@ -53,4 +56,25 @@ public static <T> boolean isEmpty(Iterable<T> iterable) { return !iterable.iterator().hasNext(); } + + public static <T> Iterable<T> singleton(T element) { + return () -> Iterators.singletonIterator(element); + } + + public static <T> Iterable<T> prependSingleton(T t, Iterable<T> iterable) { + return Iterables.concat(singleton(t), iterable); + } + + public static <T, U> Iterable<U> flatMap( + Iterable<T> iterable, Function<? super T, Iterable<U>> map) { + return Iterables.concat(Iterables.transform(iterable, val -> map.apply(val))); + } + + public static <T> Iterable<T> emptyIf(Iterable<T> iterable, boolean condition) { + if (condition) { + return Collections.emptySet(); + } else { + return iterable; + } + } }
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 f941485..51821c0 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.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -63,4 +64,12 @@ } return true; } + + public static <T, R> R fold(Collection<T> items, R identity, BiFunction<R, T, R> acc) { + R result = identity; + for (T item : items) { + result = acc.apply(result, item); + } + return result; + } }
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java new file mode 100644 index 0000000..7f48f2a --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.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; + +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.TypeReference; +import java.util.Iterator; + +public class MethodReferenceUtils { + + public static String toSourceStringWithoutHolderAndReturnType(MethodReference methodReference) { + return toSourceString(methodReference, false, false); + } + + public static String toSourceString( + MethodReference methodReference, boolean includeHolder, boolean includeReturnType) { + StringBuilder builder = new StringBuilder(); + if (includeReturnType) { + builder.append(methodReference.getReturnType().getTypeName()).append(" "); + } + if (includeHolder) { + builder.append(methodReference.getHolderClass().getTypeName()).append("."); + } + builder.append(methodReference.getMethodName()).append("("); + Iterator<TypeReference> formalTypesIterator = methodReference.getFormalTypes().iterator(); + if (formalTypesIterator.hasNext()) { + builder.append(formalTypesIterator.next().getTypeName()); + while (formalTypesIterator.hasNext()) { + builder.append(", ").append(formalTypesIterator.next().getTypeName()); + } + } + return builder.append(")").toString(); + } +}
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 3c3d37c..7410339 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
@@ -45,6 +45,10 @@ return inverse.getOrDefault(value, Collections.emptySet()); } + public Set<K> getKeysOrNull(V value) { + return inverse.get(value); + } + public boolean isEmpty() { return backing.isEmpty(); }
diff --git a/src/test/java/com/android/tools/r8/ProguardVersion.java b/src/test/java/com/android/tools/r8/ProguardVersion.java index 8c9050a..20560fc 100644 --- a/src/test/java/com/android/tools/r8/ProguardVersion.java +++ b/src/test/java/com/android/tools/r8/ProguardVersion.java
@@ -20,6 +20,10 @@ this.version = version; } + public static ProguardVersion getLatest() { + return V7_0_0; + } + public Path getProguardScript() { Path scriptDirectory = Paths.get(ToolHelper.THIRD_PARTY_DIR).resolve("proguard"); if (this == V7_0_0) { @@ -33,8 +37,12 @@ return scriptDirectory.resolve("bin/proguard.sh"); } + public String getVersion() { + return version; + } + @Override public String toString() { - return "Proguard " + version.toString(); + return "Proguard " + version; } }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java index c03cd49..0b9c896 100644 --- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -31,6 +31,7 @@ @RunWith(VmTestRunner.class) public class R8RunExamplesAndroidOTest extends RunExamplesAndroidOTest<R8Command.Builder> { + private static final ArrayList<String> PROGUARD_OPTIONS = Lists.newArrayList( "-keepclasseswithmembers public class * {", " public static void main(java.lang.String[]);", @@ -161,7 +162,6 @@ @Override @Test public void lambdaDesugaringNPlus() throws Throwable { - expectThrowsWithHorizontalClassMerging(); test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods") .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) .withInterfaceMethodDesugaring(OffOrAuto.Auto) @@ -179,7 +179,7 @@ .withBuilderTransformation(ToolHelper::allowTestProguardOptions) .withBuilderTransformation( b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown())) - .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus")) + .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus")) .run(); } @@ -207,7 +207,7 @@ .run(); } - private void checkLambdaCount(CodeInspector inspector, int expectedCount, String prefix) { + private void checkLambdaCount(CodeInspector inspector, int maxExpectedCount, String prefix) { int count = 0; for (FoundClassSubject clazz : inspector.allClasses()) { if (clazz.isSynthesizedJavaLambdaClass() && @@ -215,7 +215,7 @@ count++; } } - assertEquals(expectedCount, count); + assertEquals(maxExpectedCount, count); } private void checkTestMultipleInterfacesCheckCastCount(
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java index 27c1570..f18a09f 100644 --- a/src/test/java/com/android/tools/r8/TestBase.java +++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1713,32 +1713,4 @@ .compile() .writeToZip(); } - - public static <E extends Throwable> void assertThrowsWithHorizontalClassMerging( - ThrowingAction<E> action) throws E { - try { - action.execute(); - if (isHorizontalClassMergingEnabled()) { - fail(); - } - } catch (Throwable throwable) { - if (!isHorizontalClassMergingEnabled()) { - throw throwable; - } - } - } - - public void expectThrowsWithHorizontalClassMerging() { - expectThrowsWithHorizontalClassMergingIf(true); - } - - public void expectThrowsWithHorizontalClassMergingIf(boolean condition) { - if (isHorizontalClassMergingEnabled() && condition) { - thrown.expect(Throwable.class); - } - } - - private static boolean isHorizontalClassMergingEnabled() { - return System.getProperty("com.android.tools.r8.horizontalClassMerging") != null; - } }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 9676011..3ad6107 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -189,6 +189,8 @@ Paths.get(LIBS_DIR, "library_desugar_conversions.zip"); public static final Path DESUGAR_LIB_JSON_FOR_TESTING = Paths.get("src/library_desugar/desugar_jdk_libs.json"); + public static final Path DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3 = + Paths.get("src/library_desugar/desugar_jdk_libs_alternative_3.json"); public static boolean isLocalDevelopment() { return System.getProperty("local_development", "0").equals("1");
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java index bf103e4..11d410f 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -47,7 +47,6 @@ @Test public void testStaticMethodRelaxation() throws Exception { - expectThrowsWithHorizontalClassMerging(); String expectedOutput = StringUtils.lines( "A::baz()", @@ -80,6 +79,7 @@ testForR8(parameters.getBackend()) .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableMemberValuePropagationAnnotations() .addKeepMainRule(mainClass) .addOptionsModification(o -> o.enableArgumentRemoval = enableArgumentRemoval)
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java b/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java index 01ec674..a6c40ff 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java
@@ -6,7 +6,9 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; +import com.android.tools.r8.NoHorizontalClassMerging; +@NoHorizontalClassMerging public class BB extends A { @NeverInline @NeverPropagateValue
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java index 2665473..dbdb906 100644 --- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java +++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -9,6 +9,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.TestParametersCollection; @@ -34,7 +35,6 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addProgramClasses(TestClass.class, A.class, B3.class, B4.class) .addProgramClassFileData( @@ -53,6 +53,7 @@ .addKeepMainRule(TestClass.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::inspect) @@ -134,6 +135,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class B2 extends A { // By hoisting B1.superBridge() to A this method bridge redundant. @@ -160,11 +162,13 @@ // instruction targeting B3.virtualBridge() that fails with a NoSuchMethodError in the Enqueuer, // but this should never be the case in practice. @NeverClassInline + @NoHorizontalClassMerging static class B3 extends A {} // The fact that this class declares superBridge() and virtualBridge() should not prevent // us from hoisting other bridges to A. @NeverClassInline + @NoHorizontalClassMerging static class B4 extends A { @NeverInline @@ -181,6 +185,7 @@ // This class declares the same bridges, but with different (bridge) behavior. They are candidates // for hoisting, but will not be hoisted because it is better to hoist the bridges declared on B1. @NeverClassInline + @NoHorizontalClassMerging static class B5 extends A { @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java new file mode 100644 index 0000000..2c4ac9d --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java
@@ -0,0 +1,89 @@ +// 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 static org.junit.Assert.assertEquals; + +import com.android.tools.r8.DataEntryResource; +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B; +import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main; +import com.android.tools.r8.classmerging.horizontal.ServiceLoaderTest.A; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.DataResourceConsumerForTesting; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +public class AdaptResourceFileContentsTest extends HorizontalClassMergingTestBase { + public AdaptResourceFileContentsTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void testR8() throws Exception { + DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); + CodeInspector codeInspector = + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer) + .enableNeverClassInliningAnnotations() + .addDataEntryResources( + DataEntryResource.fromString( + "foo.txt", Origin.unknown(), A.class.getTypeName(), B.class.getTypeName())) + .addKeepRules("-adaptresourcefilecontents foo.txt") + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, + inspector -> inspector.assertMergedInto(B.class, A.class)) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("a", "b") + .inspector(); + + ClassSubject aClassSubject = codeInspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + + ClassSubject bClassSubject = codeInspector.clazz(B.class); + assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging)); + + // Check that the class name has been rewritten. + String newClassBName = + (enableHorizontalClassMerging ? aClassSubject : bClassSubject).getFinalName(); + assertEquals( + dataResourceConsumer.get("foo.txt"), + ImmutableList.of(aClassSubject.getFinalName(), newClassBName)); + } + + @NeverClassInline + public static class A { + public A() { + System.out.println("a"); + } + } + + @NeverClassInline + public static class B { + public B() { + System.out.println("b"); + } + } + + public static class Main { + public static void main(String[] args) { + new A(); + new B(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java new file mode 100644 index 0000000..ea50082 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
@@ -0,0 +1,107 @@ +// 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.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.DataEntryResource; +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.DataResourceConsumerForTesting; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +public class AdaptVerticallyMergedResourceFileContentsTest extends HorizontalClassMergingTestBase { + public AdaptVerticallyMergedResourceFileContentsTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void testR8() throws Exception { + DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); + CodeInspector codeInspector = + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer) + .enableNeverClassInliningAnnotations() + .addDataEntryResources( + DataEntryResource.fromString( + "foo.txt", + Origin.unknown(), + Parent.class.getTypeName(), + A.class.getTypeName(), + B.class.getTypeName())) + .addKeepRules("-adaptresourcefilecontents foo.txt") + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, + inspector -> inspector.assertMergedInto(B.class, A.class)) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("a", "foo parent", "b") + .inspector(); + + assertThat(codeInspector.clazz(Parent.class), not(isPresent())); + + ClassSubject aClassSubject = codeInspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + + ClassSubject bClassSubject = codeInspector.clazz(B.class); + assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging)); + + // Check that the class name has been rewritten. + String newClassName = + (enableHorizontalClassMerging ? aClassSubject : bClassSubject).getFinalName(); + assertEquals( + dataResourceConsumer.get("foo.txt"), + ImmutableList.of(aClassSubject.getFinalName(), aClassSubject.getFinalName(), newClassName)); + } + + @NeverClassInline + public static class Parent { + @NeverInline + public void foo() { + System.out.println("foo parent"); + } + } + + @NeverClassInline + public static class A extends Parent { + public A() { + System.out.println("a"); + } + } + + @NeverClassInline + public static class B { + public B() { + System.out.println("b"); + } + } + + public static class Main { + @NeverInline + public static void parent(Parent p) { + p.foo(); + } + + public static void main(String[] args) { + parent(new A()); + new B(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java index 7dae8b6..565c1bd 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
@@ -44,16 +44,20 @@ ClassSubject bClassSubject = codeInspector.clazz(B.class); assertThat(bClassSubject, isPresent()); - methodSubject = bClassSubject.method("void", "foo"); - assertThat(methodSubject, isPackagePrivate()); + if (enableHorizontalClassMerging) { + methodSubject = bClassSubject.method("void", "foo$bridge"); + assertThat(methodSubject, isPackagePrivate()); + } assertThat( codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging)); ClassSubject dClassSubject = codeInspector.clazz(D.class); assertThat(dClassSubject, isPresent()); - methodSubject = dClassSubject.method("void", "foo"); - assertThat(methodSubject, isPublic()); + if (enableHorizontalClassMerging) { + methodSubject = dClassSubject.method("void", "foo$bridge"); + assertThat(methodSubject, isPublic()); + } ClassSubject eClassSubject = codeInspector.clazz(E.class); assertThat(eClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java index 9783627..185e090 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -60,7 +60,7 @@ assertThat( otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField())); - MethodSubject printSubject = aClassSubject.method("void", "print"); + MethodSubject printSubject = aClassSubject.method("void", "print$bridge"); assertThat(printSubject, isPresent()); assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
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 109b0bc..a30149f 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
@@ -67,7 +67,7 @@ assertThat( otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField())); - MethodSubject printSubject = aClassSubject.method("void", "print"); + MethodSubject printSubject = aClassSubject.method("void", "print$bridge"); assertThat(printSubject, isPresent()); assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java index fea20ed..f6def29 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -60,7 +60,7 @@ assertThat( otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField())); - MethodSubject printSubject = aClassSubject.method("void", "print"); + MethodSubject printSubject = aClassSubject.method("void", "print$bridge"); assertThat(printSubject, isPresent()); assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java new file mode 100644 index 0000000..a27fdbd --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
@@ -0,0 +1,78 @@ +// 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 org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.R8TestBuilder; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; +import org.junit.Test; + +public class InstantiatedAndUninstantiatedClassMergingTest extends HorizontalClassMergingTestBase { + + public InstantiatedAndUninstantiatedClassMergingTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + @Test + public void testR8() throws Exception { + test(testForR8(parameters.getBackend())); + } + + @Test + public void testR8Compat() throws Exception { + test(testForR8Compat(parameters.getBackend())); + } + + private <T extends R8TestBuilder<T>> void test(T testBuilder) throws Exception { + testBuilder + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + assertThat(inspector.clazz(Instantiated.class), isPresent()); + assertThat(inspector.clazz(Uninstantiated.class), isPresent()); + }) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Instantiated", "Uninstantiated"); + } + + static class TestClass { + + public static void main(String[] args) { + new Instantiated(); + Uninstantiated.method(); + } + } + + @NeverClassInline + public static final class Instantiated { + + Instantiated() { + System.out.println("Instantiated"); + } + } + + public static final class Uninstantiated { + + @NeverInline + static void method() { + System.out.println("Uninstantiated"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java index 605ff5e..2256e5d 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -57,7 +57,7 @@ assertThat( otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField())); - MethodSubject printSubject = aClassSubject.method("void", "print"); + MethodSubject printSubject = aClassSubject.method("void", "print$bridge"); assertThat(printSubject, isPresent()); assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java index 2fa8996..483e702 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java
@@ -54,6 +54,7 @@ } } + @NoVerticalClassMerging public abstract static class A implements I {} @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java new file mode 100644 index 0000000..00d6e59 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
@@ -0,0 +1,123 @@ +// 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.naming.retrace.StackTrace.isSame; +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 com.android.tools.r8.TestRuntime.CfRuntime; +import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import org.junit.Before; +import org.junit.Test; + +public class MergedVirtualMethodStackTraceTest extends HorizontalClassMergingTestBase { + public MergedVirtualMethodStackTraceTest( + TestParameters parameters, boolean enableHorizontalClassMerging) { + super(parameters, enableHorizontalClassMerging); + } + + public StackTrace expectedStackTrace; + + @Before + public void setup() throws Exception { + // Get the expected stack trace by running on the JVM. + expectedStackTrace = + testForJvm() + .addTestClasspath() + .run(CfRuntime.getSystemRuntime(), Program.Main.class) + .assertFailure() + .map(StackTrace::extractFromJvm); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(Program.class) + .addKeepMainRule(Program.Main.class) + .addKeepAttributeLineNumberTable() + .addKeepAttributeSourceFile() + .addDontWarn(C.class) + .addOptionsModification( + options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .addHorizontallyMergedClassesInspectorIf( + enableHorizontalClassMerging, + inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class)) + .run(parameters.getRuntime(), Program.Main.class) + .inspectStackTrace( + (stackTrace, codeInspector) -> { + assertThat(codeInspector.clazz(Program.A.class), isPresent()); + assertThat( + codeInspector.clazz(Program.B.class), + notIf(isPresent(), enableHorizontalClassMerging)); + if (enableHorizontalClassMerging) { + StackTrace expectedStackTraceWithMergedMethod = + StackTrace.builder() + .add(expectedStackTrace) + + .add( + 1, + StackTraceLine.builder() + .setClassName(Program.A.class.getTypeName()) + .setMethodName("foo$bridge") + .setFileName("Program.java") + .setFileName(getClass().getSimpleName() + ".java") + .setLineNumber(stackTrace.get(1).lineNumber) + .build()) + .build(); + assertThat(stackTrace, isSame(expectedStackTraceWithMergedMethod)); + } + }); + } + + public static class C { + public static void foo() { + System.out.println("foo c"); + } + } + + public static class Program { + @NeverClassInline + public static class A { + @NeverInline + public void foo() { + System.out.println("foo a"); + try { + // Undefined reference, prevents inlining. + C.foo(); + } catch (NoClassDefFoundError e) { + } + } + } + + @NeverClassInline + public static class B { + @NeverInline + public void foo() { + try { + // Undefined reference, prevents inlining. + C.foo(); + } catch (NoClassDefFoundError e) { + } + throw new RuntimeException(); + } + } + + public static class Main { + public static void main(String[] args) { + new A().foo(); + new B().foo(); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java index 412311e..de0e481 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java +++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.BaseCompilerCommand; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestCompileResult; import com.android.tools.r8.TestParameters; @@ -35,6 +36,8 @@ @RunWith(Parameterized.class) public class CompatKeepClassMemberNamesTestRunner extends TestBase { + private static final ProguardVersion PG = ProguardVersion.getLatest(); + private static Class<?> MAIN_CLASS = CompatKeepClassMemberNamesTest.class; private static Class<?> BAR_CLASS = CompatKeepClassMemberNamesTest.Bar.class; private static Collection<Class<?>> CLASSES = @@ -114,7 +117,7 @@ @Test public void testWithoutRulesPG() throws Exception { - testWithoutRules(testForProguard()); + testWithoutRules(testForProguard(PG)); } @Test @@ -157,7 +160,7 @@ @Test public void testWithMembersRulePG() throws Exception { - assertMembersRuleCompatResult(buildWithMembersRule(testForProguard()).compile()); + assertMembersRuleCompatResult(buildWithMembersRule(testForProguard(PG)).compile()); } @Test @@ -209,7 +212,7 @@ @Test public void testWithNonStaticMembersRulePG() throws Exception { - assertBarIsAbsent(buildWithNonStaticMembersRule(testForProguard()).compile()); + assertBarIsAbsent(buildWithNonStaticMembersRule(testForProguard(PG)).compile()); } @Test @@ -268,7 +271,7 @@ @Test public void testWithMembersRuleEnableMinificationPG() throws Exception { assertMembersRuleEnableMinificationCompatResult( - buildWithMembersRuleEnableMinification(testForProguard()).compile()); + buildWithMembersRuleEnableMinification(testForProguard(PG)).compile()); } @Test @@ -313,7 +316,7 @@ @Test public void testWithMembersStarRulePG() throws Exception { - testWithMembersStarRule(testForProguard()); + testWithMembersStarRule(testForProguard(PG)); } @Test @@ -362,7 +365,7 @@ @Test public void testWithMemberNamesRulePG() throws Exception { - assertMemberNamesRuleCompatResult(buildWithMemberNamesRule(testForProguard()).compile()); + assertMemberNamesRuleCompatResult(buildWithMemberNamesRule(testForProguard(PG)).compile()); } @Test @@ -375,7 +378,8 @@ @Test public void testWithMemberNamesRuleFullR8() throws Exception { - assertBarIsAbsent(buildWithMemberNamesRule(testForR8Compat(parameters.getBackend())).compile()); + assertMemberNamesRuleCompatResult( + buildWithMemberNamesRule(testForR8(parameters.getBackend())).compile()); } // Tests for "-keepclassmembernames" and *no* minification. @@ -419,7 +423,7 @@ @Test public void testWithMemberNamesRuleEnableMinificationPG() throws Exception { assertMemberNamesRuleEnableMinificationCompatResult( - buildWithMemberNamesRuleEnableMinification(testForProguard()).compile()); + buildWithMemberNamesRuleEnableMinification(testForProguard(PG)).compile()); } @Test @@ -432,7 +436,7 @@ @Test public void testWithMemberNamesRuleEnableMinificationFullR8() throws Exception { - assertBarIsAbsent( + assertMemberNamesRuleEnableMinificationCompatResult( buildWithMemberNamesRuleEnableMinification(testForR8(parameters.getBackend())).compile()); } }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java index 44bcc80..13d59dc 100644 --- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java +++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -2108,6 +2108,14 @@ } return false; } + if (isInLambdaClass(mirror, location)) { + // Lambda classes must be skipped since they are only wrappers around lambda code. + if (DEBUG_TESTS) { + System.out.println("Skipping lambda class wrapper method"); + } + wrapper.enqueueCommandFirst(stepCommand); + return true; + } if (isSyntheticMethod(mirror, location)) { if (DEBUG_TESTS) { System.out.println("Skipping synthetic method"); @@ -2152,6 +2160,11 @@ return false; } + private static boolean isInLambdaClass(VmMirror mirror, Location location) { + String classSig = mirror.getClassSignature(location.classID); + return classSig.contains("$$Lambda$"); + } + private static boolean isLambdaMethod(VmMirror mirror, Location location) { String methodName = mirror.getMethodName(location.classID, location.methodID); return methodName.startsWith("lambda$");
diff --git a/src/test/java/com/android/tools/r8/desugar/LambdaHasNonSyntheticMethodTest.java b/src/test/java/com/android/tools/r8/desugar/LambdaHasNonSyntheticMethodTest.java new file mode 100644 index 0000000..656cea6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/LambdaHasNonSyntheticMethodTest.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.desugar; + +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.lang.reflect.Method; +import java.lang.reflect.Modifier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class LambdaHasNonSyntheticMethodTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevels().build(); + } + + public LambdaHasNonSyntheticMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addInnerClasses(getClass()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + interface MyCallable<T> { + T call() throws Exception; + } + + static class TestClass { + + private static void assertNotNull(Object o) { + if (o == null) throw new AssertionError(); + } + + private static void assertSame(Object o1, Object o2) { + if (o1 != o2) throw new AssertionError(); + } + + private static void assertFalse(boolean value) { + if (value) throw new AssertionError(); + } + + private static <T> void assertLambdaMethodIsNotSynthetic(T instance, Class<?> iface) + throws Exception { + Method ifaceMethod = null; + for (Method method : iface.getDeclaredMethods()) { + if (Modifier.isAbstract(method.getModifiers())) { + ifaceMethod = method; + break; + } + } + assertNotNull(ifaceMethod); + Method lambdaMethod = + instance.getClass().getMethod(ifaceMethod.getName(), ifaceMethod.getParameterTypes()); + assertSame(ifaceMethod.getReturnType(), lambdaMethod.getReturnType()); + assertSame(instance.getClass(), lambdaMethod.getDeclaringClass()); + assertFalse(lambdaMethod.isSynthetic()); + assertFalse(lambdaMethod.isBridge()); + } + + public static void main(String[] args) throws Exception { + StringBuilder builder = new StringBuilder("Hello, world"); + MyCallable<String> instance = builder::toString; + assertLambdaMethodIsNotSynthetic(instance, MyCallable.class); + System.out.println(instance.call()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java index c2a8275..f71c916 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -60,7 +60,7 @@ : "Caught j$.io.UncheckedIOException"); } - DesugaredLibraryConfiguration configurationWithBufferedReader( + DesugaredLibraryConfiguration configurationAlternative3( InternalOptions options, boolean libraryCompilation, TestParameters parameters) { // Parse the current configuration and amend the configuration for BufferedReader.lines. The // configuration is the same for both program and library. @@ -69,28 +69,15 @@ options.reporter, libraryCompilation, parameters.getApiLevel().getLevel()) - .parse( - StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING), - builder -> { - if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) { - builder.putRewritePrefix( - "java.io.DesugarBufferedReader", "j$.io.DesugarBufferedReader"); - builder.putRewritePrefix( - "java.io.UncheckedIOException", "j$.io.UncheckedIOException"); - builder.putRetargetCoreLibMember( - "java.io.BufferedReader#lines", "java.io.DesugarBufferedReader"); - } - }); + .parse(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3)); } private void configurationForProgramCompilation(InternalOptions options) { - options.desugaredLibraryConfiguration = - configurationWithBufferedReader(options, false, parameters); + options.desugaredLibraryConfiguration = configurationAlternative3(options, false, parameters); } private void configurationForLibraryCompilation(InternalOptions options) { - options.desugaredLibraryConfiguration = - configurationWithBufferedReader(options, true, parameters); + options.desugaredLibraryConfiguration = configurationAlternative3(options, true, parameters); } @Test @@ -156,7 +143,7 @@ .addOptionsModification( options -> options.desugaredLibraryConfiguration = - configurationWithBufferedReader(options, false, parameters)) + configurationAlternative3(options, false, parameters)) .addInnerClasses(BufferedReaderTest.class) .setMinApi(parameters.getApiLevel()) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) @@ -184,7 +171,7 @@ .addOptionsModification( options -> options.desugaredLibraryConfiguration = - configurationWithBufferedReader(options, false, parameters)) + configurationAlternative3(options, false, parameters)) .addInnerClasses(BufferedReaderTest.class) .addKeepMainRule(TestClass.class) .setMinApi(parameters.getApiLevel())
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 b02b2e9..c7465ca 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
@@ -21,6 +21,8 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.InternalOptions; @@ -67,6 +69,11 @@ return buildDesugaredLibrary(apiLevel, "", false); } + protected Path buildDesugaredLibrary( + AndroidApiLevel apiLevel, Consumer<InternalOptions> optionsModifier) { + return buildDesugaredLibrary(apiLevel, "", false, ImmutableList.of(), optionsModifier); + } + protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel, String keepRules) { return buildDesugaredLibrary(apiLevel, keepRules, true); } @@ -186,6 +193,21 @@ return desugaredLib; } + protected DesugaredLibraryConfiguration configurationWithSupportAllCallbacksFromLibrary( + InternalOptions options, + boolean libraryCompilation, + TestParameters parameters, + boolean supportAllCallbacksFromLibrary) { + return new DesugaredLibraryConfigurationParser( + options.dexItemFactory(), + options.reporter, + libraryCompilation, + parameters.getApiLevel().getLevel()) + .parse( + StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING), + builder -> builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary)); + } + public interface KeepRuleConsumer extends StringConsumer { String get();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java index 14f3ded..e90d8d2 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import java.nio.file.Path; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; @@ -34,6 +35,7 @@ private final TestParameters parameters; private final boolean shrinkDesugaredLibrary; + private final boolean supportAllCallbacksFromLibrary; private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N; private static final String EXPECTED_RESULT = @@ -42,16 +44,24 @@ "forEach called", "action called from java consumer", "forEach called"); + private static final String FAILING_EXPECTED_RESULT = + StringUtils.lines( + "action called from j$ consumer", "forEach called", "action called from java consumer"); private static Path CUSTOM_LIB; - @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}") + @Parameters(name = "{0}, shrink: {1}, supportCallbacks: {2}") public static List<Object[]> data() { return buildParameters( - getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values()); + getConversionParametersUpToExcluding(MIN_SUPPORTED), + BooleanUtils.values(), + BooleanUtils.values()); } public ConversionIntroduceInterfaceMethodTest( - TestParameters parameters, boolean shrinkDesugaredLibrary) { + TestParameters parameters, + boolean shrinkDesugaredLibrary, + boolean supportAllCallbacksFromLibrary) { + this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary; this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; this.parameters = parameters; } @@ -75,6 +85,11 @@ .addLibraryClasses(CustomLibClass.class) .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .addOptionsModification( + opt -> + opt.desugaredLibraryConfiguration = + configurationWithSupportAllCallbacksFromLibrary( + opt, false, parameters, supportAllCallbacksFromLibrary)) .compile() .inspect(this::assertDoubleForEach) .inspect(this::assertWrapperMethodsPresent) @@ -85,11 +100,13 @@ shrinkDesugaredLibrary) .addRunClasspathFiles(CUSTOM_LIB) .run(parameters.getRuntime(), Executor.class) - .assertSuccessWithOutput(EXPECTED_RESULT); + .apply( + r -> + r.assertSuccessWithOutput( + supportAllCallbacksFromLibrary ? EXPECTED_RESULT : FAILING_EXPECTED_RESULT)); } private void assertDoubleForEach(CodeInspector inspector) { - System.out.println(inspector.allClasses().size()); FoundClassSubject myCollection = inspector.allClasses().stream() .filter( @@ -103,7 +120,7 @@ .collect(toSingle()); assertEquals( "Missing duplicated forEach", - 2, + supportAllCallbacksFromLibrary ? 2 : 1, IterableUtils.size( myCollection .getDexProgramClass() @@ -133,6 +150,11 @@ .addKeepMainRule(Executor.class) .addLibraryClasses(CustomLibClass.class) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .addOptionsModification( + opt -> + opt.desugaredLibraryConfiguration = + configurationWithSupportAllCallbacksFromLibrary( + opt, false, parameters, supportAllCallbacksFromLibrary)) .compile() .inspect(this::assertDoubleForEach) .inspect(this::assertWrapperMethodsPresent) @@ -143,7 +165,10 @@ shrinkDesugaredLibrary) .addRunClasspathFiles(CUSTOM_LIB) .run(parameters.getRuntime(), Executor.class) - .assertSuccessWithOutput(EXPECTED_RESULT); + .apply( + r -> + r.assertSuccessWithOutput( + supportAllCallbacksFromLibrary ? EXPECTED_RESULT : FAILING_EXPECTED_RESULT)); } static class CustomLibClass { @@ -197,7 +222,7 @@ @Override public Iterator<E> iterator() { - return null; + return (Iterator<E>) Collections.singletonList(null).iterator(); } @NotNull
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java index 1f433d4..812cd34 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
@@ -6,55 +6,84 @@ import static junit.framework.TestCase.assertEquals; +import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestRuntime.DexRuntime; import com.android.tools.r8.ToolHelper.DexVm; 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.Box; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.nio.file.Path; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +@RunWith(Parameterized.class) public class DuplicateAPIDesugaredLibTest extends DesugaredLibraryTestBase { + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N; + + @Parameterized.Parameters(name = "{0}, shrinkDesugaredLibrary: {1}") + public static List<Object[]> data() { + return buildParameters( + getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values()); + } + + public DuplicateAPIDesugaredLibTest(TestParameters parameters, boolean shrinkDesugaredLibrary) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + } + @Test public void testLib() throws Exception { - Box<Path> desugaredLibBox = new Box<>(); - Path customLib = - testForD8() - .addProgramClasses(CustomLibClass.class) - .setMinApi(AndroidApiLevel.B) - .compile() - .writeToZip(); - String stdOut = - testForD8() - .setMinApi(AndroidApiLevel.B) - .addProgramClasses(Executor.class) - .addLibraryClasses(CustomLibClass.class) - .enableCoreLibraryDesugaring(AndroidApiLevel.B) - .compile() - .addDesugaredCoreLibraryRunClassPath( - (AndroidApiLevel api) -> { - desugaredLibBox.set(this.buildDesugaredLibrary(api)); - return desugaredLibBox.get(); - }, - AndroidApiLevel.B) - .addRunClasspathFiles(customLib) - .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class) - .assertSuccess() - .getStdOut(); - assertDupMethod(new CodeInspector(desugaredLibBox.get())); - assertLines2By2Correct(stdOut); + for (Boolean supportAllCallbacksFromLibrary : BooleanUtils.values()) { + Box<Path> desugaredLibBox = new Box<>(); + Path customLib = + testForD8() + .addProgramClasses(CustomLibClass.class) + .setMinApi(AndroidApiLevel.B) + .compile() + .writeToZip(); + String stdOut = + testForD8() + .setMinApi(AndroidApiLevel.B) + .addProgramClasses(Executor.class) + .addLibraryClasses(CustomLibClass.class) + .enableCoreLibraryDesugaring(AndroidApiLevel.B) + .compile() + .addDesugaredCoreLibraryRunClassPath( + (AndroidApiLevel api) -> { + desugaredLibBox.set( + this.buildDesugaredLibrary( + api, + opt -> + opt.desugaredLibraryConfiguration = + configurationWithSupportAllCallbacksFromLibrary( + opt, true, parameters, supportAllCallbacksFromLibrary))); + return desugaredLibBox.get(); + }, + AndroidApiLevel.B) + .addRunClasspathFiles(customLib) + .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class) + .assertSuccess() + .getStdOut(); + assertDupMethod(new CodeInspector(desugaredLibBox.get()), supportAllCallbacksFromLibrary); + assertLines2By2Correct(stdOut); + } } - private void assertDupMethod(CodeInspector inspector) { + private void assertDupMethod(CodeInspector inspector, boolean supportAllCallbacksFromLibrary) { ClassSubject clazz = inspector.clazz("j$.util.concurrent.ConcurrentHashMap"); assertEquals( - 2, + supportAllCallbacksFromLibrary ? 2 : 1, clazz.virtualMethods().stream().filter(m -> m.getOriginalName().equals("forEach")).count()); }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg index da7bcba..e7560e1f 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
@@ -14,11 +14,11 @@ -dontwarn sun.misc.Unsafe # Application classes that will be serialized/deserialized over Gson --keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$Data { <fields>; } --keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableConcurrentHashMap --keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableHashMap --keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableMap --keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableConcurrentMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClass$Data { <fields>; } +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClass$NullableConcurrentHashMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClass$NullableHashMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClass$NullableMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClass$NullableConcurrentMap -keep class com.android.tools.r8.desugar.desugaredlibrary.gson.OptionalTestClass$Data { <fields>; } # Prevent R8 from stripping interface information from TypeAdapter, TypeAdapterFactory, @@ -28,6 +28,9 @@ -keep class * implements com.google.gson.JsonSerializer -keep class * implements com.google.gson.JsonDeserializer +# Prevent R8 from removing the generic signature of TypeToken +-keep,allowobfuscation class * extends com.google.gson.reflect.TypeToken + # Prevent R8 from leaving Data object members always null -keepclassmembers,allowobfuscation class * { @com.google.gson.annotations.SerializedName <fields>;
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java index a04b0c1..5a788bf 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java +++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -57,11 +57,8 @@ @BeforeClass public static void beforeAll() throws Exception { - assertThrowsWithHorizontalClassMerging( - () -> { - r8Lib11NoDesugar = compileR8(false); - r8Lib11Desugar = compileR8(true); - }); + r8Lib11NoDesugar = compileR8(false); + r8Lib11Desugar = compileR8(true); } private static Path compileR8(boolean desugar) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java index da5dc5c..8256c29 100644 --- a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java +++ b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.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.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.BooleanUtils; @@ -43,7 +44,6 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(InitializedClassesInInstanceMethodsTest.class) .addKeepMainRule(TestClass.class) @@ -54,6 +54,7 @@ }) .allowAccessModification() .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableInliningAnnotations() .setMinApi(parameters.getApiLevel()) .compile() @@ -137,6 +138,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class B { @NeverInline
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java index b7bff95..d0242b4 100644 --- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java +++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
@@ -29,7 +29,6 @@ List<String> additionalProguardConfiguration = ImmutableList.of( ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS + "GmsCore_proguard.config"); - Map<String, IntSet> methodProcessingIds = new ConcurrentHashMap<>(); AndroidApp app1 = buildAndTreeShakeFromDeployJar(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java index dc2b296..1375f54 100644 --- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java +++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -168,6 +168,10 @@ } private void verifyBuildersAreAbsent(CodeInspector outputInspector) { + // TODO(b/171441793): Should be optimized out but fails do to soft pinning of super class. + if (true) { + return; + } assertThat( outputInspector.clazz( "com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Builder"),
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java index 2eb4a58..17e3f64 100644 --- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java +++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
@@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; 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; @@ -41,13 +42,13 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(PutObjectWithFinalizeTest.class) .addKeepMainRule(TestClass.class) // The class staticizer does not consider the finalize() method. .addOptionsModification(options -> options.enableClassStaticizer = false) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNoVerticalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() @@ -148,5 +149,6 @@ static class B extends A {} + @NoHorizontalClassMerging static class C {} }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java index bb36308..e5746ec 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -6,12 +6,16 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersBuilder; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -31,12 +35,14 @@ private Backend backend; - @Parameterized.Parameters(name = "Backend: {0}") - public static Backend[] data() { - return ToolHelper.getBackends(); + @Parameterized.Parameters(name = "Backend: {1}") + public static List<Object[]> data() { + return buildParameters( + TestParametersBuilder.builder().withNoneRuntime().build(), ToolHelper.getBackends()); } - public MemberValuePropagationTest(TestBase.Backend backend) { + public MemberValuePropagationTest(TestParameters parameters, TestBase.Backend backend) { + parameters.assertNoneRuntime(); this.backend = backend; } @@ -56,14 +62,14 @@ public void testWriteOnlyField_dontoptimize() throws Exception { CodeInspector inspector = runR8(DONT_OPTIMIZE); ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME); - clazz.forAllMethods( - methodSubject -> { - // Dead code removal is not part of -dontoptimize. That is, even with -dontoptimize, - // field put instructions are gone with better dead code removal. - assertTrue( - methodSubject.streamInstructions().noneMatch( - i -> i.isInstancePut() || i.isStaticPut())); - }); + // With the support of 'allowshrinking' dontoptimize will now effectively pin all + // items that are not tree shaken out. The field operations will thus remain. + assertTrue(clazz.clinit().streamInstructions().anyMatch(InstructionSubject::isStaticPut)); + assertTrue( + clazz + .uniqueInstanceInitializer() + .streamInstructions() + .anyMatch(InstructionSubject::isInstancePut)); } private CodeInspector runR8(Path proguardConfig) throws Exception {
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 143f206..807b061 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
@@ -131,35 +131,32 @@ @Before public void generateR8Version() throws Exception { - assertThrowsWithHorizontalClassMerging( - () -> { - outputDir = temp.newFolder().toPath(); - Path mapFile = outputDir.resolve(DEFAULT_MAP_FILENAME); - generateR8Version(outputDir, mapFile, true); - String output; - if (parameters.isDexRuntime()) { - output = - ToolHelper.runArtNoVerificationErrors( - Collections.singletonList(outputDir.resolve(DEFAULT_DEX_FILENAME).toString()), - "inlining.Inlining", - builder -> {}, - parameters.getRuntime().asDex().getVm()); - } else { - assert parameters.isCfRuntime(); - output = - ToolHelper.runJava( - parameters.getRuntime().asCf(), - Collections.singletonList("-noverify"), - Collections.singletonList(outputDir), - "inlining.Inlining") - .stdout; - } + outputDir = temp.newFolder().toPath(); + Path mapFile = outputDir.resolve(DEFAULT_MAP_FILENAME); + generateR8Version(outputDir, mapFile, true); + String output; + if (parameters.isDexRuntime()) { + output = + ToolHelper.runArtNoVerificationErrors( + Collections.singletonList(outputDir.resolve(DEFAULT_DEX_FILENAME).toString()), + "inlining.Inlining", + builder -> {}, + parameters.getRuntime().asDex().getVm()); + } else { + assert parameters.isCfRuntime(); + output = + ToolHelper.runJava( + parameters.getRuntime().asCf(), + Collections.singletonList("-noverify"), + Collections.singletonList(outputDir), + "inlining.Inlining") + .stdout; + } - // Compare result with Java to make sure we have the same behavior. - ProcessResult javaResult = ToolHelper.runJava(getInputFile(), "inlining.Inlining"); - assertEquals(0, javaResult.exitCode); - assertEquals(javaResult.stdout, output); - }); + // Compare result with Java to make sure we have the same behavior. + ProcessResult javaResult = ToolHelper.runJava(getInputFile(), "inlining.Inlining"); + assertEquals(0, javaResult.exitCode); + assertEquals(javaResult.stdout, output); } private void checkAbsentBooleanMethod(ClassSubject clazz, String name) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java index aa4dd9a..3ac7e29 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -10,6 +10,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; @@ -41,11 +42,11 @@ @Test public void testR8() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(InvokeVirtualWithRefinedReceiverTest.class) .addKeepMainRule(MAIN) .enableNoVerticalClassMergingAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNeverClassInliningAnnotations() .enableInliningAnnotations() .addOptionsModification( @@ -148,6 +149,7 @@ } @NoVerticalClassMerging + @NoHorizontalClassMerging @NeverClassInline static class C extends A { @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java index 3fb859c..04cbc5f 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -10,6 +10,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.TestParametersCollection; @@ -39,12 +40,12 @@ @Test public void testR8() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(WithStaticizerTest.class) .addKeepMainRule(MAIN) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN) .assertSuccessWithOutputLines("Input") @@ -89,6 +90,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class Input { @NeverInline @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java index 7f5b905..bf78732 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java
@@ -62,15 +62,7 @@ .compile() .addRunClasspathFiles(program) .run(parameters.getRuntime(), TestClass.class) - .apply( - runResult -> { - if (parameters.isCfRuntime()) { - // Constant canonicalization is disabled for CF. - runResult.assertSuccessWithEmptyOutput(); - } else { - runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class); - } - }); + .assertSuccessWithEmptyOutput(); } private List<String> getAssumeValuesRule(int version) {
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 c6f7772..8d73c3a 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
@@ -64,7 +64,6 @@ @Test public void testTrivial() throws Exception { - expectThrowsWithHorizontalClassMerging(); Class<?> main = TrivialTestClass.class; Class<?>[] classes = { TrivialTestClass.class, @@ -85,6 +84,7 @@ testForR8(parameters.getBackend()) .addProgramClasses(classes) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableSideEffectAnnotations() .addKeepMainRule(main) .addKeepAttributes("LineNumberTable") @@ -221,7 +221,6 @@ @Test public void testInvalidatedRoot() throws Exception { - expectThrowsWithHorizontalClassMerging(); Class<?> main = InvalidRootsTestClass.class; Class<?>[] classes = { InvalidRootsTestClass.class, @@ -236,6 +235,7 @@ .addProgramClasses(classes) .enableProguardTestOptions() .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .addKeepMainRule(main) .addKeepAttributes("LineNumberTable") .addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java index 226d995e..2b29cea 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.ir.optimize.classinliner.invalidroot; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; public class InvalidRootsTestClass { private static int ID = 0; @@ -76,12 +77,14 @@ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next()); } + @NoHorizontalClassMerging public static class NeverReturnsNormally { public String foo() { throw new RuntimeException("NeverReturnsNormally::foo(): " + next()); } } + @NoHorizontalClassMerging public static class InitNeverReturnsNormally { public InitNeverReturnsNormally() { throw new RuntimeException("InitNeverReturnsNormally::init(): " + next()); @@ -131,6 +134,7 @@ } } + @NoHorizontalClassMerging public static class B { public String foo() { return "B::foo(" + next() + ")";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java index 7e0239c..2fd1735 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
@@ -4,6 +4,9 @@ package com.android.tools.r8.ir.optimize.classinliner.trivial; +import com.android.tools.r8.NoHorizontalClassMerging; + +@NoHorizontalClassMerging public class ClassWithFinal { public String doNothing() { return "nothing at all";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java index 287fe21..09b0ae3 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
@@ -62,8 +62,6 @@ ExistingException.class) .addKeepMainRule(TestClassCallingMethodWithNonExisting.class) .addKeepRules("-dontwarn " + NonExistingException.class.getTypeName()) - .allowDiagnosticWarningMessages( - parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) .setMinApi(parameters.getApiLevel()) .compile() .assertAllWarningMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java index 2012c6f..f5c7da8 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -54,6 +54,7 @@ .enableNeverClassInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() .enableSideEffectAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java index df009b4..9d4c185 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -180,7 +180,6 @@ "STATIC: String SimpleWithSideEffects.bar(String)", "STATIC: String SimpleWithSideEffects.foo()", "STATIC: String TrivialTestClass.next()", - "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE", "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE"), references(clazz, "testSimpleWithSideEffects", "void")); @@ -268,7 +267,6 @@ @Test public void testMoveToHost() throws Exception { - expectThrowsWithHorizontalClassMerging(); Class<?> main = MoveToHostTestClass.class; Class<?>[] classes = { NeverInline.class, @@ -287,6 +285,7 @@ testForR8(parameters.getBackend()) .addProgramClasses(classes) .enableInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableMemberValuePropagationAnnotations() .addKeepMainRule(main) .allowAccessModification() @@ -315,7 +314,6 @@ "STATIC: String movetohost.HostOkSideEffects.bar(String)", "STATIC: String movetohost.HostOkSideEffects.foo()", "STATIC: String movetohost.MoveToHostTestClass.next()", - "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE", "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"), references(clazz, "testOkSideEffects", "void"));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java index 4e5ce3a..cde24e8 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
@@ -5,7 +5,9 @@ package com.android.tools.r8.ir.optimize.staticizer.movetohost; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +@NoHorizontalClassMerging public class CandidateConflictMethod { @NeverInline public String foo() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java index 0834c000..eb8e2f6 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
@@ -5,7 +5,9 @@ package com.android.tools.r8.ir.optimize.staticizer.movetohost; import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +@NoHorizontalClassMerging public class CandidateOk { @NeverInline public String foo() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java index 4173bb2..ed970af 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java
@@ -9,6 +9,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assume.assumeTrue; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -50,10 +51,10 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(InstanceOfMethodSpecializationTest.class) .addKeepMainRule(TestClass.class) + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::inspect) @@ -157,6 +158,7 @@ } } + @NoHorizontalClassMerging public static class C extends A { @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java index e14bb7a..0de94c9 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
@@ -10,6 +10,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.BooleanUtils; @@ -48,7 +49,6 @@ @Test public void b139769782() throws Exception { - expectThrowsWithHorizontalClassMerging(); String expectedOutput = StringUtils.lines("A#foo(B)", "A#foo(B, Object)"); if (parameters.isCfRuntime() && !minification && !allowAccessModification) { @@ -63,6 +63,7 @@ .addKeepMainRule(TestClass.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .minification(minification) .allowAccessModification(allowAccessModification) .setMinApi(parameters.getRuntime()) @@ -122,6 +123,7 @@ } } + @NoHorizontalClassMerging static class B { @Override public String toString() { @@ -129,5 +131,6 @@ } } + @NoHorizontalClassMerging static class C {} }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java new file mode 100644 index 0000000..3a795be --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
@@ -0,0 +1,47 @@ +// Copyright (c) 2019, 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.kotlin.lambda; + +import static org.hamcrest.CoreMatchers.equalTo; + +import com.android.tools.r8.CompilationMode; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.KotlinTargetVersion; +import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase; +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 KotlinLambdaMergingDebugTest extends AbstractR8KotlinTestBase { + + private final TestParameters parameters; + private static final String FOLDER = "reprocess_merged_lambdas_kstyle"; + private static final String MAIN_CLASS = "reprocess_merged_lambdas_kstyle.MainKt"; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + public KotlinLambdaMergingDebugTest(TestParameters parameters) { + super(KotlinTargetVersion.JAVA_6); + this.parameters = parameters; + } + + @Test + public void testMergingKStyleLambdasAndReprocessingInDebug() throws Exception { + testForR8(parameters.getBackend()) + .setMode(CompilationMode.DEBUG) + .addProgramFiles(getKotlinJarFile(FOLDER)) + .addProgramFiles(getJavaJarFile(FOLDER)) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(MAIN_CLASS) + .allowDiagnosticWarningMessages() + .compile() + .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")); + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java index 8102aaf..2811374 100644 --- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java +++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -107,12 +107,13 @@ "-assumemayhavesideeffects class adaptresourcefilenames.pkg.innerpkg.D {", " void <init>();", "}", - "-neverclassinline class *"); + "-neverclassinline class *", + "-nohorizontalclassmerging class adaptresourcefilenames.pkg.C", + "-nohorizontalclassmerging class adaptresourcefilenames.pkg.innerpkg.D"); } @Test public void testEnabled() throws Exception { - expectThrowsWithHorizontalClassMerging(); DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting(); compileWithR8( getProguardConfigWithNeverInline(true, null),
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java index 6ababf1..d56503c 100644 --- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java +++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -63,7 +63,7 @@ @Test public void test() throws Exception { R8TestCompileResult compileResult = - testForR8(parameters.getBackend()) + testForR8Compat(parameters.getBackend()) .addProgramClasses(Main.class, Service.class, Foo.class, FooImpl.class) .addKeepMainRule(Main.class) .addKeepAttributes(
diff --git a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java index bb27506..23271e2 100644 --- a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java +++ b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
@@ -3,14 +3,13 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming.b130791310; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.ir.optimize.Inliner.Reason; @@ -108,45 +107,27 @@ this.parameters = parameters; } - private void inspect(CodeInspector inspector, boolean isR8) { + private void inspect(CodeInspector inspector) { ClassSubject holder = inspector.clazz(SomeLogic.class); assertThat(holder, isPresentAndNotRenamed()); MethodSubject someMethod = holder.uniqueMethodWithName("someMethod"); - if (isR8) { - if (onlyForceInlining) { - assertThat(someMethod, isPresentAndNotRenamed()); - } else { - assertThat(someMethod, not(isPresent())); - } - } else { - if (enableClassMerging) { - // Note that the method is not entirely gone, but merged to the implementer, along with some - // method signature modification. - assertThat(someMethod, not(isPresent())); - } else { - assertThat(someMethod, isPresentAndNotRenamed()); - } - } + assertThat(someMethod, isPresentAndNotRenamed()); } @Test public void testProguard() throws Exception { assumeFalse(onlyForceInlining); assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(CLASSES) .addKeepClassAndMembersRules(MAIN) .addKeepRules(RULES) .addTestingAnnotationsAsProgramClasses() .setMinApi(parameters.getApiLevel()) - .apply( - builder -> { - if (!enableClassMerging) { - builder.addKeepRules("-optimizations !class/merging/*"); - } - }) + .applyIf( + !enableClassMerging, builder -> builder.addKeepRules("-optimizations !class/merging/*")) .compile() - .inspect(inspector -> inspect(inspector, false)); + .inspect(this::inspect); } @Test @@ -158,14 +139,12 @@ .enableNeverClassInliningAnnotations() .setMinApi(parameters.getApiLevel()) .addOptionsModification(o -> o.enableVerticalClassMerging = enableClassMerging) - .apply( - builder -> { - if (onlyForceInlining) { + .applyIf( + onlyForceInlining, + builder -> builder.addOptionsModification( - o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE)); - } - }) + o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))) .compile() - .inspect(inspector -> inspect(inspector, true)); + .inspect(this::inspect); } }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java index 0da2866..a64903b 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -16,6 +16,7 @@ import com.google.common.base.Equivalence; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import org.hamcrest.Description; @@ -58,6 +59,11 @@ return this; } + public Builder map(int i, Function<StackTraceLine, StackTraceLine> map) { + stackTraceLines.set(i, map.apply(stackTraceLines.get(i))); + return this; + } + public StackTrace build() { return new StackTrace( stackTraceLines, @@ -123,6 +129,14 @@ this.lineNumber = lineNumber; } + public Builder builderOf() { + return new Builder() + .setFileName(fileName) + .setClassName(className) + .setLineNumber(lineNumber) + .setMethodName(methodName); + } + public static Builder builder() { return new Builder(); }
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java index e945182..65445a7 100644 --- a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java +++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -85,6 +85,7 @@ .addKeepAttributes(ProguardKeepAttributes.SIGNATURE) .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES) .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD) + .addKeepAllClassesRuleWithAllowObfuscation() .addKeepMainRule(Main.class) .addProgramClasses(Main.class) .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
diff --git a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java index 58e43469..927decb 100644 --- a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java +++ b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
@@ -62,7 +62,6 @@ @Test public void testR8() throws Exception { testForR8(parameters.getBackend()) - .allowDiagnosticWarningMessages(isDesugaring()) .addProgramClasses(PROGRAM) .addKeepMainRule(MAIN) .addClasspathClasses(LIBRARY)
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideInterfaceTest.java new file mode 100644 index 0000000..4e8bdd8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideInterfaceTest.java
@@ -0,0 +1,92 @@ +// 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.resolution.virtualtargets; + +import static org.hamcrest.CoreMatchers.containsString; + +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.TestRunResult; +import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a reproduction of b/171369796. +@RunWith(Parameterized.class) +public class PackagePrivateFinalOverrideInterfaceTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public PackagePrivateFinalOverrideInterfaceTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(ViewModel.class, I.class, Zoolander.class, Main.class) + .run(parameters.getRuntime(), Main.class) + .apply(this::assertResult); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(ViewModel.class, I.class, Zoolander.class, Main.class) + .addKeepClassAndMembersRules(ViewModel.class) + .addKeepClassAndMembersRules(I.class) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .run(parameters.getRuntime(), Main.class) + .apply(this::assertResult); + } + + public void assertResult(TestRunResult<?> runResult) { + if (parameters.isDexRuntime() + && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) { + runResult.assertFailureWithErrorThatMatches(containsString("overrides final")); + } else { + runResult.assertSuccessWithOutputLines("Zoolander::clear()"); + } + } + + public interface I { + void clear(); + } + + @NeverClassInline + public static class Zoolander extends ViewModel implements I { + + @Override + @NeverInline + public final void clear() { + System.out.println("Zoolander::clear()"); + } + } + + public static class Main { + + public static void main(String[] args) { + runClear(args.length == 0 ? new Zoolander() : null); + } + + public static void runClear(I i) { + i.clear(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java index b8b1647..6c43c35 100644 --- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java +++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -63,6 +63,7 @@ @Test public void matchMultipleTypeNamesOnLine() { + // TODO(b/171691292): We are not supporting having multiple class matches for. runRetraceTest( "%c\\s%c\\s%c", new StackTraceForTest() { @@ -79,7 +80,7 @@ @Override public List<String> retracedStackTrace() { - return ImmutableList.of("AA.AA.AA BB.BB.BB CC.CC.CC"); + return ImmutableList.of("AA.AA.AA b.b.b c.c.c"); } @Override @@ -91,6 +92,7 @@ @Test public void matchMultipleSlashNamesOnLine() { + // TODO(b/171691292): We are not supporting having multiple class matches for. runRetraceTest( "%C\\s%C\\s%C", new StackTraceForTest() { @@ -106,7 +108,7 @@ @Override public List<String> retracedStackTrace() { - return ImmutableList.of("AA/AA BB/BB CC/CC"); + return ImmutableList.of("AA/AA b/b/b c/c/c"); } @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java index edcb118..82d3915 100644 --- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java +++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -10,7 +10,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessagesImpl; @@ -136,15 +135,11 @@ @Test public void testAmbiguousStackTrace() { - // TODO(b/170797525): Remove when we have a fixed ordering. - assumeTrue(useRegExpParsing); runRetraceTest(new AmbiguousStackTrace()); } @Test public void testAmbiguousMissingLineStackTrace() { - // TODO(b/170797525): Remove when we have a fixed ordering. - assumeTrue(useRegExpParsing); runRetraceTest(new AmbiguousMissingLineStackTrace()); } @@ -190,8 +185,6 @@ @Test public void testUnknownSourceStackTrace() { - // TODO(b/170797525): Remove when we have a fixed ordering. - assumeTrue(useRegExpParsing); runRetraceTest(new UnknownSourceStackTrace()); }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java index 3e5b4ca..2e75b1d 100644 --- a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java +++ b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
@@ -6,7 +6,7 @@ import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION; import static junit.framework.TestCase.assertEquals; -import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessagesImpl; @@ -54,12 +54,12 @@ @Test public void testAmbiguousMissingLineVerbose() { + // TODO(b/169346455): Enable when separated parser. + assumeFalse(useRegExpParsing); runRetraceTest(new AmbiguousWithSignatureVerboseStackTrace()); } private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) { - // TODO(b/170293906): Remove assumption. - assumeTrue(useRegExpParsing); TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); RetraceCommand retraceCommand = RetraceCommand.builder(diagnosticsHandler)
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java index 1dfb3c9..45c103f 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java +++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
@@ -33,10 +33,10 @@ return Arrays.asList( "java.lang.IndexOutOfBoundsException", "\tat java.util.ArrayList.get(ArrayList.java:411)", - "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)", - "\t<OR> at com.android.tools.r8.Internal.void foo(int,int)(Internal.java)", + "\tat com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)", + "\t<OR> at com.android.tools.r8.Internal.void foo(int)(Internal.java)", "\t<OR> at com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)", - "\t<OR> at com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)"); + "\t<OR> at com.android.tools.r8.Internal.void foo(int,int)(Internal.java)"); } @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java index b85fe68..24d4436 100644 --- a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
@@ -8,6 +8,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; @@ -37,10 +38,10 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .enableInliningAnnotations() .enableNoVerticalClassMergingAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNeverClassInliningAnnotations() .addInnerClasses(EventuallyNonTargetedMethodTest.class) .addKeepMainRule(Main.class) @@ -72,6 +73,7 @@ } @NeverClassInline + @NoHorizontalClassMerging private static class C extends A { // Non-targeted override.
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 184fdc9..fd676a3 100644 --- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java +++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -183,7 +183,6 @@ @Test public void testConditionalEqualsKeepClassMembers() throws Exception { - expectThrowsWithHorizontalClassMerging(); GraphInspector referenceInspector = testForR8(Backend.CF) .enableGraphInspector()
diff --git a/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java index 42dab36..2b2e440 100644 --- a/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
@@ -9,6 +9,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; @@ -38,10 +39,10 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .enableInliningAnnotations() .enableNoVerticalClassMergingAnnotations() + .enableNoHorizontalClassMergingAnnotations() .enableNeverClassInliningAnnotations() .addInnerClasses(NonTargetedMethodTest.class) .addKeepMainRule(Main.class) @@ -72,6 +73,7 @@ } @NeverClassInline + @NoHorizontalClassMerging private static class C extends A { // Non-targeted override.
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java new file mode 100644 index 0000000..f7e50d9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,147 @@ +// 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.allowshrinking; + +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; +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.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; + +@RunWith(Parameterized.class) +public class ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest extends TestBase { + + private final TestParameters parameters; + private final boolean allowOptimization; + private final boolean allowObfuscation; + private final Shrinker shrinker; + + @Parameterized.Parameters(name = "{0}, opt:{1}, obf:{2}, {3}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), + BooleanUtils.values(), + BooleanUtils.values(), + ImmutableList.of(Shrinker.R8, Shrinker.PG)); + } + + public ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest( + TestParameters parameters, + boolean allowOptimization, + boolean allowObfuscation, + Shrinker shrinker) { + this.parameters = parameters; + this.allowOptimization = allowOptimization; + this.allowObfuscation = allowObfuscation; + this.shrinker = shrinker; + } + + String getExpected() { + return StringUtils.lines( + "A::foo", + // Reflective lookup of A::foo will only work if optimization and obfuscation are disabled. + Boolean.toString(!allowOptimization && !allowObfuscation), + "false"); + } + + @Test + public void test() throws Exception { + if (shrinker.isR8()) { + run(testForR8(parameters.getBackend())); + } else { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } + } + + public <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + String keepRule = + "-if class * -keepclassmembers,allowshrinking" + + (allowOptimization ? ",allowoptimization" : "") + + (allowObfuscation ? ",allowobfuscation" : "") + + " class <1> { java.lang.String foo(); java.lang.String bar(); }"; + builder + .addInnerClasses(ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.class) + .addKeepClassAndMembersRules(TestClass.class) + .addKeepRules(keepRule) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class, A.class.getTypeName()) + .assertSuccessWithOutput(getExpected()) + .inspect( + inspector -> { + ClassSubject aClass = inspector.clazz(A.class); + ClassSubject bClass = inspector.clazz(B.class); + // The class constants will force A and B to be retained, but not the methods. + assertThat(bClass, isPresentAndRenamed()); + assertThat(bClass.uniqueMethodWithName("foo"), not(isPresent())); + assertThat(bClass.uniqueMethodWithName("bar"), not(isPresent())); + + assertThat(aClass, isPresentAndRenamed()); + // The dependent rule with soft-pinning of bar never causes A::bar to be retained + // regardless of A and A::foo being retained. + assertThat(aClass.uniqueMethodWithName("bar"), not(isPresent())); + MethodSubject aFoo = aClass.uniqueMethodWithName("foo"); + if (allowOptimization) { + assertThat(aFoo, not(isPresent())); + } else { + assertThat(aFoo, isPresentAndRenamed(allowObfuscation)); + assertThat(inspector.clazz(TestClass.class).mainMethod(), invokesMethod(aFoo)); + } + }); + } + + static class A { + public String foo() { + return "A::foo"; + } + + public String bar() { + return "A::bar"; + } + } + + static class B { + public String foo() { + return "B::foo"; + } + + public String bar() { + return "B::bar"; + } + } + + static class TestClass { + + public static boolean hasFoo(String name) { + try { + return Class.forName(name).getDeclaredMethod("foo") != null; + } catch (Exception e) { + return false; + } + } + + public static void main(String[] args) { + // Direct call to A.foo, if optimization is not allowed it will be kept. + A a = new A(); + System.out.println(a.foo()); + // Reference to A should not retain A::foo when allowoptimization is set. + // Note: if using class constant A.class, PG will actually retain A::foo !? + System.out.println(hasFoo(a.getClass().getTypeName())); + // Reference to B should not retain B::foo regardless of allowoptimization. + System.out.println(hasFoo(B.class.getTypeName())); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepStaticMethodAllowShrinkingCompatibilityTest.java similarity index 77% copy from src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java copy to src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepStaticMethodAllowShrinkingCompatibilityTest.java index ca30a1f..5c99afd 100644 --- a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepStaticMethodAllowShrinkingCompatibilityTest.java
@@ -2,7 +2,7 @@ // 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.proguard; +package com.android.tools.r8.shaking.allowshrinking; import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; @@ -10,13 +10,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertTrue; -import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; 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; @@ -24,39 +25,48 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class AllowShrinkingCompatibilityTest extends TestBase { +public class ConditionalKeepStaticMethodAllowShrinkingCompatibilityTest extends TestBase { private final boolean allowOptimization; private final TestParameters parameters; - private final ProguardVersion proguardVersion; + private final Shrinker shrinker; @Parameters(name = "{1}, {2}, allow optimization: {0}") public static List<Object[]> data() { return buildParameters( BooleanUtils.values(), getTestParameters().withCfRuntimes().build(), - ProguardVersion.values()); + ImmutableList.of(Shrinker.R8, Shrinker.PG)); } - public AllowShrinkingCompatibilityTest( - boolean allowOptimization, TestParameters parameters, ProguardVersion proguardVersion) { + public ConditionalKeepStaticMethodAllowShrinkingCompatibilityTest( + boolean allowOptimization, TestParameters parameters, Shrinker shrinker) { this.allowOptimization = allowOptimization; this.parameters = parameters; - this.proguardVersion = proguardVersion; + this.shrinker = shrinker; } @Test public void test() throws Exception { - testForProguard(proguardVersion) + if (shrinker.isPG()) { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } else { + run(testForR8(parameters.getBackend())); + } + } + + private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + builder .addProgramClasses(TestClass.class, Companion.class) .addKeepMainRule(TestClass.class) .addKeepRules( - "-keep,allowshrinking" + "-if class " + + Companion.class.getTypeName() + + " -keep,allowshrinking" + (allowOptimization ? ",allowoptimization" : "") + " class " + Companion.class.getTypeName() + " { <methods>; }") - .addDontWarn(getClass()) .compile() .inspect( inspector -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java new file mode 100644 index 0000000..c9b3bc7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,137 @@ +// 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.allowshrinking; + +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; +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.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; + +@RunWith(Parameterized.class) +public class KeepAllowShrinkingCompatibilityTest extends TestBase { + + private final TestParameters parameters; + private final boolean allowOptimization; + private final boolean allowObfuscation; + private final Shrinker shrinker; + + @Parameterized.Parameters(name = "{0}, opt:{1}, obf:{2}, {3}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), + BooleanUtils.values(), + BooleanUtils.values(), + ImmutableList.of(Shrinker.R8, Shrinker.PG)); + } + + public KeepAllowShrinkingCompatibilityTest( + TestParameters parameters, + boolean allowOptimization, + boolean allowObfuscation, + Shrinker shrinker) { + this.parameters = parameters; + this.allowOptimization = allowOptimization; + this.allowObfuscation = allowObfuscation; + this.shrinker = shrinker; + } + + String getExpected() { + return StringUtils.lines( + "A::foo", + // Reflective lookup of A::foo will only work if optimization and obfuscation are disabled. + Boolean.toString(!allowOptimization && !allowObfuscation), + "false"); + } + + @Test + public void test() throws Exception { + if (shrinker.isR8()) { + run( + testForR8(parameters.getBackend()) + // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule. + .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)); + } else { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } + } + + public <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + String keepRule = + "-keep,allowshrinking" + + (allowOptimization ? ",allowoptimization" : "") + + (allowObfuscation ? ",allowobfuscation" : "") + + " class * { java.lang.String foo(); }"; + builder + .addInnerClasses(KeepAllowShrinkingCompatibilityTest.class) + .addKeepClassAndMembersRules(TestClass.class) + .addKeepRules(keepRule) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class, A.class.getTypeName()) + .assertSuccessWithOutput(getExpected()) + .inspect( + inspector -> { + ClassSubject aClass = inspector.clazz(A.class); + ClassSubject bClass = inspector.clazz(B.class); + // The class constants will force A and B to be retained, but not the foo methods. + assertThat(bClass, isPresentAndRenamed(allowObfuscation)); + assertThat(aClass, isPresentAndRenamed(allowObfuscation)); + assertThat(bClass.uniqueMethodWithName("foo"), not(isPresent())); + MethodSubject aFoo = aClass.uniqueMethodWithName("foo"); + if (allowOptimization) { + assertThat(aFoo, not(isPresent())); + } else { + assertThat(aFoo, isPresentAndRenamed(allowObfuscation)); + assertThat(inspector.clazz(TestClass.class).mainMethod(), invokesMethod(aFoo)); + } + }); + } + + static class A { + public String foo() { + return "A::foo"; + } + } + + static class B { + public String foo() { + return "B::foo"; + } + } + + static class TestClass { + + public static boolean hasFoo(String name) { + try { + return Class.forName(name).getDeclaredMethod("foo") != null; + } catch (Exception e) { + return false; + } + } + + public static void main(String[] args) { + // Direct call to A.foo, if optimization is not allowed it will be kept. + A a = new A(); + System.out.println(a.foo()); + // Reference to A should not retain A::foo when allowoptimization is set. + // Note: if using class constant A.class, PG will actually retain A::foo !? + System.out.println(hasFoo(a.getClass().getTypeName())); + // Reference to B should not retain B::foo regardless of allowoptimization. + System.out.println(hasFoo(B.class.getTypeName())); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingIncludeDescriptorClassesCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingIncludeDescriptorClassesCompatibilityTest.java new file mode 100644 index 0000000..44abb34 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingIncludeDescriptorClassesCompatibilityTest.java
@@ -0,0 +1,120 @@ +// 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.allowshrinking; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +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 KeepAllowShrinkingIncludeDescriptorClassesCompatibilityTest extends TestBase { + + private final TestParameters parameters; + private final Shrinker shrinker; + + @Parameters(name = "{0}, {1}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), ImmutableList.of(Shrinker.R8, Shrinker.PG)); + } + + public KeepAllowShrinkingIncludeDescriptorClassesCompatibilityTest( + TestParameters parameters, Shrinker shrinker) { + this.parameters = parameters; + this.shrinker = shrinker; + } + + @Test + public void test() throws Exception { + if (shrinker.isPG()) { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } else { + run(testForR8(parameters.getBackend())); + } + } + + private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + builder + .addProgramClasses(TestClass.class, A.class, B.class, SoftPinned.class) + .addKeepClassAndMembersRules(TestClass.class) + .addKeepRules( + "-keepclassmembers,allowshrinking,includedescriptorclasses class " + + SoftPinned.class.getTypeName() + + " { <methods>; }") + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("true") + .inspect( + inspector -> { + assertThat(inspector.clazz(TestClass.class), isPresent()); + + ClassSubject softPinnedClass = inspector.clazz(SoftPinned.class); + assertThat(softPinnedClass.uniqueMethodWithName("used"), isPresentAndNotRenamed()); + assertThat(softPinnedClass.uniqueMethodWithName("unused"), not(isPresent())); + + // SoftPinned.used(A) remains thus A must be present and not renamed. + assertThat(inspector.clazz(A.class), isPresentAndNotRenamed()); + + // TODO(b/171548534): Unexpectedly the behavior here is that the class B is also not + // renamed. It appears that both R8 and PG will eagerly mark the name as not needing + // to be renamed. + assertThat(inspector.clazz(B.class), isPresentAndNotRenamed()); + }); + } + + static class A { + @Override + public String toString() { + return getClass().getTypeName(); + } + } + + static class B { + @Override + public String toString() { + return getClass().getTypeName(); + } + } + + static class SoftPinned { + + public static void used(A a) { + System.out.println(a.toString().endsWith(System.nanoTime() > 0 ? "A" : "junk")); + } + + public static void unused(B b) { + System.out.println(b.toString().endsWith(System.nanoTime() > 0 ? "B" : "junk")); + } + } + + static class TestClass { + + // Kept helper so the classes A and B escape and prohibit removal. + public static void kept(Object o) { + if (System.nanoTime() < 0) { + System.out.println(o.toString()); + } + } + + public static void main(String[] args) { + A a = new A(); + B b = new B(); + kept(a); + kept(b); + SoftPinned.used(a); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java new file mode 100644 index 0000000..8b01419 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,158 @@ +// 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.allowshrinking; + +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; +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.FieldSubject; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepClassFieldsAllowShrinkingCompatibilityTest extends TestBase { + + private final TestParameters parameters; + private final boolean allowOptimization; + private final boolean allowObfuscation; + private final Shrinker shrinker; + + @Parameterized.Parameters(name = "{0}, opt:{1}, obf:{2}, {3}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), + BooleanUtils.values(), + BooleanUtils.values(), + ImmutableList.of(Shrinker.R8, Shrinker.PG)); + } + + public KeepClassFieldsAllowShrinkingCompatibilityTest( + TestParameters parameters, + boolean allowOptimization, + boolean allowObfuscation, + Shrinker shrinker) { + this.parameters = parameters; + this.allowOptimization = allowOptimization; + this.allowObfuscation = allowObfuscation; + this.shrinker = shrinker; + } + + String getExpected() { + return StringUtils.lines( + "A.foo", + // R8 will succeed in removing the field if allowoptimization is set. + Boolean.toString((shrinker.isPG() || !allowOptimization) && !allowObfuscation), + // R8 will always remove the unreferenced B.foo field. + Boolean.toString(shrinker.isPG() && !allowOptimization && !allowObfuscation)); + } + + @Test + public void test() throws Exception { + if (shrinker.isR8()) { + run( + testForR8(parameters.getBackend()) + // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule. + .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)); + } else { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } + } + + public <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + String keepRule = + "-keepclassmembers,allowshrinking" + + (allowOptimization ? ",allowoptimization" : "") + + (allowObfuscation ? ",allowobfuscation" : "") + + " class * { java.lang.String foo; java.lang.String bar; }"; + builder + .addInnerClasses(KeepClassFieldsAllowShrinkingCompatibilityTest.class) + .addKeepClassAndMembersRules(TestClass.class) + .addKeepRules(keepRule) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class, A.class.getTypeName()) + .assertSuccessWithOutput(getExpected()) + .inspect( + inspector -> { + ClassSubject aClass = inspector.clazz(A.class); + ClassSubject bClass = inspector.clazz(B.class); + // The class constants will force A and B to be retained but renamed. + assertThat(aClass, isPresentAndRenamed()); + assertThat(bClass, isPresentAndRenamed()); + + FieldSubject aFoo = aClass.uniqueFieldWithName("foo"); + FieldSubject aBar = aClass.uniqueFieldWithName("bar"); + FieldSubject bFoo = bClass.uniqueFieldWithName("foo"); + FieldSubject bBar = bClass.uniqueFieldWithName("bar"); + + if (allowOptimization) { + // PG fails to optimize out the referenced field. + assertThat(aFoo, notIf(isPresent(), shrinker.isR8())); + assertThat(aBar, not(isPresent())); + assertThat(bFoo, not(isPresent())); + assertThat(bBar, not(isPresent())); + } else { + assertThat(aFoo, isPresentAndRenamed(allowObfuscation)); + // TODO(b/171459868) It is inconsistent that the unused field A.bar is retained. + // This does not match the R8 behavior for an unused method, so there may be an + // optimization opportunity here. + // (See KeepClassMethodsAllowShrinkingCompatibilityTest regarding methods). + assertThat(aBar, isPresentAndRenamed(allowObfuscation)); + assertThat(inspector.clazz(TestClass.class).mainMethod(), accessesField(aFoo)); + if (shrinker.isR8()) { + assertThat(bFoo, not(isPresent())); + assertThat(bBar, not(isPresent())); + } else { + assertThat(bFoo, isPresentAndRenamed(allowObfuscation)); + assertThat(bBar, isPresentAndRenamed(allowObfuscation)); + } + } + }); + } + + static class A { + // Note: If the fields are final PG actually allows itself to inline the values. + public String foo = "A.foo"; + public String bar = "A.bar"; + } + + static class B { + public String foo = "B.foo"; + public String bar = "B.bar"; + } + + static class TestClass { + + public static boolean hasFoo(String name) { + try { + return Class.forName(name).getDeclaredField("foo") != null; + } catch (Exception e) { + return false; + } + } + + public static void main(String[] args) { + // Conditional instance to prohibit class inlining of A. + A a = args.length == 42 ? null : new A(); + // Direct use of A.foo, if optimization is not allowed it will be kept. + System.out.println(a.foo); + // Reference to A should not retain A.foo when allowoptimization is set. + System.out.println(hasFoo(a.getClass().getTypeName())); + // Reference to B should not retain B.foo regardless of allowoptimization. + System.out.println(hasFoo(B.class.getTypeName())); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java new file mode 100644 index 0000000..e7d5191 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,150 @@ +// 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.allowshrinking; + +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; +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.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; + +@RunWith(Parameterized.class) +public class KeepClassMethodsAllowShrinkingCompatibilityTest extends TestBase { + + private final TestParameters parameters; + private final boolean allowOptimization; + private final boolean allowObfuscation; + private final Shrinker shrinker; + + @Parameterized.Parameters(name = "{0}, opt:{1}, obf:{2}, {3}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), + BooleanUtils.values(), + BooleanUtils.values(), + ImmutableList.of(Shrinker.R8, Shrinker.PG)); + } + + public KeepClassMethodsAllowShrinkingCompatibilityTest( + TestParameters parameters, + boolean allowOptimization, + boolean allowObfuscation, + Shrinker shrinker) { + this.parameters = parameters; + this.allowOptimization = allowOptimization; + this.allowObfuscation = allowObfuscation; + this.shrinker = shrinker; + } + + String getExpected() { + return StringUtils.lines( + "A::foo", + // Reflective lookup of A::foo will only work if optimization and obfuscation are disabled. + Boolean.toString(!allowOptimization && !allowObfuscation), + "false"); + } + + @Test + public void test() throws Exception { + if (shrinker.isR8()) { + run( + testForR8(parameters.getBackend()) + // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule. + .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)); + } else { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } + } + + public <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + String keepRule = + "-keepclassmembers,allowshrinking" + + (allowOptimization ? ",allowoptimization" : "") + + (allowObfuscation ? ",allowobfuscation" : "") + + " class * { java.lang.String foo(); java.lang.String bar(); }"; + builder + .addInnerClasses(KeepClassMethodsAllowShrinkingCompatibilityTest.class) + .addKeepClassAndMembersRules(TestClass.class) + .addKeepRules(keepRule) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class, A.class.getTypeName()) + .assertSuccessWithOutput(getExpected()) + .inspect( + inspector -> { + ClassSubject aClass = inspector.clazz(A.class); + ClassSubject bClass = inspector.clazz(B.class); + // The class constants will force A and B to be retained, but not the methods. + assertThat(bClass, isPresentAndRenamed()); + assertThat(bClass.uniqueMethodWithName("foo"), not(isPresent())); + assertThat(bClass.uniqueMethodWithName("bar"), not(isPresent())); + + assertThat(aClass, isPresentAndRenamed()); + // The dependent rule with soft-pinning of bar never causes A::bar to be retained + // regardless of A and A::foo being retained. + assertThat(aClass.uniqueMethodWithName("bar"), not(isPresent())); + MethodSubject aFoo = aClass.uniqueMethodWithName("foo"); + if (allowOptimization) { + assertThat(aFoo, not(isPresent())); + } else { + assertThat(aFoo, isPresentAndRenamed(allowObfuscation)); + assertThat(inspector.clazz(TestClass.class).mainMethod(), invokesMethod(aFoo)); + } + }); + } + + static class A { + public String foo() { + return "A::foo"; + } + + public String bar() { + return "A::bar"; + } + } + + static class B { + public String foo() { + return "B::foo"; + } + + public String bar() { + return "B::bar"; + } + } + + static class TestClass { + + public static boolean hasFoo(String name) { + try { + return Class.forName(name).getDeclaredMethod("foo") != null; + } catch (Exception e) { + return false; + } + } + + public static void main(String[] args) { + // Direct call to A.foo, if optimization is not allowed it will be kept. + A a = new A(); + System.out.println(a.foo()); + // Reference to A should not retain A::foo when allowoptimization is set. + // Note: if using class constant A.class, PG will actually retain A::foo !? + System.out.println(hasFoo(a.getClass().getTypeName())); + // Reference to B should not retain B::foo regardless of allowoptimization. + System.out.println(hasFoo(B.class.getTypeName())); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepMethodNameCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepMethodNameCompatibilityTest.java new file mode 100644 index 0000000..4266e57 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepMethodNameCompatibilityTest.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.shaking.allowshrinking; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.ProguardVersion; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestShrinkerBuilder; +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 KeepMethodNameCompatibilityTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public KeepMethodNameCompatibilityTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testProguard() throws Exception { + assumeTrue(parameters.isCfRuntime()); + test(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass())); + } + + @Test + public void testR8() throws Exception { + test(testForR8(parameters.getBackend())); + } + + private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void test(T testBuilder) throws Exception { + testBuilder + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .addKeepRules( + "-keepclassmembernames class " + TestClass.class.getTypeName() + " { void test(); }") + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> + assertThat( + inspector.clazz(TestClass.class).uniqueMethodWithName("test"), isPresent())) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("foo"); + } + + static class TestClass { + + public static void main(String[] args) { + test(); + } + + static void test() { + System.out.println("foo"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepStaticFieldAllowShrinkingCompatibilityTest.java similarity index 64% copy from src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java copy to src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepStaticFieldAllowShrinkingCompatibilityTest.java index ca30a1f..2322f85 100644 --- a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepStaticFieldAllowShrinkingCompatibilityTest.java
@@ -2,21 +2,23 @@ // 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.proguard; +package com.android.tools.r8.shaking.allowshrinking; -import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertTrue; -import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.FieldSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; 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; @@ -24,30 +26,38 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class AllowShrinkingCompatibilityTest extends TestBase { +public class KeepStaticFieldAllowShrinkingCompatibilityTest extends TestBase { private final boolean allowOptimization; private final TestParameters parameters; - private final ProguardVersion proguardVersion; + private final Shrinker shrinker; @Parameters(name = "{1}, {2}, allow optimization: {0}") public static List<Object[]> data() { return buildParameters( BooleanUtils.values(), getTestParameters().withCfRuntimes().build(), - ProguardVersion.values()); + ImmutableList.of(Shrinker.R8, Shrinker.PG)); } - public AllowShrinkingCompatibilityTest( - boolean allowOptimization, TestParameters parameters, ProguardVersion proguardVersion) { + public KeepStaticFieldAllowShrinkingCompatibilityTest( + boolean allowOptimization, TestParameters parameters, Shrinker shrinker) { this.allowOptimization = allowOptimization; this.parameters = parameters; - this.proguardVersion = proguardVersion; + this.shrinker = shrinker; } @Test public void test() throws Exception { - testForProguard(proguardVersion) + if (shrinker.isPG()) { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } else { + run(testForR8(parameters.getBackend())); + } + } + + private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + builder .addProgramClasses(TestClass.class, Companion.class) .addKeepMainRule(TestClass.class) .addKeepRules( @@ -55,29 +65,29 @@ + (allowOptimization ? ",allowoptimization" : "") + " class " + Companion.class.getTypeName() - + " { <methods>; }") - .addDontWarn(getClass()) + + " { <fields>; }") .compile() .inspect( inspector -> { ClassSubject testClassSubject = inspector.clazz(TestClass.class); assertThat(testClassSubject, isPresent()); - ClassSubject companionClassSubject = inspector.clazz(Companion.class); - assertThat(companionClassSubject, notIf(isPresent(), allowOptimization)); - MethodSubject mainMethodSubject = testClassSubject.mainMethod(); - MethodSubject getMethodSubject = companionClassSubject.uniqueMethodWithName("get"); + ClassSubject companionClassSubject = inspector.clazz(Companion.class); + FieldSubject xFieldSubject = companionClassSubject.uniqueFieldWithName("x"); - if (allowOptimization) { + // PG fails to optimize fields regardless of keep flags. + if (allowOptimization && shrinker.isR8()) { + assertThat(companionClassSubject, not(isPresent())); assertTrue( testClassSubject .mainMethod() .streamInstructions() .allMatch(InstructionSubject::isReturnVoid)); } else { - assertThat(mainMethodSubject, invokesMethod(getMethodSubject)); - assertThat(getMethodSubject, isPresent()); + assertThat(companionClassSubject, isPresent()); + assertThat(mainMethodSubject, accessesField(xFieldSubject)); + assertThat(xFieldSubject, isPresent()); } }) .run(parameters.getRuntime(), TestClass.class) @@ -87,7 +97,7 @@ static class TestClass { public static void main(String[] args) { - if (Companion.get() != 42) { + if (Companion.x != 42) { System.out.println("Hello world!"); } } @@ -95,8 +105,6 @@ static class Companion { - static int get() { - return 42; - } + static int x = 42; } }
diff --git a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepStaticMethodAllowShrinkingCompatibilityTest.java similarity index 80% rename from src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java rename to src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepStaticMethodAllowShrinkingCompatibilityTest.java index ca30a1f..9dbdbb4 100644 --- a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepStaticMethodAllowShrinkingCompatibilityTest.java
@@ -2,7 +2,7 @@ // 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.proguard; +package com.android.tools.r8.shaking.allowshrinking; import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; @@ -10,13 +10,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertTrue; -import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; 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; @@ -24,30 +25,38 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class AllowShrinkingCompatibilityTest extends TestBase { +public class KeepStaticMethodAllowShrinkingCompatibilityTest extends TestBase { private final boolean allowOptimization; private final TestParameters parameters; - private final ProguardVersion proguardVersion; + private final Shrinker shrinker; @Parameters(name = "{1}, {2}, allow optimization: {0}") public static List<Object[]> data() { return buildParameters( BooleanUtils.values(), getTestParameters().withCfRuntimes().build(), - ProguardVersion.values()); + ImmutableList.of(Shrinker.R8, Shrinker.PG)); } - public AllowShrinkingCompatibilityTest( - boolean allowOptimization, TestParameters parameters, ProguardVersion proguardVersion) { + public KeepStaticMethodAllowShrinkingCompatibilityTest( + boolean allowOptimization, TestParameters parameters, Shrinker shrinker) { this.allowOptimization = allowOptimization; this.parameters = parameters; - this.proguardVersion = proguardVersion; + this.shrinker = shrinker; } @Test public void test() throws Exception { - testForProguard(proguardVersion) + if (shrinker.isPG()) { + run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass())); + } else { + run(testForR8(parameters.getBackend())); + } + } + + private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception { + builder .addProgramClasses(TestClass.class, Companion.class) .addKeepMainRule(TestClass.class) .addKeepRules( @@ -56,7 +65,6 @@ + " class " + Companion.class.getTypeName() + " { <methods>; }") - .addDontWarn(getClass()) .compile() .inspect( inspector -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/Shrinker.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/Shrinker.java new file mode 100644 index 0000000..112fd05 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/Shrinker.java
@@ -0,0 +1,38 @@ +// 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.allowshrinking; + +import com.android.tools.r8.ProguardVersion; + +class Shrinker { + + public static final Shrinker R8 = new Shrinker(null); + public static final Shrinker PG = new Shrinker(ProguardVersion.getLatest()); + public static final Shrinker PG5 = new Shrinker(ProguardVersion.V5_2_1); + public static final Shrinker PG6 = new Shrinker(ProguardVersion.V6_0_1); + public static final Shrinker PG7 = new Shrinker(ProguardVersion.V7_0_0); + + private final ProguardVersion proguardVersion; + + private Shrinker(ProguardVersion proguardVersion) { + this.proguardVersion = proguardVersion; + } + + boolean isR8() { + return proguardVersion == null; + } + + boolean isPG() { + return proguardVersion != null; + } + + public ProguardVersion getProguardVersion() { + return proguardVersion; + } + + @Override + public String toString() { + return isR8() ? "r8" : "pg" + proguardVersion.getVersion(); + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java index d0e3b9e..55556b4 100644 --- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java +++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.AssumeMayHaveSideEffects; import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.utils.codeinspector.ClassSubject; @@ -45,7 +46,6 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8Compat(backend) .enableNeverClassInliningAnnotations() .addProgramClasses(CLASSES) @@ -55,6 +55,7 @@ "-keepclassmembers class * { @**.*Annotation <fields>; }", "-keepattributes *Annotation*") .enableSideEffectAnnotations() + .enableNoHorizontalClassMergingAnnotations() .compile() .inspect( inspector -> { @@ -92,6 +93,8 @@ } class FieldAnnotationUse {} + +@NoHorizontalClassMerging class StaticFieldAnnotationUse {} @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java new file mode 100644 index 0000000..eb29f88 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
@@ -0,0 +1,141 @@ +// 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.assumenosideeffects; + +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.AssumeMayHaveSideEffects; +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.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +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 AssumeNoSideEffectsForJavaLangClassTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public AssumeNoSideEffectsForJavaLangClassTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .enableInliningAnnotations() + .enableSideEffectAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithEmptyOutput(); + } + + private void inspect(CodeInspector inspector) { + ClassSubject testClassSubject = inspector.clazz(TestClass.class); + assertThat(testClassSubject, isPresent()); + + inspectMethod(testClassSubject.uniqueMethodWithName("testModelingOfSideEffects"), false, false); + inspectMethod( + testClassSubject.uniqueMethodWithName("testModelingOfSideEffectsMaybeNull"), true, false); + inspectMethod( + testClassSubject.uniqueMethodWithName("testModelingOfSideEffectsMaybeSubclass"), + false, + true); + } + + private void inspectMethod( + MethodSubject methodSubject, boolean maybeNullReceiver, boolean maybeSubtype) { + assertThat(methodSubject, isPresent()); + assertThat( + methodSubject, onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("equals"))); + assertThat( + methodSubject, + onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("hashCode"))); + assertThat(methodSubject, onlyIf(maybeNullReceiver, invokesMethodWithName("getClass"))); + assertThat( + methodSubject, + onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("toString"))); + } + + static class TestClass { + + public static void main(String[] args) { + testModelingOfSideEffects(); + testModelingOfSideEffectsMaybeNull(); + testModelingOfSideEffectsMaybeSubclass(); + } + + @AssumeMayHaveSideEffects + @NeverInline + static void testModelingOfSideEffects() { + Object o = new Object(); + o.equals(new Object()); + o.hashCode(); + o.getClass(); + o.toString(); + } + + @NeverInline + static void testModelingOfSideEffectsMaybeNull() { + createNullableObject().equals(new Object()); + createNullableObject().hashCode(); + createNullableObject().getClass(); + createNullableObject().toString(); + } + + @NeverInline + static void testModelingOfSideEffectsMaybeSubclass() { + Object o = System.currentTimeMillis() > 0 ? new Object() : new A(); + o.equals(new Object()); + o.hashCode(); + o.getClass(); + o.toString(); + } + + static Object createNullableObject() { + return System.currentTimeMillis() > 0 ? new Object() : null; + } + } + + static class A { + + public A() { + super(); + } + + @Override + public boolean equals(Object obj) { + throw new RuntimeException(); + } + + @Override + public int hashCode() { + throw new RuntimeException(); + } + + @Override + public String toString() { + throw new RuntimeException(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java index 632863a..9d74c64 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.util.ArrayList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,7 +37,8 @@ testForR8(parameters.getBackend()) .addProgramClasses(TestClass.class) .addKeepMainRule(TestClass.class) - .addKeepRules("-assumenosideeffects class java.lang.Object { int hashCode() return 42; }") + .addKeepRules( + "-assumenosideeffects class java.util.ArrayList { int hashCode() return 42; }") .setMinApi(parameters.getApiLevel()) .compile() .inspect( @@ -58,7 +60,7 @@ static class TestClass { public static void main(String[] args) { - System.out.println(new Object().hashCode()); + System.out.println(new ArrayList<>().hashCode()); } } }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java index ecf26e9..640fd85 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java
@@ -15,13 +15,14 @@ @RunWith(Parameterized.class) public class AssumenosideeffectsPropagationWithSuperCallTest extends TestBase { private static final Class<?> MAIN = DelegatesUser.class; - private static final String JVM_OUTPUT = StringUtils.lines( - "[Base] message1", - "The end" - ); - private static final String OUTPUT_WITHOUT_MESSAGES = StringUtils.lines( - "The end" - ); + private static final String EXPECTED_OUTPUT = + StringUtils.lines("[Base] message1", "[Base] message2", "The end"); + + // With horizontal class merging enabled the method body for debug is cleared, because the + // forwarded call to the class specific implementation has no side effects. The call to the + // function from main persists. + private static final String EXPECTED_OUTPUT_WITH_HORIZONTAL_CLASS_MERGING = + StringUtils.lines("[Base] message2", "The end"); enum TestConfig { SPECIFIC_RULES, @@ -46,11 +47,13 @@ } } - public String expectedOutput() { + public String expectedOutput(TestParameters parameters) { switch (this) { case SPECIFIC_RULES: case NON_SPECIFIC_RULES_WITH_EXTENDS: - return JVM_OUTPUT; + return parameters.isCfRuntime() + ? EXPECTED_OUTPUT + : EXPECTED_OUTPUT_WITH_HORIZONTAL_CLASS_MERGING; default: throw new Unreachable(); } @@ -62,7 +65,8 @@ @Parameterized.Parameters(name = "{0} {1}") public static Collection<Object[]> data() { - return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values()); + return buildParameters( + getTestParameters().withAllRuntimesAndApiLevels().build(), TestConfig.values()); } public AssumenosideeffectsPropagationWithSuperCallTest( @@ -73,15 +77,14 @@ @Test public void testR8() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(AssumenosideeffectsPropagationWithSuperCallTest.class) .addKeepMainRule(MAIN) .addKeepRules(config.getKeepRules()) .noMinification() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN) - .assertSuccessWithOutput(config.expectedOutput()); + .assertSuccessWithOutput(config.expectedOutput(parameters)); } static class BaseClass { @@ -113,6 +116,10 @@ public static void main(String... args) { BaseClass instance = createBase(); instance.debug("message1"); + if (System.currentTimeMillis() > 0) { + instance = new BaseClass(); + } + instance.debug("message2"); System.out.println("The end"); } }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java index 5b0f344..8da32bc 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -7,17 +7,19 @@ import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; import static com.android.tools.r8.DiagnosticsMatcher.diagnosticPosition; +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf; +import static java.util.Collections.emptyList; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.ProguardVersion; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessages; import com.android.tools.r8.TestParameters; @@ -25,14 +27,12 @@ import com.android.tools.r8.position.TextPosition; import com.android.tools.r8.position.TextRange; 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.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.List; import org.hamcrest.Matcher; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -54,53 +54,45 @@ this.dontWarnObject = dontWarnObject; } - private void noCallToWait(CodeInspector inspector) { - ClassSubject classSubject = inspector.clazz(TestClass.class); - assertThat(classSubject, isPresent()); - classSubject.forAllMethods( - foundMethodSubject -> - foundMethodSubject - .instructions(InstructionSubject::isInvokeVirtual) - .forEach( - instructionSubject -> { - Assert.assertNotEquals( - "wait", instructionSubject.getMethod().name.toString()); - })); + private void checkIfWaitIsInvokedFromMain(CodeInspector inspector, boolean isR8) { + MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod(); + assertThat(mainMethodSubject, isPresent()); + assertThat( + mainMethodSubject, + onlyIf(isR8, invokesMethod("void", "java.lang.Object", "wait", emptyList()))); } private Matcher<Diagnostic> matchAssumeNoSideEffectsMessage() { return diagnosticMessage( containsString( - "The -assumenosideeffects rule matches methods on `java.lang.Object` with" - + " wildcards")); + "The -assumenosideeffects rule matches the following method(s) on java.lang.Object: ")); } private Matcher<Diagnostic> matchMessageForAllProblematicMethods() { return diagnosticMessage( allOf( - containsString("void notify()"), - containsString("void notifyAll()"), - containsString("void wait()"), - containsString("void wait(long)"), - containsString("void wait(long, int)"))); + containsString("notify()"), + containsString("notifyAll()"), + containsString("wait()"), + containsString("wait(long)"), + containsString("wait(long, int)"))); } private Matcher<Diagnostic> matchMessageForWaitMethods() { return diagnosticMessage( allOf( - containsString("void wait()"), - containsString("void wait(long)"), - containsString("void wait(long, int)"))); + containsString("wait()"), + containsString("wait(long)"), + containsString("wait(long, int)"))); } private void assertErrorsOrWarnings( TestDiagnosticMessages diagnostics, List<Matcher<Diagnostic>> matchers) { if (dontWarnObject) { + diagnostics.assertNoMessages(); + } else { diagnostics.assertOnlyWarnings(); diagnostics.assertWarningsMatch(matchers); - } else { - diagnostics.assertOnlyErrors(); - diagnostics.assertErrorsMatch(matchers); } } @@ -115,23 +107,18 @@ ImmutableList.of( allOf(matchAssumeNoSideEffectsMessage(), matchMessageForAllProblematicMethods())); - try { - testForR8(parameters.getBackend()) - .addProgramClasses(TestClass.class, B.class) - .addKeepMainRule(TestClass.class) - .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) - .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }") - .setMinApi(parameters.getApiLevel()) - .allowDiagnosticWarningMessages() - .compileWithExpectedDiagnostics( - diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) - .inspect(this::noCallToWait) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Hello, world"); - assertTrue(dontWarnObject); - } catch (CompilationFailedException e) { - assertFalse(dontWarnObject); - } + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) + .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }") + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages(!dontWarnObject) + .compileWithExpectedDiagnostics( + diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) + .inspect(inspector -> checkIfWaitIsInvokedFromMain(inspector, true)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); } @Test @@ -169,30 +156,24 @@ diagnosticOrigin(methodsRuleOrigin), diagnosticPosition(textRangeForString(methodsRule)))); - try { - testForR8(parameters.getBackend()) - .addProgramClasses(TestClass.class, B.class) - .addKeepMainRule(TestClass.class) - .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) - .apply( - b -> - b.getBuilder() - .addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin)) - .apply( - b -> - b.getBuilder() - .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin)) - .setMinApi(parameters.getApiLevel()) - .allowDiagnosticWarningMessages() - .compileWithExpectedDiagnostics( - diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) - .inspect(this::noCallToWait) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Hello, world"); - assertTrue(dontWarnObject); - } catch (CompilationFailedException e) { - assertFalse(dontWarnObject); - } + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) + .apply( + b -> + b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin)) + .apply( + b -> + b.getBuilder() + .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin)) + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages(!dontWarnObject) + .compileWithExpectedDiagnostics( + diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) + .inspect(inspector -> checkIfWaitIsInvokedFromMain(inspector, true)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); } @Test @@ -203,22 +184,9 @@ .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { hash*(); }") .setMinApi(parameters.getApiLevel()) - .allowDiagnosticWarningMessages(!dontWarnObject) - .compileWithExpectedDiagnostics( - diagnostics -> { - if (dontWarnObject) { - diagnostics.assertNoMessages(); - } else { - diagnostics.assertOnlyWarnings(); - diagnostics.assertWarningsMatch( - allOf( - matchAssumeNoSideEffectsMessage(), - diagnosticMessage(containsString("int hashCode()")))); - } - }) + .compile() .run(parameters.getRuntime(), TestClass.class) - // Code fails with exception if wait call is not removed. - .assertFailureWithErrorThatThrows(IllegalMonitorStateException.class); + .assertSuccessWithOutputLines("Hello, world"); } @Test @@ -227,23 +195,18 @@ ImmutableList.of( allOf(matchAssumeNoSideEffectsMessage(), matchMessageForAllProblematicMethods())); - try { - testForR8(parameters.getBackend()) - .addProgramClasses(TestClass.class, B.class) - .addKeepMainRule(TestClass.class) - .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) - .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }") - .setMinApi(parameters.getApiLevel()) - .allowDiagnosticWarningMessages() - .compileWithExpectedDiagnostics( - diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) - .inspect(this::noCallToWait) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Hello, world"); - assertTrue(dontWarnObject); - } catch (CompilationFailedException e) { - assertFalse(dontWarnObject); - } + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) + .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }") + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages(!dontWarnObject) + .compileWithExpectedDiagnostics( + diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) + .inspect(inspector -> checkIfWaitIsInvokedFromMain(inspector, true)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); } @Test @@ -251,36 +214,35 @@ List<Matcher<Diagnostic>> matchers = ImmutableList.of(allOf(matchAssumeNoSideEffectsMessage(), matchMessageForWaitMethods())); - try { - testForR8(parameters.getBackend()) - .addProgramClasses(TestClass.class, B.class) - .addKeepMainRule(TestClass.class) - .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) - .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }") - .setMinApi(parameters.getApiLevel()) - .allowDiagnosticWarningMessages() - .compileWithExpectedDiagnostics( - diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) - .inspect(this::noCallToWait) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Hello, world"); - assertTrue(dontWarnObject); - } catch (CompilationFailedException e) { - assertFalse(dontWarnObject); - } + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object")) + .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }") + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages(!dontWarnObject) + .compileWithExpectedDiagnostics( + diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) + .inspect(inspector -> checkIfWaitIsInvokedFromMain(inspector, true)) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); } @Test public void testR8WaitSpecificMethodMatch() throws Exception { assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject); + List<Matcher<Diagnostic>> matchers = ImmutableList.of(matchAssumeNoSideEffectsMessage()); + testForR8(parameters.getBackend()) .addProgramClasses(TestClass.class, B.class) .addKeepMainRule(TestClass.class) .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }") .setMinApi(parameters.getApiLevel()) - .compile() - .inspect(this::noCallToWait) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> assertErrorsOrWarnings(diagnostics, matchers)) + .inspect(inspector -> checkIfWaitIsInvokedFromMain(inspector, true)) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutputLines("Hello, world"); } @@ -313,7 +275,7 @@ assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject); assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(TestClass.class, B.class) .addKeepMainRule(TestClass.class) .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }") @@ -321,7 +283,7 @@ .setMinApi(parameters.getApiLevel()) .compile() .run(parameters.getRuntime(), TestClass.class) - .assertFailureWithErrorThatThrows(IllegalMonitorStateException.class); + .assertSuccessWithOutputLines("Hello, world"); } @Test @@ -329,24 +291,28 @@ assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject); assumeTrue(parameters.isCfRuntime()); - testForProguard() + testForProguard(ProguardVersion.getLatest()) .addProgramClasses(TestClass.class, B.class) .addKeepMainRule(TestClass.class) .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }") .addKeepRules("-dontwarn " + B152492625.class.getTypeName()) .setMinApi(parameters.getApiLevel()) .compile() - .inspect(this::noCallToWait) + .inspect(inspector -> checkIfWaitIsInvokedFromMain(inspector, false)) .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("Hello, world"); + .assertSuccessWithOutput("Hello"); } static class TestClass { public void m() throws Exception { - System.out.println("Hello, world"); - // test fails if wait is not removed. - wait(); + System.out.print("Hello"); + // test fails if wait is removed. + try { + wait(); + } catch (IllegalMonitorStateException e) { + System.out.println(", world"); + } } public static void main(String[] args) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java index 4d7e293..852409f 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.util.ArrayList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -36,7 +37,7 @@ testForR8(parameters.getBackend()) .addProgramClasses(TestClass.class) .addKeepMainRule(TestClass.class) - .addKeepRules("-assumevalues class java.lang.Object { int hashCode() return 42; }") + .addKeepRules("-assumevalues class java.util.ArrayList { int hashCode() return 42; }") .setMinApi(parameters.getApiLevel()) .compile() .inspect( @@ -58,7 +59,7 @@ static class TestClass { public static void main(String[] args) { - System.out.println(new Object().hashCode()); + System.out.println(new ArrayList<>().hashCode()); } } }
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/ClassSignaturesTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/ClassSignaturesTest.java deleted file mode 100644 index 7ca532d..0000000 --- a/src/test/java/com/android/tools/r8/shaking/attributes/ClassSignaturesTest.java +++ /dev/null
@@ -1,98 +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.shaking.attributes; - -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; - -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.shaking.ProguardKeepAttributes; -import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; -import java.util.Iterator; -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 ClassSignaturesTest extends TestBase { - - private final TestParameters parameters; - private final String EXPECTED = "Hello World!"; - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); - } - - public ClassSignaturesTest(TestParameters parameters) { - this.parameters = parameters; - } - - @Test - public void testRuntime() throws Exception { - testForRuntime(parameters) - .addProgramClasses(A.class, Main.class) - .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines(EXPECTED) - .inspect(this::inspect); - } - - @Test - public void testR8() throws Exception { - testForR8(parameters.getBackend()) - .addProgramClasses(A.class, Main.class) - .addKeepMainRule(Main.class) - .setMinApi(parameters.getApiLevel()) - .enableNeverClassInliningAnnotations() - .enableInliningAnnotations() - .addKeepAttributes( - ProguardKeepAttributes.SIGNATURE, - ProguardKeepAttributes.INNER_CLASSES, - ProguardKeepAttributes.ENCLOSING_METHOD) - .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines(EXPECTED) - .inspect(this::inspect); - } - - private void inspect(CodeInspector inspector) { - ClassSubject aClass = inspector.clazz(A.class); - assertThat(aClass, isPresent()); - assertEquals( - "Ljava/lang/Object;Ljava/lang/Iterable<" - + "Lcom/android/tools/r8/shaking/attributes/ClassSignaturesTest$Main;>;", - aClass.getFinalSignatureAttribute()); - } - - @NeverClassInline - public static class A implements Iterable<Main> { - - @Override - @NeverInline - public Iterator<Main> iterator() { - throw new RuntimeException("FooBar"); - } - } - - public static class Main { - - public static void main(String[] args) { - try { - new A().iterator(); - } catch (RuntimeException e) { - if (!e.getMessage().contains("FooBar")) { - throw e; - } - System.out.println("Hello World!"); - } - } - } -}
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 b441e6b..05f9731 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,8 +56,7 @@ @Test public void testKeptClassFieldAndMethodFull() throws Exception { - // TODO(b/170077516): The below should pass in false. - runTest(testForR8(parameters.getBackend()), true); + runTest(testForR8(parameters.getBackend()), false); } @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java index a3ad6d1..accb38b 100644 --- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java +++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -7,16 +7,31 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.android.tools.r8.R8FullTestBuilder; import com.android.tools.r8.TestBase; -import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestShrinkerBuilder; +import com.android.tools.r8.ThrowableConsumer; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; -import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +@RunWith(Parameterized.class) public class IncludeDescriptorClassesTest extends TestBase { + private final TestParameters testParameters; + + public IncludeDescriptorClassesTest(TestParameters parameters) { + this.testParameters = parameters; + } + + @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}") + public static List<Object[]> data() { + return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build()); + } + private class Result { final CodeInspector inspector; final CodeInspector proguardedInspector; @@ -26,7 +41,7 @@ this.proguardedInspector = proguardedInspector; } - void assertKept(Class clazz) { + void assertKept(Class<?> clazz) { assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent()); assertFalse(inspector.clazz(clazz.getCanonicalName()).isRenamed()); if (proguardedInspector != null) { @@ -35,14 +50,14 @@ } // NOTE: 'synchronized' is supposed to disable inlining of this method. - synchronized void assertRemoved(Class clazz) { + synchronized void assertRemoved(Class<?> clazz) { assertFalse(inspector.clazz(clazz.getCanonicalName()).isPresent()); if (proguardedInspector != null) { assertFalse(proguardedInspector.clazz(clazz).isPresent()); } } - void assertRenamed(Class clazz) { + void assertRenamed(Class<?> clazz) { assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent()); assertTrue(inspector.clazz(clazz.getCanonicalName()).isRenamed()); if (proguardedInspector != null) { @@ -59,22 +74,36 @@ NativeReturnType.class, StaticFieldType.class, InstanceFieldType.class); - private List<Class> mainClasses = ImmutableList.of( - MainCallMethod1.class, MainCallMethod2.class, MainCallMethod3.class); + private List<Class<?>> mainClasses = + ImmutableList.of(MainCallMethod1.class, MainCallMethod2.class, MainCallMethod3.class); - Result runTest(Class mainClass, Path proguardConfig) throws Exception { - List<Class<?>> classes = new ArrayList<>(applicationClasses); - classes.add(mainClass); + Result runTest(ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> configure) throws Exception { + return runTest(configure, ignore -> {}); + } - CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig)); + Result runTest( + ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> configure, + ThrowableConsumer<R8FullTestBuilder> configureR8) + throws Exception { + CodeInspector inspector = + testForR8(testParameters.getBackend()) + .setMinApi(testParameters.getApiLevel()) + .addProgramClasses(applicationClasses) + .apply(configure::accept) + .apply(configureR8) + .compile() + .inspector(); CodeInspector proguardedInspector = null; // Actually running Proguard should only be during development. if (isRunProguard()) { - Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar"); - Path proguardedMap = temp.newFolder().toPath().resolve("proguarded.map"); - ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig, proguardedMap); - proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardedMap); + proguardedInspector = + testForProguard() + .setMinApi(testParameters.getApiLevel()) + .addProgramClasses(applicationClasses) + .apply(configure::accept) + .compile() + .inspector(); } return new Result(inspector, proguardedInspector); @@ -83,20 +112,21 @@ @Test public void testNoIncludesDescriptorClasses() throws Exception { for (Class<?> mainClass : mainClasses) { - List<Class<?>> allClasses = new ArrayList<>(applicationClasses); - allClasses.add(mainClass); + List<String> proguardConfig = + ImmutableList.of( + "-keepclasseswithmembers class * { ", + " <fields>; ", + " native <methods>; ", + "} ", + "-allowaccessmodification "); - Path proguardConfig = writeTextToTempFile( - keepMainProguardConfiguration(mainClass), - "-keepclasseswithmembers class * { ", - " <fields>; ", - " native <methods>; ", - "} ", - "-allowaccessmodification ", - "-printmapping " - ); - - Result result = runTest(mainClass, proguardConfig); + Result result = + runTest( + builder -> + builder + .addProgramClasses(mainClass) + .addKeepMainRule(mainClass) + .addKeepRules(proguardConfig)); result.assertKept(ClassWithNativeMethods.class); // Return types are not removed as they can be needed for verification. @@ -113,17 +143,20 @@ @Test public void testKeepClassesWithMembers() throws Exception { for (Class mainClass : mainClasses) { - Path proguardConfig = writeTextToTempFile( - keepMainProguardConfiguration(mainClass), - "-keepclasseswithmembers,includedescriptorclasses class * { ", - " <fields>; ", - " native <methods>; ", - "} ", - "-allowaccessmodification ", - "-printmapping " - ); - - Result result = runTest(mainClass, proguardConfig); + List<String> proguardConfig = + ImmutableList.of( + "-keepclasseswithmembers,includedescriptorclasses class * { ", + " <fields>; ", + " native <methods>; ", + "} ", + "-allowaccessmodification "); + Result result = + runTest( + builder -> + builder + .addProgramClasses(mainClass) + .addKeepMainRule(mainClass) + .addKeepRules(proguardConfig)); // With includedescriptorclasses return type, argument type ad field type are not renamed. result.assertKept(ClassWithNativeMethods.class); @@ -137,17 +170,21 @@ @Test public void testKeepClassMembers() throws Exception { for (Class mainClass : mainClasses) { - Path proguardConfig = writeTextToTempFile( - keepMainProguardConfiguration(mainClass), - "-keepclassmembers,includedescriptorclasses class * { ", - " <fields>; ", - " native <methods>; ", - "} ", - "-allowaccessmodification ", - "-printmapping " - ); + List<String> proguardConfig = + ImmutableList.of( + "-keepclassmembers,includedescriptorclasses class * { ", + " <fields>; ", + " native <methods>; ", + "} ", + "-allowaccessmodification "); - Result result = runTest(mainClass, proguardConfig); + Result result = + runTest( + builder -> + builder + .addProgramClasses(mainClass) + .addKeepMainRule(mainClass) + .addKeepRules(proguardConfig)); // With includedescriptorclasses return type and argument type are not renamed. result.assertRenamed(ClassWithNativeMethods.class); @@ -160,20 +197,23 @@ @Test public void testKeepClassMemberNames() throws Exception { - expectThrowsWithHorizontalClassMerging(); for (Class<?> mainClass : mainClasses) { - Path proguardConfig = writeTextToTempFile( - keepMainProguardConfiguration(mainClass), - // same as -keepclassmembers,allowshrinking,includedescriptorclasses - "-keepclassmembernames,includedescriptorclasses class * { ", - " <fields>; ", - " native <methods>; ", - "} ", - "-allowaccessmodification ", - "-printmapping " - ); + List<String> proguardConfig = + ImmutableList.of( + // same as -keepclassmembers,allowshrinking,includedescriptorclasses + "-keepclassmembernames,includedescriptorclasses class * { ", + " <fields>; ", + " native <methods>; ", + "} ", + "-allowaccessmodification "); - Result result = runTest(mainClass, proguardConfig); + Result result = + runTest( + builder -> + builder + .addProgramClasses(mainClass) + .addKeepMainRule(mainClass) + .addKeepRules(proguardConfig)); boolean useNativeArgumentType = mainClass == MainCallMethod1.class || mainClass == MainCallMethod3.class;
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java index f68a434..5290df7 100644 --- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java +++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/NativeReturnType.java
@@ -4,6 +4,4 @@ package com.android.tools.r8.shaking.includedescriptorclasses; -public class NativeReturnType { - -} +public class NativeReturnType {}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java index 908f9f7..104f2cd 100644 --- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java +++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
@@ -9,6 +9,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -37,13 +38,13 @@ @Test public void test() throws Exception { - expectThrowsWithHorizontalClassMerging(); testForR8(parameters.getBackend()) .addInnerClasses(LibraryMethodOverrideTest.class) .addKeepMainRule(TestClass.class) .addOptionsModification(options -> options.enableTreeShakingOfLibraryMethodOverrides = true) .enableNeverClassInliningAnnotations() .enableNoVerticalClassMergingAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::verifyOutput) @@ -125,6 +126,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class DoesNotEscape { DoesNotEscape() { @@ -139,6 +141,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class DoesNotEscapeWithSubThatDoesNotOverride { DoesNotEscapeWithSubThatDoesNotOverride() { @@ -163,6 +166,7 @@ } @NeverClassInline + @NoHorizontalClassMerging static class DoesNotEscapeWithSubThatOverrides { DoesNotEscapeWithSubThatOverrides() { @@ -195,6 +199,7 @@ // EscapeWithSubThatOverridesAndEscapesSub is escaping from main(), unlike DoesNotEscapeWithSub- // ThatOverridesSub. @NeverClassInline + @NoHorizontalClassMerging static class DoesNotEscapeWithSubThatOverridesAndEscapes { DoesNotEscapeWithSubThatOverridesAndEscapes() {
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java index 578376e..e85f2d8 100644 --- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java +++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.code.InvokeStaticRange; import com.android.tools.r8.code.InvokeVirtual; import com.android.tools.r8.code.MoveResult; +import com.android.tools.r8.code.MoveResultObject; import com.android.tools.r8.code.MoveResultWide; import com.android.tools.r8.code.Return; import com.android.tools.r8.code.ReturnObject; @@ -42,7 +43,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.IdentityHashMap; import java.util.List; import java.util.function.Consumer; import org.junit.Assert; @@ -1299,24 +1299,23 @@ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); // The result from neither the div-int is never used. - MethodSignature signature = builder.addStaticMethod( - "void", - DEFAULT_METHOD_NAME, - ImmutableList.of("int", "int"), - 1, - " div-int v0, p0, p1", - " new-instance v0, Ljava/lang/StringBuilder;", - " invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V", - " return-void" - ); + MethodSignature signature = + builder.addStaticMethod( + "java.lang.Object", + DEFAULT_METHOD_NAME, + ImmutableList.of("int", "int"), + 1, + " div-int v0, p0, p1", + " new-instance v0, Ljava/lang/StringBuilder;", + " invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V", + " return-object v0"); builder.addMainMethod( 2, " const v0, 1", " const v1, 2", - " invoke-static { v0, v1 }, LTest;->method(II)V", - " return-void" - ); + " invoke-static { v0, v1 }, LTest;->method(II)Ljava/lang/Object;", + " return-void"); Consumer<InternalOptions> options = configureOptions( @@ -1324,20 +1323,6 @@ opts.outline.threshold = 1; opts.outline.minSize = 3; opts.outline.maxSize = 3; - - // Do not allow dead code elimination of the new-instance and invoke-direct - // instructions. - // This can be achieved by not assuming that StringBuilder is present. - DexItemFactory dexItemFactory = opts.itemFactory; - dexItemFactory.libraryTypesAssumedToBePresent = - new HashSet<>(dexItemFactory.libraryTypesAssumedToBePresent); - dexItemFactory.libraryTypesAssumedToBePresent.remove( - dexItemFactory.stringBuilderType); - // ... and not assuming that StringBuilder.<init>() cannot have side effects. - dexItemFactory.libraryMethodsWithoutSideEffects = - new IdentityHashMap<>(dexItemFactory.libraryMethodsWithoutSideEffects); - dexItemFactory.libraryMethodsWithoutSideEffects.remove( - dexItemFactory.stringBuilderMethods.defaultConstructor); }); AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); @@ -1347,9 +1332,10 @@ // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); DexCode code = method.getCode().asDexCode(); - assertEquals(2, code.instructions.length); + assertEquals(3, code.instructions.length); assertTrue(code.instructions[0] instanceof InvokeStatic); - assertTrue(code.instructions[1] instanceof ReturnVoid); + assertTrue(code.instructions[1] instanceof MoveResultObject); + assertTrue(code.instructions[2] instanceof ReturnObject); InvokeStatic invoke = (InvokeStatic) code.instructions[0]; assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java index 86a21c1..ed4d158 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -6,7 +6,9 @@ import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; +import java.util.List; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; @@ -71,10 +73,93 @@ }; } + public static Matcher<MethodSubject> invokesMethod( + String returnType, String holderType, String methodName, List<String> parameterTypes) { + return new TypeSafeMatcher<MethodSubject>() { + @Override + protected boolean matchesSafely(MethodSubject subject) { + if (!subject.isPresent()) { + return false; + } + if (!subject.getMethod().hasCode()) { + return false; + } + return subject + .streamInstructions() + .anyMatch(isInvokeWithTarget(returnType, holderType, methodName, parameterTypes)); + } + + @Override + public void describeTo(Description description) { + StringBuilder text = + new StringBuilder("invokes method `") + .append(returnType != null ? returnType : "*") + .append(" ") + .append(holderType != null ? holderType : "*") + .append(".") + .append(methodName != null ? methodName : "*") + .append("("); + if (parameterTypes != null) { + text.append( + parameterTypes.stream() + .map(parameterType -> parameterType != null ? parameterType : "*") + .collect(Collectors.joining(", "))); + } else { + text.append("..."); + } + text.append(")`"); + description.appendText(text.toString()); + } + + @Override + public void describeMismatchSafely(final MethodSubject subject, Description description) { + description.appendText("method did not"); + } + }; + } + + public static Matcher<MethodSubject> invokesMethodWithName(String name) { + return invokesMethod(null, null, name, null); + } + public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) { return instruction -> instruction.isInvoke() && instruction.getMethod() == target; } + public static Predicate<InstructionSubject> isInvokeWithTarget( + String returnType, String holderType, String methodName, List<String> parameterTypes) { + return instruction -> { + if (!instruction.isInvoke()) { + return false; + } + DexMethod invokedMethod = instruction.getMethod(); + if (returnType != null + && !invokedMethod.getReturnType().toSourceString().equals(returnType)) { + return false; + } + if (holderType != null + && !invokedMethod.getHolderType().toSourceString().equals(holderType)) { + return false; + } + if (methodName != null && !invokedMethod.getName().toSourceString().equals(methodName)) { + return false; + } + if (parameterTypes != null) { + if (parameterTypes.size() != invokedMethod.getArity()) { + return false; + } + for (int i = 0; i < parameterTypes.size(); i++) { + String parameterType = parameterTypes.get(i); + if (parameterType != null + && !invokedMethod.getParameter(i).toSourceString().equals(parameterType)) { + return false; + } + } + } + return true; + }; + } + public static Predicate<InstructionSubject> isFieldAccessWithTarget(DexField target) { return instruction -> instruction.isFieldAccess() && instruction.getField() == target; }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java index 98c8562..29bebe1 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -713,6 +713,10 @@ } } + public static <T> Matcher<T> onlyIf(boolean condition, Matcher<T> matcher) { + return notIf(matcher, !condition); + } + public static <T> Matcher<T> notIf(Matcher<T> matcher, boolean condition) { if (condition) { return not(matcher);
diff --git a/tools/r8_release.py b/tools/r8_release.py index ce56269..cc4bebd 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py
@@ -15,7 +15,7 @@ import utils -R8_DEV_BRANCH = '2.3' +R8_DEV_BRANCH = '3.0' R8_VERSION_FILE = os.path.join( 'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java') THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/test.py b/tools/test.py index 64d2ae0..d7987a7 100755 --- a/tools/test.py +++ b/tools/test.py
@@ -142,9 +142,6 @@ result.add_option('--worktree', default=False, action='store_true', help='Tests are run in worktree and should not use gradle user home.') - result.add_option('--horizontal-class-merging', '--horizontal_class_merging', - help='Enable horizontal class merging.', - default=False, action='store_true') result.add_option('--runtimes', default=None, help='Test parameter runtimes to use, separated by : (eg, none:jdk9).' @@ -297,9 +294,6 @@ return_code = gradle.RunGradle(gradle_args, throw_on_failure=False) return archive_and_return(return_code, options) - if options.horizontal_class_merging: - gradle_args.append('-PhorizontalClassMerging') - # Now run tests on selected runtime(s). if options.runtimes: if options.dex_vm != 'default':