Merge commit 'd283ff9b1e3420e67f7decf700643baf2d791ea9' into dev-release
diff --git a/build.gradle b/build.gradle
index bce1569..171ede1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -506,6 +506,8 @@
// Check if running with the JDK location from tools/jdk.py.
if (OperatingSystem.current().isWindows()) {
println "NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome
+ compileJava.options.encoding = "UTF-8"
+ compileTestJava.options.encoding = "UTF-8"
} else {
def javaHomeOut = new StringBuilder()
def javaHomeErr = new StringBuilder()
@@ -2354,7 +2356,7 @@
allprojects {
tasks.withType(Exec) {
doFirst {
- println commandLine
+ println commandLine.join(' ')
}
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodList.java b/src/main/java/com/android/tools/r8/BackportedMethodList.java
new file mode 100644
index 0000000..befc767
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BackportedMethodList.java
@@ -0,0 +1,111 @@
+// 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;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Tool to extract the list of methods which is backported by the D8 and R8 compilers.
+ *
+ * <p>The D8 and R8 compilers will backport some simple Java APIs which are not present on all API
+ * levels. One example of this is the static method <code>int Integer.divideUnsigned(int a, int b)
+ * </code> which was added from API level 26.
+ *
+ * <p>As these backported methods is supported on all API levels, tools like linters and code
+ * checkers need this information to avoid false negatives when analyzing code.
+ *
+ * <p>This tool will generate a list of all the backported methods for the associated version of D8
+ * and R8.
+ *
+ * <p>This tool will <strong>not</strong> provide information about the APIs supported when using
+ * library desugaring. That information is provided in the dependencies used for library desugaring.
+ * However, in place of library desugaring backporting will be able to backport additional methods.
+ * If library desugaring is used, then passing information about that to this tool will provide the
+ * more precise list. See b/149078312.
+ *
+ * <p>The tool is invoked by calling {@link #run(BackportedMethodListCommand)
+ * BackportedMethodList.run} with an appropriate {@link BackportedMethodListCommand}.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ * BackportedMethodList.run(BackportedMethodListCommand.builder()
+ * .setMinApiLevel(apiLevel)
+ * .setOutputPath(Paths.get("methods-list.txt"))
+ * .build());
+ * </pre>
+ *
+ * The above generates the list of backported methods for a compilation with a min API of <code>
+ * apiLevel</code> into the file <code>methods-list.txt</code>.
+ */
+@Keep
+public class BackportedMethodList {
+
+ static final String USAGE_MESSAGE =
+ StringUtils.joinLines(
+ "Usage: BackportedMethodList [options]",
+ " Options are:",
+ " --output <file> # Output result in <file>.",
+ " --min-api <number> # Minimum Android API level for the application",
+ " --desugared-lib <file> # Desugared library configuration (JSON from the",
+ " # configuration)",
+ " --lib <file> # The compilation SDK library (android.jar)",
+ " --version # Print the version of BackportedMethodList.",
+ " --help # Print this message.");
+
+ private static String formatMethod(DexMethod method) {
+ return DescriptorUtils.getClassBinaryNameFromDescriptor(method.holder.descriptor.toString())
+ + '#'
+ + method.name
+ + method.proto.toDescriptorString();
+ }
+
+ public static void run(BackportedMethodListCommand command) throws CompilationFailedException {
+ if (command.isPrintHelp()) {
+ System.out.println(USAGE_MESSAGE);
+ return;
+ }
+ if (command.isPrintVersion()) {
+ System.out.println("BackportedMethodList " + Version.getVersionString());
+ return;
+ }
+ InternalOptions options = command.getInternalOptions();
+ ExecutorService executorService = ThreadUtils.getExecutorService(options);
+ try {
+ ExceptionUtils.withD8CompilationHandler(
+ command.getReporter(),
+ () -> {
+ BackportedMethodRewriter.generateListOfBackportedMethods(
+ command.getInputApp(), options, executorService)
+ .stream()
+ .map(BackportedMethodList::formatMethod)
+ .sorted()
+ .forEach(
+ formattedMethod ->
+ command
+ .getBackportedMethodListConsumer()
+ .accept(formattedMethod, command.getReporter()));
+ command.getBackportedMethodListConsumer().finished(command.getReporter());
+ });
+ } finally {
+ executorService.shutdown();
+ }
+ }
+
+ public static void run(String[] args) throws CompilationFailedException {
+ run(BackportedMethodListCommand.parse(args).build());
+ }
+
+ public static void main(String[] args) {
+ ExceptionUtils.withMainProgramHandler(() -> run(args));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
new file mode 100644
index 0000000..a4af022
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
@@ -0,0 +1,341 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.graph.DexItemFactory;
+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.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Immutable command structure for an invocation of the {@link BackportedMethodList} tool.
+ *
+ * <p>To build a BackportedMethodList command use the {@link BackportedMethodListCommand.Builder}
+ * class. For example:
+ *
+ * <pre>
+ * BackportedMethodListCommand command = BackportedMethodListCommand.builder()
+ * .setMinApiLevel(apiLevel)
+ * .setOutputPath(Paths.get("methods-list.txt"))
+ * .build();
+ * </pre>
+ */
+@Keep
+public class BackportedMethodListCommand {
+
+ private final boolean printHelp;
+ private final boolean printVersion;
+ private final Reporter reporter;
+ private final int minApiLevel;
+ private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+ private final AndroidApp app;
+ private final StringConsumer backportedMethodListConsumer;
+ private final DexItemFactory factory;
+
+ public boolean isPrintHelp() {
+ return printHelp;
+ }
+
+ public boolean isPrintVersion() {
+ return printVersion;
+ }
+
+ Reporter getReporter() {
+ return reporter;
+ }
+
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ public DesugaredLibraryConfiguration getDesugaredLibraryConfiguration() {
+ return desugaredLibraryConfiguration;
+ }
+
+ public StringConsumer getBackportedMethodListConsumer() {
+ return backportedMethodListConsumer;
+ }
+
+ AndroidApp getInputApp() {
+ return app;
+ }
+
+ private BackportedMethodListCommand(boolean printHelp, boolean printVersion) {
+ this.printHelp = printHelp;
+ this.printVersion = printVersion;
+ this.reporter = new Reporter();
+ this.minApiLevel = -1;
+ this.desugaredLibraryConfiguration = null;
+ this.app = null;
+ this.backportedMethodListConsumer = null;
+ this.factory = null;
+ }
+
+ private BackportedMethodListCommand(
+ Reporter reporter,
+ int minApiLevel,
+ DesugaredLibraryConfiguration desugaredLibraryConfiguration,
+ AndroidApp app,
+ StringConsumer backportedMethodListConsumer,
+ DexItemFactory factory) {
+ this.printHelp = false;
+ this.printVersion = false;
+ this.reporter = reporter;
+ this.minApiLevel = minApiLevel;
+ this.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
+ this.app = app;
+ this.backportedMethodListConsumer = backportedMethodListConsumer;
+ this.factory = factory;
+ }
+
+ InternalOptions getInternalOptions() {
+ InternalOptions options = new InternalOptions(factory, getReporter());
+ options.minApiLevel = minApiLevel;
+ options.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
+ return options;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+ return new Builder(diagnosticsHandler);
+ }
+
+ public static Builder parse(String[] args) {
+ final Set<String> OPTIONS_WITH_PARAMETER =
+ ImmutableSet.of("--output", "--min-api", "--desugared-lib", "--lib");
+
+ boolean hasDefinedApiLevel = false;
+ Builder builder = builder();
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i].trim();
+ String nextArg = null;
+ if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+ if (++i < args.length) {
+ nextArg = args[i];
+ } else {
+ builder.error(new StringDiagnostic("Missing parameter for " + args[i - 1] + "."));
+ break;
+ }
+ }
+ if (arg.equals("--help")) {
+ builder.setPrintHelp(true);
+ } else if (arg.equals("--version")) {
+ builder.setPrintVersion(true);
+ } else if (arg.equals("--min-api")) {
+ if (hasDefinedApiLevel) {
+ builder.error(new StringDiagnostic("Cannot set multiple --min-api options"));
+ } else {
+ parseMinApi(builder, nextArg);
+ hasDefinedApiLevel = true;
+ }
+ } else if (arg.equals("--desugared-lib")) {
+ builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+ } else if (arg.equals("--lib")) {
+ builder.addLibraryFiles(Paths.get(nextArg));
+ } else if (arg.equals("--output")) {
+ builder.setOutputPath(Paths.get(nextArg));
+ } else {
+ builder.error(new StringDiagnostic("Unknown option: " + arg));
+ }
+ }
+ return builder;
+ }
+
+ private static void parseMinApi(Builder builder, String minApiString) {
+ int minApi;
+ try {
+ minApi = Integer.parseInt(minApiString);
+ } catch (NumberFormatException e) {
+ builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
+ return;
+ }
+ if (minApi < 1) {
+ builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
+ return;
+ }
+ builder.setMinApiLevel(minApi);
+ }
+
+ @Keep
+ public static class Builder {
+
+ private final Reporter reporter;
+ private int minApiLevel = AndroidApiLevel.B.getLevel();
+ private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
+ private final AndroidApp.Builder app;
+ private StringConsumer backportedMethodListConsumer;
+ private boolean printHelp = false;
+ private boolean printVersion = false;
+
+ private Builder() {
+ this(new DiagnosticsHandler() {});
+ }
+
+ private Builder(DiagnosticsHandler diagnosticsHandler) {
+ this.app = AndroidApp.builder();
+ this.reporter = new Reporter(diagnosticsHandler);
+ }
+
+ /**
+ * Set the minimum API level for the application compiled.
+ *
+ * <p>The tool will only report backported methods which are not present at this API level.
+ *
+ * <p>The default is 1 if never set.
+ */
+ public Builder setMinApiLevel(int minApiLevel) {
+ if (minApiLevel <= 0) {
+ reporter.error(new StringDiagnostic("Invalid minApiLevel: " + minApiLevel));
+ } else {
+ this.minApiLevel = minApiLevel;
+ }
+ return this;
+ }
+
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ /** Desugared library configuration */
+ public Builder addDesugaredLibraryConfiguration(StringResource configuration) {
+ desugaredLibraryConfigurationResources.add(configuration);
+ return this;
+ }
+
+ /** Desugared library configuration */
+ public Builder addDesugaredLibraryConfiguration(String configuration) {
+ return addDesugaredLibraryConfiguration(
+ StringResource.fromString(configuration, Origin.unknown()));
+ }
+
+ /** The compilation SDK library (android.jar) */
+ public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
+ app.addLibraryResourceProvider(provider);
+ return this;
+ }
+
+ /** The compilation SDK library (android.jar) */
+ public Builder addLibraryFiles(Path... files) {
+ addLibraryFiles(Arrays.asList(files));
+ return this;
+ }
+
+ /** The compilation SDK library (android.jar) */
+ public Builder addLibraryFiles(Collection<Path> files) {
+ for (Path path : files) {
+ app.addLibraryFile(path);
+ }
+ return this;
+ }
+
+ DesugaredLibraryConfiguration getDesugaredLibraryConfiguration(DexItemFactory factory) {
+ if (desugaredLibraryConfigurationResources.isEmpty()) {
+ return DesugaredLibraryConfiguration.empty();
+ }
+ if (desugaredLibraryConfigurationResources.size() > 1) {
+ reporter.fatalError("Only one desugared library configuration is supported.");
+ }
+ StringResource desugaredLibraryConfigurationResource =
+ desugaredLibraryConfigurationResources.get(0);
+ DesugaredLibraryConfigurationParser libraryParser =
+ new DesugaredLibraryConfigurationParser(factory, null, false, getMinApiLevel());
+ return libraryParser.parse(desugaredLibraryConfigurationResource);
+ }
+
+ /** Output file for the backported method list */
+ public Builder setOutputPath(Path outputPath) {
+ backportedMethodListConsumer =
+ new StringConsumer.FileConsumer(outputPath) {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ super.accept(string, handler);
+ super.accept(System.lineSeparator(), handler);
+ }
+ };
+ return this;
+ }
+
+ /** Consumer receiving the the backported method list */
+ public Builder setConsumer(StringConsumer consumer) {
+ this.backportedMethodListConsumer = consumer;
+ return this;
+ }
+
+ /** True if the print-help flag is enabled. */
+ public boolean isPrintHelp() {
+ return printHelp;
+ }
+
+ /** Set the value of the print-help flag. */
+ public Builder setPrintHelp(boolean printHelp) {
+ this.printHelp = printHelp;
+ return this;
+ }
+
+ /** True if the print-version flag is enabled. */
+ public boolean isPrintVersion() {
+ return printVersion;
+ }
+
+ /** Set the value of the print-version flag. */
+ public Builder setPrintVersion(boolean printVersion) {
+ this.printVersion = printVersion;
+ return this;
+ }
+
+ private void error(Diagnostic diagnostic) {
+ reporter.error(diagnostic);
+ }
+
+ public BackportedMethodListCommand build() {
+ AndroidApp library = app.build();
+ if (!desugaredLibraryConfigurationResources.isEmpty()
+ && library.getLibraryResourceProviders().isEmpty()) {
+ reporter.error(
+ new StringDiagnostic("With desugared library configuration a library is required"));
+ }
+
+ if (isPrintHelp() || isPrintVersion()) {
+ return new BackportedMethodListCommand(isPrintHelp(), isPrintVersion());
+ }
+
+ if (backportedMethodListConsumer == null) {
+ backportedMethodListConsumer =
+ new StringConsumer() {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ System.out.println(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ };
+ }
+ DexItemFactory factory = new DexItemFactory();
+ return new BackportedMethodListCommand(
+ reporter,
+ minApiLevel,
+ getDesugaredLibraryConfiguration(factory),
+ library,
+ backportedMethodListConsumer,
+ factory);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 74b5d06..e46d5c1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.library.LibraryOptimizationInfoInitializer;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.utils.AndroidApp;
@@ -178,6 +179,9 @@
}
AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+
+ new LibraryOptimizationInfoInitializer(appView).run();
+
IRConverter converter = new IRConverter(appView, timing, printer);
app = converter.convert(app, executor);
diff --git a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java b/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
deleted file mode 100644
index 90f577b..0000000
--- a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class GenerateBackportedMethodList {
-
- public static void main(String[] args) {
- BackportedMethodRewriter.generateListOfBackportedMethods(AndroidApiLevel.B).stream()
- .map(DexMethod::toSourceString)
- .sorted()
- .forEach(System.out::println);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 2aa0ac5..5e47207 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -10,9 +10,9 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.MainDexClasses;
@@ -46,7 +46,7 @@
private List<String> run(AndroidApp app, ExecutorService executor)
throws IOException {
try {
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(app, options, timing).read(executor).toDirect();
AppView<? extends AppInfoWithSubtyping> appView =
AppView.createForR8(new AppInfoWithSubtyping(application), options);
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 654b5aa..41c399f 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
@@ -84,7 +84,7 @@
assert !options.forceProguardCompatibility;
Timing timing = new Timing("PrintSeeds");
try {
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
AppView<? extends AppInfoWithSubtyping> appView =
AppView.createForR8(new AppInfoWithSubtyping(application), options);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index ab5707a..d8d93ab 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -73,7 +74,7 @@
private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
private Set<DexType> noObfuscationTypes = Sets.newIdentityHashSet();
private Set<String> keepPackageNames = Sets.newHashSet();
- private final DexApplication application;
+ private final DirectMappedDexApplication application;
private final AppInfoWithSubtyping appInfo;
private int errors;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 70d4425..78471d6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -24,11 +24,14 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
@@ -36,8 +39,11 @@
import com.android.tools.r8.ir.optimize.NestReducer;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
+import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
+import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.library.LibraryOptimizationInfoInitializer;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.kotlin.KotlinInfo;
@@ -75,6 +81,7 @@
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.shaking.VerticalClassMerger;
+import com.android.tools.r8.shaking.VerticalClassMergerGraphLense;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -253,7 +260,7 @@
"Running R8 version " + Version.LABEL + " with assertions enabled."));
}
try {
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
// Now that the dex-application is fully loaded, close any internal archive providers.
@@ -314,13 +321,15 @@
.run(executorService));
AppView<AppInfoWithLiveness> appViewWithLiveness = runEnqueuer(executorService, appView);
- application = appViewWithLiveness.appInfo().app();
+ application = appViewWithLiveness.appInfo().app().asDirect();
assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
appView.rootSet().checkAllRulesAreUsed(options);
+ new LibraryOptimizationInfoInitializer(appView).run();
+
if (options.proguardSeedsConsumer != null) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
@@ -407,13 +416,12 @@
if (options.shouldDesugarNests()) {
timing.begin("NestBasedAccessDesugaring");
R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
- boolean changed =
- appView.setGraphLense(analyzer.run(executorService, application.builder()));
- if (changed) {
+ NestedPrivateMethodLense lens = analyzer.run(executorService, application.builder());
+ if (lens != null) {
+ boolean changed = appView.setGraphLense(lens);
+ assert changed;
appViewWithLiveness.setAppInfo(
- appViewWithLiveness
- .appInfo()
- .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+ appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
}
timing.end();
} else {
@@ -429,12 +437,12 @@
timing.begin("HorizontalStaticClassMerger");
StaticClassMerger staticClassMerger =
new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
- boolean changed = appView.setGraphLense(staticClassMerger.run());
- if (changed) {
+ NestedGraphLense lens = staticClassMerger.run();
+ if (lens != null) {
+ boolean changed = appView.setGraphLense(lens);
+ assert changed;
appViewWithLiveness.setAppInfo(
- appViewWithLiveness
- .appInfo()
- .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+ appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
}
timing.end();
}
@@ -443,46 +451,46 @@
VerticalClassMerger verticalClassMerger =
new VerticalClassMerger(
application, appViewWithLiveness, executorService, timing, mainDexClasses);
- boolean changed = appView.setGraphLense(verticalClassMerger.run());
- if (changed) {
+ VerticalClassMergerGraphLense lens = verticalClassMerger.run();
+ if (lens != null) {
+ boolean changed = appView.setGraphLense(lens);
+ assert changed;
appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
- application = application.asDirect().rewrittenWithLense(appView.graphLense());
+ application = application.asDirect().rewrittenWithLens(lens);
+ lens.initializeCacheForLookupMethodInAllContexts();
appViewWithLiveness.setAppInfo(
- appViewWithLiveness
- .appInfo()
- .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+ appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+ lens.unsetCacheForLookupMethodInAllContexts();
}
timing.end();
}
if (options.enableArgumentRemoval) {
if (options.enableUnusedArgumentRemoval) {
timing.begin("UnusedArgumentRemoval");
- boolean changed =
- appView.setGraphLense(
- new UnusedArgumentsCollector(
- appViewWithLiveness, new MethodPoolCollection(appView))
- .run(executorService, timing));
- if (changed) {
- application = application.asDirect().rewrittenWithLense(appView.graphLense());
+ UnusedArgumentsGraphLense lens =
+ new UnusedArgumentsCollector(
+ appViewWithLiveness, new MethodPoolCollection(appViewWithLiveness))
+ .run(executorService, timing);
+ if (lens != null) {
+ boolean changed = appView.setGraphLense(lens);
+ assert changed;
+ assert application.asDirect().verifyNothingToRewrite(appView, lens);
appViewWithLiveness.setAppInfo(
- appViewWithLiveness
- .appInfo()
- .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+ appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
}
timing.end();
}
if (options.enableUninstantiatedTypeOptimization) {
timing.begin("UninstantiatedTypeOptimization");
- boolean changed =
- appView.setGraphLense(
- new UninstantiatedTypeOptimization(appViewWithLiveness)
- .run(new MethodPoolCollection(appView), executorService, timing));
- if (changed) {
- application = application.asDirect().rewrittenWithLense(appView.graphLense());
+ UninstantiatedTypeOptimizationGraphLense lens =
+ new UninstantiatedTypeOptimization(appViewWithLiveness)
+ .run(new MethodPoolCollection(appViewWithLiveness), executorService, timing);
+ if (lens != null) {
+ boolean changed = appView.setGraphLense(lens);
+ assert changed;
+ assert application.asDirect().verifyNothingToRewrite(appView, lens);
appViewWithLiveness.setAppInfo(
- appViewWithLiveness
- .appInfo()
- .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+ appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
}
timing.end();
}
@@ -503,7 +511,7 @@
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
try {
IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
- application = converter.optimize(executorService);
+ application = converter.optimize(executorService).asDirect();
} finally {
timing.end();
}
@@ -688,7 +696,8 @@
// Add automatic main dex classes to an eventual manual list of classes.
if (!options.mainDexKeepRules.isEmpty()) {
- application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
+ application =
+ application.builder().addToMainDexList(mainDexClasses.getClasses()).build().asDirect();
}
// Perform minification.
@@ -878,7 +887,7 @@
for (DexProgramClass programClass : application.classes()) {
KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
programClass.setKotlinInfo(kotlinInfo);
- KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo);
+ KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
}
}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index d13081c..89aa64e 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -79,6 +79,9 @@
case "l8":
L8.main(shift(args));
break;
+ case "backportedmethods":
+ BackportedMethodList.main(shift(args));
+ break;
default:
runDefault(args);
break;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index cb4baef..d4d2066 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -28,6 +28,16 @@
}
@Override
+ public boolean isInstanceOf() {
+ return true;
+ }
+
+ @Override
+ public CfInstanceOf asInstanceOf() {
+ return this;
+ }
+
+ @Override
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitTypeInsn(Opcodes.INSTANCEOF, lens.lookupInternalName(type));
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 833e5e1..43d209e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -95,6 +95,14 @@
return null;
}
+ public boolean isInstanceOf() {
+ return false;
+ }
+
+ public CfInstanceOf asInstanceOf() {
+ return null;
+ }
+
public boolean isStore() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java b/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java
new file mode 100644
index 0000000..f6702b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java
@@ -0,0 +1,10 @@
+// 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;
+
+public class InvalidDescriptorException extends InternalCompilerError {
+ public InvalidDescriptorException(String message) {
+ super(message);
+ }
+}
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 4e3df21..03f0231 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -111,7 +111,7 @@
return builder.build();
}
- public Iterable<DexProgramClass> classes() {
+ public Collection<DexProgramClass> classes() {
assert checkIfObsolete();
return app.classes();
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 96fee36..6630f9c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -5,8 +5,11 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.google.common.collect.Sets;
import java.util.ArrayDeque;
+import java.util.Collections;
import java.util.Deque;
+import java.util.Set;
/* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
* minimal amount of knowledge in the overall program, provided through classpath. Basic
@@ -89,6 +92,57 @@
return isSubtype(type, other) || isSubtype(other, type);
}
+ /** Collect all interfaces that this type directly or indirectly implements. */
+ public Set<DexType> implementedInterfaces(DexType type) {
+ assert type.isClassType();
+ DexClass clazz = definitionFor(type);
+ if (clazz == null) {
+ return Collections.emptySet();
+ }
+
+ // Fast path for a type below object with no interfaces.
+ if (clazz.superType == dexItemFactory().objectType && clazz.interfaces.isEmpty()) {
+ return clazz.isInterface() ? Collections.singleton(type) : Collections.emptySet();
+ }
+
+ // Slow path traverses the full hierarchy.
+ Set<DexType> interfaces = Sets.newIdentityHashSet();
+ if (clazz.isInterface()) {
+ interfaces.add(type);
+ }
+ Deque<DexType> workList = new ArrayDeque<>();
+ if (clazz.superType != null && clazz.superType != dexItemFactory().objectType) {
+ workList.add(clazz.superType);
+ }
+ Collections.addAll(interfaces, clazz.interfaces.values);
+ Collections.addAll(workList, clazz.interfaces.values);
+ while (!workList.isEmpty()) {
+ DexType item = workList.pollFirst();
+ DexClass definition = definitionFor(item);
+ if (definition == null) {
+ // Collect missing types for future reporting?
+ continue;
+ }
+ if (definition.superType != null && definition.superType != dexItemFactory().objectType) {
+ workList.add(definition.superType);
+ }
+ for (DexType iface : definition.interfaces.values) {
+ if (interfaces.add(iface)) {
+ workList.add(iface);
+ }
+ }
+ }
+ return interfaces;
+ }
+
+ public boolean isExternalizable(DexType type) {
+ return isSubtype(type, dexItemFactory().externalizableType);
+ }
+
+ public boolean isSerializable(DexType type) {
+ return isSubtype(type, dexItemFactory().serializableType);
+ }
+
/**
* Helper method used for emulated interface resolution (not in JVM specifications). The result
* may be abstract.
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 17e63e6..495c0b3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -14,19 +13,18 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
+import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
import java.util.function.Function;
-public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy {
+public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy implements LiveSubTypeInfo {
private static final int ROOT_LEVEL = 0;
private static final int UNKNOWN_LEVEL = -1;
@@ -35,6 +33,19 @@
// to add to it.
private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
+ @Override
+ public LiveSubTypeResult getLiveSubTypes(DexType type) {
+ // TODO(b/139464956): Remove this when we start to have live type information in the enqueuer.
+ Set<DexProgramClass> programClasses = new HashSet<>();
+ for (DexType subtype : subtypes(type)) {
+ DexProgramClass subClass = definitionForProgramType(subtype);
+ if (subClass != null) {
+ programClasses.add(subClass);
+ }
+ }
+ return new LiveSubTypeResult(programClasses, null);
+ }
+
private static class TypeInfo {
private final DexType type;
@@ -131,11 +142,16 @@
private final Map<DexType, Boolean> mayHaveFinalizeMethodDirectlyOrIndirectlyCache =
new ConcurrentHashMap<>();
- public AppInfoWithSubtyping(DexApplication application) {
+ public AppInfoWithSubtyping(DirectMappedDexApplication application) {
+ this(application, application.allClasses());
+ }
+
+ public AppInfoWithSubtyping(
+ DirectMappedDexApplication application, Collection<DexClass> classes) {
super(application);
typeInfo = new ConcurrentHashMap<>();
// Recompute subtype map if we have modified the graph.
- populateSubtypeMap(application.asDirect(), application.dexItemFactory);
+ populateSubtypeMap(classes, application::definitionFor, application.dexItemFactory);
}
protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
@@ -273,16 +289,19 @@
}
}
- private void populateSubtypeMap(DirectMappedDexApplication app, DexItemFactory dexItemFactory) {
+ private void populateSubtypeMap(
+ Collection<DexClass> classes,
+ Function<DexType, DexClass> definitions,
+ DexItemFactory dexItemFactory) {
getTypeInfo(dexItemFactory.objectType).tagAsSubtypeRoot();
Map<DexType, Set<DexType>> map = new IdentityHashMap<>();
- for (DexClass clazz : app.allClasses()) {
- populateAllSuperTypes(map, clazz.type, clazz, app::definitionFor);
+ for (DexClass clazz : classes) {
+ populateAllSuperTypes(map, clazz.type, clazz, definitions);
}
for (Map.Entry<DexType, Set<DexType>> entry : map.entrySet()) {
subtypeMap.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
}
- assert validateLevelsAreCorrect(app::definitionFor, dexItemFactory);
+ assert validateLevelsAreCorrect(definitions, dexItemFactory);
}
private boolean validateLevelsAreCorrect(
@@ -366,61 +385,6 @@
return false;
}
- /**
- * Resolve the methods implemented by the lambda expression that created the {@code callSite}.
- *
- * <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
- * is not {@code LambdaMetafactory}), the empty set is returned.
- *
- * <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
- * warning is issued.
- *
- * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
- *
- * @param callSite Call site to resolve.
- * @return Methods implemented by the lambda expression that created the {@code callSite}.
- */
- public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
- assert checkIfObsolete();
- List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
- if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
- return Collections.emptySet();
- }
- Set<DexEncodedMethod> result = new HashSet<>();
- Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
- Set<DexType> visited = Sets.newIdentityHashSet();
- while (!worklist.isEmpty()) {
- DexType iface = worklist.removeFirst();
- if (getTypeInfo(iface).isUnknown()) {
- // Skip this interface. If the lambda only implements missing library interfaces and not any
- // program interfaces, then minification and tree shaking are not interested in this
- // DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
- // minification is run on a program with a lambda interface that implements both a missing
- // library interface and a present program interface, then we might minify the method name
- // on the program interface even though it should be kept the same as the (missing) library
- // interface method. That is a shame, but minification is not suited for incomplete programs
- // anyway.
- continue;
- }
- if (!visited.add(iface)) {
- // Already visited previously. May happen due to "diamond shapes" in the interface
- // hierarchy.
- continue;
- }
- assert getTypeInfo(iface).isInterface();
- DexClass clazz = definitionFor(iface);
- if (clazz != null) {
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
- result.add(method);
- }
- }
- Collections.addAll(worklist, clazz.interfaces.values);
- }
- }
- return result;
- }
-
public boolean isStringConcat(DexMethodHandle bootstrapMethod) {
assert checkIfObsolete();
return bootstrapMethod.type.isInvokeStatic()
@@ -463,18 +427,8 @@
return !getTypeInfo(type).directSubtypes.isEmpty();
}
- /**
- * Apply the given function to all classes that directly extend this class.
- *
- * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
- * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
- * is consistent with the source language.
- */
- public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
- allImmediateExtendsSubtypes(type).forEach(f);
- }
-
- public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
+ // TODO(b/139464956): Remove this method.
+ public Iterable<DexType> allImmediateExtendsSubtypes_(DexType type) {
TypeInfo info = getTypeInfo(type);
assert info.hierarchyLevel != UNKNOWN_LEVEL;
if (info.hierarchyLevel == INTERFACE_LEVEL) {
@@ -487,18 +441,8 @@
}
}
- /**
- * Apply the given function to all classes that directly implement this interface.
- *
- * <p>The implementation does not consider how the hierarchy is encoded in the dex file, where
- * interfaces "implement" their super interfaces. Instead it takes the view of the source
- * language, where interfaces "extend" their superinterface.
- */
- public void forAllImmediateImplementsSubtypes(DexType type, Consumer<DexType> f) {
- allImmediateImplementsSubtypes(type).forEach(f);
- }
-
- public Iterable<DexType> allImmediateImplementsSubtypes(DexType type) {
+ // TODO(b/139464956): Remove this method.
+ public Iterable<DexType> allImmediateImplementsSubtypes_(DexType type) {
TypeInfo info = getTypeInfo(type);
if (info.hierarchyLevel == INTERFACE_LEVEL) {
return Iterables.filter(info.directSubtypes, subtype -> !getTypeInfo(subtype).isInterface());
@@ -511,48 +455,8 @@
return clazz == null || clazz.hasMissingSuperType(this);
}
- public boolean isExternalizable(DexType type) {
- return implementedInterfaces(type).contains(dexItemFactory().externalizableType);
- }
-
- public boolean isSerializable(DexType type) {
- return implementedInterfaces(type).contains(dexItemFactory().serializableType);
- }
-
- /** Collect all interfaces that this type directly or indirectly implements. */
- public Set<DexType> implementedInterfaces(DexType type) {
- TypeInfo info = getTypeInfo(type);
- if (info.implementedInterfaces != null) {
- return info.implementedInterfaces;
- }
- synchronized (this) {
- if (info.implementedInterfaces == null) {
- Set<DexType> interfaces = Sets.newIdentityHashSet();
- implementedInterfaces(type, interfaces);
- info.implementedInterfaces = interfaces;
- }
- }
- return info.implementedInterfaces;
- }
-
- private void implementedInterfaces(DexType type, Set<DexType> interfaces) {
- DexClass dexClass = definitionFor(type);
- // Loop to traverse the super type hierarchy of the current type.
- while (dexClass != null) {
- if (dexClass.isInterface()) {
- interfaces.add(dexClass.type);
- }
- for (DexType itf : dexClass.interfaces.values) {
- implementedInterfaces(itf, interfaces);
- }
- if (dexClass.superType == null) {
- break;
- }
- dexClass = definitionFor(dexClass.superType);
- }
- }
-
- public DexType getSingleSubtype(DexType type) {
+ // TODO(b/139464956): Remove this method.
+ public DexType getSingleSubtype_(DexType type) {
TypeInfo info = getTypeInfo(type);
assert info.hierarchyLevel != UNKNOWN_LEVEL;
if (info.directSubtypes.size() == 1) {
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 d534cba..a471dc2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -232,4 +232,6 @@
}
public abstract DirectMappedDexApplication toDirect();
+
+ public abstract boolean isDirect();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index d540b18..321b5c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -779,11 +779,11 @@
return isResolvable.isTrue();
}
- public boolean isSerializable(AppView<? extends AppInfoWithSubtyping> appView) {
+ public boolean isSerializable(AppView<? extends AppInfoWithClassHierarchy> appView) {
return appView.appInfo().isSerializable(type);
}
- public boolean isExternalizable(AppView<? extends AppInfoWithSubtyping> appView) {
+ public boolean isExternalizable(AppView<? extends AppInfoWithClassHierarchy> appView) {
return appView.appInfo().isExternalizable(type);
}
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 193fd91..c38f7c6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -34,6 +34,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
@@ -121,6 +122,15 @@
public final DexString voidDescriptor = createString("V");
public final DexString descriptorSeparator = createString("/");
+ private final DexString booleanArrayDescriptor = createString("[Z");
+ private final DexString byteArrayDescriptor = createString("[B");
+ private final DexString charArrayDescriptor = createString("[C");
+ private final DexString doubleArrayDescriptor = createString("[D");
+ private final DexString floatArrayDescriptor = createString("[F");
+ private final DexString intArrayDescriptor = createString("[I");
+ private final DexString longArrayDescriptor = createString("[J");
+ private final DexString shortArrayDescriptor = createString("[S");
+
public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
public final DexString boxedCharDescriptor = createString("Ljava/lang/Character;");
@@ -130,6 +140,7 @@
public final DexString boxedLongDescriptor = createString("Ljava/lang/Long;");
public final DexString boxedShortDescriptor = createString("Ljava/lang/Short;");
public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;");
+ public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;");
public final DexString unboxBooleanMethodName = createString("booleanValue");
public final DexString unboxByteMethodName = createString("byteValue");
@@ -260,13 +271,14 @@
public final DexString newUpdaterName = createString("newUpdater");
public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
- public final DexString classConstructorMethodName = createString(Constants.CLASS_INITIALIZER_NAME);
+ public final DexString classConstructorMethodName =
+ createString(Constants.CLASS_INITIALIZER_NAME);
public final DexString thisName = createString("this");
public final DexString enumValuesFieldName = createString("$VALUES");
- private final DexString charArrayDescriptor = createString("[C");
- private final DexType charArrayType = createType(charArrayDescriptor);
+ public final DexString enabledFieldName = createString("ENABLED");
+
public final DexString throwableArrayDescriptor = createString("[Ljava/lang/Throwable;");
public final DexType booleanType = createType(booleanDescriptor);
@@ -279,6 +291,15 @@
public final DexType shortType = createType(shortDescriptor);
public final DexType voidType = createType(voidDescriptor);
+ public final DexType booleanArrayType = createType(booleanArrayDescriptor);
+ public final DexType byteArrayType = createType(byteArrayDescriptor);
+ public final DexType charArrayType = createType(charArrayDescriptor);
+ public final DexType doubleArrayType = createType(doubleArrayDescriptor);
+ public final DexType floatArrayType = createType(floatArrayDescriptor);
+ public final DexType intArrayType = createType(intArrayDescriptor);
+ public final DexType longArrayType = createType(longArrayDescriptor);
+ public final DexType shortArrayType = createType(shortArrayDescriptor);
+
public final DexType boxedBooleanType = createType(boxedBooleanDescriptor);
public final DexType boxedByteType = createType(boxedByteDescriptor);
public final DexType boxedCharType = createType(boxedCharDescriptor);
@@ -288,6 +309,7 @@
public final DexType boxedLongType = createType(boxedLongDescriptor);
public final DexType boxedShortType = createType(boxedShortDescriptor);
public final DexType boxedNumberType = createType(boxedNumberDescriptor);
+ public final DexType boxedVoidType = createType(boxedVoidDescriptor);
public final DexType charSequenceType = createType(charSequenceDescriptor);
public final DexType charSequenceArrayType = createType(charSequenceArrayDescriptor);
@@ -422,6 +444,7 @@
public final DexType enumerationType = createType("Ljava/util/Enumeration;");
public final DexType serializableType = createType("Ljava/io/Serializable;");
public final DexType externalizableType = createType("Ljava/io/Externalizable;");
+ public final DexType cloneableType = createType("Ljava/lang/Cloneable;");
public final DexType comparableType = createType("Ljava/lang/Comparable;");
public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
@@ -525,6 +548,9 @@
classMethods.getName,
classMethods.getSimpleName,
classMethods.forName,
+ objectsMethods.requireNonNull,
+ objectsMethods.requireNonNullWithMessage,
+ objectsMethods.requireNonNullWithMessageSupplier,
stringMethods.valueOf);
// We assume library methods listed here are `public`, i.e., free from visibility side effects.
@@ -533,6 +559,7 @@
Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
Stream.of(new Pair<>(objectMethods.constructor, alwaysTrue())),
+ Stream.of(new Pair<>(objectMethods.getClass, alwaysTrue())),
mapToPredicate(classMethods.getNames, alwaysTrue()),
mapToPredicate(
stringBufferMethods.constructorMethods,
@@ -678,11 +705,35 @@
public class ObjectsMethods {
- public DexMethod requireNonNull;
+ public final DexMethod requireNonNull;
+ public final DexMethod requireNonNullWithMessage;
+ public final DexMethod requireNonNullWithMessageSupplier;
private ObjectsMethods() {
- requireNonNull = createMethod(objectsDescriptor,
- createString("requireNonNull"), objectDescriptor, new DexString[]{objectDescriptor});
+ DexString requireNonNullMethodName = createString("requireNonNull");
+ requireNonNull =
+ createMethod(objectsType, createProto(objectType, objectType), requireNonNullMethodName);
+ requireNonNullWithMessage =
+ createMethod(
+ objectsType,
+ createProto(objectType, objectType, stringType),
+ requireNonNullMethodName);
+ requireNonNullWithMessageSupplier =
+ createMethod(
+ objectsType,
+ createProto(objectType, objectType, supplierType),
+ requireNonNullMethodName);
+ }
+
+ public boolean isRequireNonNullMethod(DexMethod method) {
+ return method == requireNonNull
+ || method == requireNonNullWithMessage
+ || method == requireNonNullWithMessageSupplier;
+ }
+
+ public Iterable<DexMethod> requireNonNullMethods() {
+ return ImmutableList.of(
+ requireNonNull, requireNonNullWithMessage, requireNonNullWithMessageSupplier);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f8aaa42..3e1ed8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -69,6 +69,10 @@
return libraryClasses;
}
+ public Collection<DexClasspathClass> classpathClasses() {
+ return classpathClasses;
+ }
+
@Override
public DexDefinition definitionFor(DexReference reference) {
if (reference.isDexType()) {
@@ -126,6 +130,11 @@
}
@Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
public DirectMappedDexApplication asDirect() {
return this;
}
@@ -135,14 +144,24 @@
return "DexApplication (direct)";
}
- public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
+ public DirectMappedDexApplication rewrittenWithLens(GraphLense lens) {
// As a side effect, this will rebuild the program classes and library classes maps.
- DirectMappedDexApplication rewrittenApplication = this.builder().build().asDirect();
- assert rewrittenApplication.mappingIsValid(graphLense, allClasses.keySet());
+ DirectMappedDexApplication rewrittenApplication = builder().build().asDirect();
+ assert rewrittenApplication.mappingIsValid(lens, allClasses.keySet());
assert rewrittenApplication.verifyCodeObjectsOwners();
return rewrittenApplication;
}
+ public boolean verifyNothingToRewrite(AppView<?> appView, GraphLense lens) {
+ assert allClasses.keySet().stream()
+ .allMatch(
+ type ->
+ lens.lookupType(type) == type
+ || appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type));
+ assert verifyCodeObjectsOwners();
+ return true;
+ }
+
private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
// The lens might either map to a different type that is already present in the application
// (e.g. relinking a type) or it might encode a type that was renamed, in which case the
@@ -192,7 +211,7 @@
public static class Builder extends DexApplication.Builder<Builder> {
private final ImmutableList<DexLibraryClass> libraryClasses;
- private final ImmutableList<DexClasspathClass> classpathClasses;
+ private ImmutableList<DexClasspathClass> classpathClasses;
Builder(LazyLoadedDexApplication application) {
super(application);
@@ -214,8 +233,17 @@
return this;
}
+ public Builder addClasspathClasses(List<DexClasspathClass> classes) {
+ classpathClasses =
+ ImmutableList.<DexClasspathClass>builder()
+ .addAll(classpathClasses)
+ .addAll(classes)
+ .build();
+ return self();
+ }
+
@Override
- public DexApplication build() {
+ public DirectMappedDexApplication build() {
// Rebuild the map. This will fail if keys are not unique.
// TODO(zerny): Consider not rebuilding the map if no program classes are added.
Map<DexType, DexClass> allClasses =
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 328bd61..b9e1b5c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -16,6 +16,8 @@
DexField getField();
+ int getNumberOfReadContexts();
+
int getNumberOfWriteContexts();
DexEncodedMethod getUniqueReadContext();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 99ad00f..2b813ea 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -74,16 +74,23 @@
}
@Override
+ public int getNumberOfReadContexts() {
+ return getNumberOfAccessContexts(readsWithContexts);
+ }
+
+ @Override
public int getNumberOfWriteContexts() {
- if (writesWithContexts != null) {
- if (writesWithContexts.size() == 1) {
- return writesWithContexts.values().iterator().next().size();
- } else {
- throw new Unreachable(
- "Should only be querying the number of write contexts after flattening");
- }
+ return getNumberOfAccessContexts(writesWithContexts);
+ }
+
+ private int getNumberOfAccessContexts(Map<DexField, Set<DexEncodedMethod>> accessesWithContexts) {
+ if (accessesWithContexts == null) {
+ return 0;
}
- return 0;
+ if (accessesWithContexts.size() == 1) {
+ return accessesWithContexts.values().iterator().next().size();
+ }
+ throw new Unreachable("Should only be querying the number of access contexts after flattening");
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index afac334..f1d1a34 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
@@ -18,12 +17,10 @@
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
import java.util.function.Function;
+import java.util.function.Supplier;
/**
* A GraphLense implements a virtual view on top of the graph, used to delay global rewrites until
@@ -169,7 +166,7 @@
// This overload can be used when the graph lense is known to be context insensitive.
public DexMethod lookupMethod(DexMethod method) {
- assert isContextFreeForMethod(method);
+ assert verifyIsContextFreeForMethod(method);
return lookupMethod(method, null, null).getMethod();
}
@@ -180,12 +177,9 @@
// Context sensitive graph lenses should override this method.
public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
- assert isContextFreeForMethod(method);
DexMethod result = lookupMethod(method);
- if (result != null) {
- return ImmutableSet.of(result);
- }
- return ImmutableSet.of();
+ assert result != null;
+ return ImmutableSet.of(result);
}
public abstract DexField lookupField(DexField field);
@@ -221,7 +215,7 @@
// an assertion error.
public abstract boolean isContextFreeForMethods();
- public boolean isContextFreeForMethod(DexMethod method) {
+ public boolean verifyIsContextFreeForMethod(DexMethod method) {
return isContextFreeForMethods();
}
@@ -261,33 +255,12 @@
return true;
}
- public ImmutableList<DexReference> rewriteReferencesConservatively(List<DexReference> original) {
- ImmutableList.Builder<DexReference> builder = ImmutableList.builder();
- for (DexReference item : original) {
- if (item.isDexMethod()) {
- DexMethod method = item.asDexMethod();
- if (isContextFreeForMethod(method)) {
- builder.add(lookupMethod(method));
- } else {
- builder.addAll(lookupMethodInAllContexts(method));
- }
- } else {
- builder.add(lookupReference(item));
- }
- }
- return builder.build();
- }
-
public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) {
ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder();
for (DexReference item : original) {
if (item.isDexMethod()) {
DexMethod method = item.asDexMethod();
- if (isContextFreeForMethod(method)) {
- builder.add(lookupMethod(method));
- } else {
- builder.addAll(lookupMethodInAllContexts(method));
- }
+ builder.addAll(lookupMethodInAllContexts(method));
} else {
builder.add(lookupReference(item));
}
@@ -295,23 +268,6 @@
return builder.build();
}
- public Set<DexReference> rewriteMutableReferencesConservatively(Set<DexReference> original) {
- Set<DexReference> result = Sets.newIdentityHashSet();
- for (DexReference item : original) {
- if (item.isDexMethod()) {
- DexMethod method = item.asDexMethod();
- if (isContextFreeForMethod(method)) {
- result.add(lookupMethod(method));
- } else {
- result.addAll(lookupMethodInAllContexts(method));
- }
- } else {
- result.add(lookupReference(item));
- }
- }
- return result;
- }
-
public Object2BooleanMap<DexReference> rewriteReferencesConservatively(
Object2BooleanMap<DexReference> original) {
Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -319,12 +275,8 @@
DexReference item = entry.getKey();
if (item.isDexMethod()) {
DexMethod method = item.asDexMethod();
- if (isContextFreeForMethod(method)) {
- result.put(lookupMethod(method), entry.getBooleanValue());
- } else {
- for (DexMethod candidate : lookupMethodInAllContexts(method)) {
- result.put(candidate, entry.getBooleanValue());
- }
+ for (DexMethod candidate : lookupMethodInAllContexts(method)) {
+ result.put(candidate, entry.getBooleanValue());
}
} else {
result.put(lookupReference(item), entry.getBooleanValue());
@@ -333,22 +285,6 @@
return result;
}
- public ImmutableSet<DexType> rewriteTypesConservatively(Set<DexType> original) {
- ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
- for (DexType item : original) {
- builder.add(lookupType(item));
- }
- return builder.build();
- }
-
- public Set<DexType> rewriteMutableTypesConservatively(Set<DexType> original) {
- Set<DexType> result = Sets.newIdentityHashSet();
- for (DexType item : original) {
- result.add(lookupType(item));
- }
- return result;
- }
-
public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) {
ImmutableSortedSet.Builder<DexMethod> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
@@ -367,40 +303,12 @@
}
} else {
for (DexMethod item : original) {
- // Avoid using lookupMethodInAllContexts when possible.
- if (isContextFreeForMethod(item)) {
- builder.add(lookupMethod(item));
- } else {
- // The lense is context sensitive, but we do not have the context here. Therefore, we
- // conservatively look up the method in all contexts.
- builder.addAll(lookupMethodInAllContexts(item));
- }
+ builder.addAll(lookupMethodInAllContexts(item));
}
}
return builder.build();
}
- public SortedSet<DexMethod> rewriteMutableMethodsConservatively(Set<DexMethod> original) {
- SortedSet<DexMethod> result = new TreeSet<>(PresortedComparable::slowCompare);
- if (isContextFreeForMethods()) {
- for (DexMethod item : original) {
- result.add(lookupMethod(item));
- }
- } else {
- for (DexMethod item : original) {
- // Avoid using lookupMethodInAllContexts when possible.
- if (isContextFreeForMethod(item)) {
- result.add(lookupMethod(item));
- } else {
- // The lense is context sensitive, but we do not have the context here. Therefore, we
- // conservatively look up the method in all contexts.
- result.addAll(lookupMethodInAllContexts(item));
- }
- }
- }
- return result;
- }
-
public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
Map<T, S> original, Function<T, T> rewrite) {
ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
@@ -410,15 +318,6 @@
return builder.build();
}
- public static <T extends DexReference, S> Map<T, S> rewriteMutableReferenceKeys(
- Map<T, S> original, Function<T, T> rewrite) {
- Map<T, S> result = new IdentityHashMap<>();
- for (T item : original.keySet()) {
- result.put(rewrite.apply(item), original.get(item));
- }
- return result;
- }
-
public boolean verifyMappingToOriginalProgram(
Iterable<DexProgramClass> classes,
DexApplication originalApplication,
@@ -570,7 +469,7 @@
*/
public static class NestedGraphLense extends GraphLense {
- protected final GraphLense previousLense;
+ protected GraphLense previousLense;
protected final DexItemFactory dexItemFactory;
protected final Map<DexType, DexType> typeMap;
@@ -608,6 +507,14 @@
this.dexItemFactory = dexItemFactory;
}
+ public <T> T withAlternativeParentLens(GraphLense lens, Supplier<T> action) {
+ GraphLense oldParent = previousLense;
+ previousLense = lens;
+ T result = action.get();
+ previousLense = oldParent;
+ return result;
+ }
+
@Override
public DexType getOriginalType(DexType type) {
return previousLense.getOriginalType(type);
@@ -761,8 +668,9 @@
}
@Override
- public boolean isContextFreeForMethod(DexMethod method) {
- return previousLense.isContextFreeForMethod(method);
+ public boolean verifyIsContextFreeForMethod(DexMethod method) {
+ assert previousLense.verifyIsContextFreeForMethod(method);
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index eb24b0c..9e96ab4 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -249,6 +249,11 @@
}
@Override
+ public boolean isDirect() {
+ return false;
+ }
+
+ @Override
public String toString() {
return "Application (" + programClasses + "; " + classpathClasses + "; " + libraryClasses
+ ")";
diff --git a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
new file mode 100644
index 0000000..f812d7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.Set;
+
+@FunctionalInterface
+public interface LiveSubTypeInfo {
+
+ LiveSubTypeResult getLiveSubTypes(DexType type);
+
+ class LiveSubTypeResult {
+
+ private final Set<DexProgramClass> programClasses;
+ private final Set<DexCallSite> callSites;
+
+ public LiveSubTypeResult(Set<DexProgramClass> programClasses, Set<DexCallSite> callSites) {
+ // TODO(b/149006127): Enable when we no longer rely on callSites == null.
+ this.programClasses = programClasses;
+ this.callSites = callSites;
+ }
+
+ public Set<DexProgramClass> getProgramClasses() {
+ return programClasses;
+ }
+
+ public Set<DexCallSite> getCallSites() {
+ return callSites;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index a25bab3..f3f308f 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -75,14 +75,7 @@
public abstract DexEncodedMethod lookupInvokeStaticTarget(
DexProgramClass context, AppInfoWithClassHierarchy appInfo);
- public final Set<DexEncodedMethod> lookupVirtualDispatchTargets(
- boolean isInterface, AppInfoWithSubtyping appInfo) {
- return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
- }
-
- public abstract Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo);
-
- public abstract Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo);
+ public abstract Set<DexEncodedMethod> lookupVirtualDispatchTargets(AppInfoWithSubtyping appInfo);
/** Result for a resolution that succeeds with a known declaration/definition. */
public static class SingleResolutionResult extends ResolutionResult {
@@ -329,8 +322,15 @@
}
@Override
+ public Set<DexEncodedMethod> lookupVirtualDispatchTargets(AppInfoWithSubtyping appInfo) {
+ return initialResolutionHolder.isInterface()
+ ? lookupInterfaceTargets(appInfo)
+ : lookupVirtualTargets(appInfo);
+ }
+
// TODO(b/140204899): Leverage refined receiver type if available.
- public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+ private Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+ assert !initialResolutionHolder.isInterface();
if (resolvedMethod.isPrivateMethod()) {
// If the resolved reference is private there is no dispatch.
// This is assuming that the method is accessible, which implies self/nest access.
@@ -356,9 +356,9 @@
return result;
}
- @Override
// TODO(b/140204899): Leverage refined receiver type if available.
- public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ private Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ assert initialResolutionHolder.isInterface();
if (resolvedMethod.isPrivateMethod()) {
// If the resolved reference is private there is no dispatch.
// This is assuming that the method is accessible, which implies self/nest access.
@@ -472,12 +472,7 @@
}
@Override
- public final Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
- return null;
- }
-
- @Override
- public final Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ public Set<DexEncodedMethod> lookupVirtualDispatchTargets(AppInfoWithSubtyping appInfo) {
return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index bed6235..fdb4dc1 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -22,19 +23,22 @@
public class ClassInitializerAssertionEnablingAnalysis extends EnqueuerAnalysis {
private final DexItemFactory dexItemFactory;
private final OptimizationFeedback feedback;
+ private final DexString kotlinAssertionsEnabled;
public ClassInitializerAssertionEnablingAnalysis(
DexItemFactory dexItemFactory, OptimizationFeedback feedback) {
this.dexItemFactory = dexItemFactory;
this.feedback = feedback;
+ this.kotlinAssertionsEnabled = dexItemFactory.createString("ENABLED");
}
@Override
public void processNewlyLiveMethod(DexEncodedMethod method) {
if (method.isClassInitializer()) {
if (method.getCode().isCfCode()) {
- if (hasJavacClinitAssertionCode(method.getCode().asCfCode())) {
- feedback.setInitializerEnablingJavaAssertions(method);
+ if (hasJavacClinitAssertionCode(method.getCode().asCfCode())
+ || hasKotlincClinitAssertionCode(method)) {
+ feedback.setInitializerEnablingJavaVmAssertions(method);
}
}
}
@@ -70,6 +74,20 @@
// 5: ixor
// 13: putstatic #<n> // Field $assertionsDisabled:Z
+ // The <clinit> instruction sequence generated by kolinc for kotlin._Assertions.
+ //
+ // 0: new #2 // class kotlin/_Assertions
+ // 3: dup
+ // 4: invokespecial #31 // Method "<init>":()V
+ // 7: astore_0
+ // 8: aload_0
+ // 9: putstatic #33 // Field INSTANCE:Lkotlin/_Assertions;
+ // 12: aload_0
+ // 13: invokevirtual #37 // Method java/lang/Object.getClass:()Ljava/lang/Class;
+ // 16: invokevirtual #43 // Method java/lang/Class.desiredAssertionStatus:()Z
+ // 19: putstatic #45 // Field ENABLED:Z
+ // 22: return
+
private static List<Class<?>> javacInstructionSequence =
ImmutableList.of(
CfIf.class,
@@ -108,6 +126,36 @@
return false;
}
+ private boolean hasKotlincClinitAssertionCode(DexEncodedMethod method) {
+ if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+ CfCode code = method.getCode().asCfCode();
+ for (int i = 1; i < code.instructions.size(); i++) {
+ CfInstruction instruction = code.instructions.get(i - 1);
+ if (instruction.isInvoke()) {
+ // Check for the generated instruction sequence by looking for the call to
+ // desiredAssertionStatus() followed by the expected instruction types and finally
+ // checking the exact target of the putstatic ending the sequence.
+ CfInvoke invoke = instruction.asInvoke();
+ if (invoke.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && invoke.getMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+ if (code.instructions.get(i).isFieldInstruction()) {
+ CfFieldInstruction fieldInstruction = code.instructions.get(i).asFieldInstruction();
+ if (fieldInstruction.getOpcode() == Opcodes.PUTSTATIC
+ && fieldInstruction.getField().name == kotlinAssertionsEnabled) {
+ return true;
+ }
+ }
+ }
+ }
+ // Only check initial straight line code.
+ if (instruction.isJump()) {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
private CfFieldInstruction isJavacInstructionSequence(CfCode code, int fromIndex) {
List<Class<?>> sequence = javacInstructionSequence;
int nextExpectedInstructionIndex = 0;
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
new file mode 100644
index 0000000..c85439b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
+ implements EnqueuerInvokeAnalysis {
+
+ private final AppView<?> appView;
+ private final DesugaredLibraryAPIConverter converter;
+ private boolean callbackGenerated = false;
+ private Map<DexProgramClass, DexProgramClass> wrappersToReverseMap = null;
+
+ public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
+ this.appView = appView;
+ this.converter =
+ new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+ }
+
+ @Override
+ public void processNewlyLiveMethod(DexEncodedMethod method) {
+ converter.registerCallbackIfRequired(method);
+ }
+
+ private void traceInvoke(DexMethod invokedMethod) {
+ converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod);
+ }
+
+ @Override
+ public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
+ this.traceInvoke(invokedMethod);
+ }
+
+ @Override
+ public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
+ this.traceInvoke(invokedMethod);
+ }
+
+ @Override
+ public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
+ this.traceInvoke(invokedMethod);
+ }
+
+ @Override
+ public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
+ this.traceInvoke(invokedMethod);
+ }
+
+ @Override
+ public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
+ this.traceInvoke(invokedMethod);
+ }
+
+ public List<DexEncodedMethod> generateCallbackMethods() {
+ assert !callbackGenerated;
+ callbackGenerated = true;
+ return converter.generateCallbackMethods();
+ }
+
+ public Set<DexProgramClass> generateWrappers() {
+ assert wrappersToReverseMap == null;
+ wrappersToReverseMap = converter.synthesizeWrappersAndMapToReverse();
+ return wrappersToReverseMap.keySet();
+ }
+
+ // Generate a mock classpath class for all vivified types.
+ // Types will be available at runtime in the desugared library dex file.
+ public List<DexClasspathClass> generateWrappersSuperTypeMock() {
+ List<DexClasspathClass> classpathClasses = new ArrayList<>();
+ for (DexProgramClass wrapper : wrappersToReverseMap.keySet()) {
+ boolean mockIsInterface = wrapper.interfaces.size() == 1;
+ DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
+ if (appView.definitionFor(mockType) == null) {
+ assert DesugaredLibraryAPIConverter.isVivifiedType(mockType);
+ assert wrapper.instanceFields().size() == 1;
+ DexType typeToMock = wrapper.instanceFields().get(0).field.type;
+ DexClass classToMock = appView.definitionFor(typeToMock);
+ assert classToMock != null;
+ DexClasspathClass mockedSuperClass =
+ converter.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+ classpathClasses.add(mockedSuperClass);
+ for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
+ // The mock is generated at the end of the enqueuing phase, so we need to manually set the
+ // library override.
+ assert mockedSuperClass.lookupVirtualMethod(virtualMethod.method) != null;
+ virtualMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+ }
+ }
+ }
+ return classpathClasses;
+ }
+
+ public DesugaredLibraryConversionWrapperAnalysis registerWrite(
+ DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
+ registration.accept(getInitializer(wrapper));
+ return this;
+ }
+
+ public DesugaredLibraryConversionWrapperAnalysis registerReads(
+ DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
+ // The field of each wrapper is read exclusively in all virtual methods and the reverse wrapper
+ // convert method.
+ for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
+ registration.accept(virtualMethod);
+ }
+ DexProgramClass reverseWrapper = wrappersToReverseMap.get(wrapper);
+ if (reverseWrapper != null) {
+ registration.accept(getConvertMethod(reverseWrapper));
+ }
+ return this;
+ }
+
+ private DexEncodedMethod getInitializer(DexProgramClass wrapper) {
+ DexEncodedMethod initializer =
+ wrapper.lookupDirectMethod(DexEncodedMethod::isInstanceInitializer);
+ assert initializer != null;
+ return initializer;
+ }
+
+ private DexEncodedMethod getConvertMethod(DexProgramClass wrapper) {
+ DexEncodedMethod convertMethod = wrapper.lookupDirectMethod(DexEncodedMethod::isStatic);
+ assert convertMethod != null;
+ assert convertMethod.method.name == appView.dexItemFactory().convertMethodName;
+ return convertMethod;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
new file mode 100644
index 0000000..d0a39b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.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.graph.analysis;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerInvokeAnalysis {
+
+ /**
+ * Each traceInvokeXX method is called when a corresponding invoke is found while tracing a live
+ * method.
+ */
+ void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context);
+
+ void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context);
+
+ void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context);
+
+ void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context);
+
+ void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index e2881ad..37a8dc6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -95,9 +95,6 @@
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
Inliner inliner) {
- if (method.method.toSourceString().contains("proto2.BuilderWithReusedSettersTestClass")) {
- System.out.println();
- }
strengthenCheckCastInstructions(code);
ProtoInliningReasonStrategy inliningReasonStrategy =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c70ff33..007fd9f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -24,6 +24,14 @@
return null;
}
+ public boolean isSingleConstClassValue() {
+ return false;
+ }
+
+ public SingleConstClassValue asSingleConstClassValue() {
+ return null;
+ }
+
public boolean isSingleEnumValue() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 887a171..80ca063 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -6,10 +6,13 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
import java.util.concurrent.ConcurrentHashMap;
public class AbstractValueFactory {
+ private ConcurrentHashMap<DexType, SingleConstClassValue> singleConstClassValues =
+ new ConcurrentHashMap<>();
private ConcurrentHashMap<DexField, SingleEnumValue> singleEnumValues = new ConcurrentHashMap<>();
private ConcurrentHashMap<DexField, SingleFieldValue> singleFieldValues =
new ConcurrentHashMap<>();
@@ -17,6 +20,10 @@
private ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
new ConcurrentHashMap<>();
+ public SingleConstClassValue createSingleConstClassValue(DexType type) {
+ return singleConstClassValues.computeIfAbsent(type, SingleConstClassValue::new);
+ }
+
public SingleEnumValue createSingleEnumValue(DexField field) {
return singleEnumValues.computeIfAbsent(field, SingleEnumValue::new);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
new file mode 100644
index 0000000..a6bf499
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.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.ir.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.classClassType;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+
+public class SingleConstClassValue extends SingleValue {
+
+ private final DexType type;
+
+ /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+ SingleConstClassValue(DexType type) {
+ this.type = type;
+ }
+
+ @Override
+ public boolean isSingleConstClassValue() {
+ return true;
+ }
+
+ @Override
+ public SingleConstClassValue asSingleConstClassValue() {
+ return this;
+ }
+
+ public DexType getType() {
+ return type;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o;
+ }
+
+ @Override
+ public int hashCode() {
+ return type.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "SingleConstClassValue(" + type.toSourceString() + ")";
+ }
+
+ @Override
+ public Instruction createMaterializingInstruction(
+ AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
+ TypeLatticeElement typeLattice = info.getTypeLattice();
+ DebugLocalInfo debugLocalInfo = info.getLocalInfo();
+ assert typeLattice.isClassType();
+ assert appView
+ .isSubtype(
+ appView.dexItemFactory().classType,
+ typeLattice.asClassTypeLatticeElement().getClassType())
+ .isTrue();
+ Value returnedValue =
+ code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
+ ConstClass instruction = new ConstClass(returnedValue, type);
+ assert !instruction.instructionMayHaveSideEffects(appView, code.method.method.holder);
+ return instruction;
+ }
+
+ @Override
+ public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ if (baseType.isClassType()) {
+ DexClass clazz = appView.definitionFor(type);
+ return clazz != null
+ && clazz.isResolvable(appView)
+ && isClassTypeVisibleFromContext(appView, context, clazz);
+ }
+ assert baseType.isPrimitiveType();
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 4c3822a..f4bb790 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -527,6 +527,15 @@
return !phis.isEmpty();
}
+ public boolean hasDeadPhi(AppView<?> appView, IRCode code) {
+ for (Phi phi : phis) {
+ if (phi.isDead(appView, code)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public List<Phi> getPhis() {
return phis;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 3cae82a..98f4c97 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -223,8 +223,7 @@
}
@Override
- public void replaceCurrentInstructionWithConstInt(
- AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+ public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
if (current == null) {
throw new IllegalStateException();
}
@@ -244,10 +243,7 @@
@Override
public void replaceCurrentInstructionWithStaticGet(
- AppView<? extends AppInfoWithSubtyping> appView,
- IRCode code,
- DexField field,
- Set<Value> affectedValues) {
+ AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
if (current == null) {
throw new IllegalStateException();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index f58d657..4a58946 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -16,6 +16,8 @@
import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -187,4 +189,12 @@
public void buildCf(CfBuilder builder) {
builder.add(new CfConstClass(clazz));
}
+
+ @Override
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+ if (!instructionMayHaveSideEffects(appView, context)) {
+ return appView.abstractValueFactory().createSingleConstClassValue(context);
+ }
+ return UnknownValue.getInstance();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index f070e0f..ff88eed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -38,17 +38,13 @@
}
@Override
- public void replaceCurrentInstructionWithConstInt(
- AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
- instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+ public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
}
@Override
public void replaceCurrentInstructionWithStaticGet(
- AppView<? extends AppInfoWithSubtyping> appView,
- IRCode code,
- DexField field,
- Set<Value> affectedValues) {
+ AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
instructionIterator.replaceCurrentInstructionWithStaticGet(
appView, code, field, affectedValues);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a8b539e..623da4d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -64,14 +64,10 @@
Value insertConstIntInstruction(IRCode code, InternalOptions options, int value);
- void replaceCurrentInstructionWithConstInt(
- AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value);
+ void replaceCurrentInstructionWithConstInt(IRCode code, int value);
void replaceCurrentInstructionWithStaticGet(
- AppView<? extends AppInfoWithSubtyping> appView,
- IRCode code,
- DexField field,
- Set<Value> affectedValues);
+ AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues);
/**
* Replace the current instruction with null throwing instructions.
@@ -81,10 +77,10 @@
* @param blockIterator basic block iterator used to iterate the blocks.
* @param blocksToRemove set passed where blocks that were detached from the graph, but not
* removed yet are added. When inserting `throw null`, catch handlers whose guard does not
- * catch NPE will be removed, but not yet removed using the passed block
- * <code>blockIterator</code>. When iterating using <code>blockIterator</code> after then
- * method returns the blocks in this set must be skipped when iterating with the active
- * <code>blockIterator</code> and ultimately removed.
+ * catch NPE will be removed, but not yet removed using the passed block <code>blockIterator
+ * </code>. When iterating using <code>blockIterator</code> after then method returns the
+ * blocks in this set must be skipped when iterating with the active <code>blockIterator
+ * </code> and ultimately removed.
* @param affectedValues set passed where values depending on detached blocks will be added.
*/
void replaceCurrentInstructionWithThrowNull(
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 451b9a5..7624412 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
@@ -83,21 +83,11 @@
if (!isInvokeMethodWithDynamicDispatch() || !appView.appInfo().hasLiveness()) {
return null;
}
- Collection<DexEncodedMethod> targets;
- if (isInvokeVirtual()) {
- targets =
- appView
- .appInfo()
- .resolveMethodOnClass(method.holder, method)
- .lookupVirtualTargets(appView.appInfo());
- } else {
- assert isInvokeInterface();
- targets =
- appView
- .appInfo()
- .resolveMethodOnInterface(method.holder, method)
- .lookupInterfaceTargets(appView.appInfo());
- }
+ Collection<DexEncodedMethod> targets =
+ appView
+ .appInfo()
+ .resolveMethod(method.holder, method)
+ .lookupVirtualDispatchTargets(appView.appInfo());
if (targets == null) {
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 5d782c3..e1d3d75 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
@@ -8,6 +8,7 @@
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.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -109,6 +110,11 @@
result, appView.appInfo().lookupStaticTargetOnItself(invokedMethod, invocationContext));
return result;
}
+ // Allow optimizing static library invokes in D8.
+ DexClass clazz = appView.definitionFor(getInvokedMethod().holder);
+ if (clazz != null && clazz.isLibraryClass()) {
+ return appView.definitionFor(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, invocationContext);
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 da5a5e6..bdc7ce4 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
@@ -8,6 +8,7 @@
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.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -99,6 +100,18 @@
TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
}
+ // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is used
+ // for library modeling.
+ DexType holder = getInvokedMethod().holder;
+ if (holder.isClassType()) {
+ DexClass clazz = appView.definitionFor(holder);
+ if (clazz != null && clazz.isLibraryClass()) {
+ DexEncodedMethod singleTargetCandidate = appView.definitionFor(getInvokedMethod());
+ if (singleTargetCandidate != null && (clazz.isFinal() || singleTargetCandidate.isFinal())) {
+ return singleTargetCandidate;
+ }
+ }
+ }
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 6de9e58..2a82bcc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -51,17 +51,13 @@
}
@Override
- public void replaceCurrentInstructionWithConstInt(
- AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
- currentBlockIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+ public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
+ currentBlockIterator.replaceCurrentInstructionWithConstInt(code, value);
}
@Override
public void replaceCurrentInstructionWithStaticGet(
- AppView<? extends AppInfoWithSubtyping> appView,
- IRCode code,
- DexField field,
- Set<Value> affectedValues) {
+ AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
currentBlockIterator.replaceCurrentInstructionWithStaticGet(
appView, code, field, affectedValues);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 06f1f58..6f3bf92 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -47,6 +47,14 @@
// Incoming calls to this method.
private final Set<Node> callers = new TreeSet<>();
+ // Incoming field read edges to this method (i.e., the set of methods that read a field written
+ // by the current method).
+ private final Set<Node> readers = new TreeSet<>();
+
+ // Outgoing field read edges from this method (i.e., the set of methods that write a field read
+ // by the current method).
+ private final Set<Node> writers = new TreeSet<>();
+
public Node(DexEncodedMethod method) {
this.method = method;
}
@@ -57,12 +65,17 @@
public void addCallerConcurrently(Node caller, boolean likelySpuriousCallEdge) {
if (caller != this && !likelySpuriousCallEdge) {
+ boolean changedCallers;
synchronized (callers) {
- callers.add(caller);
+ changedCallers = callers.add(caller);
numberOfCallSites++;
}
- synchronized (caller.callees) {
- caller.callees.add(this);
+ if (changedCallers) {
+ synchronized (caller.callees) {
+ caller.callees.add(this);
+ }
+ // Avoid redundant field read edges (call edges are considered stronger).
+ removeReaderConcurrently(caller);
}
} else {
synchronized (callers) {
@@ -71,22 +84,74 @@
}
}
- public void removeCaller(Node caller) {
- callers.remove(caller);
- caller.callees.remove(this);
- }
-
- public void cleanCalleesForRemoval() {
- assert callers.isEmpty();
- for (Node callee : callees) {
- callee.callers.remove(this);
+ public void addReaderConcurrently(Node reader) {
+ if (reader != this) {
+ synchronized (callers) {
+ if (callers.contains(reader)) {
+ // Avoid redundant field read edges (call edges are considered stronger).
+ return;
+ }
+ boolean readersChanged;
+ synchronized (readers) {
+ readersChanged = readers.add(reader);
+ }
+ if (readersChanged) {
+ synchronized (reader.writers) {
+ reader.writers.add(this);
+ }
+ }
+ }
}
}
- public void cleanCallersForRemoval() {
+ private void removeReaderConcurrently(Node reader) {
+ synchronized (readers) {
+ readers.remove(reader);
+ }
+ synchronized (reader.writers) {
+ reader.writers.remove(this);
+ }
+ }
+
+ public void removeCaller(Node caller) {
+ boolean callersChanged = callers.remove(caller);
+ assert callersChanged;
+ boolean calleesChanged = caller.callees.remove(this);
+ assert calleesChanged;
+ assert !hasReader(caller);
+ }
+
+ public void removeReader(Node reader) {
+ boolean readersChanged = readers.remove(reader);
+ assert readersChanged;
+ boolean writersChanged = reader.writers.remove(this);
+ assert writersChanged;
+ assert !hasCaller(reader);
+ }
+
+ public void cleanCalleesAndWritersForRemoval() {
+ assert callers.isEmpty();
+ assert readers.isEmpty();
+ for (Node callee : callees) {
+ boolean changed = callee.callers.remove(this);
+ assert changed;
+ }
+ for (Node writer : writers) {
+ boolean changed = writer.readers.remove(this);
+ assert changed;
+ }
+ }
+
+ public void cleanCallersAndReadersForRemoval() {
assert callees.isEmpty();
+ assert writers.isEmpty();
for (Node caller : callers) {
- caller.callees.remove(this);
+ boolean changed = caller.callees.remove(this);
+ assert changed;
+ }
+ for (Node reader : readers) {
+ boolean changed = reader.writers.remove(this);
+ assert changed;
}
}
@@ -98,6 +163,14 @@
return callees;
}
+ public Set<Node> getReadersWithDeterministicOrder() {
+ return readers;
+ }
+
+ public Set<Node> getWritersWithDeterministicOrder() {
+ return writers;
+ }
+
public int getNumberOfCallSites() {
return numberOfCallSites;
}
@@ -110,12 +183,20 @@
return callers.contains(method);
}
+ public boolean hasReader(Node method) {
+ return readers.contains(method);
+ }
+
+ public boolean hasWriter(Node method) {
+ return writers.contains(method);
+ }
+
public boolean isRoot() {
- return callers.isEmpty();
+ return callers.isEmpty() && readers.isEmpty();
}
public boolean isLeaf() {
- return callees.isEmpty();
+ return callees.isEmpty() && writers.isEmpty();
}
@Override
@@ -161,6 +242,10 @@
final Set<Node> nodes;
final CycleEliminationResult cycleEliminationResult;
+ CallGraph(Set<Node> nodes) {
+ this(nodes, null);
+ }
+
CallGraph(Set<Node> nodes, CycleEliminationResult cycleEliminationResult) {
this.nodes = nodes;
this.cycleEliminationResult = cycleEliminationResult;
@@ -182,11 +267,11 @@
}
public Set<DexEncodedMethod> extractLeaves() {
- return extractNodes(Node::isLeaf, Node::cleanCallersForRemoval);
+ return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval);
}
public Set<DexEncodedMethod> extractRoots() {
- return extractNodes(Node::isRoot, Node::cleanCalleesForRemoval);
+ return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval);
}
private Set<DexEncodedMethod> extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
@@ -202,6 +287,7 @@
}
}
removed.forEach(clean);
+ assert !result.isEmpty();
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index 42842a2..5b94efb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -20,7 +20,7 @@
}
@Override
- void process(ExecutorService executorService) throws ExecutionException {
+ void populateGraph(ExecutorService executorService) throws ExecutionException {
ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index ade31ca..610240e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
@@ -11,7 +13,10 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
@@ -20,20 +25,18 @@
import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -43,17 +46,24 @@
import java.util.function.Predicate;
abstract class CallGraphBuilderBase {
+
final AppView<AppInfoWithLiveness> appView;
+ private final FieldAccessInfoCollection<?> fieldAccessInfoCollection;
final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
new ConcurrentHashMap<>();
CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
+ this.fieldAccessInfoCollection = appView.appInfo().getFieldAccessInfoCollection();
}
public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException {
- process(executorService);
+ timing.begin("Build IR processing order constraints");
+ timing.begin("Build call graph");
+ populateGraph(executorService);
+ assert verifyNoRedundantFieldReadEdges();
+ timing.end();
assert verifyAllMethodsWithCodeExists();
appView.withGeneratedMessageLiteBuilderShrinker(
@@ -62,16 +72,28 @@
timing.begin("Cycle elimination");
// Sort the nodes for deterministic cycle elimination.
Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values());
- CycleEliminator cycleEliminator =
- new CycleEliminator(nodesWithDeterministicOrder, appView.options());
- CycleEliminationResult cycleEliminationResult = cycleEliminator.breakCycles();
+ CycleEliminator cycleEliminator = new CycleEliminator();
+ CycleEliminationResult cycleEliminationResult =
+ cycleEliminator.breakCycles(nodesWithDeterministicOrder);
timing.end();
- assert cycleEliminator.breakCycles().numberOfRemovedEdges() == 0; // The cycles should be gone.
+ timing.end();
+ assert cycleEliminator.breakCycles(nodesWithDeterministicOrder).numberOfRemovedCallEdges()
+ == 0; // The cycles should be gone.
return new CallGraph(nodesWithDeterministicOrder, cycleEliminationResult);
}
- abstract void process(ExecutorService executorService) throws ExecutionException;
+ abstract void populateGraph(ExecutorService executorService) throws ExecutionException;
+
+ /** Verify that there are no field read edges in the graph if there is also a call graph edge. */
+ private boolean verifyNoRedundantFieldReadEdges() {
+ for (Node writer : nodes.values()) {
+ for (Node reader : writer.getReadersWithDeterministicOrder()) {
+ assert !writer.hasCaller(reader);
+ }
+ }
+ return true;
+ }
Node getOrCreateNode(DexEncodedMethod method) {
synchronized (nodes) {
@@ -83,31 +105,31 @@
class InvokeExtractor extends UseRegistry {
- private final Node caller;
+ private final Node currentMethod;
private final Predicate<DexEncodedMethod> targetTester;
- InvokeExtractor(Node caller, Predicate<DexEncodedMethod> targetTester) {
+ InvokeExtractor(Node currentMethod, Predicate<DexEncodedMethod> targetTester) {
super(appView.dexItemFactory());
- this.caller = caller;
+ this.currentMethod = currentMethod;
this.targetTester = targetTester;
}
- private void addClassInitializerTarget(DexClass clazz) {
+ private void addClassInitializerTarget(DexProgramClass clazz) {
assert clazz != null;
- if (clazz.isProgramClass() && clazz.hasClassInitializer()) {
- addTarget(clazz.getClassInitializer(), false);
+ if (clazz.hasClassInitializer()) {
+ addCallEdge(clazz.getClassInitializer(), false);
}
}
private void addClassInitializerTarget(DexType type) {
assert type.isClassType();
- DexClass clazz = appView.definitionFor(type);
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
if (clazz != null) {
addClassInitializerTarget(clazz);
}
}
- private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
+ private void addCallEdge(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
if (!targetTester.test(callee)) {
return;
}
@@ -122,11 +144,20 @@
return;
}
assert callee.isProgramMethod(appView);
- getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
+ getOrCreateNode(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge);
+ }
+
+ private void addFieldReadEdge(DexEncodedMethod writer) {
+ assert !writer.accessFlags.isAbstract();
+ if (!targetTester.test(writer)) {
+ return;
+ }
+ assert writer.isProgramMethod(appView);
+ getOrCreateNode(writer).addReaderConcurrently(currentMethod);
}
private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
- DexEncodedMethod source = caller.method;
+ DexEncodedMethod source = currentMethod.method;
DexMethod context = source.method;
GraphLenseLookupResult result =
appView.graphLense().lookupMethod(originalMethod, context, originalType);
@@ -143,15 +174,15 @@
DexEncodedMethod singleTarget =
appView.appInfo().lookupSingleTarget(type, method, context.holder);
if (singleTarget != null) {
- assert !source.accessFlags.isBridge() || singleTarget != caller.method;
- DexClass clazz = appView.definitionFor(singleTarget.method.holder);
- assert clazz != null;
- if (clazz.isProgramClass()) {
+ assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
+ DexProgramClass clazz =
+ asProgramClassOrNull(appView.definitionFor(singleTarget.method.holder));
+ if (clazz != null) {
// For static invokes, the class could be initialized.
if (type == Invoke.Type.STATIC) {
addClassInitializerTarget(clazz);
}
- addTarget(singleTarget, false);
+ addCallEdge(singleTarget, false);
}
}
}
@@ -181,7 +212,7 @@
ResolutionResult resolution =
appView.appInfo().resolveMethod(method.holder, method, isInterface);
if (resolution.isVirtualTarget()) {
- return resolution.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
+ return resolution.lookupVirtualDispatchTargets(appView.appInfo());
}
return null;
});
@@ -190,17 +221,46 @@
possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
for (DexEncodedMethod possibleTarget : possibleTargets) {
if (possibleTarget.isProgramMethod(appView)) {
- addTarget(possibleTarget, likelySpuriousCallEdge);
+ addCallEdge(possibleTarget, likelySpuriousCallEdge);
}
}
}
}
- private void processFieldAccess(DexField field) {
- // Any field access implicitly calls the class initializer.
+ private void processFieldRead(DexField field) {
+ if (!field.holder.isClassType()) {
+ return;
+ }
+
+ DexEncodedField encodedField = appView.appInfo().resolveField(field);
+ if (encodedField == null || appView.appInfo().isPinned(encodedField.field)) {
+ return;
+ }
+
+ DexProgramClass clazz =
+ asProgramClassOrNull(appView.definitionFor(encodedField.field.holder));
+ if (clazz == null) {
+ return;
+ }
+
+ // Each static field access implicitly triggers the class initializer.
+ if (encodedField.isStatic()) {
+ addClassInitializerTarget(clazz);
+ }
+
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.field);
+ if (fieldAccessInfo != null) {
+ if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
+ fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
+ }
+ }
+ }
+
+ private void processFieldWrite(DexField field) {
if (field.holder.isClassType()) {
DexEncodedField encodedField = appView.appInfo().resolveField(field);
if (encodedField != null && encodedField.isStatic()) {
+ // Each static field access implicitly triggers the class initializer.
addClassInitializerTarget(field.holder);
}
}
@@ -237,14 +297,14 @@
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- processFieldAccess(field);
+ public boolean registerInstanceFieldRead(DexField field) {
+ processFieldRead(field);
return false;
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- processFieldAccess(field);
+ public boolean registerInstanceFieldWrite(DexField field) {
+ processFieldWrite(field);
return false;
}
@@ -258,13 +318,13 @@
@Override
public boolean registerStaticFieldRead(DexField field) {
- processFieldAccess(field);
+ processFieldRead(field);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
- processFieldAccess(field);
+ processFieldWrite(field);
return false;
}
@@ -294,79 +354,108 @@
this.caller = caller;
this.callee = callee;
}
+ }
- public void remove() {
- callee.removeCaller(caller);
+ static class StackEntryInfo {
+
+ final int index;
+ final Node predecessor;
+
+ boolean processed;
+
+ StackEntryInfo(int index, Node predecessor) {
+ this.index = index;
+ this.predecessor = predecessor;
}
}
static class CycleEliminationResult {
- private Map<Node, Set<Node>> removedEdges;
+ private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges;
- CycleEliminationResult(Map<Node, Set<Node>> removedEdges) {
- this.removedEdges = removedEdges;
+ CycleEliminationResult(Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges) {
+ this.removedCallEdges = removedCallEdges;
}
- void forEachRemovedCaller(Node callee, Consumer<Node> fn) {
- removedEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
+ void forEachRemovedCaller(DexEncodedMethod callee, Consumer<DexEncodedMethod> fn) {
+ removedCallEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
}
- int numberOfRemovedEdges() {
- int numberOfRemovedEdges = 0;
- for (Set<Node> nodes : removedEdges.values()) {
- numberOfRemovedEdges += nodes.size();
+ int numberOfRemovedCallEdges() {
+ int numberOfRemovedCallEdges = 0;
+ for (Set<DexEncodedMethod> nodes : removedCallEdges.values()) {
+ numberOfRemovedCallEdges += nodes.size();
}
- return numberOfRemovedEdges;
+ return numberOfRemovedCallEdges;
}
}
- private final Collection<Node> nodes;
- private final InternalOptions options;
-
// DFS stack.
private Deque<Node> stack = new ArrayDeque<>();
- // Set of nodes on the DFS stack.
- private Set<Node> stackSet = Sets.newIdentityHashSet();
+ // Nodes on the DFS stack.
+ private Map<Node, StackEntryInfo> stackEntryInfo = new IdentityHashMap<>();
+
+ // Subset of the DFS stack, where the nodes on the stack satisfy that the edge from the
+ // predecessor to the node itself is a field read edge.
+ //
+ // This stack is used to efficiently compute if there is a field read edge inside a cycle when
+ // a cycle is found.
+ private Deque<Node> writerStack = new ArrayDeque<>();
// Set of nodes that have been visited entirely.
private Set<Node> marked = Sets.newIdentityHashSet();
+ // Call edges that should be removed when the caller has been processed. These are not removed
+ // directly since that would lead to ConcurrentModificationExceptions.
+ private Map<Node, Set<Node>> calleesToBeRemoved = new IdentityHashMap<>();
+
+ // Field read edges that should be removed when the reader has been processed. These are not
+ // removed directly since that would lead to ConcurrentModificationExceptions.
+ private Map<Node, Set<Node>> writersToBeRemoved = new IdentityHashMap<>();
+
// Mapping from callee to the set of callers that were removed from the callee.
- private Map<Node, Set<Node>> removedEdges = new IdentityHashMap<>();
+ private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges = new IdentityHashMap<>();
- private int maxDepth = 0;
+ // Set of nodes from which cycle elimination must be rerun to ensure that all cycles will be
+ // removed.
+ private LinkedHashSet<Node> revisit = new LinkedHashSet<>();
- CycleEliminator(Collection<Node> nodes, InternalOptions options) {
- this.options = options;
+ CycleEliminationResult breakCycles(Collection<Node> roots) {
+ // Break cycles in this call graph by removing edges causing cycles. We do this in a fixpoint
+ // because the algorithm does not guarantee that all cycles will be removed from the graph
+ // when we remove an edge in the middle of a cycle that contains another cycle.
+ do {
+ traverse(roots);
+ roots = revisit;
+ prepareForNewTraversal();
+ } while (!roots.isEmpty());
- // Call to reorderNodes must happen after assigning options.
- this.nodes =
- options.testing.nondeterministicCycleElimination
- ? reorderNodes(new ArrayList<>(nodes))
- : nodes;
- }
-
- CycleEliminationResult breakCycles() {
- // Break cycles in this call graph by removing edges causing cycles.
- traverse();
-
- CycleEliminationResult result = new CycleEliminationResult(removedEdges);
+ CycleEliminationResult result = new CycleEliminationResult(removedCallEdges);
if (Log.ENABLED) {
- Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedEdges());
- Log.info(getClass(), "# max call graph depth: %s", maxDepth);
+ Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedCallEdges());
}
reset();
return result;
}
- private void reset() {
+ private void prepareForNewTraversal() {
+ assert calleesToBeRemoved.isEmpty();
assert stack.isEmpty();
- assert stackSet.isEmpty();
+ assert stackEntryInfo.isEmpty();
+ assert writersToBeRemoved.isEmpty();
+ assert writerStack.isEmpty();
marked.clear();
- maxDepth = 0;
- removedEdges = new IdentityHashMap<>();
+ revisit = new LinkedHashSet<>();
+ }
+
+ private void reset() {
+ assert marked.isEmpty();
+ assert revisit.isEmpty();
+ assert stack.isEmpty();
+ assert stackEntryInfo.isEmpty();
+ assert writerStack.isEmpty();
+ removedCallEdges = new IdentityHashMap<>();
}
private static class WorkItem {
@@ -406,12 +495,12 @@
}
private static class IteratorWorkItem extends WorkItem {
- private final Node caller;
- private final Iterator<Node> callees;
+ private final Node callerOrReader;
+ private final Iterator<Node> calleesAndWriters;
- IteratorWorkItem(Node caller, Iterator<Node> callees) {
- this.caller = caller;
- this.callees = callees;
+ IteratorWorkItem(Node callerOrReader, Iterator<Node> calleesAndWriters) {
+ this.callerOrReader = callerOrReader;
+ this.calleesAndWriters = calleesAndWriters;
}
@Override
@@ -425,134 +514,164 @@
}
}
- private void traverse() {
- Deque<WorkItem> workItems = new ArrayDeque<>(nodes.size());
- for (Node node : nodes) {
+ private void traverse(Collection<Node> roots) {
+ Deque<WorkItem> workItems = new ArrayDeque<>(roots.size());
+ for (Node node : roots) {
workItems.addLast(new NodeWorkItem(node));
}
while (!workItems.isEmpty()) {
WorkItem workItem = workItems.removeFirst();
if (workItem.isNode()) {
Node node = workItem.asNode().node;
- if (Log.ENABLED) {
- if (stack.size() > maxDepth) {
- maxDepth = stack.size();
- }
- }
-
if (marked.contains(node)) {
// Already visited all nodes that can be reached from this node.
continue;
}
- push(node);
+ Node predecessor = stack.isEmpty() ? null : stack.peek();
+ push(node, predecessor);
- // The callees must be sorted before calling traverse recursively. This ensures that
- // cycles are broken the same way across multiple compilations.
- Collection<Node> callees = node.getCalleesWithDeterministicOrder();
-
- if (options.testing.nondeterministicCycleElimination) {
- callees = reorderNodes(new ArrayList<>(callees));
- }
- workItems.addFirst(new IteratorWorkItem(node, callees.iterator()));
+ // The callees and writers must be sorted before calling traverse recursively.
+ // This ensures that cycles are broken the same way across multiple compilations.
+ Iterator<Node> calleesAndWriterIterator =
+ Iterators.concat(
+ node.getCalleesWithDeterministicOrder().iterator(),
+ node.getWritersWithDeterministicOrder().iterator());
+ workItems.addFirst(new IteratorWorkItem(node, calleesAndWriterIterator));
} else {
assert workItem.isIterator();
IteratorWorkItem iteratorWorkItem = workItem.asIterator();
- Node newCaller = iterateCallees(iteratorWorkItem.callees, iteratorWorkItem.caller);
- if (newCaller != null) {
+ Node newCallerOrReader =
+ iterateCalleesAndWriters(
+ iteratorWorkItem.calleesAndWriters, iteratorWorkItem.callerOrReader);
+ if (newCallerOrReader != null) {
// We did not finish the work on this iterator, so add it again.
workItems.addFirst(iteratorWorkItem);
- workItems.addFirst(new NodeWorkItem(newCaller));
+ workItems.addFirst(new NodeWorkItem(newCallerOrReader));
} else {
- assert !iteratorWorkItem.callees.hasNext();
- pop(iteratorWorkItem.caller);
- marked.add(iteratorWorkItem.caller);
+ assert !iteratorWorkItem.calleesAndWriters.hasNext();
+ pop(iteratorWorkItem.callerOrReader);
+ marked.add(iteratorWorkItem.callerOrReader);
+
+ Collection<Node> calleesToBeRemovedFromCaller =
+ calleesToBeRemoved.remove(iteratorWorkItem.callerOrReader);
+ if (calleesToBeRemovedFromCaller != null) {
+ calleesToBeRemovedFromCaller.forEach(
+ callee -> {
+ callee.removeCaller(iteratorWorkItem.callerOrReader);
+ recordCallEdgeRemoval(iteratorWorkItem.callerOrReader, callee);
+ });
+ }
+
+ Collection<Node> writersToBeRemovedFromReader =
+ writersToBeRemoved.remove(iteratorWorkItem.callerOrReader);
+ if (writersToBeRemovedFromReader != null) {
+ writersToBeRemovedFromReader.forEach(
+ writer -> writer.removeReader(iteratorWorkItem.callerOrReader));
+ }
}
}
}
}
- private Node iterateCallees(Iterator<Node> calleeIterator, Node node) {
- while (calleeIterator.hasNext()) {
- Node callee = calleeIterator.next();
- boolean foundCycle = stackSet.contains(callee);
- if (foundCycle) {
- // Found a cycle that needs to be eliminated.
- if (edgeRemovalIsSafe(node, callee)) {
- // Break the cycle by removing the edge node->callee.
- if (options.testing.nondeterministicCycleElimination) {
- callee.removeCaller(node);
- } else {
- // Need to remove `callee` from `node.callees` using the iterator to prevent a
- // ConcurrentModificationException. This is not needed when nondeterministic cycle
- // elimination is enabled, because we iterate a copy of `node.callees` in that case.
- calleeIterator.remove();
- callee.getCallersWithDeterministicOrder().remove(node);
- }
- recordEdgeRemoval(node, callee);
-
- if (Log.ENABLED) {
- Log.info(
- CallGraph.class,
- "Removed call edge from method '%s' to '%s'",
- node.method.toSourceString(),
- callee.method.toSourceString());
- }
- } else {
- assert foundCycle;
-
- // The cycle has a method that is marked as force inline.
- LinkedList<Node> cycle = extractCycle(callee);
-
- if (Log.ENABLED) {
- Log.info(
- CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
- }
-
- // Break the cycle by finding an edge that can be removed without breaking force
- // inlining. If that is not possible, this call fails with a compilation error.
- CallEdge edge = findCallEdgeForRemoval(cycle);
-
- // The edge will be null if this cycle has already been eliminated as a result of
- // another cycle elimination.
- if (edge != null) {
- assert edgeRemovalIsSafe(edge.caller, edge.callee);
-
- // Break the cycle by removing the edge caller->callee.
- edge.remove();
- recordEdgeRemoval(edge.caller, edge.callee);
-
- if (Log.ENABLED) {
- Log.info(
- CallGraph.class,
- "Removed call edge from force inlined method '%s' to '%s' to ensure that "
- + "force inlining will succeed",
- node.method.toSourceString(),
- callee.method.toSourceString());
- }
- }
-
- // Recover the stack.
- recoverStack(cycle);
- }
- } else {
- return callee;
+ private Node iterateCalleesAndWriters(
+ Iterator<Node> calleeOrWriterIterator, Node callerOrReader) {
+ while (calleeOrWriterIterator.hasNext()) {
+ Node calleeOrWriter = calleeOrWriterIterator.next();
+ StackEntryInfo calleeOrWriterStackEntryInfo = stackEntryInfo.get(calleeOrWriter);
+ boolean foundCycle = calleeOrWriterStackEntryInfo != null;
+ if (!foundCycle) {
+ return calleeOrWriter;
}
+
+ // Found a cycle that needs to be eliminated. If it is a field read edge, then remove it
+ // right away.
+ boolean isFieldReadEdge = calleeOrWriter.hasReader(callerOrReader);
+ if (isFieldReadEdge) {
+ removeFieldReadEdge(callerOrReader, calleeOrWriter);
+ continue;
+ }
+
+ // Otherwise, it is a call edge. Check if there is a field read edge in the cycle, and if
+ // so, remove that edge.
+ if (!writerStack.isEmpty()) {
+ Node lastKnownWriter = writerStack.peek();
+ StackEntryInfo lastKnownWriterStackEntryInfo = stackEntryInfo.get(lastKnownWriter);
+ boolean cycleContainsLastKnownWriter =
+ lastKnownWriterStackEntryInfo.index > calleeOrWriterStackEntryInfo.index;
+ if (cycleContainsLastKnownWriter) {
+ assert verifyCycleSatisfies(
+ calleeOrWriter,
+ cycle ->
+ cycle.contains(lastKnownWriter)
+ && cycle.contains(lastKnownWriterStackEntryInfo.predecessor));
+ if (!lastKnownWriterStackEntryInfo.processed) {
+ removeFieldReadEdge(lastKnownWriterStackEntryInfo.predecessor, lastKnownWriter);
+ revisit.add(lastKnownWriter);
+ lastKnownWriterStackEntryInfo.processed = true;
+ }
+ continue;
+ }
+ }
+
+ // It is a call edge, and the cycle does not contain any field read edges. In this case, we
+ // remove the call edge if it is safe according to force inlining.
+ if (callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter)) {
+ // Break the cycle by removing the edge node->calleeOrWriter.
+ // Need to remove `calleeOrWriter` from `node.callees` using the iterator to prevent a
+ // ConcurrentModificationException.
+ removeCallEdge(callerOrReader, calleeOrWriter);
+ continue;
+ }
+
+ // The call edge cannot be removed due to force inlining. Find another call edge in the
+ // cycle that can safely be removed instead.
+ LinkedList<Node> cycle = extractCycle(calleeOrWriter);
+
+ // Break the cycle by finding an edge that can be removed without breaking force
+ // inlining. If that is not possible, this call fails with a compilation error.
+ CallEdge edge = findCallEdgeForRemoval(cycle);
+
+ // The edge will be null if this cycle has already been eliminated as a result of
+ // another cycle elimination.
+ if (edge != null) {
+ assert callEdgeRemovalIsSafe(edge.caller, edge.callee);
+
+ // Break the cycle by removing the edge caller->callee.
+ removeCallEdge(edge.caller, edge.callee);
+ }
+
+ // Recover the stack.
+ recoverStack(cycle);
}
return null;
}
- private void push(Node node) {
+ private void push(Node node, Node predecessor) {
stack.push(node);
- boolean changed = stackSet.add(node);
- assert changed;
+ assert !stackEntryInfo.containsKey(node);
+ stackEntryInfo.put(node, new StackEntryInfo(stack.size() - 1, predecessor));
+ if (predecessor != null && predecessor.getWritersWithDeterministicOrder().contains(node)) {
+ writerStack.push(node);
+ }
}
private void pop(Node node) {
Node popped = stack.pop();
assert popped == node;
- boolean changed = stackSet.remove(node);
- assert changed;
+ assert stackEntryInfo.containsKey(node);
+ stackEntryInfo.remove(node);
+ if (writerStack.peek() == popped) {
+ writerStack.pop();
+ }
+ }
+
+ private void removeCallEdge(Node caller, Node callee) {
+ calleesToBeRemoved.computeIfAbsent(caller, ignore -> Sets.newIdentityHashSet()).add(callee);
+ }
+
+ private void removeFieldReadEdge(Node reader, Node writer) {
+ writersToBeRemoved.computeIfAbsent(reader, ignore -> Sets.newIdentityHashSet()).add(writer);
}
private LinkedList<Node> extractCycle(Node entry) {
@@ -564,15 +683,29 @@
return cycle;
}
+ private boolean verifyCycleSatisfies(Node entry, Predicate<LinkedList<Node>> predicate) {
+ LinkedList<Node> cycle = extractCycle(entry);
+ assert predicate.test(cycle);
+ recoverStack(cycle);
+ return true;
+ }
+
private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
Node callee = extractedCycle.getLast();
for (Node caller : extractedCycle) {
+ if (caller.hasWriter(callee)) {
+ // Not a call edge.
+ assert !caller.hasCallee(callee);
+ assert !callee.hasCaller(caller);
+ callee = caller;
+ continue;
+ }
if (!caller.hasCallee(callee)) {
// No need to break any edges since this cycle has already been broken previously.
assert !callee.hasCaller(caller);
return null;
}
- if (edgeRemovalIsSafe(caller, callee)) {
+ if (callEdgeRemovalIsSafe(caller, callee)) {
return new CallEdge(caller, callee);
}
callee = caller;
@@ -580,14 +713,17 @@
throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
}
- private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+ private static boolean callEdgeRemovalIsSafe(Node callerOrReader, Node calleeOrWriter) {
// All call edges where the callee is a method that should be force inlined must be kept,
// to guarantee that the IR converter will process the callee before the caller.
- return !callee.method.getOptimizationInfo().forceInline();
+ assert calleeOrWriter.hasCaller(callerOrReader);
+ return !calleeOrWriter.method.getOptimizationInfo().forceInline();
}
- private void recordEdgeRemoval(Node caller, Node callee) {
- removedEdges.computeIfAbsent(callee, ignore -> SetUtils.newIdentityHashSet(2)).add(caller);
+ private void recordCallEdgeRemoval(Node caller, Node callee) {
+ removedCallEdges
+ .computeIfAbsent(callee.method, ignore -> SetUtils.newIdentityHashSet(2))
+ .add(caller.method);
}
private void recoverStack(LinkedList<Node> extractedCycle) {
@@ -596,13 +732,5 @@
stack.push(descendingIt.next());
}
}
-
- private Collection<Node> reorderNodes(List<Node> nodes) {
- assert options.testing.nondeterministicCycleElimination;
- if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
- Collections.shuffle(nodes);
- }
- return nodes;
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 83ffaeb..a16d39d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
import com.android.tools.r8.ir.optimize.PhiOptimizations;
import com.android.tools.r8.ir.optimize.peepholes.BasicBlockMuncher;
@@ -127,11 +128,11 @@
this.code = code;
}
- public CfCode build(CodeRewriter rewriter) {
+ public CfCode build(DeadCodeRemover deadCodeRemover) {
computeInitializers();
TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
typeVerificationHelper.computeVerificationTypes();
- rewriter.converter.deadCodeRemover.run(code);
+ assert deadCodeRemover.verifyNoDeadCode(code);
rewriteNots();
LoadStoreHelper loadStoreHelper = new LoadStoreHelper(appView, code, typeVerificationHelper);
loadStoreHelper.insertLoadsAndStores();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 67a0178..b1b6e81 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -42,6 +42,7 @@
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -224,7 +225,8 @@
? null
: new InterfaceMethodRewriter(appView, this);
this.lambdaRewriter = new LambdaRewriter(appView);
- this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView);
+ this.desugaredLibraryAPIConverter =
+ new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
this.twrCloseResourceRewriter = null;
this.lambdaMerger = null;
this.covariantReturnTypeAnnotationTransformer = null;
@@ -266,14 +268,13 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
: null;
+ this.libraryMethodOptimizer = new LibraryMethodOptimizer(appView);
if (options.testing.forceAssumeNoneInsertion) {
assumers.add(new AliasIntroducer(appView));
}
if (options.enableNonNullTracking) {
assumers.add(new NonNullTracker(appView));
}
- this.desugaredLibraryAPIConverter =
- appView.rewritePrefix.isRewriting() ? new DesugaredLibraryAPIConverter(appView) : null;
if (appView.enableWholeProgramOptimizations()) {
assert appView.appInfo().hasLiveness();
assert appView.rootSet() != null;
@@ -291,7 +292,6 @@
}
this.fieldAccessAnalysis =
FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null;
- this.libraryMethodOptimizer = new LibraryMethodOptimizer(appViewWithLiveness);
this.libraryMethodOverrideAnalysis =
options.enableTreeShakingOfLibraryMethodOverrides
? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
@@ -323,13 +323,17 @@
options.enableServiceLoaderRewriting
? new ServiceLoaderRewriter(appViewWithLiveness)
: null;
+ this.desugaredLibraryAPIConverter =
+ appView.rewritePrefix.isRewriting()
+ ? new DesugaredLibraryAPIConverter(
+ appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
+ : null;
this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
} else {
this.classInliner = null;
this.classStaticizer = null;
this.dynamicTypeOptimization = null;
this.fieldAccessAnalysis = null;
- this.libraryMethodOptimizer = null;
this.libraryMethodOverrideAnalysis = null;
this.inliner = null;
this.lambdaMerger = null;
@@ -342,6 +346,10 @@
this.typeChecker = null;
this.d8NestBasedAccessDesugaring =
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
+ this.desugaredLibraryAPIConverter =
+ appView.rewritePrefix.isRewriting()
+ ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
+ : null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumUnboxer = null;
@@ -637,7 +645,7 @@
PostMethodProcessor.Builder postMethodProcessorBuilder =
new PostMethodProcessor.Builder(getOptimizationsForPostIRProcessing());
{
- timing.begin("Build call graph");
+ timing.begin("Build primary method processor");
PrimaryMethodProcessor primaryMethodProcessor =
PrimaryMethodProcessor.create(
appView.withLiveness(), postMethodProcessorBuilder, executorService, timing);
@@ -747,7 +755,7 @@
code -> {
outliner.applyOutliningCandidate(code);
printMethod(code, "IR after outlining (SSA)", null);
- finalizeIR(
+ removeDeadCodeAndFinalizeIR(
code.method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
},
executorService);
@@ -852,7 +860,7 @@
// StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
// unused out-values.
codeRewriter.rewriteMoveResult(code);
- deadCodeRemover.run(code);
+ deadCodeRemover.run(code, Timing.empty());
CodeRewriter.removeAssumeInstructions(appView, code);
consumer.accept(code);
},
@@ -871,7 +879,8 @@
IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
assert code != null;
codeRewriter.rewriteMoveResult(code);
- finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+ removeDeadCodeAndFinalizeIR(
+ method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
}
private void collectLambdaMergingCandidates(DexApplication application) {
@@ -896,7 +905,7 @@
DexApplication.Builder<?> builder, ExecutorService executorService)
throws ExecutionException {
if (desugaredLibraryAPIConverter != null) {
- desugaredLibraryAPIConverter.generateWrappers(builder, this, executorService);
+ desugaredLibraryAPIConverter.finalizeWrappers(builder, this, executorService);
}
}
@@ -922,8 +931,9 @@
Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
}
assert code.isConsistentSSA();
- code.traceBlocks();
Timing timing = Timing.empty();
+ deadCodeRemover.run(code, timing);
+ code.traceBlocks();
RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
method.setCode(code, registerAllocator, appView);
if (Log.ENABLED) {
@@ -1328,9 +1338,7 @@
// Dead code removal. Performed after simplifications to remove code that becomes dead
// as a result of those simplifications. The following optimizations could reveal more
// dead code which is removed right before register allocation in performRegisterAllocation.
- timing.begin("Remove dead code");
- deadCodeRemover.run(code);
- timing.end();
+ deadCodeRemover.run(code, timing);
assert code.isConsistentSSA();
if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
@@ -1487,31 +1495,12 @@
assert code.verifyTypes(appView);
+ deadCodeRemover.run(code, timing);
+
if (appView.enableWholeProgramOptimizations()) {
- if (libraryMethodOverrideAnalysis != null) {
- timing.begin("Analyze library method overrides");
- libraryMethodOverrideAnalysis.analyze(code);
- timing.end();
- }
-
- if (fieldAccessAnalysis != null) {
- timing.begin("Analyze field accesses");
- fieldAccessAnalysis.recordFieldAccesses(code, feedback, methodProcessor);
- if (classInitializerDefaultsResult != null) {
- fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
- }
- timing.end();
- }
-
- // Arguments can be changed during the debug mode.
- if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
- timing.begin("Collect call-site info");
- appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
- timing.end();
- }
-
timing.begin("Collect optimization info");
- collectOptimizationInfo(code, classInitializerDefaultsResult, feedback);
+ collectOptimizationInfo(
+ method, code, classInitializerDefaultsResult, feedback, methodProcessor, timing);
timing.end();
}
@@ -1546,17 +1535,50 @@
// Compute optimization info summary for the current method unless it is pinned
// (in that case we should not be making any assumptions about the behavior of the method).
public void collectOptimizationInfo(
+ DexEncodedMethod method,
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
- OptimizationFeedback feedback) {
+ OptimizationFeedback feedback,
+ MethodProcessor methodProcessor,
+ Timing timing) {
+ if (libraryMethodOverrideAnalysis != null) {
+ timing.begin("Analyze library method overrides");
+ libraryMethodOverrideAnalysis.analyze(code);
+ timing.end();
+ }
+
+ if (fieldAccessAnalysis != null) {
+ timing.begin("Analyze field accesses");
+ fieldAccessAnalysis.recordFieldAccesses(code, feedback, methodProcessor);
+ if (classInitializerDefaultsResult != null) {
+ fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
+ }
+ timing.end();
+ }
+
+ // Arguments can be changed during the debug mode.
+ boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+ if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
+ timing.begin("Collect call-site info");
+ appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+ timing.end();
+ }
+
if (appView.appInfo().withLiveness().isPinned(code.method.method)) {
return;
}
+
methodOptimizationInfoCollector
.collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
FieldValueAnalysis.run(appView, code, classInitializerDefaultsResult, feedback, code.method);
}
+ public void removeDeadCodeAndFinalizeIR(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ deadCodeRemover.run(code, timing);
+ finalizeIR(method, code, feedback, timing);
+ }
+
public void finalizeIR(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
code.traceBlocks();
@@ -1581,7 +1603,7 @@
private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
assert !method.getCode().isDexCode();
CfBuilder builder = new CfBuilder(appView, method, code);
- CfCode result = builder.build(codeRewriter);
+ CfCode result = builder.build(deadCodeRemover);
method.setCode(result, appView);
markProcessed(method, code, feedback);
}
@@ -1633,9 +1655,7 @@
IRCode code, DexEncodedMethod method, Timing timing) {
// Always perform dead code elimination before register allocation. The register allocator
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
- timing.begin("Remove dead code");
- deadCodeRemover.run(code);
- timing.end();
+ assert deadCodeRemover.verifyNoDeadCode(code);
materializeInstructionBeforeLongOperationsWorkaround(code);
workaroundForwardingInitializerBug(code);
timing.begin("Allocate registers");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index a15cbe4..ca144f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -60,7 +60,7 @@
void setInstanceInitializerInfo(
DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo);
- void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
+ void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method);
void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 43b801e..b55f54f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -26,7 +26,7 @@
return new OneTimeMethodProcessor(null);
}
- static OneTimeMethodProcessor getInstance(Collection<DexEncodedMethod> methodsToProcess) {
+ public static OneTimeMethodProcessor getInstance(Collection<DexEncodedMethod> methodsToProcess) {
return new OneTimeMethodProcessor(methodsToProcess);
}
@@ -40,9 +40,8 @@
return wave != null && wave.contains(method);
}
- <E extends Exception> void forEachWave(
- ThrowingConsumer<DexEncodedMethod, E> consumer,
- ExecutorService executorService)
+ public <E extends Exception> void forEachWave(
+ ThrowingConsumer<DexEncodedMethod, E> consumer, ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(wave, consumer, executorService);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
index ef446d1..bfaf62c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
@@ -21,7 +21,7 @@
}
@Override
- void process(ExecutorService executorService) throws ExecutionException {
+ void populateGraph(ExecutorService executorService) throws ExecutionException {
ThreadUtils.processItems(seeds, this::processMethod, executorService);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index acba17d..8fb6afb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
-import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IROrdering;
@@ -20,7 +19,6 @@
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
-import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -81,18 +79,12 @@
Set<DexEncodedMethod> reprocessing = Sets.newIdentityHashSet();
int waveCount = 1;
while (!nodes.isEmpty()) {
- Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
- extractLeaves(
- nodes,
- leaf -> {
- wave.add(leaf.method);
-
- // Reprocess methods that invoke a method with a single call site.
- if (callSiteInformation.hasSingleCallSite(leaf.method.method)) {
- callGraph.cycleEliminationResult.forEachRemovedCaller(
- leaf, caller -> reprocessing.add(caller.method));
- }
- });
+ Set<DexEncodedMethod> wave = callGraph.extractLeaves();
+ for (DexEncodedMethod method : wave) {
+ if (callSiteInformation.hasSingleCallSite(method.method)) {
+ callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
+ }
+ }
waves.addLast(shuffle.order(wave));
if (Log.ENABLED && Log.isLoggingEnabledFor(PrimaryMethodProcessor.class)) {
Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
@@ -105,26 +97,6 @@
return waves;
}
- /**
- * Extract the next set of leaves (nodes with an outgoing call degree of 0) if any.
- *
- * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
- * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
- */
- static void extractLeaves(Set<Node> nodes, Consumer<Node> fn) {
- Set<Node> removed = Sets.newIdentityHashSet();
- Iterator<Node> nodeIterator = nodes.iterator();
- while (nodeIterator.hasNext()) {
- Node node = nodeIterator.next();
- if (node.isLeaf()) {
- fn.accept(node);
- nodeIterator.remove();
- removed.add(node);
- }
- }
- removed.forEach(Node::cleanCallersForRemoval);
- }
-
@Override
public boolean isProcessedConcurrently(DexEncodedMethod method) {
return wave != null && wave.contains(method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index fe87616..6ff25d9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,12 +4,18 @@
package com.android.tools.r8.ir.desugar;
+import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+
+import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -26,11 +32,13 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites;
@@ -43,11 +51,14 @@
import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -92,15 +103,29 @@
&& appView.options().minApiLevel <= AndroidApiLevel.LATEST.getLevel();
}
- public static List<DexMethod> generateListOfBackportedMethods(AndroidApiLevel apiLevel) {
- List<DexMethod> methods = new ArrayList<>();
- InternalOptions options = new InternalOptions();
- options.minApiLevel = apiLevel.getLevel();
- AppView<?> appView = AppView.createForD8(null, options);
- BackportedMethodRewriter.RewritableMethods rewritableMethods =
- new BackportedMethodRewriter.RewritableMethods(options, appView);
- rewritableMethods.visit(methods::add);
- return methods;
+ public static List<DexMethod> generateListOfBackportedMethods(
+ AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
+ try {
+ List<DexMethod> methods = new ArrayList<>();
+ PrefixRewritingMapper rewritePrefix =
+ options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ AppInfo appInfo = null;
+ if (androidApp != null) {
+ DexApplication app =
+ new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+ appInfo =
+ options.desugaredLibraryConfiguration.getRewritePrefix().isEmpty()
+ ? new AppInfo(app)
+ : new AppInfoWithClassHierarchy(app);
+ }
+ AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+ BackportedMethodRewriter.RewritableMethods rewritableMethods =
+ new BackportedMethodRewriter.RewritableMethods(options, appView);
+ rewritableMethods.visit(methods::add);
+ return methods;
+ } catch (ExecutionException e) {
+ throw unwrapExecutionException(e);
+ }
}
public void desugar(IRCode code) {
@@ -108,6 +133,7 @@
return; // Nothing to do!
}
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
@@ -165,7 +191,7 @@
}
}
- provider.rewriteInvoke(invoke, iterator, code, appView);
+ provider.rewriteInvoke(invoke, iterator, code, appView, affectedValues);
if (provider.requiresGenerationOfCode()) {
DexMethod newMethod = provider.provideMethod(appView);
@@ -173,6 +199,9 @@
holders.add(code.method.method.holder);
}
}
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
}
private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
@@ -1441,6 +1470,17 @@
new MethodGenerator(
method, BackportedMethods::CharacterMethods_toStringCodepoint, "toStringCodepoint"));
+ // CharSequence
+ type = factory.charSequenceType;
+
+ // int CharSequence.compare(CharSequence, CharSequence)
+ name = factory.createString("compare");
+ proto =
+ factory.createProto(factory.intType, factory.charSequenceType, factory.charSequenceType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(method, BackportedMethods::CharSequenceMethods_compare, "compare"));
+
// String
type = factory.stringType;
@@ -1574,10 +1614,10 @@
};
MethodInvokeRewriter[] rewriters =
new MethodInvokeRewriter[] {
- OptionalMethodRewrites::rewriteOrElseGet,
- OptionalMethodRewrites::rewriteDoubleOrElseGet,
- OptionalMethodRewrites::rewriteLongOrElseGet,
- OptionalMethodRewrites::rewriteIntOrElseGet,
+ OptionalMethodRewrites::rewriteOrElseGet,
+ OptionalMethodRewrites::rewriteDoubleOrElseGet,
+ OptionalMethodRewrites::rewriteLongOrElseGet,
+ OptionalMethodRewrites::rewriteIntOrElseGet,
};
DexString name = factory.createString("orElseThrow");
for (int i = 0; i < optionalTypes.length; i++) {
@@ -1714,8 +1754,12 @@
this.method = method;
}
- public abstract void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator,
- IRCode code, AppView<?> appView);
+ public abstract void rewriteInvoke(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ IRCode code,
+ AppView<?> appView,
+ Set<Value> affectedValues);
public abstract DexMethod provideMethod(AppView<?> appView);
@@ -1737,8 +1781,12 @@
}
@Override
- public void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator, IRCode code,
- AppView<?> appView) {
+ public void rewriteInvoke(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ IRCode code,
+ AppView<?> appView,
+ Set<Value> affectedValues) {
iterator.replaceCurrentInstruction(
new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
}
@@ -1777,8 +1825,12 @@
@Override
public void rewriteInvoke(
- InvokeMethod invoke, InstructionListIterator iterator, IRCode code, AppView<?> appView) {
- rewriter.rewrite(invoke, iterator, appView.dexItemFactory());
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ IRCode code,
+ AppView<?> appView,
+ Set<Value> affectedValues) {
+ rewriter.rewrite(invoke, iterator, appView.dexItemFactory(), affectedValues);
assert code.isConsistentSSA();
}
@@ -1815,8 +1867,12 @@
}
@Override
- public void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator, IRCode code,
- AppView<?> appView) {
+ public void rewriteInvoke(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ IRCode code,
+ AppView<?> appView,
+ Set<Value> affectedValues) {
iterator.replaceCurrentInstruction(
new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
}
@@ -1895,6 +1951,10 @@
private interface MethodInvokeRewriter {
- void rewrite(InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory);
+ void rewrite(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues);
}
}
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 dc0e8d6..ceed9ef 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
@@ -8,9 +8,11 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -60,17 +62,27 @@
public class DesugaredLibraryAPIConverter {
static final String VIVIFIED_PREFIX = "$-vivified-$.";
+ private static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
private final AppView<?> appView;
private final DexItemFactory factory;
+ // For debugging only, allows to assert that synthesized code in R8 have been synthesized in the
+ // Enqueuer and not during IR processing.
+ private final Mode mode;
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new HashMap<>();
private final Set<DexMethod> trackedCallBackAPIs;
private final Set<DexMethod> trackedAPIs;
- public DesugaredLibraryAPIConverter(AppView<?> appView) {
+ public enum Mode {
+ GENERATE_CALLBACKS_AND_WRAPPERS,
+ ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED;
+ }
+
+ public DesugaredLibraryAPIConverter(AppView<?> appView, Mode mode) {
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.mode = mode;
this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
if (appView.options().testing.trackDesugaredAPIConversions) {
trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -81,13 +93,25 @@
}
}
+ public static boolean isVivifiedType(DexType type) {
+ return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
+ }
+
+ boolean canGenerateWrappersAndCallbacks() {
+ return mode == Mode.GENERATE_CALLBACKS_AND_WRAPPERS;
+ }
+
public void desugar(IRCode code) {
if (wrapperSynthesizor.hasSynthesized(code.method.method.holder)) {
return;
}
- generateCallBackIfNeeded(code);
+ if (!canGenerateWrappersAndCallbacks()) {
+ assert validateCallbackWasGeneratedInEnqueuer(code.method);
+ } else {
+ registerCallbackIfRequired(code.method);
+ }
ListIterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
@@ -100,25 +124,47 @@
}
InvokeMethod invokeMethod = instruction.asInvokeMethod();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
- // Rewriting is required only on calls to library methods which are not desugared.
- if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
- || invokedMethod.holder.isArrayType()) {
- continue;
- }
- DexClass dexClass = appView.definitionFor(invokedMethod.holder);
- if (dexClass == null || !dexClass.isLibraryClass()) {
- continue;
- }
// Library methods do not understand desugared types, hence desugared types have to be
// converted around non desugared library calls for the invoke to resolve.
- if (appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView)) {
+ if (shouldRewriteInvoke(invokedMethod)) {
rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
}
}
}
}
- private void generateCallBackIfNeeded(IRCode code) {
+ private boolean validateCallbackWasGeneratedInEnqueuer(DexEncodedMethod encodedMethod) {
+ if (!shouldRegisterCallback(encodedMethod)) {
+ return true;
+ }
+ DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.method.holder);
+ DexMethod installedCallback =
+ methodWithVivifiedTypeInSignature(encodedMethod.method, holderClass.type, appView);
+ assert holderClass.lookupMethod(installedCallback) != null;
+ return true;
+ }
+
+ private boolean shouldRewriteInvoke(DexMethod invokedMethod) {
+ if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
+ || invokedMethod.holder.isArrayType()) {
+ return false;
+ }
+ DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+ if (dexClass == null || !dexClass.isLibraryClass()) {
+ return false;
+ }
+ return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
+ }
+
+ public void registerCallbackIfRequired(DexEncodedMethod encodedMethod) {
+ if (shouldRegisterCallback(encodedMethod)) {
+ DexClass dexClass = appView.definitionFor(encodedMethod.method.holder);
+ assert dexClass != null;
+ registerCallback(dexClass, encodedMethod);
+ }
+ }
+
+ private boolean shouldRegisterCallback(DexEncodedMethod encodedMethod) {
// Any override of a library method can be called by the library.
// We duplicate the method to have a vivified type version callable by the library and
// a type version callable by the program. We need to add the vivified version to the rootset
@@ -126,10 +172,10 @@
// library type), but the enqueuer cannot see that.
// To avoid too much computation we first look if the method would need to be rewritten if
// it would override a library method, then check if it overrides a library method.
- if (code.method.isPrivateMethod() || code.method.isStatic()) {
- return;
+ if (encodedMethod.isPrivateMethod() || encodedMethod.isStatic()) {
+ return false;
}
- DexMethod method = code.method.method;
+ DexMethod method = encodedMethod.method;
if (method.holder.isArrayType()
|| !appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto, appView)
|| appView
@@ -137,17 +183,16 @@
.desugaredLibraryConfiguration
.getEmulateLibraryInterface()
.containsKey(method.holder)) {
- return;
+ return false;
}
DexClass dexClass = appView.definitionFor(method.holder);
if (dexClass == null) {
- return;
+ return false;
}
- if (overridesLibraryMethod(dexClass, method)) {
- generateCallBack(dexClass, code.method);
- }
+ return overridesLibraryMethod(dexClass, method);
}
+
private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
// We look up everywhere to see if there is a supertype/interface implementing the method...
LinkedList<DexType> workList = new LinkedList<>();
@@ -182,17 +227,19 @@
return foundOverrideToRewrite;
}
- private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
- if (dexClass.isInterface()
- && originalMethod.isDefaultMethod()
- && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
- || appView.options().isDesugaredLibraryCompilation())) {
- // Interface method desugaring has been performed before and all the call-backs will be
- // generated in all implementors of the interface. R8 cannot introduce new
- // default methods at this point, but R8 does not need to do anything (the interface
- // already implements the vivified version through inheritance, and all implementors
- // support the call-back correctly).
- return;
+ private synchronized void registerCallback(DexClass dexClass, DexEncodedMethod originalMethod) {
+ // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
+ // methods will be desugared.
+ // In D8, this happens after interface method desugaring, we cannot introduce new default
+ // methods, but we do not need to since this is a library override (invokes will resolve) and
+ // all implementors have been enhanced with a forwarding method which will be duplicated.
+ if (!appView.enableWholeProgramOptimizations()) {
+ if (dexClass.isInterface()
+ && originalMethod.isDefaultMethod()
+ && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+ || appView.options().isDesugaredLibraryCompilation())) {
+ return;
+ }
}
if (trackedCallBackAPIs != null) {
trackedCallBackAPIs.add(originalMethod.method);
@@ -225,25 +272,46 @@
return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
}
- public void generateWrappers(
+ public void finalizeWrappers(
DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
throws ExecutionException {
+ // In D8, we generate the wrappers here. In R8, wrappers have already been generated in the
+ // enqueuer, so nothing needs to be done.
+ if (appView.enableWholeProgramOptimizations()) {
+ return;
+ }
+ List<DexEncodedMethod> callbacks = generateCallbackMethods();
+ irConverter.processMethodsConcurrently(callbacks, executorService);
+ wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+ }
+
+ public List<DexEncodedMethod> generateCallbackMethods() {
if (appView.options().testing.trackDesugaredAPIConversions) {
generateTrackDesugaredAPIWarnings(trackedAPIs, "");
generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
}
+ List<DexEncodedMethod> result = new ArrayList<>();
for (DexClass dexClass : callBackMethods.keySet()) {
- Set<DexEncodedMethod> dexEncodedMethods =
+ List<DexEncodedMethod> dexEncodedMethods =
generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
dexClass.appendVirtualMethods(dexEncodedMethods);
- irConverter.processMethodsConcurrently(dexEncodedMethods, executorService);
+ result.addAll(dexEncodedMethods);
}
- wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
+ return result;
}
- private Set<DexEncodedMethod> generateCallbackMethods(
+ public Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
+ return wrapperSynthesizor.synthesizeWrappersAndMapToReverse();
+ }
+
+ public DexClasspathClass synthesizeClasspathMock(
+ DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+ return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+ }
+
+ private List<DexEncodedMethod> generateCallbackMethods(
Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
- Set<DexEncodedMethod> newDexEncodedMethods = new HashSet<>();
+ List<DexEncodedMethod> newDexEncodedMethods = new ArrayList<>();
for (DexEncodedMethod originalMethod : originalMethods) {
DexMethod methodToInstall =
methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
@@ -256,6 +324,7 @@
newDexEncodedMethod.setCode(cfCode, appView);
newDexEncodedMethods.add(newDexEncodedMethod);
}
+ assert Sets.newHashSet(newDexEncodedMethods).size() == newDexEncodedMethods.size();
return newDexEncodedMethods;
}
@@ -296,6 +365,24 @@
return vivifiedType;
}
+ public void registerWrappersForLibraryInvokeIfRequired(DexMethod invokedMethod) {
+ if (!shouldRewriteInvoke(invokedMethod)) {
+ return;
+ }
+ if (trackedAPIs != null) {
+ trackedAPIs.add(invokedMethod);
+ }
+ DexType returnType = invokedMethod.proto.returnType;
+ if (appView.rewritePrefix.hasRewrittenType(returnType, appView) && canConvert(returnType)) {
+ registerConversionWrappers(returnType, vivifiedTypeFor(returnType, appView));
+ }
+ for (DexType argType : invokedMethod.proto.parameters.values) {
+ if (appView.rewritePrefix.hasRewrittenType(argType, appView) && canConvert(argType)) {
+ registerConversionWrappers(argType, argType);
+ }
+ }
+ }
+
private void rewriteLibraryInvoke(
IRCode code,
InvokeMethod invokeMethod,
@@ -434,9 +521,23 @@
IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
- invokeMethod.outValue().replaceUsers(convertedValue);
- return new InvokeStatic(
- conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
+ Value outValue = invokeMethod.outValue();
+ outValue.replaceUsers(convertedValue);
+ // The only user of out value is now the new invoke static, so no type propagation is required.
+ outValue.setTypeLattice(
+ TypeLatticeElement.fromDexType(
+ returnVivifiedType, outValue.getTypeLattice().nullability(), appView));
+ return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(outValue));
+ }
+
+ private void registerConversionWrappers(DexType type, DexType srcType) {
+ if (appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type) == null) {
+ if (type == srcType) {
+ wrapperSynthesizor.getTypeWrapper(type);
+ } else {
+ wrapperSynthesizor.getVivifiedTypeWrapper(type);
+ }
+ }
}
public DexMethod createConversionMethod(DexType type, DexType srcType, DexType destType) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index bf48a16..069015f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
@@ -12,6 +13,7 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -19,6 +21,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.FieldAccessFlags;
@@ -31,13 +34,14 @@
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -45,8 +49,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
// I am responsible for the generation of wrappers used to call library APIs when desugaring
// libraries. Wrappers can be both ways, wrapping the desugarType as a type, or the type as
@@ -98,37 +100,36 @@
public static final String VIVIFIED_TYPE_WRAPPER_SUFFIX = "$-V-WRP";
private final AppView<?> appView;
- private final Map<DexType, Pair<DexType, DexProgramClass>> typeWrappers =
- new ConcurrentHashMap<>();
- private final Map<DexType, Pair<DexType, DexProgramClass>> vivifiedTypeWrappers =
- new ConcurrentHashMap<>();
+ private final DexString dexWrapperPrefix;
+ private final Map<DexType, DexType> typeWrappers = new ConcurrentHashMap<>();
+ private final Map<DexType, DexType> vivifiedTypeWrappers = new ConcurrentHashMap<>();
// The invalidWrappers are wrappers with incorrect behavior because of final methods that could
// not be overridden. Such wrappers are awful because the runtime behavior is undefined and does
// not raise explicit errors. So we register them here and conversion methods for such wrappers
// raise a runtime exception instead of generating the wrapper.
private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
- private final Set<DexType> generatedWrappers = Sets.newConcurrentHashSet();
private final DexItemFactory factory;
private final DesugaredLibraryAPIConverter converter;
+ private final DexString vivifiedSourceFile;
DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.dexWrapperPrefix = factory.createString("L" + WRAPPER_PREFIX);
this.converter = converter;
+ this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
}
- public static boolean isSynthesizedWrapper(DexType clazz) {
- return clazz.descriptor.toString().contains(WRAPPER_PREFIX);
+ public static boolean isSynthesizedWrapper(DexType type) {
+ // Slow path, but more convenient since no instance is needed. Use hasSynthesized(DexType) when
+ // possible.
+ return type.descriptor.toString().startsWith("L" + WRAPPER_PREFIX);
}
boolean hasSynthesized(DexType type) {
- return generatedWrappers.contains(type);
+ return type.descriptor.startsWith(dexWrapperPrefix);
}
- // Wrapper initial generation section.
- // 1. Generate wrappers without conversion methods.
- // 2. Compute wrapper types.
-
boolean canGenerateWrapper(DexType type) {
DexClass dexClass = appView.definitionFor(type);
if (dexClass == null) {
@@ -138,15 +139,11 @@
}
DexType getTypeWrapper(DexType type) {
- return getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers, this::generateTypeWrapper);
+ return getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers);
}
DexType getVivifiedTypeWrapper(DexType type) {
- return getWrapper(
- type,
- VIVIFIED_TYPE_WRAPPER_SUFFIX,
- vivifiedTypeWrappers,
- this::generateVivifiedTypeWrapper);
+ return getWrapper(type, VIVIFIED_TYPE_WRAPPER_SUFFIX, vivifiedTypeWrappers);
}
private DexType createWrapperType(DexType type, String suffix) {
@@ -164,46 +161,35 @@
+ ";");
}
- private DexType getWrapper(
- DexType type,
- String suffix,
- Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
- BiFunction<DexClass, DexType, DexProgramClass> wrapperGenerator) {
- // Answers the DexType of the wrapper. Generate the wrapper DexProgramClass if not already done,
- // except the conversions methods. Conversion method generation is postponed to know if the
- // reverse wrapper is present at generation time.
- // We generate the type while locking the concurrent hash map, but we release the lock before
- // generating the actual class to avoid locking for too long (hence the Pair).
+ private DexType getWrapper(DexType type, String suffix, Map<DexType, DexType> wrappers) {
assert !type.toString().startsWith(DesugaredLibraryAPIConverter.VIVIFIED_PREFIX);
- Box<Boolean> toGenerate = new Box<>(false);
- Pair<DexType, DexProgramClass> pair =
- wrappers.computeIfAbsent(
- type,
- t -> {
- toGenerate.set(true);
- DexType wrapperType = createWrapperType(type, suffix);
- generatedWrappers.add(wrapperType);
- return new Pair<>(wrapperType, null);
- });
- if (toGenerate.get()) {
- assert pair.getSecond() == null;
- DexClass dexClass = appView.definitionFor(type);
- // The dexClass should be a library class, so it cannot be null.
- assert dexClass != null
- && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
- if (dexClass.accessFlags.isFinal()) {
- throw appView
- .options()
- .reporter
- .fatalError(
- new StringDiagnostic(
- "Cannot generate a wrapper for final class "
- + dexClass.type
- + ". Add a custom conversion in the desugared library."));
- }
- pair.setSecond(wrapperGenerator.apply(dexClass, pair.getFirst()));
+ return wrappers.computeIfAbsent(
+ type,
+ t -> {
+ DexType wrapperType = createWrapperType(type, suffix);
+ assert converter.canGenerateWrappersAndCallbacks()
+ || appView.definitionForProgramType(wrapperType) != null
+ : "Wrapper " + wrapperType + " should have been generated in the enqueuer.";
+ return wrapperType;
+ });
+ }
+
+ private DexClass getValidClassToWrap(DexType type) {
+ DexClass dexClass = appView.definitionFor(type);
+ // The dexClass should be a library class, so it cannot be null.
+ assert dexClass != null
+ && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
+ if (dexClass.accessFlags.isFinal()) {
+ throw appView
+ .options()
+ .reporter
+ .fatalError(
+ new StringDiagnostic(
+ "Cannot generate a wrapper for final class "
+ + dexClass.type
+ + ". Add a custom conversion in the desugared library."));
}
- return pair.getFirst();
+ return dexClass;
}
private DexType vivifiedTypeFor(DexType type) {
@@ -212,11 +198,12 @@
private DexProgramClass generateTypeWrapper(DexClass dexClass, DexType typeWrapperType) {
DexType type = dexClass.type;
- DexEncodedField wrapperField = synthesizeWrappedValueField(typeWrapperType, type);
+ DexEncodedField wrapperField = synthesizeWrappedValueEncodedField(typeWrapperType, type);
return synthesizeWrapper(
vivifiedTypeFor(type),
dexClass,
synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
+ generateTypeConversion(type, typeWrapperType),
wrapperField);
}
@@ -224,11 +211,12 @@
DexClass dexClass, DexType vivifiedTypeWrapperType) {
DexType type = dexClass.type;
DexEncodedField wrapperField =
- synthesizeWrappedValueField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
+ synthesizeWrappedValueEncodedField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
return synthesizeWrapper(
type,
dexClass,
synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
+ generateVivifiedTypeConversion(type, vivifiedTypeWrapperType),
wrapperField);
}
@@ -236,6 +224,7 @@
DexType wrappingType,
DexClass clazz,
DexEncodedMethod[] virtualMethods,
+ DexEncodedMethod conversionMethod,
DexEncodedField wrapperField) {
boolean isItf = clazz.isInterface();
DexType superType = isItf ? factory.objectType : wrappingType;
@@ -257,9 +246,7 @@
DexAnnotationSet.empty(),
DexEncodedField.EMPTY_ARRAY, // No static fields.
new DexEncodedField[] {wrapperField},
- new DexEncodedMethod[] {
- synthesizeConstructor(wrapperField.field)
- }, // Conversions methods will be added later.
+ new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
virtualMethods,
factory.getSkipNameValidationForTesting(),
getChecksumSupplier(this, clazz.type),
@@ -365,6 +352,51 @@
return finalizeWrapperMethods(generatedMethods, finalMethods);
}
+ private DexEncodedMethod[] synthesizeVirtualMethodsForClasspathMock(
+ DexClass dexClass, DexType mockType) {
+ List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+ List<DexEncodedMethod> generatedMethods = new ArrayList<>();
+ // Generate only abstract methods for library override detection.
+ for (DexEncodedMethod dexEncodedMethod : dexMethods) {
+ DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+ assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+ if (!dexEncodedMethod.isFinal()) {
+ DexMethod methodToInstall =
+ DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+ dexEncodedMethod.method, mockType, appView);
+ DexEncodedMethod newDexEncodedMethod =
+ newSynthesizedMethod(methodToInstall, dexEncodedMethod, null);
+ generatedMethods.add(newDexEncodedMethod);
+ }
+ }
+ return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+ }
+
+ DexClasspathClass synthesizeClasspathMock(
+ DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+ return new DexClasspathClass(
+ mockType,
+ Kind.CF,
+ new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
+ ClassAccessFlags.fromDexAccessFlags(
+ Constants.ACC_SYNTHETIC
+ | Constants.ACC_PUBLIC
+ | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ vivifiedSourceFile,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ synthesizeVirtualMethodsForClasspathMock(classToMock, mockType),
+ appView.dexItemFactory().getSkipNameValidationForTesting());
+ }
+
private DexEncodedMethod[] finalizeWrapperMethods(
List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
if (finalMethods.isEmpty()) {
@@ -394,7 +426,11 @@
DexMethod methodToInstall, DexEncodedMethod template, Code code) {
MethodAccessFlags newFlags = template.accessFlags.copy();
assert newFlags.isPublic();
- newFlags.unsetAbstract();
+ if (code == null) {
+ newFlags.setAbstract();
+ } else {
+ newFlags.unsetAbstract();
+ }
// TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
newFlags.unsetBridge();
newFlags.setSynthetic();
@@ -446,8 +482,12 @@
return implementedMethods;
}
- private DexEncodedField synthesizeWrappedValueField(DexType holder, DexType fieldType) {
- DexField field = factory.createField(holder, fieldType, factory.wrapperFieldName);
+ private DexField wrappedValueField(DexType holder, DexType fieldType) {
+ return factory.createField(holder, fieldType, factory.wrapperFieldName);
+ }
+
+ private DexEncodedField synthesizeWrappedValueEncodedField(DexType holder, DexType fieldType) {
+ DexField field = wrappedValueField(holder, fieldType);
// Field is package private to be accessible from convert methods without a getter.
FieldAccessFlags fieldAccessFlags =
FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
@@ -479,86 +519,89 @@
true);
}
- // Wrapper finalization section.
- // 1. Generate conversions methods (convert(type)).
- // 2. Add the synthesized classes.
- // 3. Process all methods.
-
- void finalizeWrappers(
+ void finalizeWrappersForD8(
DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
throws ExecutionException {
- assert verifyAllClassesGenerated();
- // We register first, then optimize to avoid warnings due to missing parameter types.
- registerWrappers(builder, typeWrappers);
- registerWrappers(builder, vivifiedTypeWrappers);
- finalizeWrappers(irConverter, executorService, typeWrappers, this::generateTypeConversions);
- finalizeWrappers(
- irConverter,
- executorService,
- vivifiedTypeWrappers,
- this::generateVivifiedTypeConversions);
+ Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
+ registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers.values());
}
- private void registerWrappers(
- DexApplication.Builder<?> builder, Map<DexType, Pair<DexType, DexProgramClass>> wrappers) {
- for (DexType type : wrappers.keySet()) {
- DexProgramClass pgrmClass = wrappers.get(type).getSecond();
- assert pgrmClass != null;
- registerSynthesizedClass(pgrmClass, builder);
+ private Map<DexType, DexProgramClass> synthesizeWrappers() {
+ Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
+ // Generating a wrapper may require other wrappers to be generated, iterate until fix point.
+ while (synthesizedWrappers.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
+ for (DexType type : typeWrappers.keySet()) {
+ DexType typeWrapperType = typeWrappers.get(type);
+ if (!synthesizedWrappers.containsKey(typeWrapperType)) {
+ synthesizedWrappers.put(
+ typeWrapperType, generateTypeWrapper(getValidClassToWrap(type), typeWrapperType));
+ }
+ }
+ for (DexType type : vivifiedTypeWrappers.keySet()) {
+ DexType vivifiedTypeWrapperType = vivifiedTypeWrappers.get(type);
+ if (!synthesizedWrappers.containsKey(vivifiedTypeWrapperType)) {
+ synthesizedWrappers.put(
+ vivifiedTypeWrapperType,
+ generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType));
+ }
+ }
}
+ return synthesizedWrappers;
}
- private void finalizeWrappers(
+ private Map<DexType, DexType> reverseWrapperMap() {
+ Map<DexType, DexType> reverseWrapperMap = new IdentityHashMap<>();
+ for (DexType type : typeWrappers.keySet()) {
+ reverseWrapperMap.put(typeWrappers.get(type), vivifiedTypeWrappers.get(type));
+ }
+ for (DexType type : vivifiedTypeWrappers.keySet()) {
+ reverseWrapperMap.put(vivifiedTypeWrappers.get(type), typeWrappers.get(type));
+ }
+ return reverseWrapperMap;
+ }
+
+ Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
+ Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
+ Map<DexType, DexType> reverseMap = reverseWrapperMap();
+ Map<DexProgramClass, DexProgramClass> wrappersAndReverse = new IdentityHashMap<>();
+ for (DexProgramClass wrapper : synthesizedWrappers.values()) {
+ wrappersAndReverse.put(wrapper, synthesizedWrappers.get(reverseMap.get(wrapper.type)));
+ }
+ return wrappersAndReverse;
+ }
+
+ private void registerAndProcessWrappers(
+ DexApplication.Builder<?> builder,
IRConverter irConverter,
ExecutorService executorService,
- Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
- BiConsumer<DexType, DexProgramClass> generateConversions)
+ Collection<DexProgramClass> wrappers)
throws ExecutionException {
- for (DexType type : wrappers.keySet()) {
- DexProgramClass pgrmClass = wrappers.get(type).getSecond();
- assert pgrmClass != null;
- generateConversions.accept(type, pgrmClass);
- irConverter.optimizeSynthesizedClass(pgrmClass, executorService);
+ for (DexProgramClass wrapper : wrappers) {
+ builder.addSynthesizedClass(wrapper, false);
+ appView.appInfo().addSynthesizedClass(wrapper);
}
+ irConverter.optimizeSynthesizedClasses(wrappers, executorService);
}
- private boolean verifyAllClassesGenerated() {
- for (Pair<DexType, DexProgramClass> pair : vivifiedTypeWrappers.values()) {
- assert pair.getSecond() != null;
- }
- for (Pair<DexType, DexProgramClass> pair : typeWrappers.values()) {
- assert pair.getSecond() != null;
- }
- return true;
+ private DexEncodedMethod generateTypeConversion(DexType type, DexType typeWrapperType) {
+ DexType reverse = vivifiedTypeWrappers.get(type);
+ return synthesizeConversionMethod(
+ typeWrapperType,
+ type,
+ type,
+ vivifiedTypeFor(type),
+ reverse == null ? null : wrappedValueField(reverse, vivifiedTypeFor(type)));
}
- private void registerSynthesizedClass(
- DexProgramClass synthesizedClass, DexApplication.Builder<?> builder) {
- builder.addSynthesizedClass(synthesizedClass, false);
- appView.appInfo().addSynthesizedClass(synthesizedClass);
- }
-
- private void generateTypeConversions(DexType type, DexProgramClass synthesizedClass) {
- Pair<DexType, DexProgramClass> reverse = vivifiedTypeWrappers.get(type);
- assert reverse == null || reverse.getSecond() != null;
- synthesizedClass.addDirectMethod(
- synthesizeConversionMethod(
- synthesizedClass.type,
- type,
- type,
- vivifiedTypeFor(type),
- reverse == null ? null : reverse.getSecond()));
- }
-
- private void generateVivifiedTypeConversions(DexType type, DexProgramClass synthesizedClass) {
- Pair<DexType, DexProgramClass> reverse = typeWrappers.get(type);
- synthesizedClass.addDirectMethod(
- synthesizeConversionMethod(
- synthesizedClass.type,
- type,
- vivifiedTypeFor(type),
- type,
- reverse == null ? null : reverse.getSecond()));
+ private DexEncodedMethod generateVivifiedTypeConversion(
+ DexType type, DexType vivifiedTypeWrapperType) {
+ DexType reverse = typeWrappers.get(type);
+ return synthesizeConversionMethod(
+ vivifiedTypeWrapperType,
+ type,
+ vivifiedTypeFor(type),
+ type,
+ reverse == null ? null : wrappedValueField(reverse, type));
}
private DexEncodedMethod synthesizeConversionMethod(
@@ -566,7 +609,7 @@
DexType type,
DexType argType,
DexType returnType,
- DexClass reverseWrapperClassOrNull) {
+ DexField reverseFieldOrNull) {
DexMethod method =
factory.createMethod(
holder, factory.createProto(returnType, argType), factory.convertMethodName);
@@ -582,15 +625,11 @@
holder)
.generateCfCode();
} else {
- DexField uniqueFieldOrNull =
- reverseWrapperClassOrNull == null
- ? null
- : reverseWrapperClassOrNull.instanceFields().get(0).field;
cfCode =
new APIConverterWrapperConversionCfCodeProvider(
appView,
argType,
- uniqueFieldOrNull,
+ reverseFieldOrNull,
factory.createField(holder, returnType, factory.wrapperFieldName))
.generateCfCode();
}
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 6a79659..0e8cbae 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
@@ -930,8 +930,7 @@
// make original default methods abstract, remove bridge methods, create dispatch
// classes if needed.
AppInfo appInfo = appView.appInfo();
- for (Entry<DexType, DexProgramClass> entry :
- processInterfaces(builder, flavour).entrySet()) {
+ for (Entry<DexType, DexProgramClass> entry : processInterfaces(builder, flavour).entrySet()) {
// Don't need to optimize synthesized class since all of its methods
// are just moved from interfaces and don't need to be re-processed.
DexProgramClass synthesizedClass = entry.getValue();
@@ -961,8 +960,7 @@
&& clazz.isInterface() == mustBeInterface;
}
- private Map<DexType, DexProgramClass> processInterfaces(
- Builder<?> builder, Flavor flavour) {
+ private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
NestedGraphLense.Builder graphLensBuilder = InterfaceProcessorNestedGraphLense.builder();
InterfaceProcessor processor = new InterfaceProcessor(appView, this);
for (DexProgramClass clazz : builder.getProgramClasses()) {
@@ -1043,27 +1041,28 @@
return false;
}
- public void warnMissingInterface(
- DexClass classToDesugar, DexClass implementing, DexType missing) {
+ private boolean shouldIgnoreFromReports(DexType missing) {
+ return appView.rewritePrefix.hasRewrittenType(missing, appView)
+ || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
+ || DesugaredLibraryAPIConverter.isVivifiedType(missing)
+ || isCompanionClassType(missing)
+ || emulatedInterfaces.containsValue(missing)
+ || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing);
+ }
+
+ void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
// We use contains() on non hashed collection, but we know it's a 8 cases collection.
// j$ interfaces won't be missing, they are in the desugared library.
- if (!emulatedInterfaces.values().contains(missing)) {
- options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
+ if (shouldIgnoreFromReports(missing)) {
+ return;
}
+ options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
}
private void warnMissingType(DexMethod referencedFrom, DexType missing) {
// Companion/Emulated interface/Conversion classes for desugared library won't be missing,
// they are in the desugared library.
- if (appView.rewritePrefix.hasRewrittenType(missing, appView)
- || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
- || isCompanionClassType(missing)
- || appView
- .options()
- .desugaredLibraryConfiguration
- .getCustomConversions()
- .values()
- .contains(missing)) {
+ if (shouldIgnoreFromReports(missing)) {
return;
}
DexMethod method = appView.graphLense().getOriginalMethodSignature(referencedFrom);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 608fd4a..007f170 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -173,6 +173,7 @@
/** Remove lambda deserialization methods. */
public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
+ boolean anyRemoved = false;
for (DexProgramClass clazz : classes) {
// Search for a lambda deserialization method and remove it if found.
List<DexEncodedMethod> directMethods = clazz.directMethods();
@@ -185,14 +186,15 @@
assert encoded.accessFlags.isStatic();
assert encoded.accessFlags.isSynthetic();
clazz.removeDirectMethod(i);
+ anyRemoved = true;
// We assume there is only one such method in the class.
- return true;
+ break;
}
}
}
}
- return false;
+ return anyRemoved;
}
/** Generates lambda classes and adds them to the builder. */
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
index 78d5702..ded1538 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
@@ -75,12 +75,9 @@
}
@Override
- public boolean isContextFreeForMethod(DexMethod method) {
- if (!previousLense.isContextFreeForMethod(method)) {
- return false;
- }
- DexMethod previous = previousLense.lookupMethod(method);
- return !methodMap.containsKey(previous);
+ public boolean verifyIsContextFreeForMethod(DexMethod method) {
+ assert !methodMap.containsKey(previousLense.lookupMethod(method));
+ return true;
}
// This is true because mappings specific to this class can be filled.
@@ -145,11 +142,11 @@
putFieldMap.put(from, to);
}
- public GraphLense build(AppView<?> appView, DexType nestConstructorType) {
+ public NestedPrivateMethodLense build(AppView<?> appView, DexType nestConstructorType) {
assert typeMap.isEmpty();
assert fieldMap.isEmpty();
if (getFieldMap.isEmpty() && methodMap.isEmpty() && putFieldMap.isEmpty()) {
- return appView.graphLense();
+ return null;
}
return new NestedPrivateMethodLense(
appView, nestConstructorType, methodMap, getFieldMap, putFieldMap, appView.graphLense());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index 1a6c365..c4c9bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -34,7 +33,8 @@
super(appView);
}
- public GraphLense run(ExecutorService executorService, DexApplication.Builder<?> appBuilder)
+ public NestedPrivateMethodLense run(
+ ExecutorService executorService, DexApplication.Builder<?> appBuilder)
throws ExecutionException {
assert !appView.options().canUseNestBasedAccess()
|| appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 744b10c..02cb581 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -179,6 +179,120 @@
ImmutableList.of());
}
+ public static CfCode CharSequenceMethods_compare(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ CfLabel label6 = new CfLabel();
+ CfLabel label7 = new CfLabel();
+ CfLabel label8 = new CfLabel();
+ CfLabel label9 = new CfLabel();
+ CfLabel label10 = new CfLabel();
+ CfLabel label11 = new CfLabel();
+ CfLabel label12 = new CfLabel();
+ CfLabel label13 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 8,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/CharSequence;"),
+ options.itemFactory.createProto(options.itemFactory.createType("I")),
+ options.itemFactory.createString("length")),
+ true),
+ new CfStore(ValueType.INT, 2),
+ label1,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/CharSequence;"),
+ options.itemFactory.createProto(options.itemFactory.createType("I")),
+ options.itemFactory.createString("length")),
+ true),
+ new CfStore(ValueType.INT, 3),
+ label2,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+ label3,
+ new CfConstNumber(0, ValueType.INT),
+ new CfReturn(ValueType.INT),
+ label4,
+ new CfConstNumber(0, ValueType.INT),
+ new CfStore(ValueType.INT, 4),
+ label5,
+ new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.INT, 3),
+ new CfInvoke(
+ 184,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/Math;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("I"),
+ options.itemFactory.createType("I"),
+ options.itemFactory.createType("I")),
+ options.itemFactory.createString("min")),
+ false),
+ new CfStore(ValueType.INT, 5),
+ label6,
+ new CfLoad(ValueType.INT, 4),
+ new CfLoad(ValueType.INT, 5),
+ new CfIfCmp(If.Type.GE, ValueType.INT, label12),
+ label7,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.INT, 4),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/CharSequence;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("C"), options.itemFactory.createType("I")),
+ options.itemFactory.createString("charAt")),
+ true),
+ new CfStore(ValueType.INT, 6),
+ label8,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.INT, 4),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/CharSequence;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("C"), options.itemFactory.createType("I")),
+ options.itemFactory.createString("charAt")),
+ true),
+ new CfStore(ValueType.INT, 7),
+ label9,
+ new CfLoad(ValueType.INT, 6),
+ new CfLoad(ValueType.INT, 7),
+ new CfIfCmp(If.Type.EQ, ValueType.INT, label11),
+ label10,
+ new CfLoad(ValueType.INT, 6),
+ new CfLoad(ValueType.INT, 7),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+ new CfReturn(ValueType.INT),
+ label11,
+ new CfIinc(4, 1),
+ new CfGoto(label6),
+ label12,
+ new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.INT, 3),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+ new CfReturn(ValueType.INT),
+ label13),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode CharacterMethods_compare(InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
index daf63b2..90654b2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
@@ -13,10 +13,14 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.Xor;
import java.util.List;
+import java.util.Set;
public final class BooleanMethodRewrites {
public static void rewriteLogicalAnd(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
List<Value> inValues = invoke.inValues();
assert inValues.size() == 2;
@@ -25,7 +29,10 @@
}
public static void rewriteLogicalOr(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
List<Value> inValues = invoke.inValues();
assert inValues.size() == 2;
@@ -34,7 +41,10 @@
}
public static void rewriteLogicalXor(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
List<Value> inValues = invoke.inValues();
assert inValues.size() == 2;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
index 5f54256..695f51f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
@@ -9,24 +9,35 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
import java.util.Collections;
+import java.util.Set;
public final class CollectionMethodRewrites {
private CollectionMethodRewrites() {}
public static void rewriteListOfEmpty(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
rewriteToCollectionMethod(invoke, iterator, factory, "emptyList");
}
public static void rewriteSetOfEmpty(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
rewriteToCollectionMethod(invoke, iterator, factory, "emptySet");
}
public static void rewriteMapOfEmpty(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
rewriteToCollectionMethod(invoke, iterator, factory, "emptyMap");
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
index 9a4ef45..a5d557d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
@@ -8,13 +8,18 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
public final class FloatMethodRewrites {
private FloatMethodRewrites() {}
public static void rewriteHashCode(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeStatic mathInvoke = new InvokeStatic(
factory.createMethod(factory.boxedFloatType, invoke.getInvokedMethod().proto,
"floatToIntBits"), invoke.outValue(), invoke.inValues(), false);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
index b842b71..348194b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
@@ -12,13 +12,17 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
import java.util.List;
+import java.util.Set;
public final class LongMethodRewrites {
private LongMethodRewrites() {}
public static void rewriteCompare(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
List<Value> inValues = invoke.inValues();
assert inValues.size() == 2;
iterator.replaceCurrentInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index 86d7494..b43abaa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -8,18 +8,25 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
import java.util.List;
+import java.util.Set;
public final class NumericMethodRewrites {
- public static void rewriteToInvokeMath(InvokeMethod invoke, InstructionListIterator iterator,
- DexItemFactory factory) {
+ public static void rewriteToInvokeMath(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeStatic mathInvoke = new InvokeStatic(
factory.createMethod(factory.mathType, invoke.getInvokedMethod().proto,
invoke.getInvokedMethod().name), invoke.outValue(), invoke.inValues(), false);
iterator.replaceCurrentInstruction(mathInvoke);
}
- public static void rewriteToAddInstruction(InvokeMethod invoke, InstructionListIterator iterator,
- DexItemFactory factory) {
+ public static void rewriteToAddInstruction(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
List<Value> values = invoke.inValues();
assert values.size() == 2;
@@ -28,8 +35,11 @@
iterator.replaceCurrentInstruction(add);
}
- public static void rewriteAsIdentity(InvokeMethod invoke, InstructionListIterator iterator,
- DexItemFactory factory) {
+ public static void rewriteAsIdentity(
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
List<Value> values = invoke.inValues();
assert values.size() == 1;
invoke.outValue().replaceUsers(values.get(0));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index 83d32cc..911faa5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -11,11 +11,16 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
public final class ObjectsMethodRewrites {
public static void rewriteToArraysHashCode(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
DexType arraysType = factory.createType(factory.arraysDescriptor);
DexMethod hashCodeMethod =
factory.createMethod(arraysType, invoke.getInvokedMethod().proto, "hashCode");
@@ -25,10 +30,14 @@
}
public static void rewriteRequireNonNull(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeVirtual getClass =
new InvokeVirtual(factory.objectMethods.getClass, null, invoke.inValues());
- if (invoke.outValue() != null) {
+ if (invoke.hasOutValue()) {
+ affectedValues.addAll(invoke.outValue().affectedValues());
invoke.outValue().replaceUsers(invoke.inValues().get(0));
invoke.setOutValue(null);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
index 7965b0b..c10a292 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
@@ -8,13 +8,18 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
public final class OptionalMethodRewrites {
private OptionalMethodRewrites() {}
public static void rewriteOrElseGet(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeVirtual getInvoke = new InvokeVirtual(
factory.createMethod(factory.optionalType, invoke.getInvokedMethod().proto,
"get"), invoke.outValue(), invoke.inValues());
@@ -22,7 +27,10 @@
}
public static void rewriteDoubleOrElseGet(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeVirtual getInvoke = new InvokeVirtual(
factory.createMethod(factory.optionalDoubleType, invoke.getInvokedMethod().proto,
"getAsDouble"), invoke.outValue(), invoke.inValues());
@@ -30,7 +38,10 @@
}
public static void rewriteIntOrElseGet(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeVirtual getInvoke = new InvokeVirtual(
factory.createMethod(factory.optionalIntType, invoke.getInvokedMethod().proto,
"getAsInt"), invoke.outValue(), invoke.inValues());
@@ -38,7 +49,10 @@
}
public static void rewriteLongOrElseGet(
- InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+ InvokeMethod invoke,
+ InstructionListIterator iterator,
+ DexItemFactory factory,
+ Set<Value> affectedValues) {
InvokeVirtual getInvoke = new InvokeVirtual(
factory.createMethod(factory.optionalLongType, invoke.getInvokedMethod().proto,
"getAsLong"), invoke.outValue(), invoke.inValues());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index b19e557..da20118 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -200,7 +200,7 @@
* // method with "assert xxx";
* void m() {
* if (!$assertionsDisabled) {
- * if (xxx) {
+ * if (!xxx) {
* throw new AssertionError(...);
* }
* }
@@ -208,7 +208,8 @@
* }
* </pre>
*
- * With the rewriting below (and other rewritings) the resulting code is:
+ * With the rewriting below and AssertionTransformation.DISABLE (and other rewritings) the
+ * resulting code is:
*
* <pre>
* class A {
@@ -216,6 +217,80 @@
* }
* }
* </pre>
+ *
+ * With AssertionTransformation.ENABLE (and other rewritings) the resulting code is:
+ *
+ * <pre>
+ * class A {
+ * static boolean $assertionsDisabled;
+ * void m() {
+ * if (!xxx) {
+ * throw new AssertionError(...);
+ * }
+ * }
+ * }
+ * </pre>
+ * For Kotlin the Class instance method desiredAssertionStatus() is only called for the class
+ * kotlin._Assertions, where kotlin._Assertions.class.desiredAssertionStatus() is read into the
+ * static field kotlin._Assertions.ENABLED.
+ *
+ * <pre>
+ * class _Assertions {
+ * public static boolean ENABLED = _Assertions.class.desiredAssertionStatus();
+ * }
+ * </pre>
+ *
+ * <p>(actual code
+ * https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/util/AssertionsJVM.kt)
+ *
+ * <p>The class:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * assert(xxx)
+ * }
+ * }
+ * </pre>
+ *
+ * Is compiled into:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * if (!xxx) {
+ * if (kotlin._Assertions.ENABLED) {
+ * throw new AssertionError("Assertion failed")
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * With the rewriting below and AssertionTransformation.DISABLE (and other rewritings) the resulting code is:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * if (!xxx) {}
+ * }
+ * }
+ * </pre>
+ *
+ * With AssertionTransformation.ENABLE (and other rewritings) the resulting code is:
+ *
+ * <pre>
+ * class A {
+ * void m() {
+ * if (!xxx) {
+ * throw new AssertionError("Assertion failed")
+ * }
+ * }
+ * }
+ * </pre>
+
+ * NOTE: that in Kotlin the assertion condition is always calculated. So it is still present in
+ * the code and even for AssertionTransformation.DISABLE.
*/
public void run(DexEncodedMethod method, IRCode code, Timing timing) {
if (enabled) {
@@ -242,7 +317,7 @@
}
clinit = clazz.getClassInitializer();
}
- if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+ if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaVmAssertions()) {
return;
}
@@ -253,7 +328,11 @@
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
- iterator.replaceCurrentInstruction(code.createIntConstant(0));
+ if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+ rewriteKotlinAssertionEnable(code, transformation, iterator, invoke);
+ } else {
+ iterator.replaceCurrentInstruction(code.createIntConstant(0));
+ }
}
} else if (current.isStaticPut()) {
StaticPut staticPut = current.asStaticPut();
@@ -269,4 +348,41 @@
}
}
}
+
+ private void rewriteKotlinAssertionEnable(
+ IRCode code,
+ AssertionTransformation transformation,
+ InstructionListIterator iterator,
+ InvokeMethod invoke) {
+ if (iterator.hasNext() && transformation == AssertionTransformation.DISABLE) {
+ // Check if the invocation of Class.desiredAssertionStatus() is followed by a static
+ // put to kotlin._Assertions.ENABLED, and if so remove both instructions.
+ // See comment in ClassInitializerAssertionEnablingAnalysis for the expected instruction
+ // sequence.
+ Instruction nextInstruction = iterator.next();
+ if (nextInstruction.isStaticPut()
+ && nextInstruction.asStaticPut().getField().holder
+ == dexItemFactory.kotlin.kotlinAssertions
+ && nextInstruction.asStaticPut().getField().name == dexItemFactory.enabledFieldName
+ && invoke.outValue().numberOfUsers() == 1
+ && invoke.outValue().numberOfPhiUsers() == 0
+ && invoke.outValue().singleUniqueUser() == nextInstruction) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ Instruction prevInstruction = iterator.previous();
+ assert prevInstruction == invoke;
+ iterator.removeOrReplaceByDebugLocalRead();
+ } else {
+ Instruction instruction = iterator.previous();
+ assert instruction == nextInstruction;
+ instruction = iterator.previous();
+ assert instruction == invoke;
+ instruction = iterator.next();
+ assert instruction == invoke;
+ iterator.replaceCurrentInstruction(code.createIntConstant(0));
+ }
+ } else {
+ iterator.replaceCurrentInstruction(
+ code.createIntConstant(transformation == AssertionTransformation.ENABLE ? 1 : 0));
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 73214ba..0ebe028 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -84,7 +84,7 @@
}
}
- public void removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
+ public AssumeDynamicTypeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
if (!assumeDynamicTypeInstructionsToRemove.isEmpty()) {
for (BasicBlock block : code.blocks) {
if (blocksToBeRemoved.contains(block)) {
@@ -99,6 +99,7 @@
}
}
}
+ return this;
}
public void finish() {
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 51fe0fe..55db2fd 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
@@ -1177,11 +1177,13 @@
}
// Replace result uses for methods where something is known about what is returned.
- public void rewriteMoveResult(IRCode code) {
- if (options.isGeneratingClassFiles()) {
- return;
+ public boolean rewriteMoveResult(IRCode code) {
+ if (options.isGeneratingClassFiles() || !code.metadata().mayHaveInvokeMethod()) {
+ return false;
}
+
AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
+ boolean changed = false;
boolean mayHaveRemovedTrivialPhi = false;
Set<Value> affectedValues = Sets.newIdentityHashSet();
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
@@ -1191,74 +1193,39 @@
if (blocksToBeRemoved.contains(block)) {
continue;
}
+
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- Value outValue = invoke.outValue();
- // TODO(b/124246610): extend to other variants that receive error messages or supplier.
- if (invoke.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
- Value obj = invoke.arguments().get(0);
- if ((outValue == null && obj.hasLocalInfo())
- || (outValue != null && !obj.hasSameOrNoLocal(outValue))) {
- continue;
- }
- Nullability nullability = obj.getTypeLattice().nullability();
- if (nullability.isDefinitelyNotNull()) {
- if (outValue != null) {
- affectedValues.addAll(outValue.affectedValues());
- mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
- outValue.replaceUsers(obj);
- }
- iterator.removeOrReplaceByDebugLocalRead();
- } else if (obj.isAlwaysNull(appView) && appView.appInfo().hasSubtyping()) {
- iterator.replaceCurrentInstructionWithThrowNull(
- appView.withSubtyping(), code, blockIterator, blocksToBeRemoved, affectedValues);
- }
- } else if (outValue != null && !outValue.hasLocalInfo()) {
- if (appView
- .dexItemFactory()
- .libraryMethodsReturningReceiver
- .contains(invoke.getInvokedMethod())) {
- if (checkArgumentType(invoke, 0)) {
- affectedValues.addAll(outValue.affectedValues());
- assumeDynamicTypeRemover.markUsersForRemoval(invoke.outValue());
- mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
- outValue.replaceUsers(invoke.arguments().get(0));
- invoke.setOutValue(null);
- }
- } else if (appView.appInfo().hasLiveness()) {
- // Check if the invoked method is known to return one of its arguments.
- DexEncodedMethod target =
- invoke.lookupSingleTarget(appView.withLiveness(), code.method.method.holder);
- if (target != null && target.getOptimizationInfo().returnsArgument()) {
- int argumentIndex = target.getOptimizationInfo().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);
- assert outValue.verifyCompatible(argument.outType());
- // Make sure that we are only narrowing information here. Note, in cases where
- // we cannot find the definition of types, computing lessThanOrEqual will
- // return false unless it is object.
- if (argument
- .getTypeLattice()
- .lessThanOrEqual(outValue.getTypeLattice(), appView)) {
- affectedValues.addAll(outValue.affectedValues());
- assumeDynamicTypeRemover.markUsersForRemoval(outValue);
- mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
- outValue.replaceUsers(argument);
- invoke.setOutValue(null);
- }
- }
- }
+ InvokeMethod invoke = iterator.next().asInvokeMethod();
+ if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) {
+ continue;
+ }
+
+ // Check if the invoked method is known to return one of its arguments.
+ DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
+ if (target != null && target.getOptimizationInfo().returnsArgument()) {
+ int argumentIndex = target.getOptimizationInfo().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);
+ Value outValue = invoke.outValue();
+ assert outValue.verifyCompatible(argument.outType());
+ // Make sure that we are only narrowing information here. Note, in cases where
+ // we cannot find the definition of types, computing lessThanOrEqual will
+ // return false unless it is object.
+ if (argument.getTypeLattice().lessThanOrEqual(outValue.getTypeLattice(), appView)) {
+ affectedValues.addAll(outValue.affectedValues());
+ assumeDynamicTypeRemover.markUsersForRemoval(outValue);
+ mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
+ outValue.replaceUsers(argument);
+ invoke.setOutValue(null);
+ changed = true;
}
}
}
}
}
- assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved);
- assumeDynamicTypeRemover.finish();
+ assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
if (!blocksToBeRemoved.isEmpty()) {
code.removeBlocks(blocksToBeRemoved);
code.removeAllTrivialPhis(affectedValues);
@@ -1270,6 +1237,7 @@
new TypeAnalysis(appView).narrowing(affectedValues);
}
assert code.isConsistentSSA();
+ return changed;
}
enum RemoveCheckCastInstructionIfTrivialResult {
@@ -2160,15 +2128,6 @@
public void simplifyDebugLocals(IRCode code) {
for (BasicBlock block : code.blocks) {
- for (Phi phi : block.getPhis()) {
- if (!phi.hasLocalInfo() && phi.numberOfUsers() == 1 && phi.numberOfAllUsers() == 1) {
- Instruction instruction = phi.singleUniqueUser();
- if (instruction.isDebugLocalWrite()) {
- removeDebugWriteOfPhi(code, phi, instruction.asDebugLocalWrite());
- }
- }
- }
-
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction prevInstruction = iterator.peekPrevious();
@@ -2202,29 +2161,6 @@
}
}
- private void removeDebugWriteOfPhi(IRCode code, Phi phi, DebugLocalWrite write) {
- assert write.src() == phi;
- InstructionListIterator iterator = phi.getBlock().listIterator(code);
- while (iterator.hasNext()) {
- Instruction next = iterator.next();
- if (!next.isDebugLocalWrite()) {
- // If the debug write is not in the block header bail out.
- return;
- }
- if (next == write) {
- // Associate the phi with the local.
- phi.setLocalInfo(write.getLocalInfo());
- // Replace uses of the write with the phi.
- write.outValue().replaceUsers(phi);
- // Safely remove the write.
- // TODO(zerny): Once phis become instructions, move debug values there instead of a nop.
- iterator.removeOrReplaceByDebugLocalRead();
- return;
- }
- assert next.getLocalInfo().name != write.getLocalInfo().name;
- }
- }
-
private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
private final InternalOptions options;
@@ -2507,7 +2443,7 @@
} else {
DexType context = code.method.method.holder;
AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
- if (abstractValue.isSingleEnumValue()) {
+ if (abstractValue.isSingleConstClassValue() || abstractValue.isSingleFieldValue()) {
AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
if (abstractValue == otherAbstractValue) {
simplifyIfWithKnownCondition(code, block, theIf, 0);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index f94b7a7..ef9c66c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import java.util.ArrayDeque;
import java.util.Collection;
@@ -34,12 +35,15 @@
this.codeRewriter = codeRewriter;
}
- public void run(IRCode code) {
- removeUnneededCatchHandlers(code);
+ public void run(IRCode code, Timing timing) {
+ timing.begin("Remove dead code");
+
+ codeRewriter.rewriteMoveResult(code);
+
+ // We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction
+ // is the only throwing instruction in a block. Removing unneeded catch handlers can lead to
+ // more dead instructions.
Deque<BasicBlock> worklist = new ArrayDeque<>();
- // We may encounter unneeded catch handlers again, e.g., if a dead instruction (due to
- // const-string canonicalization for example) is the only throwing instruction in a block.
- // Removing unneeded catch handlers can lead to more dead instructions.
do {
worklist.addAll(code.topologicallySortedBlocks());
while (!worklist.isEmpty()) {
@@ -49,7 +53,26 @@
}
} while (removeUnneededCatchHandlers(code));
assert code.isConsistentSSA();
- codeRewriter.rewriteMoveResult(code);
+
+ timing.end();
+ }
+
+ public boolean verifyNoDeadCode(IRCode code) {
+ assert !codeRewriter.rewriteMoveResult(code);
+ assert !removeUnneededCatchHandlers(code);
+ for (BasicBlock block : code.blocks) {
+ assert !block.hasDeadPhi(appView, code);
+ for (Instruction instruction : block.getInstructions()) {
+ // No unused move-result instructions.
+ assert !instruction.isInvoke()
+ || !instruction.hasOutValue()
+ || instruction.outValue().hasAnyUsers();
+ // No dead instructions.
+ assert !instruction.canBeDeadCode(appView, code)
+ || (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code));
+ }
+ }
+ return true;
}
// Add the block from where the value originates to the worklist.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
index 88166e5..c38b7b0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Phi;
@@ -250,6 +251,27 @@
return Reason.ELIGIBLE;
}
+ // An If using enum as inValue is valid if it matches e == null
+ // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
+ if (instruction.isIf()) {
+ If anIf = instruction.asIf();
+ assert (anIf.getType() == If.Type.EQ || anIf.getType() == If.Type.NE)
+ : "Comparing a reference with " + anIf.getType().toString();
+ // e == null.
+ if (anIf.isZeroTest()) {
+ return Reason.ELIGIBLE;
+ }
+ // e == MyEnum.X
+ TypeLatticeElement leftType = anIf.lhs().getTypeLattice();
+ TypeLatticeElement rightType = anIf.rhs().getTypeLattice();
+ if (leftType.equalUpToNullability(rightType)) {
+ assert leftType.isClassType();
+ assert leftType.asClassTypeLatticeElement().getClassType() == enumClass.type;
+ return Reason.ELIGIBLE;
+ }
+ return Reason.INVALID_IF_TYPES;
+ }
+
if (instruction.isAssume()) {
Value outValue = instruction.outValue();
return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
@@ -318,6 +340,7 @@
INVALID_FIELD_PUT,
FIELD_PUT_ON_ENUM,
TYPE_MISSMATCH_FIELD_PUT,
+ INVALID_IF_TYPES,
OTHER_UNSUPPORTED_INSTRUCTION;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 5bbc662..7618cb6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -26,12 +26,13 @@
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.MoveException;
import com.android.tools.r8.ir.code.Phi;
@@ -57,6 +58,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
@@ -605,39 +607,7 @@
shouldSynthesizeMonitorEnterExit && !target.isStatic();
if (shouldSynthesizeNullCheckForReceiver
&& !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
- List<Value> arguments = code.collectArguments();
- if (!arguments.isEmpty()) {
- Value receiver = arguments.get(0);
- assert receiver.isThis();
-
- BasicBlock entryBlock = code.entryBlock();
-
- // Insert a new block between the last argument instruction and the first actual
- // instruction of the method.
- BasicBlock throwBlock =
- entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
- assert !throwBlock.hasCatchHandlers();
-
- // Link the entry block to the successor of the newly inserted block.
- BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
- entryBlock.link(continuationBlock);
-
- // Replace the last instruction of the entry block, which is now a goto instruction,
- // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
- // is null.
- If ifInstruction = new If(If.Type.EQ, receiver);
- entryBlock.replaceLastInstruction(ifInstruction, code);
- assert ifInstruction.getTrueTarget() == throwBlock;
- assert ifInstruction.fallthroughBlock() == continuationBlock;
-
- // Replace the single goto instruction in the newly inserted block by `throw null`.
- InstructionListIterator iterator = throwBlock.listIterator(code);
- Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
- iterator.next();
- iterator.replaceCurrentInstruction(new Throw(nullValue));
- } else {
- assert false : "Unable to synthesize a null check for the receiver";
- }
+ synthesizeNullCheckForReceiver(appView, code);
}
// Insert monitor-enter and monitor-exit instructions if the method is synchronized.
@@ -757,6 +727,34 @@
assert code.isConsistentSSA();
return new InlineeWithReason(code, reason);
}
+
+ private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) {
+ List<Value> arguments = code.collectArguments();
+ if (!arguments.isEmpty()) {
+ Value receiver = arguments.get(0);
+ assert receiver.isThis();
+
+ BasicBlock entryBlock = code.entryBlock();
+
+ // Insert a new block between the last argument instruction and the first actual
+ // instruction of the method.
+ BasicBlock throwBlock =
+ entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+ assert !throwBlock.hasCatchHandlers();
+
+ InstructionListIterator iterator = throwBlock.listIterator(code);
+ iterator.setInsertionPosition(entryBlock.exit().getPosition());
+ if (appView.options().canUseRequireNonNull()) {
+ DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+ iterator.add(new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver)));
+ } else {
+ DexMethod getClassMethod = appView.dexItemFactory().objectMethods.getClass;
+ iterator.add(new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver)));
+ }
+ } else {
+ assert false : "Unable to synthesize a null check for the receiver";
+ }
+ }
}
static class InlineeWithReason {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index e50011a..a4c70be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -443,7 +443,7 @@
// For each of the actual potential targets, derive constraints based on the accessibility
// of the method itself.
Collection<DexEncodedMethod> targets =
- resolutionResult.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
+ resolutionResult.lookupVirtualDispatchTargets(appView.appInfo());
for (DexEncodedMethod target : targets) {
methodHolder = graphLense.lookupType(target.method.holder);
assert appView.definitionFor(methodHolder) != null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index 8b74c30..aeedc02 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
@@ -31,11 +31,10 @@
public abstract class MemberPoolCollection<T extends Descriptor> {
final Equivalence<T> equivalence;
- final AppView<? extends AppInfoWithSubtyping> appView;
+ final AppView<AppInfoWithLiveness> appView;
final Map<DexClass, MemberPool<T>> memberPools = new ConcurrentHashMap<>();
- MemberPoolCollection(
- AppView<? extends AppInfoWithSubtyping> appView, Equivalence<T> equivalence) {
+ MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<T> equivalence) {
this.appView = appView;
this.equivalence = equivalence;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 46e0256..04b2874 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -4,12 +4,12 @@
package com.android.tools.r8.ir.optimize;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
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.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Predicates;
import java.util.function.Predicate;
@@ -33,13 +33,13 @@
private final Predicate<DexEncodedMethod> methodTester;
- public MethodPoolCollection(AppView<? extends AppInfoWithSubtyping> appView) {
+ public MethodPoolCollection(AppView<AppInfoWithLiveness> appView) {
super(appView, MethodSignatureEquivalence.get());
this.methodTester = Predicates.alwaysTrue();
}
public MethodPoolCollection(
- AppView<? extends AppInfoWithSubtyping> appView, Predicate<DexEncodedMethod> methodTester) {
+ AppView<AppInfoWithLiveness> appView, Predicate<DexEncodedMethod> methodTester) {
super(appView, MethodSignatureEquivalence.get());
this.methodTester = methodTester;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index c0a67fc..958749e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -94,15 +94,15 @@
}
}
- // Each block with one or more catch handlers may have at most one throwing instruction.
- if (instruction.instructionTypeCanThrow() && block.hasCatchHandlers()) {
- return;
- }
+ if (instruction.instructionTypeCanThrow()) {
+ // Each block with one or more catch handlers may have at most one throwing instruction.
+ if (block.hasCatchHandlers()) {
+ return;
+ }
- // If the instruction can throw and one of the normal successor blocks has a catch handler,
- // then we cannot merge the instruction into the predecessor, since this would change the
- // exceptional control flow.
- if (instruction.instructionInstanceCanThrow()) {
+ // If the instruction can throw and one of the normal successor blocks has a catch handler,
+ // then we cannot merge the instruction into the predecessor, since this would change the
+ // exceptional control flow.
for (BasicBlock normalSuccessor : normalSuccessors) {
if (normalSuccessor.hasCatchHandlers()) {
return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index b871461..c234bc3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
@@ -62,7 +61,7 @@
DISALLOW_ARGUMENT_REMOVAL
}
- static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
+ public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
private final Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod;
@@ -116,9 +115,8 @@
this.typeChecker = new TypeChecker(appView);
}
- public GraphLense run(
+ public UninstantiatedTypeOptimizationGraphLense run(
MethodPoolCollection methodPoolCollection, ExecutorService executorService, Timing timing) {
-
try {
methodPoolCollection.buildAll(executorService, timing);
} catch (Exception e) {
@@ -144,7 +142,7 @@
return new UninstantiatedTypeOptimizationGraphLense(
methodMapping, removedArgumentsInfoPerMethod, appView);
}
- return appView.graphLense();
+ return null;
}
private void processClass(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 705b2bd..d8568d5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -53,7 +53,7 @@
private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
private final Map<DexMethod, RemovedArgumentsInfo> removedArguments = new IdentityHashMap<>();
- static class UnusedArgumentsGraphLense extends NestedGraphLense {
+ public static class UnusedArgumentsGraphLense extends NestedGraphLense {
private final Map<DexMethod, RemovedArgumentsInfo> removedArguments;
@@ -95,7 +95,8 @@
this.methodPoolCollection = methodPoolCollection;
}
- public GraphLense run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ public UnusedArgumentsGraphLense run(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
ThreadUtils.awaitFutures(
Streams.stream(appView.appInfo().classes())
.map(this::runnableForClass)
@@ -122,7 +123,7 @@
removedArguments);
}
- return appView.graphLense();
+ return null;
}
private class UsedSignatures {
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 a5b8d52..9231d05 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
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.classinliner;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.google.common.base.Predicates.alwaysFalse;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
@@ -84,6 +85,7 @@
AssumeAndCheckCastAliasedValueConfiguration.getInstance();
private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
private final Inliner inliner;
private final Function<DexClass, EligibilityStatus> isClassEligible;
private final MethodProcessor methodProcessor;
@@ -117,6 +119,7 @@
DexEncodedMethod method,
Instruction root) {
this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
this.inliner = inliner;
this.isClassEligible = isClassEligible;
this.method = method;
@@ -259,6 +262,14 @@
InvokeMethod invokeMethod = user.asInvokeMethod();
DexEncodedMethod singleTarget =
invokeMethod.lookupSingleTarget(appView, method.method.holder);
+ if (singleTarget == null) {
+ return user; // Not eligible.
+ }
+
+ if (isEligibleLibraryMethodCall(invokeMethod, singleTarget)) {
+ continue;
+ }
+
if (!isEligibleSingleTarget(singleTarget)) {
return user; // Not eligible.
}
@@ -266,7 +277,7 @@
// Eligible constructor call (for new instance roots only).
if (user.isInvokeDirect()) {
InvokeDirect invoke = user.asInvokeDirect();
- if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
+ if (dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
boolean isCorrespondingConstructorCall =
root.isNewInstance()
&& !invoke.inValues().isEmpty()
@@ -430,11 +441,11 @@
}
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+ if (invokedMethod == dexItemFactory.objectMethods.constructor) {
continue;
}
- if (!appView.dexItemFactory().isConstructor(invokedMethod)) {
+ if (!dexItemFactory.isConstructor(invokedMethod)) {
throw new IllegalClassInlinerStateException();
}
@@ -482,7 +493,7 @@
if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+ if (invokedMethod == dexItemFactory.objectMethods.constructor) {
continue;
}
@@ -535,14 +546,36 @@
private void removeMiscUsages(IRCode code) {
boolean needToRemoveUnreachableBlocks = false;
for (Instruction user : eligibleInstance.uniqueUsers()) {
- // Remove the call to java.lang.Object.<init>().
- if (user.isInvokeDirect()) {
- InvokeDirect invoke = user.asInvokeDirect();
- if (root.isNewInstance()
- && invoke.getInvokedMethod() == appView.dexItemFactory().objectMethods.constructor) {
+ if (user.isInvokeMethod()) {
+ InvokeMethod invoke = user.asInvokeMethod();
+
+ // Remove the call to java.lang.Object.<init>().
+ if (user.isInvokeDirect()) {
+ if (root.isNewInstance()
+ && invoke.getInvokedMethod() == dexItemFactory.objectMethods.constructor) {
+ removeInstruction(invoke);
+ continue;
+ }
+ }
+
+ if (user.isInvokeStatic()) {
+ assert invoke.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull;
removeInstruction(invoke);
continue;
}
+
+ DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+ if (singleTarget != null) {
+ Predicate<InvokeMethod> noSideEffectsPredicate =
+ dexItemFactory.libraryMethodsWithoutSideEffects.getOrDefault(
+ singleTarget.method, alwaysFalse());
+ if (noSideEffectsPredicate.test(invoke)) {
+ if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+ removeInstruction(invoke);
+ continue;
+ }
+ }
+ }
}
if (user.isIf()) {
@@ -672,7 +705,7 @@
private InliningInfo isEligibleConstructorCall(
InvokeDirect invoke, DexEncodedMethod singleTarget) {
- assert appView.dexItemFactory().isConstructor(invoke.getInvokedMethod());
+ assert dexItemFactory.isConstructor(invoke.getInvokedMethod());
assert isEligibleSingleTarget(singleTarget);
// Must be a constructor called on the receiver.
@@ -704,7 +737,6 @@
}
// Check that the entire constructor chain can be inlined into the current context.
- DexItemFactory dexItemFactory = appView.dexItemFactory();
DexMethod parent = instanceInitializerInfo.getParent();
while (parent != dexItemFactory.objectMethods.constructor) {
if (parent == null) {
@@ -873,6 +905,9 @@
if (!isEligibleSingleTarget(singleTarget)) {
return false;
}
+ if (singleTarget.isLibraryMethodOverride().isTrue()) {
+ return false;
+ }
return isEligibleVirtualMethodCall(
null,
invokedMethod,
@@ -934,8 +969,7 @@
}
private boolean isExtraMethodCall(InvokeMethod invoke) {
- if (invoke.isInvokeDirect()
- && appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
+ if (invoke.isInvokeDirect() && dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
return false;
}
if (invoke.isInvokeMethodWithReceiver()) {
@@ -1019,6 +1053,18 @@
return true;
}
+ private boolean isEligibleLibraryMethodCall(InvokeMethod invoke, DexEncodedMethod singleTarget) {
+ Predicate<InvokeMethod> noSideEffectsPredicate =
+ dexItemFactory.libraryMethodsWithoutSideEffects.get(singleTarget.method);
+ if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(invoke)) {
+ return !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
+ }
+ if (singleTarget.method == dexItemFactory.objectsMethods.requireNonNull) {
+ return !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
+ }
+ return false;
+ }
+
private boolean isEligibleParameterUsages(
InvokeMethod invoke,
List<Value> arguments,
@@ -1123,7 +1169,7 @@
}
private boolean isInstanceInitializerEligibleForClassInlining(DexMethod method) {
- if (method == appView.dexItemFactory().objectMethods.constructor) {
+ if (method == dexItemFactory.objectMethods.constructor) {
return true;
}
DexEncodedMethod encodedMethod = appView.definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 36ccecf..29fcf04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -143,7 +143,7 @@
}
@Override
- public boolean isInitializerEnablingJavaAssertions() {
+ public boolean isInitializerEnablingJavaVmAssertions() {
return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 46749fd..b7b4973 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -60,7 +60,7 @@
InstanceInitializerInfo getInstanceInitializerInfo();
- boolean isInitializerEnablingJavaAssertions();
+ boolean isInitializerEnablingJavaVmAssertions();
AbstractValue getAbstractReturnValue();
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 b97cc0d..d1a6c3a 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
@@ -74,6 +74,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Return;
@@ -224,6 +225,22 @@
return;
}
+ case INVOKE_STATIC:
+ {
+ InvokeStatic invoke = insn.asInvokeStatic();
+ DexEncodedMethod singleTarget =
+ invoke.lookupSingleTarget(appView, method.method.holder);
+ if (singleTarget == null) {
+ return; // Not allowed.
+ }
+ if (singleTarget.method == dexItemFactory.objectsMethods.requireNonNull) {
+ if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+ continue;
+ }
+ }
+ return;
+ }
+
case INVOKE_VIRTUAL:
{
InvokeVirtual invoke = insn.asInvokeVirtual();
@@ -284,7 +301,7 @@
}
private ParameterUsage collectParameterUsages(int i, Value value) {
- ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
+ ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i, dexItemFactory);
for (Instruction user : value.aliasedUsers()) {
if (!builder.note(user)) {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 27f8be0..0fec1f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -235,7 +235,7 @@
}
@Override
- public synchronized void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ public synchronized void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {
getMethodOptimizationInfoForUpdating(method).setInitializerEnablingJavaAssertions();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 8d6e8c4..ee5a745 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -113,8 +113,7 @@
DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {}
@Override
- public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
- }
+ public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {}
@Override
public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 65ea424..ad3080b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -87,7 +87,7 @@
@Override
public void methodReturnsArgument(DexEncodedMethod method, int argument) {
- // Ignored.
+ method.getMutableOptimizationInfo().markReturnsArgument(argument);
}
@Override
@@ -120,7 +120,7 @@
@Override
public void methodNeverReturnsNull(DexEncodedMethod method) {
- // Ignored.
+ method.getMutableOptimizationInfo().neverReturnsNull();
}
@Override
@@ -162,7 +162,7 @@
}
@Override
- public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {
method.getMutableOptimizationInfo().setInitializerEnablingJavaAssertions();
}
@@ -173,12 +173,12 @@
@Override
public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
- // Ignored.
+ method.getMutableOptimizationInfo().setNonNullParamOrThrow(facts);
}
@Override
public void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts) {
- // Ignored.
+ method.getMutableOptimizationInfo().setNonNullParamOnNormalExits(facts);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index e911307..921ae28 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
@@ -12,6 +13,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
@@ -139,6 +141,8 @@
private final int index;
private final Value arg;
+ private final DexItemFactory dexItemFactory;
+
private final Set<Type> ifZeroTestTypes = new HashSet<>();
private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>();
@@ -148,9 +152,10 @@
private boolean isReturned = false;
private boolean isUsedInMonitor = false;
- ParameterUsageBuilder(Value arg, int index) {
+ ParameterUsageBuilder(Value arg, int index, DexItemFactory dexItemFactory) {
this.arg = arg;
this.index = index;
+ this.dexItemFactory = dexItemFactory;
}
// Returns false if the instruction is not supported.
@@ -172,6 +177,9 @@
if (instruction.isInvokeMethodWithReceiver()) {
return note(instruction.asInvokeMethodWithReceiver());
}
+ if (instruction.isInvokeStatic()) {
+ return note(instruction.asInvokeStatic());
+ }
if (instruction.isReturn()) {
return note(instruction.asReturn());
}
@@ -238,6 +246,16 @@
return false;
}
+ private boolean note(InvokeStatic invokeInstruction) {
+ if (invokeInstruction.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
+ if (!invokeInstruction.hasOutValue() || !invokeInstruction.outValue().hasAnyUsers()) {
+ ifZeroTestTypes.add(Type.EQ);
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean note(Return returnInstruction) {
assert returnInstruction.inValues().size() == 1
&& returnInstruction.inValues().get(0).getAliasedValue() == arg;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index dc67349..d2e78f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -106,7 +106,7 @@
BooleanUtils.intValue(defaultOptInfo.triggersClassInitBeforeAnySideEffect())
* TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG;
defaultFlags |=
- BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaAssertions())
+ BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaVmAssertions())
* INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.isReachabilitySensitive())
@@ -287,7 +287,7 @@
}
@Override
- public boolean isInitializerEnablingJavaAssertions() {
+ public boolean isInitializerEnablingJavaVmAssertions() {
return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
}
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 edd657b..31444c6 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
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.optimize.library;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -21,10 +20,10 @@
public class BooleanMethodOptimizer implements LibraryMethodModelCollection {
- private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
- BooleanMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+ BooleanMethodOptimizer(AppView<?> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
}
@@ -57,9 +56,9 @@
StaticGet staticGet = definition.asStaticGet();
DexField field = staticGet.getField();
if (field == dexItemFactory.booleanMembers.TRUE) {
- instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 1);
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
} else if (field == dexItemFactory.booleanMembers.FALSE) {
- instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 0);
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index 67a7588..e2999df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.optimize.library;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
@@ -29,9 +28,10 @@
private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
new IdentityHashMap<>();
- public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+ public LibraryMethodOptimizer(AppView<?> appView) {
this.appView = appView;
register(new BooleanMethodOptimizer(appView));
+ register(new ObjectsMethodOptimizer(appView));
register(new StringMethodOptimizer(appView));
if (LogMethodOptimizer.isEnabled(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
new file mode 100644
index 0000000..050640c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -0,0 +1,66 @@
+// 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 com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import java.util.BitSet;
+
+public class LibraryOptimizationInfoInitializer {
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+
+ private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
+
+ public LibraryOptimizationInfoInitializer(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ public void run() {
+ modelLibraryMethodsReturningNonNull();
+ modelLibraryMethodsReturningReceiver();
+ modelRequireNonNullMethods();
+ }
+
+ private void modelLibraryMethodsReturningNonNull() {
+ for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
+ DexEncodedMethod definition = appView.definitionFor(method);
+ if (definition != null) {
+ feedback.methodNeverReturnsNull(definition);
+ }
+ }
+ }
+
+ private void modelLibraryMethodsReturningReceiver() {
+ for (DexMethod method : dexItemFactory.libraryMethodsReturningReceiver) {
+ DexEncodedMethod definition = appView.definitionFor(method);
+ if (definition != null) {
+ feedback.methodReturnsArgument(definition, 0);
+ }
+ }
+ }
+
+ private void modelRequireNonNullMethods() {
+ for (DexMethod requireNonNullMethod : dexItemFactory.objectsMethods.requireNonNullMethods()) {
+ DexEncodedMethod definition = appView.definitionFor(requireNonNullMethod);
+ if (definition != null) {
+ feedback.methodReturnsArgument(definition, 0);
+
+ BitSet nonNullParamOrThrow = new BitSet();
+ nonNullParamOrThrow.set(0);
+ feedback.setNonNullParamOrThrow(definition, nonNullParamOrThrow);
+
+ BitSet nonNullParamOnNormalExits = new BitSet();
+ nonNullParamOnNormalExits.set(0);
+ feedback.setNonNullParamOnNormalExits(definition, nonNullParamOnNormalExits);
+ }
+ }
+ }
+}
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 f65b57a..6cd36b3 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
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.optimize.library;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +14,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.ProguardConfiguration;
import java.util.Set;
public class LogMethodOptimizer implements LibraryMethodModelCollection {
@@ -26,7 +26,7 @@
private static final int ERROR = 6;
private static final int ASSERT = 7;
- private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final AppView<?> appView;
private final DexType logType;
@@ -38,7 +38,7 @@
private final DexMethod eMethod;
private final DexMethod wtfMethod;
- LogMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+ LogMethodOptimizer(AppView<?> appView) {
this.appView = appView;
DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -89,7 +89,9 @@
}
public static boolean isEnabled(AppView<?> appView) {
- return appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel() >= VERBOSE;
+ ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+ return proguardConfiguration != null
+ && proguardConfiguration.getMaxRemovedAndroidLogLevel() >= VERBOSE;
}
@Override
@@ -146,7 +148,7 @@
private void replaceInvokeWithConstNumber(
IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
- instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
} else {
instructionIterator.removeOrReplaceByDebugLocalRead();
}
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
new file mode 100644
index 0000000..975aca8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -0,0 +1,54 @@
+// 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 com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class ObjectsMethodOptimizer implements LibraryMethodModelCollection {
+
+ private final DexItemFactory dexItemFactory;
+
+ ObjectsMethodOptimizer(AppView<?> appView) {
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.objectsType;
+ }
+
+ @Override
+ public void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexEncodedMethod singleTarget,
+ Set<Value> affectedValues) {
+ if (dexItemFactory.objectsMethods.isRequireNonNullMethod(singleTarget.method)) {
+ optimizeRequireNonNull(instructionIterator, invoke, affectedValues);
+ }
+ }
+
+ private void optimizeRequireNonNull(
+ InstructionListIterator instructionIterator, InvokeMethod invoke, Set<Value> affectedValues) {
+ Value inValue = invoke.inValues().get(0);
+ if (inValue.getTypeLattice().isDefinitelyNotNull()) {
+ Value outValue = invoke.outValue();
+ if (outValue != null) {
+ affectedValues.addAll(outValue.affectedValues());
+ outValue.replaceUsers(inValue);
+ }
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ }
+}
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 5e4033b..501a761 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
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.optimize.library;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -20,10 +19,10 @@
public class StringMethodOptimizer implements LibraryMethodModelCollection {
- private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
- StringMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+ StringMethodOptimizer(AppView<?> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
}
@@ -53,7 +52,7 @@
Value second = invoke.arguments().get(1).getAliasedValue();
if (isPrunedClassNameComparison(first, second, context)
|| isPrunedClassNameComparison(second, first, context)) {
- instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 0);
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 247f13e..e0ca9093 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -25,6 +25,8 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -32,7 +34,6 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -49,6 +50,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -60,8 +62,8 @@
private final IRConverter converter;
// Optimization order matters, hence a collection that preserves orderings.
- private final Map<DexEncodedMethod, ImmutableList.Builder<Consumer<IRCode>>> processingQueue =
- new IdentityHashMap<>();
+ private final Map<DexEncodedMethod, ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>>
+ processingQueue = new IdentityHashMap<>();
private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
@@ -265,7 +267,7 @@
private void enqueueMethodsWithCodeOptimizations(
Iterable<DexEncodedMethod> methods,
- Consumer<ImmutableList.Builder<Consumer<IRCode>>> extension) {
+ Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
for (DexEncodedMethod method : methods) {
extension.accept(processingQueue.computeIfAbsent(method, ignore -> ImmutableList.builder()));
}
@@ -274,18 +276,20 @@
/**
* Processes the given methods concurrently using the given strategy.
*
- * <p>Note that, when the strategy {@link #rewriteReferences(IRCode)} is being applied, it is
- * important that we never inline a method from `methods` which has still not been reprocessed.
- * This could lead to broken code, because the strategy that rewrites the broken references is
- * applied *before* inlining (because the broken references in the inlinee are never rewritten).
- * We currently avoid this situation by processing all the methods concurrently
+ * <p>Note that, when the strategy {@link #rewriteReferences(IRCode, MethodProcessor)} is being
+ * applied, it is important that we never inline a method from `methods` which has still not been
+ * reprocessed. This could lead to broken code, because the strategy that rewrites the broken
+ * references is applied *before* inlining (because the broken references in the inlinee are never
+ * rewritten). We currently avoid this situation by processing all the methods concurrently
* (inlining of a method that is processed concurrently is not allowed).
*/
private void processMethodsConcurrently(
OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
- ThreadUtils.processItems(
- processingQueue.keySet(),
- method -> forEachMethod(method, processingQueue.get(method).build(), feedback),
+ Set<DexEncodedMethod> wave = processingQueue.keySet();
+ OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.getInstance(wave);
+ methodProcessor.forEachWave(
+ method ->
+ forEachMethod(method, processingQueue.get(method).build(), feedback, methodProcessor),
executorService);
// TODO(b/140767158): No need to clear if we can do every thing in one go.
processingQueue.clear();
@@ -294,25 +298,33 @@
// TODO(b/140766440): Should be part or variant of PostProcessor.
private void forEachMethod(
DexEncodedMethod method,
- Collection<Consumer<IRCode>> codeOptimizations,
- OptimizationFeedback feedback) {
+ Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
+ OptimizationFeedback feedback,
+ OneTimeMethodProcessor methodProcessor) {
Origin origin = appView.appInfo().originFor(method.method.holder);
IRCode code = method.buildIR(appView, origin);
- codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
+ codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
CodeRewriter.removeAssumeInstructions(appView, code);
- converter.finalizeIR(method, code, feedback, Timing.empty());
+ converter.removeDeadCodeAndFinalizeIR(method, code, feedback, Timing.empty());
}
- private void insertAssumeInstructions(IRCode code) {
+ private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
CodeRewriter.insertAssumeInstructions(code, converter.assumers);
}
- private Consumer<IRCode> collectOptimizationInfo(OptimizationFeedback feedback) {
- return code ->
- converter.collectOptimizationInfo(code, ClassInitializerDefaultsResult.empty(), feedback);
+ private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
+ OptimizationFeedback feedback) {
+ return (code, methodProcessor) ->
+ converter.collectOptimizationInfo(
+ code.method,
+ code,
+ ClassInitializerDefaultsResult.empty(),
+ feedback,
+ methodProcessor,
+ Timing.empty());
}
- private void removeCandidateInstantiation(IRCode code) {
+ private void removeCandidateInstantiation(IRCode code, MethodProcessor methodProcessor) {
CandidateInfo candidateInfo = hostClassInits.get(code.method);
assert candidateInfo != null;
@@ -339,11 +351,11 @@
assert false : "Must always be able to find and remove the instantiation";
}
- private void removeReferencesToThis(IRCode code) {
+ private void removeReferencesToThis(IRCode code, MethodProcessor methodProcessor) {
fixupStaticizedThisUsers(code, code.getThis());
}
- private void rewriteReferences(IRCode code) {
+ private void rewriteReferences(IRCode code, MethodProcessor methodProcessor) {
// Process all singleton field reads and rewrite their users.
List<StaticGet> singletonFieldReads =
Streams.stream(code.instructionIterator())
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 1b34adf..941d7e2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -35,33 +35,60 @@
public final DexItemFactory factory;
+ public final DexType kotlinAssertions;
public final Functional functional;
public final Intrinsics intrinsics;
public final Metadata metadata;
+ // Mappings from JVM types to Kotlin types (of type DexType)
final Map<DexType, DexType> knownTypeConversion;
public Kotlin(DexItemFactory factory) {
this.factory = factory;
+ this.kotlinAssertions = factory.createType(addKotlinPrefix("_Assertions;"));
this.functional = new Functional();
this.intrinsics = new Intrinsics();
this.metadata = new Metadata();
+ // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
this.knownTypeConversion =
ImmutableMap.<DexType, DexType>builder()
// https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
+ // Boxed primitives and arrays
.put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;")))
+ .put(factory.booleanArrayType, factory.createType(addKotlinPrefix("BooleanArray;")))
.put(factory.byteType, factory.createType(addKotlinPrefix("Byte;")))
- .put(factory.charType, factory.createType(addKotlinPrefix("Character;")))
+ .put(factory.byteArrayType, factory.createType(addKotlinPrefix("ByteArray;")))
+ .put(factory.charType, factory.createType(addKotlinPrefix("Char;")))
+ .put(factory.charArrayType, factory.createType(addKotlinPrefix("CharArray;")))
.put(factory.shortType, factory.createType(addKotlinPrefix("Short;")))
+ .put(factory.shortArrayType, factory.createType(addKotlinPrefix("ShortArray;")))
.put(factory.intType, factory.createType(addKotlinPrefix("Int;")))
+ .put(factory.intArrayType, factory.createType(addKotlinPrefix("IntArray;")))
.put(factory.longType, factory.createType(addKotlinPrefix("Long;")))
+ .put(factory.longArrayType, factory.createType(addKotlinPrefix("LongArray;")))
.put(factory.floatType, factory.createType(addKotlinPrefix("Float;")))
+ .put(factory.floatArrayType, factory.createType(addKotlinPrefix("FloatArray;")))
.put(factory.doubleType, factory.createType(addKotlinPrefix("Double;")))
+ .put(factory.doubleArrayType, factory.createType(addKotlinPrefix("DoubleArray;")))
+ // Other intrinsics
.put(factory.voidType, factory.createType(addKotlinPrefix("Unit;")))
+ .put(factory.objectType, factory.createType(addKotlinPrefix("Any;")))
+ .put(factory.boxedVoidType, factory.createType(addKotlinPrefix("Nothing;")))
.put(factory.stringType, factory.createType(addKotlinPrefix("String;")))
- // TODO(b/70169921): Collections?
+ .put(factory.charSequenceType, factory.createType(addKotlinPrefix("CharSequence;")))
+ .put(factory.throwableType, factory.createType(addKotlinPrefix("Throwable;")))
+ .put(factory.cloneableType, factory.createType(addKotlinPrefix("Cloneable;")))
+ .put(factory.boxedNumberType, factory.createType(addKotlinPrefix("Number;")))
+ .put(factory.comparableType, factory.createType(addKotlinPrefix("Comparable;")))
+ .put(factory.enumType, factory.createType(addKotlinPrefix("Enum;")))
+ // TODO(b/70169921): (Mutable) Collections?
+ // .../jvm/functions/FunctionN -> .../FunctionN
+ .putAll(
+ IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
+ i -> factory.createType(addKotlinPrefix("jvm/functions/Function" + i + ";")),
+ i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
.build();
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 0edb554..48f29c2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -14,7 +14,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.List;
@@ -48,6 +50,16 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
+ && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
+ String renamedClassifier = toRenamedClassifier(clazz.type, appView, lens);
+ if (renamedClassifier != null) {
+ assert !kmClass.getName().equals(renamedClassifier);
+ kmClass.setName(renamedClassifier);
+ }
+ }
+
+ // Rewriting upward hierarchy.
List<KmType> superTypes = kmClass.getSupertypes();
superTypes.clear();
for (DexType itfType : clazz.interfaces.values) {
@@ -66,7 +78,29 @@
superTypes.add(toKmType(addKotlinPrefix("Any;")));
}
- if (!appView.options().enableKotlinMetadataRewriting) {
+ // Rewriting downward hierarchies: nested.
+ List<String> nestedClasses = kmClass.getNestedClasses();
+ nestedClasses.clear();
+ for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+ DexString renamedInnerName = lens.lookupInnerName(innerClassAttribute, appView.options());
+ if (renamedInnerName != null) {
+ nestedClasses.add(renamedInnerName.toString());
+ }
+ }
+
+ // Rewriting downward hierarchies: sealed.
+ List<String> sealedSubclasses = kmClass.getSealedSubclasses();
+ sealedSubclasses.clear();
+ if (IS_SEALED.invoke(kmClass.getFlags())) {
+ for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+ String classifier = toRenamedClassifier(subtype, appView, lens);
+ if (classifier != null) {
+ sealedSubclasses.add(classifier);
+ }
+ }
+ }
+
+ if (!appView.options().enableKotlinMetadataRewritingForMembers) {
return;
}
List<KmConstructor> constructors = kmClass.getConstructors();
@@ -81,18 +115,9 @@
}
}
- rewriteDeclarationContainer(kmClass, appView, lens);
+ // TODO(b/70169921): enum entries
- List<String> sealedSubclasses = kmClass.getSealedSubclasses();
- sealedSubclasses.clear();
- if (IS_SEALED.invoke(kmClass.getFlags())) {
- for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
- String classifier = toRenamedClassifier(subtype, appView, lens);
- if (classifier != null) {
- sealedSubclasses.add(classifier);
- }
- }
- }
+ rewriteDeclarationContainer(kmClass, appView, lens);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index f8c1c12..07ec533 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -36,6 +36,7 @@
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
// TODO(b/70169921): no idea yet!
assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ || appView.options().enableKotlinMetadataRewritingForRenamedClasses
: toString();
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 94a993b..58c5aff 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -37,7 +37,7 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- if (!appView.options().enableKotlinMetadataRewriting) {
+ if (!appView.options().enableKotlinMetadataRewritingForMembers) {
return;
}
rewriteDeclarationContainer(kmPackage, appView, lens);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 9eb70f8..5eac335 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -37,7 +37,7 @@
@Override
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- if (!appView.options().enableKotlinMetadataRewriting) {
+ if (!appView.options().enableKotlinMetadataRewritingForMembers) {
return;
}
rewriteDeclarationContainer(kmPackage, appView, lens);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index 5382e44..62b5355 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
+import com.android.tools.r8.utils.Reporter;
import java.util.HashMap;
import java.util.Map;
import kotlinx.metadata.KmDeclarationContainer;
@@ -88,36 +89,37 @@
}
}
- public static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo) {
+ public static void markKotlinMemberInfo(
+ DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) {
if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) {
return;
}
if (kotlinInfo.isClass()) {
- markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass);
+ markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass, reporter);
} else if (kotlinInfo.isFile()) {
- markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage);
+ markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage, reporter);
} else if (kotlinInfo.isClassPart()) {
- markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage);
+ markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage, reporter);
} else {
throw new Unreachable("Unexpected KotlinInfo: " + kotlinInfo);
}
}
private static void markKotlinMemberInfo(
- DexClass clazz, KmDeclarationContainer kmDeclarationContainer) {
+ DexClass clazz, KmDeclarationContainer kmDeclarationContainer, Reporter reporter) {
Map<String, KmFunction> kmFunctionMap = new HashMap<>();
Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
kmDeclarationContainer.getFunctions().forEach(kmFunction -> {
- KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction);
+ KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction, reporter);
if (functionProcessor.signature() != null) {
kmFunctionMap.put(functionProcessor.signature().asString(), kmFunction);
}
});
kmDeclarationContainer.getProperties().forEach(kmProperty -> {
- KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
+ KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty, reporter);
if (propertyProcessor.fieldSignature() != null) {
kmPropertyFieldMap.put(propertyProcessor.fieldSignature().asString(), kmProperty);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index bdd7f60..fadf9cb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -3,9 +3,22 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+
+import com.android.tools.r8.errors.InvalidDescriptorException;
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.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmConstructorExtensionVisitor;
import kotlinx.metadata.KmConstructorVisitor;
@@ -24,6 +37,94 @@
class KotlinMetadataJvmExtensionUtils {
+ // Mappings from Kotlin types to JVM types (of String)
+ private static final Map<String, String> knownTypeConversion =
+ // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
+ ImmutableMap.<String, String>builder()
+ // Boxed primitives and arrays
+ .put(addKotlinPrefix("Boolean;"), "Z")
+ .put(addKotlinPrefix("BooleanArray;"), "[Z")
+ .put(addKotlinPrefix("Byte;"), "B")
+ .put(addKotlinPrefix("ByteArray;"), "[B")
+ .put(addKotlinPrefix("Char;"), "C")
+ .put(addKotlinPrefix("CharArray;"), "[C")
+ .put(addKotlinPrefix("Short;"), "S")
+ .put(addKotlinPrefix("ShortArray;"), "[S")
+ .put(addKotlinPrefix("Int;"), "I")
+ .put(addKotlinPrefix("IntArray;"), "[I")
+ .put(addKotlinPrefix("Long;"), "J")
+ .put(addKotlinPrefix("LongArray;"), "[J")
+ .put(addKotlinPrefix("Float;"), "F")
+ .put(addKotlinPrefix("FloatArray;"), "[F")
+ .put(addKotlinPrefix("Double;"), "D")
+ .put(addKotlinPrefix("DoubleArray;"), "[D")
+ // Other intrinsics
+ .put(addKotlinPrefix("Unit;"), "V")
+ .put(addKotlinPrefix("Any;"), "Ljava/lang/Object;")
+ .put(addKotlinPrefix("Nothing;"), "Ljava/lang/Void;")
+ .putAll(ImmutableList.of(
+ "String", "CharSequence", "Throwable", "Cloneable", "Number", "Comparable", "Enum")
+ .stream().collect(Collectors.toMap(
+ t -> addKotlinPrefix(t + ";"),
+ t -> "Ljava/lang/" + t + ";")))
+ // Collections
+ .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
+ .stream().collect(Collectors.toMap(
+ t -> addKotlinPrefix("collections/" + t + ";"),
+ t -> "Ljava/util/" + t + ";")))
+ .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
+ .stream().collect(Collectors.toMap(
+ t -> addKotlinPrefix("collections/Mutable" + t + ";"),
+ t -> "Ljava/util/" + t + ";")))
+ .put(addKotlinPrefix("collections/Iterable;"), "Ljava/lang/Iterable;")
+ .put(addKotlinPrefix("collections/MutableIterable;"), "Ljava/lang/Iterable;")
+ .put(addKotlinPrefix("collections/Map.Entry;"), "Ljava/util/Map$Entry;")
+ .put(addKotlinPrefix("collections/MutableMap.MutableEntry;"), "Ljava/util/Map$Entry;")
+ // .../FunctionN -> .../jvm/functions/FunctionN
+ .putAll(
+ IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
+ i -> addKotlinPrefix("Function" + i + ";"),
+ i -> addKotlinPrefix("jvm/functions/Function" + i + ";"))))
+ .build();
+
+ private static String remapKotlinType(String type) {
+ if (knownTypeConversion.containsKey(type)) {
+ return knownTypeConversion.get(type);
+ }
+ return type;
+ }
+
+ // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
+ // See b/70169921#comment57 for more details.
+ // E.g., desc: (Labc/xyz/C;Lkotlin/Function1;)kotlin/Unit
+ // remapped desc would be: (Labc/xyz/C;Lkotlin/jvm/functions/Function1;)V
+ private static String remapKotlinTypeInDesc(String desc, Reporter reporter) {
+ if (desc == null) {
+ return null;
+ }
+ if (desc.isEmpty()) {
+ return desc;
+ }
+ String[] parameterTypes;
+ try {
+ parameterTypes = DescriptorUtils.getArgumentTypeDescriptors(desc);
+ for (int i = 0; i < parameterTypes.length; i++) {
+ parameterTypes[i] = remapKotlinType(parameterTypes[i]);
+ }
+ } catch (InvalidDescriptorException e) {
+ // JvmMethodSignature from @Metadata is not 100% reliable (due to its own optimization using
+ // map, relocation in r8lib, etc.)
+ reporter.info(
+ new StringDiagnostic(
+ "Invalid descriptor (deserialized from Kotlin @Metadata): " + desc));
+ return desc;
+ }
+ int index = desc.indexOf(')');
+ assert 0 < index && index < desc.length() : desc;
+ String returnType = remapKotlinType(desc.substring(index + 1));
+ return "(" + StringUtils.join(Arrays.asList(parameterTypes), "") + ")" + returnType;
+ }
+
static JvmFieldSignature toJvmFieldSignature(DexField field) {
return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
}
@@ -42,7 +143,7 @@
static class KmConstructorProcessor {
private JvmMethodSignature signature = null;
- KmConstructorProcessor(KmConstructor kmConstructor) {
+ KmConstructorProcessor(KmConstructor kmConstructor, Reporter reporter) {
kmConstructor.accept(new KmConstructorVisitor() {
@Override
public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -58,6 +159,12 @@
};
}
});
+ if (signature != null) {
+ String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+ if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+ signature = new JvmMethodSignature(signature.getName(), remappedDesc);
+ }
+ }
}
JvmMethodSignature signature() {
@@ -69,7 +176,7 @@
// Custom name via @JvmName("..."). Otherwise, null.
private JvmMethodSignature signature = null;
- KmFunctionProcessor(KmFunction kmFunction) {
+ KmFunctionProcessor(KmFunction kmFunction, Reporter reporter) {
kmFunction.accept(new KmFunctionVisitor() {
@Override
public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -85,6 +192,12 @@
};
}
});
+ if (signature != null) {
+ String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+ if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+ signature = new JvmMethodSignature(signature.getName(), remappedDesc);
+ }
+ }
}
JvmMethodSignature signature() {
@@ -99,7 +212,7 @@
// Custom getter via @set:JvmName("..."). Otherwise, null.
private JvmMethodSignature setterSignature = null;
- KmPropertyProcessor(KmProperty kmProperty) {
+ KmPropertyProcessor(KmProperty kmProperty, Reporter reporter) {
kmProperty.accept(new KmPropertyVisitor() {
@Override
public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -123,6 +236,24 @@
};
}
});
+ if (fieldSignature != null) {
+ String remappedDesc = remapKotlinType(fieldSignature.getDesc());
+ if (remappedDesc != null && !remappedDesc.equals(fieldSignature.getDesc())) {
+ fieldSignature = new JvmFieldSignature(fieldSignature.getName(), remappedDesc);
+ }
+ }
+ if (getterSignature != null) {
+ String remappedDesc = remapKotlinTypeInDesc(getterSignature.getDesc(), reporter);
+ if (remappedDesc != null && !remappedDesc.equals(getterSignature.getDesc())) {
+ getterSignature = new JvmMethodSignature(getterSignature.getName(), remappedDesc);
+ }
+ }
+ if (setterSignature != null) {
+ String remappedDesc = remapKotlinTypeInDesc(setterSignature.getDesc(), reporter);
+ if (remappedDesc != null && !remappedDesc.equals(setterSignature.getDesc())) {
+ setterSignature = new JvmMethodSignature(setterSignature.getName(), remappedDesc);
+ }
+ }
}
JvmFieldSignature fieldSignature() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 5658b0d..b295da3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -68,7 +68,10 @@
if (kotlinInfo != null) {
// If @Metadata is still associated, this class should not be renamed
// (by {@link ClassNameMinifier} of course).
+ // Or, we start maintaining @Metadata for renamed classes.
+ // TODO(b/70169921): if this option is settled down, this assertion is meaningless.
assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ || appView.options().enableKotlinMetadataRewritingForRenamedClasses
: clazz.toSourceString() + " != "
+ lens.lookupType(clazz.type, appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 8a8773a..94c65e3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -46,16 +46,16 @@
static String toRenamedClassifier(
DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
- // E.g., [Ljava/lang/String; -> kotlin/Array
- if (type.isArrayType()) {
- return NAME + "/Array";
- }
- // E.g., void -> kotlin/Unit
+ // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
assert convertedType != null;
return descriptorToKotlinClassifier(convertedType.toDescriptorString());
}
+ // E.g., [Ljava/lang/String; -> kotlin/Array
+ if (type.isArrayType()) {
+ return NAME + "/Array";
+ }
// For library or classpath class, synthesize @Metadata always.
// For a program class, make sure it is live.
if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
@@ -79,6 +79,9 @@
// and/or why wiping out flags works for KmType but not KmFunction?!
KmType kmType = new KmType(flagsOf());
kmType.visitClass(classifier);
+ // TODO(b/70169921): Need to set arguments as type parameter.
+ // E.g., for kotlin/Function1<P1, R>, P1 and R are recorded inside KmType.arguments, which
+ // enables kotlinc to resolve `this` type and return type of the lambda.
return kmType;
}
@@ -128,7 +131,7 @@
}
DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
// For a library method override, we should not have renamed it.
- assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
+ assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name
: method.toSourceString() + " -> " + renamedMethod.toSourceString();
// TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
// value from general JVM flags?
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 59f7dce..28a6eb1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -53,6 +53,7 @@
void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
// TODO(b/70169921): no idea yet!
assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ || appView.options().enableKotlinMetadataRewritingForRenamedClasses
: toString();
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d13cc3b..1c75720 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -115,7 +115,9 @@
if (!renaming.containsKey(clazz.type)) {
DexString renamed = computeName(clazz.type);
renaming.put(clazz.type, renamed);
- KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
+ if (!appView.options().enableKotlinMetadataRewritingForRenamedClasses) {
+ KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
+ }
// If the class is a member class and it has used $ separator, its renamed name should have
// the same separator (as long as inner-class attribute is honored).
assert !keepInnerClassStructure
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 8896b56..b178579 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -84,34 +84,52 @@
private void reserveFieldNames() {
// Reserve all field names that need to be reserved.
- for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
- ReservedFieldNamingState reservedNames = null;
- for (DexEncodedField field : clazz.fields()) {
- DexString reservedName = strategy.getReservedName(field, clazz);
- if (reservedName != null) {
- if (reservedNames == null) {
- reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
- }
- reservedNames.markReservedDirectly(reservedName, field.field.name, field.field.type);
- if (reservedName != field.field.name) {
- renaming.put(field.field, reservedName);
- }
- }
- }
+ appView
+ .appInfo()
+ .forEachTypeInHierarchyOfLiveProgramClasses(
+ clazz -> {
+ ReservedFieldNamingState reservedNames = null;
+ for (DexEncodedField field : clazz.fields()) {
+ DexString reservedName = strategy.getReservedName(field, clazz);
+ if (reservedName != null) {
+ if (reservedNames == null) {
+ reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
+ }
+ reservedNames.markReservedDirectly(
+ reservedName, field.field.name, field.field.type);
+ // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
+ if (reservedName != field.field.name) {
+ renaming.put(field.field, reservedName);
+ }
+ }
+ }
- // For interfaces, propagate reserved names to all implementing classes.
- if (clazz.isInterface() && reservedNames != null) {
- for (DexType implementationType :
- appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) {
- DexClass implementation = appView.definitionFor(implementationType);
- if (implementation != null) {
- assert !implementation.isInterface();
- getOrCreateReservedFieldNamingState(implementationType)
- .includeReservations(reservedNames);
- }
- }
- }
- }
+ // For interfaces, propagate reserved names to all implementing classes.
+ if (clazz.isInterface() && reservedNames != null) {
+ for (DexType implementationType :
+ appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) {
+ DexClass implementation = appView.definitionFor(implementationType);
+ if (implementation != null) {
+ assert !implementation.isInterface();
+ getOrCreateReservedFieldNamingState(implementationType)
+ .includeReservations(reservedNames);
+ }
+ }
+ }
+ });
+
+ // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
+ appView
+ .appInfo()
+ .forEachReferencedClasspathClass(
+ clazz -> {
+ for (DexEncodedField field : clazz.fields()) {
+ DexString reservedName = strategy.getReservedName(field, clazz);
+ if (reservedName != null && reservedName != field.field.name) {
+ renaming.put(field.field, reservedName);
+ }
+ }
+ });
propagateReservedFieldNamesUpwards();
}
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 00b5315..bbefa18 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -454,7 +454,7 @@
for (DexEncodedMethod method : implementedMethods) {
Wrapper<DexMethod> wrapped = equivalence.wrap(method.method);
InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
- assert groupState != null;
+ assert groupState != null : wrapped;
groupState.addCallSite(callSite);
callSiteMethods.add(wrapped);
}
@@ -636,19 +636,23 @@
}
private void computeReservationFrontiersForAllImplementingClasses() {
- for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
- // TODO(b/133091438): Extend the if check to test for !clazz.isLibrary().
- if (!clazz.isInterface()) {
- for (DexType directlyImplemented : appView.appInfo().implementedInterfaces(clazz.type)) {
- InterfaceReservationState iState = interfaceStateMap.get(directlyImplemented);
- if (iState != null) {
- DexType frontierType = minifierState.getFrontier(clazz.type);
- assert minifierState.getReservationState(frontierType) != null;
- iState.reservationTypes.add(frontierType);
- }
- }
- }
- }
+ appView
+ .appInfo()
+ .forEachTypeInHierarchyOfLiveProgramClasses(
+ clazz -> {
+ // TODO(b/133091438): Extend the if check to test for !clazz.isLibrary().
+ if (!clazz.isInterface()) {
+ for (DexType directlyImplemented :
+ appView.appInfo().implementedInterfaces(clazz.type)) {
+ InterfaceReservationState iState = interfaceStateMap.get(directlyImplemented);
+ if (iState != null) {
+ DexType frontierType = minifierState.getFrontier(clazz.type);
+ assert minifierState.getReservationState(frontierType) != null;
+ iState.reservationTypes.add(frontierType);
+ }
+ }
+ }
+ });
}
private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexMethod>> groups) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index d506d84..2e68b4f 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -276,14 +275,6 @@
DexString reservedName = strategy.getReservedName(method, holder);
if (reservedName != null) {
state.reserveName(reservedName, method.method);
- // This is reserving names which after prefix rewriting will actually override a library
- // method.
- if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
- state.reserveName(
- reservedName,
- DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
- method.method, method.method.holder, appView));
- }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index e768589..359de49 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -213,11 +213,13 @@
final AppView<?> appView;
private final DexItemFactory factory;
+ private final boolean desugaredLibraryRenaming;
public MinifierMemberNamingStrategy(AppView<?> appView) {
super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.desugaredLibraryRenaming = appView.rewritePrefix.isRewriting();
}
@Override
@@ -260,6 +262,12 @@
|| appView.rootSet().mayNotBeMinified(method.method, appView)) {
return method.method.name;
}
+ if (desugaredLibraryRenaming
+ && method.isLibraryMethodOverride().isTrue()
+ && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+ // With desugared library, call-backs names are reserved here.
+ return method.method.name;
+ }
return null;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 814ef4c..7b6d191 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -44,6 +44,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -90,15 +91,19 @@
timing.begin("MappingInterfaces");
Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
AppInfoWithLiveness appInfo = appView.appInfo();
- for (DexClass dexClass : appInfo.app().asDirect().allClasses()) {
- if (dexClass.isInterface()) {
- // Only visit top level interfaces because computeMapping will visit the hierarchy.
- if (dexClass.interfaces.isEmpty()) {
- computeMapping(dexClass.type, nonPrivateMembers);
- }
- interfaces.add(dexClass);
- }
- }
+ Consumer<DexClass> consumer =
+ dexClass -> {
+ if (dexClass.isInterface()) {
+ // Only visit top level interfaces because computeMapping will visit the hierarchy.
+ if (dexClass.interfaces.isEmpty()) {
+ computeMapping(dexClass.type, nonPrivateMembers);
+ }
+ interfaces.add(dexClass);
+ }
+ };
+ // For union-find of interface methods we also need to add the library types above live types.
+ appInfo.forEachTypeInHierarchyOfLiveProgramClasses(consumer);
+ appInfo.forEachReferencedClasspathClass(consumer::accept);
assert nonPrivateMembers.isEmpty();
timing.end();
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 8d3e956..89e7c9d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -262,8 +262,10 @@
* <ul>
* <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
* <li>at dalvik.system.NativeStart.main(:99)
- * <li>dalvik.system.NativeStart.main(Foo.java:)
+ * <li>at dalvik.system.NativeStart.main(Foo.java:)
* <li>at dalvik.system.NativeStart.main(Native Method)
+ * <li>at classloader/named_module@version/foo.bar.baz(:20)
+ * <li>at classloader//foo.bar.baz(:20)
* </ul>
*
* <p>Empirical evidence suggests that the "at" string is never localized.
@@ -275,6 +277,8 @@
private final String startingWhitespace;
private final String at;
+ private final String classLoaderName;
+ private final String moduleName;
private final String clazz;
private final String method;
private final String methodAsString;
@@ -285,6 +289,8 @@
private AtLine(
String startingWhitespace,
String at,
+ String classLoaderName,
+ String moduleName,
String clazz,
String method,
String methodAsString,
@@ -293,6 +299,8 @@
boolean isAmbiguous) {
this.startingWhitespace = startingWhitespace;
this.at = at;
+ this.classLoaderName = classLoaderName;
+ this.moduleName = moduleName;
this.clazz = clazz;
this.method = method;
this.methodAsString = methodAsString;
@@ -314,11 +322,13 @@
|| line.charAt(firstNonWhiteSpace + 2) != ' ') {
return null;
}
- int classStartIndex = firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
- if (classStartIndex >= line.length() || classStartIndex != firstNonWhiteSpace + 3) {
+ int classClassLoaderOrModuleStartIndex =
+ firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
+ if (classClassLoaderOrModuleStartIndex >= line.length()
+ || classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
return null;
}
- int parensStart = firstCharFromIndex(line, classStartIndex, '(');
+ int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
if (parensStart >= line.length()) {
return null;
}
@@ -330,7 +340,7 @@
return null;
}
int methodSeparator = line.lastIndexOf('.', parensStart);
- if (methodSeparator <= classStartIndex) {
+ if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
return null;
}
// Check if we have a filename and position.
@@ -348,11 +358,32 @@
} else {
fileName = line.substring(parensStart + 1, parensEnd);
}
+ String classLoaderName = null;
+ String moduleName = null;
+ int classStartIndex = classClassLoaderOrModuleStartIndex;
+ int classLoaderOrModuleEndIndex =
+ firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
+ if (classLoaderOrModuleEndIndex < methodSeparator) {
+ int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
+ if (moduleEndIndex < methodSeparator) {
+ // The stack trace contains both a class loader and module
+ classLoaderName =
+ line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
+ moduleName = line.substring(classLoaderOrModuleEndIndex + 1, moduleEndIndex);
+ classStartIndex = moduleEndIndex + 1;
+ } else {
+ moduleName =
+ line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
+ classStartIndex = classLoaderOrModuleEndIndex + 1;
+ }
+ }
String className = line.substring(classStartIndex, methodSeparator);
String methodName = line.substring(methodSeparator + 1, parensStart);
return new AtLine(
line.substring(0, firstNonWhiteSpace),
- line.substring(firstNonWhiteSpace, classStartIndex),
+ line.substring(firstNonWhiteSpace, classClassLoaderOrModuleStartIndex),
+ classLoaderName,
+ moduleName,
className,
methodName,
className + "." + methodName,
@@ -368,6 +399,27 @@
@Override
List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
List<StackTraceLine> lines = new ArrayList<>();
+ String retraceClassLoaderName = classLoaderName;
+ if (retraceClassLoaderName != null) {
+ ClassReference classLoaderReference = Reference.classFromTypeName(retraceClassLoaderName);
+ retraceBase
+ .retrace(classLoaderReference)
+ .forEach(
+ classElement -> {
+ retraceClassAndMethods(
+ retraceBase, verbose, lines, classElement.getClassReference().getTypeName());
+ });
+ } else {
+ retraceClassAndMethods(retraceBase, verbose, lines, retraceClassLoaderName);
+ }
+ return lines;
+ }
+
+ private void retraceClassAndMethods(
+ RetraceBase retraceBase,
+ boolean verbose,
+ List<StackTraceLine> lines,
+ String classLoaderName) {
ClassReference classReference = Reference.classFromTypeName(clazz);
retraceBase
.retrace(classReference)
@@ -380,6 +432,8 @@
new AtLine(
startingWhitespace,
at,
+ classLoaderName,
+ moduleName,
methodReference.getHolderClass().getTypeName(),
methodReference.getMethodName(),
methodDescriptionFromMethodReference(methodReference, verbose),
@@ -390,13 +444,20 @@
: linePosition,
methodElement.getRetraceMethodResult().isAmbiguous()));
});
- return lines;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(startingWhitespace);
sb.append(at);
+ if (classLoaderName != null) {
+ sb.append(classLoaderName);
+ sb.append("/");
+ }
+ if (moduleName != null) {
+ sb.append(moduleName);
+ sb.append("/");
+ }
sb.append(methodAsString);
sb.append("(");
sb.append(fileName);
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 a2a3380..dcc6877 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -6,10 +6,11 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -24,11 +25,14 @@
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableList;
@@ -44,14 +48,15 @@
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
+import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
/** Encapsulates liveness and reachability information for an application. */
@@ -184,7 +189,7 @@
// TODO(zerny): Clean up the constructors so we have just one.
AppInfoWithLiveness(
- DexApplication application,
+ DirectMappedDexApplication application,
Set<DexType> liveTypes,
Set<DexType> instantiatedAnnotationTypes,
Set<DexType> instantiatedAppServices,
@@ -396,7 +401,7 @@
private AppInfoWithLiveness(
AppInfoWithLiveness previous,
- DexApplication application,
+ DirectMappedDexApplication application,
Collection<DexType> removedClasses,
Collection<DexReference> additionalPinnedItems) {
this(
@@ -448,90 +453,6 @@
assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
}
- private AppInfoWithLiveness(
- AppInfoWithLiveness previous, DirectMappedDexApplication application, GraphLense lense) {
- super(application);
- this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
- this.instantiatedAnnotationTypes =
- rewriteItems(previous.instantiatedAnnotationTypes, lense::lookupType);
- this.instantiatedAppServices =
- rewriteItems(previous.instantiatedAppServices, lense::lookupType);
- this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
- this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
- this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
- this.failedResolutionTargets =
- lense.rewriteMethodsConservatively(previous.failedResolutionTargets);
- this.bootstrapMethods = lense.rewriteMethodsConservatively(previous.bootstrapMethods);
- this.methodsTargetedByInvokeDynamic =
- lense.rewriteMethodsConservatively(previous.methodsTargetedByInvokeDynamic);
- this.virtualMethodsTargetedByInvokeDirect =
- lense.rewriteMethodsConservatively(previous.virtualMethodsTargetedByInvokeDirect);
- this.liveMethods = lense.rewriteMethodsConservatively(previous.liveMethods);
- this.fieldAccessInfoCollection =
- previous.fieldAccessInfoCollection.rewrittenWithLens(application, lense);
- this.pinnedItems = lense.rewriteReferencesConservatively(previous.pinnedItems);
- this.virtualInvokes =
- rewriteKeysConservativelyWhileMergingValues(
- previous.virtualInvokes, lense::lookupMethodInAllContexts);
- this.interfaceInvokes =
- rewriteKeysConservativelyWhileMergingValues(
- previous.interfaceInvokes, lense::lookupMethodInAllContexts);
- this.superInvokes =
- rewriteKeysConservativelyWhileMergingValues(
- previous.superInvokes, lense::lookupMethodInAllContexts);
- this.directInvokes =
- rewriteKeysConservativelyWhileMergingValues(
- previous.directInvokes, lense::lookupMethodInAllContexts);
- this.staticInvokes =
- rewriteKeysConservativelyWhileMergingValues(
- previous.staticInvokes, lense::lookupMethodInAllContexts);
- // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
- // after second tree shaking.
- this.callSites = previous.callSites;
- // Don't rewrite pruned types - the removed types are identified by their original name.
- this.prunedTypes = previous.prunedTypes;
- this.mayHaveSideEffects =
- rewriteReferenceKeys(previous.mayHaveSideEffects, lense::lookupReference);
- this.noSideEffects = rewriteReferenceKeys(previous.noSideEffects, lense::lookupReference);
- this.assumedValues = rewriteReferenceKeys(previous.assumedValues, lense::lookupReference);
- assert lense.assertDefinitionsNotModified(
- previous.alwaysInline.stream()
- .map(this::definitionFor)
- .filter(Objects::nonNull)
- .collect(Collectors.toList()));
- this.alwaysInline = lense.rewriteMethodsWithRenamedSignature(previous.alwaysInline);
- this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
- this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
- this.whyAreYouNotInlining =
- lense.rewriteMethodsWithRenamedSignature(previous.whyAreYouNotInlining);
- this.keepConstantArguments =
- lense.rewriteMethodsWithRenamedSignature(previous.keepConstantArguments);
- this.keepUnusedArguments =
- lense.rewriteMethodsWithRenamedSignature(previous.keepUnusedArguments);
- this.reprocess = lense.rewriteMethodsWithRenamedSignature(previous.reprocess);
- this.neverReprocess = lense.rewriteMethodsWithRenamedSignature(previous.neverReprocess);
- assert lense.assertDefinitionsNotModified(
- previous.neverMerge.stream()
- .map(this::definitionFor)
- .filter(Objects::nonNull)
- .collect(Collectors.toList()));
- this.alwaysClassInline = previous.alwaysClassInline.rewriteItems(lense::lookupType);
- this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
- this.neverMerge = rewriteItems(previous.neverMerge, lense::lookupType);
- this.neverPropagateValue = lense.rewriteReferencesConservatively(previous.neverPropagateValue);
- this.identifierNameStrings =
- lense.rewriteReferencesConservatively(previous.identifierNameStrings);
- // Switchmap classes should never be affected by renaming.
- assert lense.assertDefinitionsNotModified(
- previous.switchMaps.keySet().stream()
- .map(this::definitionFor)
- .filter(Objects::nonNull)
- .collect(Collectors.toList()));
- this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
- this.enumValueInfoMaps = rewriteReferenceKeys(previous.enumValueInfoMaps, lense::lookupType);
- this.constClassReferences = previous.constClassReferences;
- }
-
public AppInfoWithLiveness(
AppInfoWithLiveness previous,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
@@ -632,6 +553,99 @@
}
/**
+ * Resolve the methods implemented by the lambda expression that created the {@code callSite}.
+ *
+ * <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
+ * is not {@code LambdaMetafactory}), the empty set is returned.
+ *
+ * <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
+ * warning is issued.
+ *
+ * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
+ *
+ * @param callSite Call site to resolve.
+ * @return Methods implemented by the lambda expression that created the {@code callSite}.
+ */
+ public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
+ assert checkIfObsolete();
+ List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
+ if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
+ return Collections.emptySet();
+ }
+ Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+ Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
+ Set<DexType> visited = Sets.newIdentityHashSet();
+ while (!worklist.isEmpty()) {
+ DexType iface = worklist.removeFirst();
+ if (!visited.add(iface)) {
+ // Already visited previously. May happen due to "diamond shapes" in the interface
+ // hierarchy.
+ continue;
+ }
+ DexClass clazz = definitionFor(iface);
+ if (clazz == null) {
+ // Skip this interface. If the lambda only implements missing library interfaces and not any
+ // program interfaces, then minification and tree shaking are not interested in this
+ // DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
+ // minification is run on a program with a lambda interface that implements both a missing
+ // library interface and a present program interface, then we might minify the method name
+ // on the program interface even though it should be kept the same as the (missing) library
+ // interface method. That is a shame, but minification is not suited for incomplete programs
+ // anyway.
+ continue;
+ }
+ assert clazz.isInterface();
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+ result.add(method);
+ }
+ }
+ Collections.addAll(worklist, clazz.interfaces.values);
+ }
+ return result;
+ }
+
+ // TODO(b/139464956): Reimplement using only reachable types.
+ public DexProgramClass getSingleDirectSubtype(DexProgramClass clazz) {
+ DexType subtype = super.getSingleSubtype_(clazz.type);
+ return subtype == null ? null : asProgramClassOrNull(definitionFor(subtype));
+ }
+
+ /**
+ * Apply the given function to all classes that directly extend this class.
+ *
+ * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
+ * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
+ * is consistent with the source language.
+ */
+ // TODO(b/139464956): Reimplement using only reachable types.
+ public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
+ allImmediateExtendsSubtypes(type).forEach(f);
+ }
+
+ // TODO(b/139464956): Reimplement using only reachable types.
+ public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
+ return super.allImmediateExtendsSubtypes_(type);
+ }
+
+ /**
+ * Apply the given function to all classes that directly implement this interface.
+ *
+ * <p>The implementation does not consider how the hierarchy is encoded in the dex file, where
+ * interfaces "implement" their super interfaces. Instead it takes the view of the source
+ * language, where interfaces "extend" their superinterface.
+ */
+ // TODO(b/139464956): Reimplement using only reachable types.
+ public void forAllImmediateImplementsSubtypes(DexType type, Consumer<DexType> f) {
+ allImmediateImplementsSubtypes(type).forEach(f);
+ }
+
+ // TODO(b/139464956): Reimplement using only reachable types.
+ public Iterable<DexType> allImmediateImplementsSubtypes(DexType type) {
+ return super.allImmediateImplementsSubtypes_(type);
+ }
+
+ /**
* Const-classes is a conservative set of types that may be lock-candidates and cannot be merged.
* When using synchronized blocks, we cannot ensure that const-class locks will not flow in. This
* can potentially cause incorrect behavior when merging classes. A conservative choice is to not
@@ -641,28 +655,6 @@
return constClassReferences.contains(type);
}
- public AppInfoWithLiveness withStaticFieldWrites(
- Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts) {
- assert checkIfObsolete();
- if (writesWithContexts.isEmpty()) {
- return this;
- }
- AppInfoWithLiveness result = new AppInfoWithLiveness(this);
- writesWithContexts.forEach(
- (encodedField, contexts) -> {
- DexField field = encodedField.field;
- FieldAccessInfoImpl fieldAccessInfo = result.fieldAccessInfoCollection.get(field);
- if (fieldAccessInfo == null) {
- fieldAccessInfo = new FieldAccessInfoImpl(field);
- result.fieldAccessInfoCollection.extend(field, fieldAccessInfo);
- }
- for (DexEncodedMethod context : contexts) {
- fieldAccessInfo.recordWrite(field, context);
- }
- });
- return result;
- }
-
public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
assert checkIfObsolete();
if (noLongerWrittenFields.isEmpty()) {
@@ -682,13 +674,6 @@
return result;
}
- private <T extends PresortedComparable<T>> SortedSet<T> filter(
- Set<T> items, Predicate<T> predicate) {
- return ImmutableSortedSet.copyOf(
- PresortedComparable::slowCompareTo,
- items.stream().filter(predicate).collect(Collectors.toList()));
- }
-
public Map<DexField, EnumValueInfo> getEnumValueInfoMapFor(DexType enumClass) {
assert checkIfObsolete();
return enumValueInfoMaps.get(enumClass);
@@ -941,17 +926,90 @@
* DexApplication object.
*/
public AppInfoWithLiveness prunedCopyFrom(
- DexApplication application,
+ DirectMappedDexApplication application,
Collection<DexType> removedClasses,
Collection<DexReference> additionalPinnedItems) {
assert checkIfObsolete();
return new AppInfoWithLiveness(this, application, removedClasses, additionalPinnedItems);
}
- public AppInfoWithLiveness rewrittenWithLense(
- DirectMappedDexApplication application, GraphLense lense) {
+ public AppInfoWithLiveness rewrittenWithLens(
+ DirectMappedDexApplication application, NestedGraphLense lens) {
assert checkIfObsolete();
- return new AppInfoWithLiveness(this, application, lense);
+ // The application has already been rewritten with all of lens' parent lenses. Therefore, we
+ // temporarily replace lens' parent lens with an identity lens to avoid the overhead of
+ // traversing the entire lens chain upon each lookup during the rewriting.
+ return lens.withAlternativeParentLens(
+ GraphLense.getIdentityLense(), () -> createRewrittenAppInfoWithLiveness(application, lens));
+ }
+
+ private AppInfoWithLiveness createRewrittenAppInfoWithLiveness(
+ DirectMappedDexApplication application, NestedGraphLense lens) {
+ // Switchmap classes should never be affected by renaming.
+ assert lens.assertDefinitionsNotModified(
+ switchMaps.keySet().stream()
+ .map(this::definitionFor)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+
+ assert lens.assertDefinitionsNotModified(
+ neverMerge.stream()
+ .map(this::definitionFor)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+
+ assert lens.assertDefinitionsNotModified(
+ alwaysInline.stream()
+ .map(this::definitionFor)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+
+ return new AppInfoWithLiveness(
+ application,
+ rewriteItems(liveTypes, lens::lookupType),
+ rewriteItems(instantiatedAnnotationTypes, lens::lookupType),
+ rewriteItems(instantiatedAppServices, lens::lookupType),
+ rewriteItems(instantiatedTypes, lens::lookupType),
+ lens.rewriteMethodsConservatively(targetedMethods),
+ lens.rewriteMethodsConservatively(failedResolutionTargets),
+ lens.rewriteMethodsConservatively(bootstrapMethods),
+ lens.rewriteMethodsConservatively(methodsTargetedByInvokeDynamic),
+ lens.rewriteMethodsConservatively(virtualMethodsTargetedByInvokeDirect),
+ lens.rewriteMethodsConservatively(liveMethods),
+ fieldAccessInfoCollection.rewrittenWithLens(application, lens),
+ rewriteKeysConservativelyWhileMergingValues(
+ virtualInvokes, lens::lookupMethodInAllContexts),
+ rewriteKeysConservativelyWhileMergingValues(
+ interfaceInvokes, lens::lookupMethodInAllContexts),
+ rewriteKeysConservativelyWhileMergingValues(superInvokes, lens::lookupMethodInAllContexts),
+ rewriteKeysConservativelyWhileMergingValues(directInvokes, lens::lookupMethodInAllContexts),
+ rewriteKeysConservativelyWhileMergingValues(staticInvokes, lens::lookupMethodInAllContexts),
+ // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
+ // after second tree shaking.
+ callSites,
+ lens.rewriteReferencesConservatively(pinnedItems),
+ rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
+ rewriteReferenceKeys(noSideEffects, lens::lookupReference),
+ rewriteReferenceKeys(assumedValues, lens::lookupReference),
+ lens.rewriteMethodsWithRenamedSignature(alwaysInline),
+ lens.rewriteMethodsWithRenamedSignature(forceInline),
+ lens.rewriteMethodsWithRenamedSignature(neverInline),
+ lens.rewriteMethodsWithRenamedSignature(whyAreYouNotInlining),
+ lens.rewriteMethodsWithRenamedSignature(keepConstantArguments),
+ lens.rewriteMethodsWithRenamedSignature(keepUnusedArguments),
+ lens.rewriteMethodsWithRenamedSignature(reprocess),
+ lens.rewriteMethodsWithRenamedSignature(neverReprocess),
+ alwaysClassInline.rewriteItems(lens::lookupType),
+ rewriteItems(neverClassInline, lens::lookupType),
+ rewriteItems(neverMerge, lens::lookupType),
+ lens.rewriteReferencesConservatively(neverPropagateValue),
+ lens.rewriteReferencesConservatively(identifierNameStrings),
+ // Don't rewrite pruned types - the removed types are identified by their original name.
+ prunedTypes,
+ rewriteReferenceKeys(switchMaps, lens::lookupField),
+ rewriteReferenceKeys(enumValueInfoMaps, lens::lookupType),
+ rewriteItems(instantiatedLambdas, lens::lookupType),
+ constClassReferences);
}
/**
@@ -1343,4 +1401,78 @@
assert this.enumValueInfoMaps.isEmpty();
return new AppInfoWithLiveness(this, switchMaps, enumValueInfoMaps);
}
+
+ public void forEachLiveProgramClass(Consumer<DexProgramClass> fn) {
+ for (DexType type : liveTypes) {
+ fn.accept(definitionFor(type).asProgramClass());
+ }
+ }
+
+ /**
+ * Visit all class definitions of classpath classes that are referenced in the compilation unit.
+ *
+ * <p>TODO(b/139464956): Only traverse the classpath types referenced from the live program.
+ * Conservatively traces all classpath classes for now.
+ */
+ public void forEachReferencedClasspathClass(Consumer<DexClasspathClass> fn) {
+ app().asDirect().classpathClasses().forEach(fn);
+ }
+
+ /**
+ * Visits all class definitions that are a live program type or a type above it in the hierarchy.
+ *
+ * <p>Any given definition will be visited at most once. No guarantees are places on the order.
+ */
+ public void forEachTypeInHierarchyOfLiveProgramClasses(Consumer<DexClass> fn) {
+ forEachTypeInHierarchyOfLiveProgramClasses(
+ fn, ListUtils.map(liveTypes, t -> definitionFor(t).asProgramClass()), callSites, this);
+ }
+
+ // Split in a static method so it can be used during construction.
+ static void forEachTypeInHierarchyOfLiveProgramClasses(
+ Consumer<DexClass> fn,
+ Collection<DexProgramClass> liveProgramClasses,
+ Set<DexCallSite> callSites,
+ AppInfoWithClassHierarchy appInfo) {
+ Set<DexType> seen = Sets.newIdentityHashSet();
+ Deque<DexType> worklist = new ArrayDeque<>();
+ liveProgramClasses.forEach(c -> seen.add(c.type));
+ for (DexProgramClass liveProgramClass : liveProgramClasses) {
+ fn.accept(liveProgramClass);
+ DexType superType = liveProgramClass.superType;
+ if (superType != null && seen.add(superType)) {
+ worklist.add(superType);
+ }
+ for (DexType iface : liveProgramClass.interfaces.values) {
+ if (seen.add(iface)) {
+ worklist.add(iface);
+ }
+ }
+ }
+ for (DexCallSite callSite : callSites) {
+ List<DexType> interfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+ if (interfaces != null) {
+ for (DexType iface : interfaces) {
+ if (seen.add(iface)) {
+ worklist.add(iface);
+ }
+ }
+ }
+ }
+ while (!worklist.isEmpty()) {
+ DexType type = worklist.pop();
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null) {
+ fn.accept(clazz);
+ if (clazz.superType != null && seen.add(clazz.superType)) {
+ worklist.add(clazz.superType);
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ if (seen.add(iface)) {
+ worklist.add(iface);
+ }
+ }
+ }
+ }
+ }
}
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 f8cdb36..ba1bdb5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -28,10 +28,9 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -46,6 +45,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.KeyedDexItem;
@@ -55,7 +55,9 @@
import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -155,6 +157,7 @@
private final Mode mode;
private Set<EnqueuerAnalysis> analyses = Sets.newIdentityHashSet();
+ private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
private final AppInfoWithSubtyping appInfo;
private final AppView<? extends AppInfoWithSubtyping> appView;
private final InternalOptions options;
@@ -235,10 +238,8 @@
* Set of direct methods that are the immediate target of an invoke-dynamic.
*/
private final Set<DexMethod> methodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
- /**
- * Set of direct lambda methods that are the immediate target of an invoke-dynamic.
- */
- private final Set<DexMethod> lambdaMethodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
+ /** Set of direct lambda implementation methods that have been desugared, thus they may move. */
+ private final Set<DexMethod> desugaredLambdaImplementationMethods = Sets.newIdentityHashSet();
/**
* Set of virtual methods that are the immediate target of an invoke-direct.
*/
@@ -317,6 +318,7 @@
private final GraphReporter graphReporter;
private final LambdaRewriter lambdaRewriter;
+ private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
private final Map<DexType, LambdaClass> lambdaClasses = new IdentityHashMap<>();
private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
new IdentityHashMap<>();
@@ -356,6 +358,14 @@
unknownInstantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
instantiatedInterfaceTypes = Sets.newIdentityHashSet();
lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+
+ if (appView.rewritePrefix.isRewriting() && mode.isInitialTreeShaking()) {
+ desugaredLibraryWrapperAnalysis = new DesugaredLibraryConversionWrapperAnalysis(appView);
+ registerAnalysis(desugaredLibraryWrapperAnalysis);
+ registerInvokeAnalysis(desugaredLibraryWrapperAnalysis);
+ } else {
+ desugaredLibraryWrapperAnalysis = null;
+ }
}
public Mode getMode() {
@@ -379,7 +389,12 @@
}
public Enqueuer registerAnalysis(EnqueuerAnalysis analysis) {
- this.analyses.add(analysis);
+ analyses.add(analysis);
+ return this;
+ }
+
+ private Enqueuer registerInvokeAnalysis(EnqueuerInvokeAnalysis analysis) {
+ invokeAnalyses.add(analysis);
return this;
}
@@ -639,6 +654,9 @@
classesWithSerializableLambdas.add(context.holder);
}
}
+ if (descriptor.delegatesToLambdaImplMethod()) {
+ desugaredLambdaImplementationMethods.add(descriptor.implHandle.asMethod());
+ }
} else {
callSites.add(callSite);
}
@@ -650,10 +668,6 @@
assert implHandle != null;
DexMethod method = implHandle.asMethod();
- if (descriptor.delegatesToLambdaImplMethod()) {
- lambdaMethodsTargetedByInvokeDynamic.add(method);
- }
-
if (!methodsTargetedByInvokeDynamic.add(method)) {
return;
}
@@ -808,6 +822,7 @@
Log.verbose(getClass(), "Register invokeDirect `%s`.", invokedMethod);
}
handleInvokeOfDirectTarget(invokedMethod, reason);
+ invokeAnalyses.forEach(analysis -> analysis.traceInvokeDirect(invokedMethod, context));
return true;
}
@@ -831,6 +846,7 @@
Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
}
markVirtualMethodAsReachable(method, true, context, keepReason);
+ invokeAnalyses.forEach(analysis -> analysis.traceInvokeInterface(method, context));
return true;
}
@@ -873,6 +889,7 @@
Log.verbose(getClass(), "Register invokeStatic `%s`.", invokedMethod);
}
handleInvokeOfStaticTarget(invokedMethod, reason);
+ invokeAnalyses.forEach(analysis -> analysis.traceInvokeStatic(invokedMethod, context));
return true;
}
@@ -889,6 +906,7 @@
Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
}
workList.enqueueMarkReachableSuperAction(invokedMethod, currentMethod);
+ invokeAnalyses.forEach(analysis -> analysis.traceInvokeSuper(invokedMethod, context));
return true;
}
@@ -920,6 +938,7 @@
Log.verbose(getClass(), "Register invokeVirtual `%s`.", invokedMethod);
}
markVirtualMethodAsReachable(invokedMethod, false, context, reason);
+ invokeAnalyses.forEach(analysis -> analysis.traceInvokeVirtual(invokedMethod, context));
return true;
}
@@ -1661,6 +1680,7 @@
ResolutionResult firstResolution =
appView.appInfo().resolveMethod(instantiatedClass, method.method);
markResolutionAsLive(libraryClass, firstResolution);
+ markOverridesAsLibraryMethodOverrides(method.method, instantiatedClass);
// Due to API conversion, some overrides can be hidden since they will be rewritten. See
// class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -1675,9 +1695,9 @@
ResolutionResult secondResolution =
appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
markResolutionAsLive(libraryClass, secondResolution);
+ markOverridesAsLibraryMethodOverrides(methodToResolve, instantiatedClass);
}
- markOverridesAsLibraryMethodOverrides(method, instantiatedClass);
}
}
@@ -1694,13 +1714,13 @@
}
private void markOverridesAsLibraryMethodOverrides(
- DexEncodedMethod libraryMethod, DexProgramClass instantiatedClass) {
+ DexMethod libraryMethod, DexProgramClass instantiatedClass) {
Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(instantiatedClass);
Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(instantiatedClass);
while (!worklist.isEmpty()) {
DexProgramClass clazz = worklist.removeFirst();
assert visited.contains(clazz);
- DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod.method);
+ DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod);
if (libraryMethodOverride != null) {
if (libraryMethodOverride.isLibraryMethodOverride().isTrue()) {
continue;
@@ -2048,7 +2068,7 @@
Set<DexEncodedMethod> possibleTargets =
// TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
new SingleResolutionResult(holder, resolution.holder, resolution.method)
- .lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
+ .lookupVirtualDispatchTargets(appInfo);
if (possibleTargets == null || possibleTargets.isEmpty()) {
return;
}
@@ -2310,7 +2330,12 @@
liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
}
- DexApplication app = postProcessLambdaDesugaring(appInfo);
+ // A direct appInfo is required to add classpath classes in wrapper post processing.
+ assert appInfo.app().isDirect() : "Expected a direct appInfo after enqueuing.";
+ DirectMappedDexApplication.Builder appBuilder = appInfo.app().asDirect().builder();
+ postProcessLambdaDesugaring(appBuilder);
+ postProcessLibraryConversionWrappers(appBuilder);
+ DirectMappedDexApplication app = appBuilder.build();
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
@@ -2363,11 +2388,51 @@
return appInfoWithLiveness;
}
- private DexApplication postProcessLambdaDesugaring(AppInfoWithSubtyping appInfo) {
- if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
- return appInfo.app();
+ private void postProcessLibraryConversionWrappers(DirectMappedDexApplication.Builder appBuilder) {
+ if (desugaredLibraryWrapperAnalysis == null) {
+ return;
}
- Builder<?> appBuilder = appInfo.app().builder();
+
+ // Generate first the callbacks since they may require extra wrappers.
+ List<DexEncodedMethod> callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
+ for (DexEncodedMethod callback : callbacks) {
+ DexProgramClass clazz = getProgramClassOrNull(callback.method.holder);
+ targetedMethods.add(callback, graphReporter.fakeReportShouldNotBeUsed());
+ liveMethods.add(clazz, callback, graphReporter.fakeReportShouldNotBeUsed());
+ }
+
+ // Generate the wrappers.
+ Set<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
+ for (DexProgramClass wrapper : wrappers) {
+ appBuilder.addProgramClass(wrapper);
+ liveTypes.add(wrapper, graphReporter.fakeReportShouldNotBeUsed());
+ instantiatedTypes.add(wrapper, graphReporter.fakeReportShouldNotBeUsed());
+ // Mark all methods on the wrapper as live and targeted.
+ for (DexEncodedMethod method : wrapper.methods()) {
+ targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
+ liveMethods.add(wrapper, method, graphReporter.fakeReportShouldNotBeUsed());
+ }
+ // Register wrapper unique field reads and unique write.
+ assert wrapper.instanceFields().size() == 1;
+ DexField field = wrapper.instanceFields().get(0).field;
+ FieldAccessInfoImpl info = new FieldAccessInfoImpl(field);
+ fieldAccessInfoCollection.extend(field, info);
+ desugaredLibraryWrapperAnalysis
+ .registerWrite(wrapper, writeContext -> info.recordWrite(field, writeContext))
+ .registerReads(wrapper, readContext -> info.recordRead(field, readContext));
+ }
+
+ // Add all vivified types as classpath classes.
+ // They will be available at runtime in the desugared library dex file.
+ List<DexClasspathClass> mockVivifiedClasses =
+ desugaredLibraryWrapperAnalysis.generateWrappersSuperTypeMock();
+ appBuilder.addClasspathClasses(mockVivifiedClasses);
+ }
+
+ private void postProcessLambdaDesugaring(DirectMappedDexApplication.Builder appBuilder) {
+ if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
+ return;
+ }
for (LambdaClass lambdaClass : lambdaClasses.values()) {
// Add all desugared classes to the application, main-dex list, and mark them instantiated.
DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
@@ -2411,8 +2476,6 @@
for (DexProgramClass clazz : classesWithSerializableLambdas) {
clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
}
-
- return appBuilder.build();
}
private void rewriteLambdaCallSites(
@@ -2644,11 +2707,13 @@
}
private void unpinLambdaMethods() {
- for (DexMethod method : lambdaMethodsTargetedByInvokeDynamic) {
+ assert desugaredLambdaImplementationMethods.isEmpty()
+ || options.desugarState == DesugarState.ON;
+ for (DexMethod method : desugaredLambdaImplementationMethods) {
pinnedItems.remove(method);
rootSet.prune(method);
}
- lambdaMethodsTargetedByInvokeDynamic.clear();
+ desugaredLambdaImplementationMethods.clear();
}
// Package protected due to entry point from worklist.
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index bfefefa..718b9a9 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -6,12 +6,12 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Maps;
import java.util.Map;
@@ -29,14 +29,14 @@
private final Set<DexType> roots;
private final AppInfoWithSubtyping appInfo;
private final Map<DexType, Boolean> annotationTypeContainEnum;
- private final DexApplication dexApplication;
+ private final DirectMappedDexApplication dexApplication;
private final MainDexClasses.Builder mainDexClassesBuilder;
/**
* @param roots Classes which code may be executed before secondary dex files loading.
* @param application the dex appplication.
*/
- public MainDexListBuilder(Set<DexProgramClass> roots, DexApplication application) {
+ public MainDexListBuilder(Set<DexProgramClass> roots, DirectMappedDexApplication application) {
this.dexApplication = application;
this.appInfo = new AppInfoWithSubtyping(dexApplication);
// Only consider program classes for the root set.
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index bae5b40..b89678e 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
@@ -225,7 +224,7 @@
this.mainDexClasses = mainDexClasses;
}
- public GraphLense run() {
+ public NestedGraphLense run() {
for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
MergeGroup group = satisfiesMergeCriteria(clazz);
if (group != MergeGroup.DONT_MERGE) {
@@ -242,7 +241,7 @@
return buildGraphLense();
}
- private GraphLense buildGraphLense() {
+ private NestedGraphLense buildGraphLense() {
if (!fieldMapping.isEmpty() || !methodMapping.isEmpty()) {
BiMap<DexField, DexField> originalFieldSignatures = fieldMapping.inverse();
BiMap<DexMethod, DexMethod> originalMethodSignatures = methodMapping.inverse();
@@ -255,7 +254,7 @@
appView.graphLense(),
appView.dexItemFactory());
}
- return appView.graphLense();
+ return null;
}
private MergeGroup satisfiesMergeCriteria(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 116e315..dc66249 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,6 +12,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.KeyedDexItem;
@@ -56,10 +56,10 @@
: UsagePrinter.DONT_PRINT;
}
- public DexApplication run(DexApplication application) {
+ public DirectMappedDexApplication run(DirectMappedDexApplication application) {
Timing timing = application.timing;
timing.begin("Pruning application...");
- DexApplication result;
+ DirectMappedDexApplication result;
try {
result = removeUnused(application).build();
} finally {
@@ -68,7 +68,7 @@
return result;
}
- private DexApplication.Builder<?> removeUnused(DexApplication application) {
+ private DirectMappedDexApplication.Builder removeUnused(DirectMappedDexApplication application) {
return application.builder()
.replaceProgramClasses(getNewProgramClasses(application.classes()));
}
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 36a7030..8217920 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -345,22 +345,21 @@
// Note that the property "singleSubtype == null" cannot change during merging, since we visit
// classes in a top-down order.
- DexType singleSubtype = appInfo.getSingleSubtype(clazz.type);
+ DexProgramClass singleSubtype = appInfo.getSingleDirectSubtype(clazz);
if (singleSubtype == null) {
// TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
// its subclass if [clazz] is not live. This should only be done, though, if it does not
// lead to members being duplicated.
return false;
}
- if (singleSubtype != null
- && appView.appServices().allServiceTypes().contains(clazz.type)
- && appInfo.isPinned(singleSubtype)) {
+ if (appView.appServices().allServiceTypes().contains(clazz.type)
+ && appInfo.isPinned(singleSubtype.type)) {
if (Log.ENABLED) {
AbortReason.SERVICE_LOADER.printLogMessageForClass(clazz);
}
return false;
}
- if (appInfo.isSerializable(singleSubtype) && !appInfo.isSerializable(clazz.type)) {
+ if (singleSubtype.isSerializable(appView) && !appInfo.isSerializable(clazz.type)) {
// https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
// 1.10 The Serializable Interface
// ...
@@ -373,7 +372,7 @@
// We rename constructors to private methods and mark them to be forced-inlined, so we have to
// check if we can force-inline all constructors.
if (method.isInstanceInitializer()) {
- AbortReason reason = disallowInlining(method, singleSubtype);
+ AbortReason reason = disallowInlining(method, singleSubtype.type);
if (reason != null) {
// Cannot guarantee that markForceInline() will work.
if (Log.ENABLED) {
@@ -390,10 +389,9 @@
}
return false;
}
- DexClass targetClass = appView.definitionFor(singleSubtype);
// We abort class merging when merging across nests or from a nest to non-nest.
// Without nest this checks null == null.
- if (targetClass.getNestHost() != clazz.getNestHost()) {
+ if (singleSubtype.getNestHost() != clazz.getNestHost()) {
if (Log.ENABLED) {
AbortReason.MERGE_ACROSS_NESTS.printLogMessageForClass(clazz);
}
@@ -415,7 +413,7 @@
}
return false;
}
- DexClass targetClass = appInfo.definitionFor(appInfo.getSingleSubtype(clazz.type));
+ DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
// For interface types, this is more complicated, see:
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
// We basically can't move the clinit, since it is not called when implementing classes have
@@ -533,7 +531,8 @@
public OverloadedMethodSignaturesRetriever() {
for (DexProgramClass mergeCandidate : mergeCandidates) {
- mergeeCandidates.add(appInfo.getSingleSubtype(mergeCandidate.type));
+ DexProgramClass candidate = appInfo.getSingleDirectSubtype(mergeCandidate);
+ mergeeCandidates.add(candidate.type);
}
}
@@ -614,7 +613,7 @@
}
}
- public GraphLense run() {
+ public VerticalClassMergerGraphLense run() {
timing.begin("merge");
// Visit the program classes in a top-down order according to the class hierarchy.
TopDownClassHierarchyTraversal.forProgramClasses(appView)
@@ -624,18 +623,19 @@
}
timing.end();
timing.begin("fixup");
- GraphLense result = new TreeFixer().fixupTypeReferences();
+ VerticalClassMergerGraphLense lens = new TreeFixer().fixupTypeReferences();
timing.end();
- assert result.assertDefinitionsNotModified(
+ assert lens == null || verifyGraphLens(lens);
+ return lens;
+ }
+
+ private boolean verifyGraphLens(VerticalClassMergerGraphLense graphLense) {
+ assert graphLense.assertDefinitionsNotModified(
appInfo.alwaysInline.stream()
.map(appInfo::definitionFor)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- assert verifyGraphLense(result);
- return result;
- }
- private boolean verifyGraphLense(GraphLense graphLense) {
assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet());
// Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
@@ -737,7 +737,7 @@
Set<DexEncodedMethod> interfaceTargets =
appInfo
.resolveMethodOnInterface(method.method.holder, method.method)
- .lookupInterfaceTargets(appInfo);
+ .lookupVirtualDispatchTargets(appInfo);
// If [method] is not even an interface-target, then we can safely merge it. Otherwise we
// need to check for a conflict.
@@ -762,8 +762,7 @@
assert isMergeCandidate(clazz, pinnedTypes);
- DexProgramClass targetClass =
- appInfo.definitionFor(appInfo.getSingleSubtype(clazz.type)).asProgramClass();
+ DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
assert !mergedClasses.containsKey(targetClass.type);
boolean clazzOrTargetClassHasBeenMerged =
@@ -1430,7 +1429,7 @@
renamedMembersLense, mergedClasses);
private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
- private GraphLense fixupTypeReferences() {
+ private VerticalClassMergerGraphLense fixupTypeReferences() {
// Globally substitute merged class types in protos and holders.
for (DexProgramClass clazz : appInfo.classes()) {
fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
@@ -1441,9 +1440,11 @@
for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
synthesizedBridge.updateMethodSignatures(this::fixupMethod);
}
- GraphLense graphLense = lensBuilder.build(appView, mergedClasses);
- new AnnotationFixer(graphLense).run(appView.appInfo().classes());
- return graphLense;
+ VerticalClassMergerGraphLense lens = lensBuilder.build(appView, mergedClasses);
+ if (lens != null) {
+ new AnnotationFixer(lens).run(appView.appInfo().classes());
+ }
+ return lens;
}
private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index a265d99..eded127 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -16,6 +16,7 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -53,6 +54,8 @@
private Set<DexMethod> mergedMethods;
private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
+ private Map<DexMethod, Set<DexType>> contextsForContextSensitiveMethods;
+
private VerticalClassMergerGraphLense(
AppView<?> appView,
Map<DexType, DexType> typeMap,
@@ -78,6 +81,24 @@
this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
}
+ public void initializeCacheForLookupMethodInAllContexts() {
+ assert contextsForContextSensitiveMethods == null;
+ contextsForContextSensitiveMethods = new IdentityHashMap<>();
+ contextualVirtualToDirectMethodMaps.forEach(
+ (type, virtualToDirectMethodMap) -> {
+ for (DexMethod method : virtualToDirectMethodMap.keySet()) {
+ contextsForContextSensitiveMethods
+ .computeIfAbsent(method, ignore -> Sets.newIdentityHashSet())
+ .add(type);
+ }
+ });
+ }
+
+ public void unsetCacheForLookupMethodInAllContexts() {
+ assert contextsForContextSensitiveMethods != null;
+ contextsForContextSensitiveMethods = null;
+ }
+
@Override
public DexType getOriginalType(DexType type) {
return previousLense.getOriginalType(type);
@@ -91,7 +112,8 @@
@Override
public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
- assert isContextFreeForMethod(method) || (context != null && type != null);
+ assert context != null || verifyIsContextFreeForMethod(method);
+ assert context == null || type != null;
DexMethod previousContext =
originalMethodSignaturesForBridges.containsKey(context)
? originalMethodSignaturesForBridges.get(context)
@@ -123,14 +145,14 @@
@Override
public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+ assert contextsForContextSensitiveMethods != null;
ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
builder.add(methodMap.getOrDefault(previous, previous));
- for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
- contextualVirtualToDirectMethodMaps.values()) {
- GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous);
- if (lookup != null) {
- builder.add(lookup.getMethod());
+ Set<DexType> contexts = contextsForContextSensitiveMethods.get(previous);
+ if (contexts != null) {
+ for (DexType context : contexts) {
+ builder.add(contextualVirtualToDirectMethodMaps.get(context).get(previous).getMethod());
}
}
}
@@ -143,17 +165,11 @@
}
@Override
- public boolean isContextFreeForMethod(DexMethod method) {
- if (!previousLense.isContextFreeForMethod(method)) {
- return false;
- }
+ public boolean verifyIsContextFreeForMethod(DexMethod method) {
+ assert previousLense.verifyIsContextFreeForMethod(method);
DexMethod previous = previousLense.lookupMethod(method);
- for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
- contextualVirtualToDirectMethodMaps.values()) {
- if (virtualToDirectMethodMap.containsKey(previous)) {
- return false;
- }
- }
+ assert contextualVirtualToDirectMethodMaps.values().stream()
+ .noneMatch(virtualToDirectMethodMap -> virtualToDirectMethodMap.containsKey(previous));
return true;
}
@@ -221,9 +237,10 @@
return newBuilder;
}
- public GraphLense build(AppView<?> appView, Map<DexType, DexType> mergedClasses) {
+ public VerticalClassMergerGraphLense build(
+ AppView<?> appView, Map<DexType, DexType> mergedClasses) {
if (mergedClasses.isEmpty()) {
- return appView.graphLense();
+ return null;
}
BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
// Build new graph lense.
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 70d6df0..51ab446 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InvalidDescriptorException;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -370,6 +371,12 @@
return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
}
+ // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
+ // See b/70169921#comment25 for more details.
+ private static String backwardRelocatedName(String name) {
+ return name.replace("com/android/tools/r8/jetbrains/", "");
+ }
+
/**
* Get a fully qualified name from a classifier in Kotlin metadata.
* @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
@@ -385,16 +392,16 @@
public void visitClass(String name) {
// TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
// See b/70169921#comment25 for more details.
- String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
- descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+ assert descriptor.get() == null : descriptor.get();
+ descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
}
@Override
public void visitTypeAlias(String name) {
// TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
// See b/70169921#comment25 for more details.
- String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
- descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+ assert descriptor.get() == null : descriptor.get();
+ descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
}
});
return descriptor.get();
@@ -619,7 +626,7 @@
while ((c = methodDescriptor.charAt(charIdx)) != ')') {
switch (c) {
case 'V':
- throw new Unreachable();
+ throw new InvalidDescriptorException(methodDescriptor);
case 'Z':
case 'C':
case 'B':
@@ -632,7 +639,8 @@
break;
case '[':
startType = charIdx;
- while (methodDescriptor.charAt(++charIdx) == '[') {}
+ while (methodDescriptor.charAt(++charIdx) == '[')
+ ;
if (methodDescriptor.charAt(charIdx) == 'L') {
while (methodDescriptor.charAt(++charIdx) != ';')
;
@@ -646,7 +654,7 @@
argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
break;
default:
- throw new Unreachable();
+ throw new InvalidDescriptorException(methodDescriptor);
}
charIdx++;
}
@@ -654,18 +662,27 @@
}
public static int getArgumentCount(final String methodDescriptor) {
+ int length = methodDescriptor.length();
int charIdx = 1;
char c;
int argCount = 0;
- while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
+ while (charIdx < length && (c = methodDescriptor.charAt(charIdx++)) != ')') {
if (c == 'L') {
- while (methodDescriptor.charAt(charIdx++) != ';')
+ while (charIdx < length && methodDescriptor.charAt(charIdx++) != ';')
;
+ // Check if the inner loop found ';' within the boundary.
+ if (charIdx >= length || methodDescriptor.charAt(charIdx - 1) != ';') {
+ throw new InvalidDescriptorException(methodDescriptor);
+ }
argCount++;
} else if (c != '[') {
argCount++;
}
}
+ // Check if the outer loop found ')' within the boundary.
+ if (charIdx >= length || methodDescriptor.charAt(charIdx - 1) != ')') {
+ throw new InvalidDescriptorException(methodDescriptor);
+ }
return argCount;
}
}
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 5b0400f..c6330b1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -224,7 +224,11 @@
public boolean enablePropagationOfDynamicTypesAtCallSites = true;
// TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
public boolean enablePropagationOfConstantsAtCallSites = false;
- public boolean enableKotlinMetadataRewriting = true;
+ // At 2.0, part of @Metadata up to this flag is rewritten, which is super-type hierarchy.
+ public boolean enableKotlinMetadataRewritingForMembers = true;
+ // Up to 2.0, Kotlin @Metadata is removed if the associated class is renamed.
+ // Under this flag, Kotlin @Metadata is generally kept and modified for all program classes.
+ public boolean enableKotlinMetadataRewritingForRenamedClasses = true;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
public boolean enableCfInterfaceMethodDesugaring = false;
@@ -258,6 +262,8 @@
// TODO(b/138917494): Disable until we have numbers on potential performance penalties.
public boolean enableRedundantConstNumberOptimization = false;
+ public boolean enablePcDebugInfoOutput = false;
+
// Number of threads to use while processing the dex files.
public int numberOfThreads = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED;
// Print smali disassembly.
@@ -1018,7 +1024,6 @@
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
public boolean forceJumboStringProcessing = false;
- public boolean nondeterministicCycleElimination = false;
public Set<Inliner.Reason> validInliningReasons = null;
public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
@@ -1143,6 +1148,11 @@
enablePropagationOfDynamicTypesAtCallSites = false;
}
+ public boolean canUseDexPcAsDebugInformation() {
+ // TODO(b/37830524): Enable for min-api 26 (OREO) and above.
+ return enablePcDebugInfoOutput;
+ }
+
public boolean isInterfaceMethodDesugaringEnabled() {
// This condition is to filter out tests that never set program consumer.
if (!hasConsumer()) {
@@ -1162,6 +1172,10 @@
return intermediate || hasMinApi(AndroidApiLevel.L);
}
+ public boolean canUseRequireNonNull() {
+ return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
+ }
+
public boolean canUseSuppressedExceptions() {
return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index e9eb31f..7bcf1c9 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -328,8 +328,12 @@
Code code = method.getCode();
if (code != null) {
if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
- optimizeDexCodePositions(
- method, application, kotlinRemapper, mappedPositions, identityMapping);
+ if (appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1) {
+ optimizeDexCodePositionsForPc(method, kotlinRemapper, mappedPositions);
+ } else {
+ optimizeDexCodePositions(
+ method, appView, kotlinRemapper, mappedPositions, identityMapping);
+ }
} else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
optimizeCfCodePositions(method, kotlinRemapper, mappedPositions, appView);
}
@@ -548,11 +552,12 @@
private static void optimizeDexCodePositions(
DexEncodedMethod method,
- DexApplication application,
+ AppView<?> appView,
PositionRemapper positionRemapper,
List<MappedPosition> mappedPositions,
boolean identityMapping) {
// Do the actual processing for each method.
+ final DexApplication application = appView.appInfo().app();
DexCode dexCode = method.getCode().asDexCode();
DexDebugInfo debugInfo = dexCode.getDebugInfo();
List<DexDebugEvent> processedEvents = new ArrayList<>();
@@ -589,10 +594,10 @@
getCurrentMethod(),
getCurrentCallerPosition());
Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
+ positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
if (currentPosition != position) {
inlinedOriginalPosition.set(true);
}
- positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
emittedPc = getCurrentPc();
}
@@ -646,18 +651,74 @@
debugInfo.parameters,
processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
- // TODO(b/111253214) Remove this as soon as we have external tests testing not only the
- // remapping but whether the non-positional debug events remain intact.
- if (identityMapping && !inlinedOriginalPosition.get()) {
- assert optimizedDebugInfo.startLine == debugInfo.startLine;
- assert optimizedDebugInfo.events.length == debugInfo.events.length;
- for (int i = 0; i < debugInfo.events.length; ++i) {
- assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
- }
- }
+ assert !identityMapping
+ || inlinedOriginalPosition.get()
+ || checkIdentityMapping(debugInfo, optimizedDebugInfo);
+
dexCode.setDebugInfo(optimizedDebugInfo);
}
+ private static void optimizeDexCodePositionsForPc(
+ DexEncodedMethod method,
+ PositionRemapper positionRemapper,
+ List<MappedPosition> mappedPositions) {
+ // Do the actual processing for each method.
+ DexCode dexCode = method.getCode().asDexCode();
+ DexDebugInfo debugInfo = dexCode.getDebugInfo();
+
+ Pair<Integer, Position> lastPosition = new Pair<>();
+
+ DexDebugEventVisitor visitor =
+ new DexDebugPositionState(debugInfo.startLine, method.method) {
+ @Override
+ public void visit(Default defaultEvent) {
+ super.visit(defaultEvent);
+ assert getCurrentLine() >= 0;
+ if (lastPosition.getSecond() != null) {
+ remapAndAddForPc(
+ lastPosition.getFirst(),
+ getCurrentPc(),
+ lastPosition.getSecond(),
+ positionRemapper,
+ mappedPositions);
+ }
+ lastPosition.setFirst(getCurrentPc());
+ lastPosition.setSecond(
+ new Position(
+ getCurrentLine(),
+ getCurrentFile(),
+ getCurrentMethod(),
+ getCurrentCallerPosition()));
+ }
+ };
+
+ for (DexDebugEvent event : debugInfo.events) {
+ event.accept(visitor);
+ }
+
+ if (lastPosition.getSecond() != null) {
+ int lastPc = dexCode.instructions[dexCode.instructions.length - 1].getOffset();
+ remapAndAddForPc(
+ lastPosition.getFirst(),
+ lastPc + 1,
+ lastPosition.getSecond(),
+ positionRemapper,
+ mappedPositions);
+ }
+
+ dexCode.setDebugInfo(null);
+ }
+
+ private static boolean checkIdentityMapping(
+ DexDebugInfo originalDebugInfo, DexDebugInfo optimizedDebugInfo) {
+ assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
+ assert optimizedDebugInfo.events.length == originalDebugInfo.events.length;
+ for (int i = 0; i < originalDebugInfo.events.length; ++i) {
+ assert optimizedDebugInfo.events[i].equals(originalDebugInfo.events[i]);
+ }
+ return true;
+ }
+
private static void optimizeCfCodePositions(
DexEncodedMethod method,
PositionRemapper positionRemapper,
@@ -667,8 +728,7 @@
CfCode oldCode = method.getCode().asCfCode();
List<CfInstruction> oldInstructions = oldCode.getInstructions();
List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
- for (int i = 0; i < oldInstructions.size(); ++i) {
- CfInstruction oldInstruction = oldInstructions.get(i);
+ for (CfInstruction oldInstruction : oldInstructions) {
CfInstruction newInstruction;
if (oldInstruction instanceof CfPosition) {
CfPosition cfPosition = (CfPosition) oldInstruction;
@@ -702,4 +762,19 @@
oldPosition.method, oldPosition.line, oldPosition.callerPosition, newPosition.line));
return newPosition;
}
+
+ private static void remapAndAddForPc(
+ int startPc,
+ int endPc,
+ Position position,
+ PositionRemapper remapper,
+ List<MappedPosition> mappedPositions) {
+ Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
+ Position oldPosition = remappedPosition.getFirst();
+ for (int currentPc = startPc; currentPc < endPc; currentPc++) {
+ mappedPositions.add(
+ new MappedPosition(
+ oldPosition.method, oldPosition.line, oldPosition.callerPosition, currentPc));
+ }
+ }
}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 5297ee9..64ce874 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -72,5 +72,3 @@
-neverinline class * {
@classmerging.NeverInline <methods>;
}
-
--printmapping
diff --git a/src/test/examplesJava11/backport/CharSequenceBackportJava11Main.java b/src/test/examplesJava11/backport/CharSequenceBackportJava11Main.java
new file mode 100644
index 0000000..d0888f8
--- /dev/null
+++ b/src/test/examplesJava11/backport/CharSequenceBackportJava11Main.java
@@ -0,0 +1,46 @@
+package backport;
+
+public final class CharSequenceBackportJava11Main {
+ public static void main(String[] args) {
+ testCompare();
+ }
+
+ private static void testCompare() {
+ assertTrue(CharSequence.compare("Hello", "Hello") == 0);
+
+ assertTrue(CharSequence.compare("Hey", "Hello") > 0);
+ assertTrue(CharSequence.compare("Hello", "Hey") < 0);
+
+ assertTrue(CharSequence.compare("Hel", "Hello") < 0);
+ assertTrue(CharSequence.compare("Hello", "Hel") > 0);
+
+ assertTrue(CharSequence.compare("", "") == 0);
+ assertTrue(CharSequence.compare("", "Hello") < 0);
+ assertTrue(CharSequence.compare("Hello", "") > 0);
+
+ // Different CharSequence types:
+ assertTrue(CharSequence.compare("Hello", new StringBuilder("Hello")) == 0);
+ assertTrue(CharSequence.compare(new StringBuffer("hey"), "Hello") > 0);
+ assertTrue(CharSequence.compare(new StringBuffer("Hello"), new StringBuilder("Hey")) < 0);
+
+ try {
+ throw new AssertionError(CharSequence.compare(null, "Hello"));
+ } catch (NullPointerException expected) {
+ }
+ try {
+ throw new AssertionError(CharSequence.compare("Hello", null));
+ } catch (NullPointerException expected) {
+ }
+ try {
+ // Ensure a == b fast path does not happen before null checks.
+ throw new AssertionError(CharSequence.compare(null, null));
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ private static void assertTrue(boolean value) {
+ if (!value) {
+ throw new AssertionError("Expected <true> but was <false>");
+ }
+ }
+}
diff --git a/src/test/examplesJava11/backport/CharacterBackportJava11Main.java b/src/test/examplesJava11/backport/CharacterBackportJava11Main.java
index a580b5b..487c7b0 100644
--- a/src/test/examplesJava11/backport/CharacterBackportJava11Main.java
+++ b/src/test/examplesJava11/backport/CharacterBackportJava11Main.java
@@ -1,3 +1,7 @@
+// 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 backport;
public final class CharacterBackportJava11Main {
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
new file mode 100644
index 0000000..bb344d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BackportedMethodListTest {
+
+ @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ enum Mode {
+ NO_LIBRARY,
+ LIBRARY,
+ LIBRARY_DESUGAR
+ }
+
+ @Parameterized.Parameters(name = "Mode: {0}")
+ public static Object[] data() {
+ return Mode.values();
+ }
+
+ private final Mode mode;
+
+ public BackportedMethodListTest(Mode mode) {
+ this.mode = mode;
+ }
+
+ private static class ListStringConsumer implements StringConsumer {
+ List<String> strings = new ArrayList<>();
+ boolean finished = false;
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ strings.add(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ finished = true;
+ }
+ }
+
+ private void checkContent(int apiLevel, List<String> backports) {
+ // Java 8 methods added at various API levels.
+ assertEquals(
+ apiLevel < AndroidApiLevel.K.getLevel(), backports.contains("java/lang/Byte#compare(BB)I"));
+ assertEquals(
+ apiLevel < AndroidApiLevel.N.getLevel(),
+ backports.contains("java/lang/Integer#hashCode(I)I"));
+ assertEquals(
+ apiLevel < AndroidApiLevel.O.getLevel(),
+ backports.contains("java/lang/Short#toUnsignedLong(S)J"));
+
+ // Java 9, 10 and 11 Optional methods which require Android N or library desugaring.
+ assertEquals(
+ mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+ backports.contains(
+ "java/util/Optional#or(Ljava/util/function/Supplier;)Ljava/util/Optional;"));
+ assertEquals(
+ mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+ backports.contains("java/util/OptionalInt#orElseThrow()I"));
+ assertEquals(
+ mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+ backports.contains("java/util/OptionalLong#isEmpty()Z"));
+
+ // Java 9, 10 and 11 methods.
+ assertTrue(backports.contains("java/lang/StrictMath#multiplyExact(JI)J"));
+ assertTrue(backports.contains("java/util/List#copyOf(Ljava/util/Collection;)Ljava/util/List;"));
+ assertTrue(backports.contains("java/lang/Character#toString(I)Ljava/lang/String;"));
+ }
+
+ private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
+ builder
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+ }
+
+ @Test
+ public void testConsumer() throws Exception {
+ for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
+ ListStringConsumer consumer = new ListStringConsumer();
+ BackportedMethodListCommand.Builder builder =
+ BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setConsumer(consumer);
+ if (mode == Mode.LIBRARY) {
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+ } else if (mode == Mode.LIBRARY_DESUGAR) {
+ addLibraryDesugaring(builder);
+ }
+ BackportedMethodList.run(builder.build());
+ assertTrue(consumer.finished);
+ checkContent(apiLevel, consumer.strings);
+ }
+ }
+
+ @Test
+ public void testFile() throws Exception {
+ for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
+ Path output = temp.newFile().toPath();
+ BackportedMethodListCommand.Builder builder =
+ BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setOutputPath(output);
+ if (mode == Mode.LIBRARY) {
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+ } else if (mode == Mode.LIBRARY_DESUGAR) {
+ addLibraryDesugaring(builder);
+ }
+ BackportedMethodList.run(builder.build());
+ checkContent(apiLevel, Files.readAllLines(output));
+ }
+ }
+
+ @Test
+ public void testFullList() throws Exception {
+ Assume.assumeTrue(mode == Mode.NO_LIBRARY);
+ ListStringConsumer consumer = new ListStringConsumer();
+ // Not setting neither min API level not library should produce the full list.
+ BackportedMethodList.run(BackportedMethodListCommand.builder().setConsumer(consumer).build());
+ assertTrue(consumer.finished);
+ checkContent(1, consumer.strings);
+ }
+
+ @Test
+ public void requireLibraryForDesugar() {
+ Assume.assumeTrue(mode == Mode.LIBRARY_DESUGAR);
+ // Require library when a desugar configuration is passed.
+ try {
+ BackportedMethodList.run(
+ BackportedMethodListCommand.builder()
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .setConsumer(new ListStringConsumer())
+ .build());
+ fail("Expected failure");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 84ecee1..e621784 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -53,7 +53,10 @@
List<String> args = new ArrayList<>();
args.add("--output=" + outJar.toString());
args.addAll(injars.stream().map(Path::toString).collect(Collectors.toList()));
- ProcessResult result = ToolHelper.runDX(args.toArray(StringUtils.EMPTY_ARRAY));
+ ProcessResult result =
+ ToolHelper.runProcess(
+ ToolHelper.createProcessBuilderForRunningDx(args.toArray(StringUtils.EMPTY_ARRAY)),
+ getStdoutForTesting());
if (result.exitCode != 0) {
throw new CompilationFailedException(result.toString());
}
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 0ebe047..11e6692 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -137,7 +137,7 @@
command.addAll(programJars.stream().map(Path::toString).collect(Collectors.toList()));
ProcessBuilder processBuilder = new ProcessBuilder(command);
- ProcessResult processResult = ToolHelper.runProcess(processBuilder);
+ ProcessResult processResult = ToolHelper.runProcess(processBuilder, getStdoutForTesting());
assertEquals(processResult.stderr, 0, processResult.exitCode);
String proguardMap =
proguardMapFile.toFile().exists()
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index a470bb3..c3ce381 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -6,8 +6,12 @@
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
public abstract class KotlinTestBase extends TestBase {
@@ -28,6 +32,13 @@
this.targetVersion = targetVersion;
}
+ protected static List<Path> getKotlinFilesInTestPackage(Package pkg) throws IOException {
+ String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg.getName());
+ return Files.walk(Paths.get(ToolHelper.TESTS_DIR, "java", folder))
+ .filter(path -> path.toString().endsWith(".kt"))
+ .collect(Collectors.toList());
+ }
+
protected static Path getKotlinFileInTest(String folder, String fileName) {
return Paths.get(ToolHelper.TESTS_DIR, "java", folder, fileName + FileUtils.KT_EXTENSION);
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 9012ca4..9463a49 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -101,7 +101,7 @@
command.add("-dontobfuscate");
}
ProcessBuilder pbuilder = new ProcessBuilder(command);
- ProcessResult result = ToolHelper.runProcess(pbuilder);
+ ProcessResult result = ToolHelper.runProcess(pbuilder, getStdoutForTesting());
if (result.exitCode != 0) {
throw new CompilationFailedException(result.toString());
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 39ac985..db80105 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -110,7 +110,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
.run();
}
@@ -142,7 +142,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -150,7 +150,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
.run();
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 50eb3be..2ec28b8 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -65,6 +65,13 @@
return self();
}
+ public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
+ ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
+ throws E, IOException, ExecutionException {
+ consumer.accept(getOriginalStackTrace(), new CodeInspector(app, proguardMap));
+ return self();
+ }
+
public GraphInspector graphInspector() throws IOException, ExecutionException {
assertSuccess();
return graphInspector.get();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index c1a612f..5094ec5 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
@@ -72,7 +73,6 @@
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
@@ -82,7 +82,6 @@
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -236,6 +235,10 @@
return ClassFileTransformer.create(clazz);
}
+ public static ClassFileTransformer transformer(byte[] clazz) {
+ return ClassFileTransformer.create(clazz);
+ }
+
// Actually running Proguard should only be during development.
private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
// Actually running r8.jar in a forked process.
@@ -437,12 +440,12 @@
protected static AndroidApp.Builder buildClasses(
Collection<Class<?>> programClasses, Collection<Class<?>> libraryClasses) throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
- for (Class clazz : programClasses) {
+ for (Class<?> clazz : programClasses) {
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
}
if (!libraryClasses.isEmpty()) {
PreloadedClassFileProvider.Builder libraryBuilder = PreloadedClassFileProvider.builder();
- for (Class clazz : libraryClasses) {
+ for (Class<?> clazz : libraryClasses) {
Path file = ToolHelper.getClassFileForTestClass(clazz);
libraryBuilder.addResource(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()),
Files.readAllBytes(file));
@@ -455,7 +458,7 @@
protected static AndroidApp readClassesAndRuntimeJar(
List<Class<?>> programClasses, Backend backend) throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
- for (Class clazz : programClasses) {
+ for (Class<?> clazz : programClasses) {
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
}
if (backend == Backend.DEX) {
@@ -477,7 +480,7 @@
* Copy test classes to the specified directory.
*/
protected void copyTestClasses(Path dest, Class... classes) throws IOException {
- for (Class clazz : classes) {
+ for (Class<?> clazz : classes) {
Path path = dest.resolve(clazz.getCanonicalName().replace('.', '/') + ".class");
Files.createDirectories(path.getParent());
Files.copy(ToolHelper.getClassFileForTestClass(clazz), path);
@@ -510,7 +513,7 @@
/** Create a temporary JAR file containing the specified test classes. */
protected void addTestClassesToJar(JarOutputStream out, Iterable<Class<?>> classes)
throws IOException {
- for (Class clazz : classes) {
+ for (Class<?> clazz : classes) {
try (FileInputStream in =
new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
@@ -587,7 +590,8 @@
throws Exception {
Timing timing = Timing.empty();
InternalOptions options = new InternalOptions();
- DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+ DirectMappedDexApplication application =
+ new ApplicationReader(app, options, timing).read().toDirect();
AppView<AppInfoWithSubtyping> appView =
AppView.createForR8(new AppInfoWithSubtyping(application), options);
appView.setAppServices(AppServices.builder(appView).build());
@@ -645,7 +649,7 @@
}
protected static DexMethod buildNullaryVoidMethod(
- Class clazz, String name, DexItemFactory factory) {
+ Class<?> clazz, String name, DexItemFactory factory) {
return buildMethod(
Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
factory);
@@ -659,7 +663,7 @@
}
private static List<ProguardConfigurationRule> buildKeepRuleForClass(
- Class clazz, DexItemFactory factory) {
+ Class<?> clazz, DexItemFactory factory) {
Builder keepRuleBuilder = ProguardKeepRule.builder();
keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
@@ -671,7 +675,7 @@
}
private static List<ProguardConfigurationRule> buildKeepRuleForClassAndMethods(
- Class clazz, DexItemFactory factory) {
+ Class<?> clazz, DexItemFactory factory) {
Builder keepRuleBuilder = ProguardKeepRule.builder();
keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
@@ -735,20 +739,6 @@
return jarTestClasses(classes.toArray(new Class<?>[]{}));
}
- /**
- * Get the class name generated by javac.
- */
- protected static String getJavacGeneratedClassName(Class clazz) {
- List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
- Class enclosing = clazz;
- while (enclosing.getEnclosingClass() != null) {
- parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1));
- parts.remove(parts.size() - 1);
- enclosing = clazz.getEnclosingClass();
- }
- return String.join(".", parts);
- }
-
protected static List<Object[]> buildParameters(Object... arraysOrIterables) {
Function<Object, List<Object>> arrayOrIterableToList =
arrayOrIterable -> {
@@ -897,23 +887,16 @@
* Generate a Proguard configuration for keeping the "static void main(String[])" method of the
* specified class.
*/
- public static String keepMainProguardConfiguration(Class clazz) {
- return keepMainProguardConfiguration(clazz, ImmutableList.of());
+ public static String keepMainProguardConfiguration(Class<?> clazz) {
+ return keepMainProguardConfiguration(clazz.getTypeName());
}
/**
* Generate a Proguard configuration for keeping the "static void main(String[])" method of the
* specified class.
*/
- public static String keepMainProguardConfiguration(Class clazz, List<String> additionalLines) {
- String modifier = (clazz.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC ? "public " : "";
- return String.join(System.lineSeparator(),
- Iterables.concat(ImmutableList.of(
- "-keep " + modifier + "class " + getJavacGeneratedClassName(clazz) + " {",
- " public static void main(java.lang.String[]);",
- "}",
- "-printmapping"),
- additionalLines));
+ public static String keepMainProguardConfiguration(Class<?> clazz, List<String> additionalLines) {
+ return keepMainProguardConfiguration(clazz.getTypeName()) + StringUtils.lines(additionalLines);
}
/**
@@ -923,15 +906,12 @@
* The class is assumed to be public.
*/
public static String keepMainProguardConfiguration(String clazz) {
- return "-keep public class " + clazz + " {\n"
- + " public static void main(java.lang.String[]);\n"
- + "}\n"
- + "-printmapping\n";
+ return StringUtils.lines(
+ "-keep class " + clazz + " {", " public static void main(java.lang.String[]);", "}");
}
public static String noShrinkingNoMinificationProguardConfiguration() {
- return "-dontshrink\n"
- + "-dontobfuscate\n";
+ return StringUtils.lines("-dontshrink", "-dontobfuscate");
}
/**
@@ -939,7 +919,7 @@
* specified class and specify if -allowaccessmodification and -dontobfuscate are added as well.
*/
public static String keepMainProguardConfiguration(
- Class clazz, boolean allowaccessmodification, boolean obfuscate) {
+ Class<?> clazz, boolean allowaccessmodification, boolean obfuscate) {
return keepMainProguardConfiguration(clazz)
+ (allowaccessmodification ? "-allowaccessmodification\n" : "")
+ (obfuscate ? "-printmapping\n" : "-dontobfuscate\n");
@@ -956,7 +936,7 @@
* Generate a Proguard configuration for keeping the "static void main(String[])" method of the
* specified class and add rules to inline methods with the inlining annotation.
*/
- public static String keepMainProguardConfigurationWithInliningAnnotation(Class clazz) {
+ public static String keepMainProguardConfigurationWithInliningAnnotation(Class<?> clazz) {
return "-forceinline class * { @com.android.tools.r8.ForceInline *; }"
+ System.lineSeparator()
+ "-neverinline class * { @com.android.tools.r8.NeverInline *; }"
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 628e9c5..5f0843f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
@@ -12,6 +16,7 @@
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.ForwardingOutputStream;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThrowingOutputStream;
import com.google.common.base.Suppliers;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -52,7 +57,9 @@
private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
private ByteArrayOutputStream stdout = null;
+ private PrintStream oldStdout = null;
private ByteArrayOutputStream stderr = null;
+ private PrintStream oldStderr = null;
protected OutputMode outputMode = OutputMode.DexIndexed;
TestCompilerBuilder(TestState state, B builder, Backend backend) {
@@ -95,34 +102,43 @@
builder.addLibraryFiles(TestBase.runtimeJar(backend));
}
}
- PrintStream oldOut = System.out;
- PrintStream oldErr = System.err;
- CR cr = null;
+ assertNull(oldStdout);
+ oldStdout = System.out;
+ assertNull(oldStderr);
+ oldStderr = System.err;
+ CR cr;
try {
if (stdout != null) {
+ assertTrue(allowStdoutMessages);
System.setOut(new PrintStream(new ForwardingOutputStream(stdout, System.out)));
+ } else if (!allowStdoutMessages) {
+ System.setOut(
+ new PrintStream(
+ new ThrowingOutputStream<>(
+ () -> new AssertionError("Unexpected print to stdout"))));
}
if (stderr != null) {
+ assertTrue(allowStderrMessages);
System.setErr(new PrintStream(new ForwardingOutputStream(stderr, System.err)));
+ } else if (!allowStderrMessages) {
+ System.setErr(
+ new PrintStream(
+ new ThrowingOutputStream<>(
+ () -> new AssertionError("Unexpected print to stderr"))));
}
- cr = internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
- cr.addRunClasspathFiles(additionalRunClassPath);
+ cr =
+ internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
+ .addRunClasspathFiles(additionalRunClassPath);
return cr;
} finally {
if (stdout != null) {
getState().setStdout(stdout.toString());
- System.setOut(oldOut);
- if (cr != null && !allowStdoutMessages) {
- cr.assertNoStdout();
- }
}
+ System.setOut(oldStdout);
if (stderr != null) {
getState().setStderr(stderr.toString());
- System.setErr(oldErr);
- if (cr != null && !allowStderrMessages) {
- cr.assertNoStderr();
- }
}
+ System.setErr(oldStderr);
}
}
@@ -319,6 +335,16 @@
return allowStdoutMessages();
}
+ /**
+ * If {@link #allowStdoutMessages} is false, then {@link System#out} will be replaced temporarily
+ * by a {@link ThrowingOutputStream}. To allow the testing infrastructure to print messages to the
+ * terminal, this method provides a reference to the original {@link System#out}.
+ */
+ public PrintStream getStdoutForTesting() {
+ assertNotNull(oldStdout);
+ return oldStdout;
+ }
+
public T allowStderrMessages() {
allowStdoutMessages = true;
return self();
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index bbc9d4e..46bf0a1 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -112,6 +114,10 @@
}
public RR assertSuccessWithOutputLines(String... expected) {
+ return assertSuccessWithOutputLines(Arrays.asList(expected));
+ }
+
+ public RR assertSuccessWithOutputLines(List<String> expected) {
return assertSuccessWithOutput(StringUtils.lines(expected));
}
@@ -129,14 +135,10 @@
return new CodeInspector(app);
}
- public RR inspect(ThrowingConsumer<CodeInspector, NoSuchMethodException> consumer)
- throws IOException, ExecutionException {
+ public <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
+ throws IOException, ExecutionException, E {
CodeInspector inspector = inspector();
- try {
- consumer.accept(inspector);
- } catch (NoSuchMethodException exception) {
- throw new RuntimeException(exception);
- }
+ consumer.accept(inspector);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2defd19..13afb51 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -205,6 +205,10 @@
return addKeepRules("-keepattributes " + String.join(",", attributes));
}
+ public T addKeepAttributeLineNumberTable() {
+ return addKeepRules("-keepattributes LineNumberTable");
+ }
+
public T addKeepAllAttributes() {
return addKeepAttributes("*");
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index effe9d9..f9c1086 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.AssemblyWriter;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
@@ -66,6 +67,7 @@
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -780,6 +782,18 @@
return path;
}
+ public static Path getMostRecentAndroidJar() {
+ List<AndroidApiLevel> apiLevels = AndroidApiLevel.getAndroidApiLevelsSorted();
+ ListIterator<AndroidApiLevel> iterator = apiLevels.listIterator(apiLevels.size());
+ while (iterator.hasPrevious()) {
+ AndroidApiLevel apiLevel = iterator.previous();
+ if (hasAndroidJar(apiLevel)) {
+ return getAndroidJar(apiLevel);
+ }
+ }
+ throw new Unreachable("Unable to find a most recent android.jar");
+ }
+
public static Path getKotlinStdlibJar() {
Path path = Paths.get(KT_STDLIB);
assert Files.exists(path) : "Expected kotlin stdlib jar";
@@ -910,7 +924,7 @@
case V9_0_0:
return AndroidApiLevel.P;
case V8_1_0:
- return AndroidApiLevel.O;
+ return AndroidApiLevel.O_MR1;
case V7_0_0:
return AndroidApiLevel.N;
case V6_0_1:
@@ -1053,14 +1067,13 @@
return String.join("/", parts);
}
- public static DexApplication buildApplication(List<String> fileNames)
+ public static DirectMappedDexApplication buildApplication(List<String> fileNames)
throws IOException, ExecutionException {
return buildApplicationWithAndroidJar(fileNames, getDefaultAndroidJar());
}
- public static DexApplication buildApplicationWithAndroidJar(
- List<String> fileNames, Path androidJar)
- throws IOException, ExecutionException {
+ public static DirectMappedDexApplication buildApplicationWithAndroidJar(
+ List<String> fileNames, Path androidJar) throws IOException, ExecutionException {
AndroidApp input =
AndroidApp.builder()
.addProgramFiles(ListUtils.map(fileNames, Paths::get))
@@ -1244,6 +1257,15 @@
}
public static ProcessResult runDX(Path workingDirectory, String... args) throws IOException {
+ return runProcess(createProcessBuilderForRunningDx(workingDirectory, args));
+ }
+
+ public static ProcessBuilder createProcessBuilderForRunningDx(String... args) {
+ return createProcessBuilderForRunningDx(null, args);
+ }
+
+ public static ProcessBuilder createProcessBuilderForRunningDx(
+ Path workingDirectory, String... args) {
Assume.assumeTrue(ToolHelper.artSupported());
DXCommandBuilder builder = new DXCommandBuilder();
for (String arg : args) {
@@ -1253,7 +1275,7 @@
if (workingDirectory != null) {
pb.directory(workingDirectory.toFile());
}
- return runProcess(pb);
+ return pb;
}
public static ProcessResult runJava(Class clazz) throws Exception {
@@ -1968,8 +1990,13 @@
}
public static ProcessResult runProcess(ProcessBuilder builder) throws IOException {
+ return runProcess(builder, System.out);
+ }
+
+ public static ProcessResult runProcess(ProcessBuilder builder, PrintStream out)
+ throws IOException {
String command = String.join(" ", builder.command());
- System.out.println(command);
+ out.println(command);
return drainProcessOutputStreams(builder.start(), command);
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index c37c71b..6bc3814 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -5,67 +5,65 @@
package com.android.tools.r8.bridgeremoval;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
-import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.lang.reflect.Method;
-import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class RemoveVisibilityBridgeMethodsTest extends TestBase {
- private void run(boolean obfuscate) throws Exception {
- List<Class<?>> classes = ImmutableList.of(Outer.class, Main.class);
- String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate);
- CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
+ private final boolean minification;
+ private final TestParameters parameters;
- List<Method> removedMethods = ImmutableList.of(
- Outer.SubClass.class.getMethod("method"),
- Outer.StaticSubClass.class.getMethod("method"));
+ @Parameterized.Parameters(name = "{1}, minification: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
- removedMethods.forEach(method -> assertFalse(inspector.method(method).isPresent()));
+ public RemoveVisibilityBridgeMethodsTest(boolean minification, TestParameters parameters) {
+ this.minification = minification;
+ this.parameters = parameters;
}
@Test
- public void testWithObfuscation() throws Exception {
- run(true);
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(RemoveVisibilityBridgeMethodsTest.class)
+ .addKeepMainRule(Main.class)
+ .allowAccessModification()
+ .minification(minification)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
}
- @Test
- public void testWithoutObfuscation() throws Exception {
- run(false);
- }
-
- @Test
- public void regressionTest_b76383728_WithObfuscation() throws Exception {
- runRegressionTest_b76383728(true);
- }
-
- @Test
- public void regressionTest_b76383728_WithoutObfuscation() throws Exception {
- runRegressionTest_b76383728(false);
+ private void inspect(CodeInspector inspector) throws Exception {
+ assertThat(inspector.method(Outer.SubClass.class.getMethod("method")), not(isPresent()));
+ assertThat(inspector.method(Outer.StaticSubClass.class.getMethod("method")), not(isPresent()));
}
/**
* Regression test for b76383728 to make sure we correctly identify and remove real visibility
* forward bridge methods synthesized by javac.
*/
- private void runRegressionTest_b76383728(boolean obfuscate) throws Exception {
+ @Test
+ public void regressionTest_b76383728() throws Exception {
JasminBuilder jasminBuilder = new JasminBuilder();
ClassBuilder superClass = jasminBuilder.addClass("SuperClass");
@@ -92,39 +90,71 @@
"dup",
"invokespecial " + subclass.name + "/<init>()V",
"invokevirtual " + subclass.name + "/getMethod()Ljava/lang/String;",
- "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
- "return"
- );
+ "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+ "return");
- final String mainClassName = mainClass.name;
-
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, true, obfuscate);
+ List<byte[]> programClassFileData = jasminBuilder.buildClasses();
// Run input program on java.
- Path outputDirectory = temp.newFolder().toPath();
- jasminBuilder.writeClassFiles(outputDirectory);
- ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
- assertEquals(0, javaResult.exitCode);
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(programClassFileData)
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutputLines("Hello World");
+ }
- AndroidApp optimizedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
- // Disable inlining to avoid the (short) tested method from being inlined and then removed.
- internalOptions -> internalOptions.enableInlining = false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(programClassFileData)
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(options -> options.enableInlining = false)
+ .allowAccessModification()
+ .minification(minification)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(superClass.name);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject =
+ classSubject.method("java.lang.String", "method", Collections.emptyList());
+ assertThat(methodSubject, isPresent());
- // Run optimized (output) program on ART
- String artResult = runOnArt(optimizedApp, mainClassName);
- assertEquals(javaResult.stdout, artResult);
+ classSubject = inspector.clazz(subclass.name);
+ assertThat(classSubject, isPresent());
+ methodSubject =
+ classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
+ assertThat(methodSubject, isPresent());
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutputLines("Hello World");
+ }
- CodeInspector inspector = new CodeInspector(optimizedApp);
+ static class Main {
- ClassSubject classSubject = inspector.clazz(superClass.name);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject
- .method("java.lang.String", "method", Collections.emptyList());
- assertThat(methodSubject, isPresent());
+ public static void main(String[] args) {
+ new Outer().create().method();
+ new Outer.StaticSubClass().method();
+ }
+ }
- classSubject = inspector.clazz(subclass.name);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
- assertThat(methodSubject, isPresent());
+ static class Outer {
+
+ class SuperClass {
+ public void method() {}
+ }
+
+ // As SuperClass is package private SubClass will have a bridge method for "method".
+ public class SubClass extends SuperClass {}
+
+ public SubClass create() {
+ return new SubClass();
+ }
+
+ static class StaticSuperClass {
+ public void method() {}
+ }
+
+ // As SuperClass is package private SubClass will have a bridge method for "method".
+ public static class StaticSubClass extends StaticSuperClass {}
}
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
index 4c8ac31..28ad6b9 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
@@ -7,7 +7,7 @@
public class Main {
public static void main(String[] args) {
- (new Outer()).create().method();
- (new Outer.StaticSubClass()).method();
+ new Outer().create().method();
+ new Outer.StaticSubClass().method();
}
}
diff --git a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
index b81d457..16dfaa7 100644
--- a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
@@ -1,23 +1,77 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-
package com.android.tools.r8.cf;
-public class InlineCmpDoubleTest {
- public static void main(String[] args) {
- inlinee(42);
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.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 InlineCmpDoubleTest extends TestBase {
+
+ private final boolean enableInlining;
+ private final TestParameters parameters;
+
+ public InlineCmpDoubleTest(boolean enableInlining, TestParameters parameters) {
+ this.enableInlining = enableInlining;
+ this.parameters = parameters;
}
- public static void inlinee(int x) {
- inlineMe(x + 41);
+ @Parameters(name = "{1}, inlining: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public static int inlineMe(int x) {
- // Side effect to ensure that the invocation is not removed simply because the method does not
- // have any side effects.
- System.out.println("In InlineCmpDoubleTest.inlineMe()");
- double a = x / 255.0;
- return a < 64.0 ? 42 : 43;
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.enableInlining = enableInlining)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ MethodSubject method =
+ clazz.method(new MethodSignature("inlineMe", "int", ImmutableList.of("int")));
+ assertEquals(enableInlining, !method.isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ inlinee(42);
+ }
+
+ public static void inlinee(int x) {
+ inlineMe(x + 41);
+ }
+
+ public static int inlineMe(int x) {
+ // Side effect to ensure that the invocation is not removed simply because the method does not
+ // have any side effects.
+ System.out.println("In InlineCmpDoubleTest.inlineMe()");
+ double a = x / 255.0;
+ return a < 64.0 ? 42 : 43;
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTestRunner.java b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTestRunner.java
deleted file mode 100644
index dbcf557..0000000
--- a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTestRunner.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.cf;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class InlineCmpDoubleTestRunner {
-
- private static final Class CLASS = InlineCmpDoubleTest.class;
-
- private final boolean enableInlining;
-
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
- public InlineCmpDoubleTestRunner(boolean enableInlining) {
- this.enableInlining = enableInlining;
- }
-
- @Parameters(name = "inlining={0}")
- public static Boolean[] data() {
- return new Boolean[]{true, false};
- }
-
- @Test
- public void test() throws Exception {
- byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
- AndroidAppConsumers appBuilder = new AndroidAppConsumers();
- Path outPath = temp.getRoot().toPath().resolve("out.jar");
- List<String> proguardKeepMain = ImmutableList.of(TestBase.keepMainProguardConfiguration(CLASS));
- R8Command command = R8Command.builder()
- .setMode(CompilationMode.RELEASE)
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
- .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
- .addProguardConfiguration(proguardKeepMain, Origin.unknown())
- .addClassProgramData(inputClass, Origin.unknown())
- .build();
-
- AndroidApp app = ToolHelper.runR8(command, options -> {
- options.enableInlining = enableInlining;
- });
-
- assertEquals(0, ToolHelper.runJava(outPath, CLASS.getCanonicalName()).exitCode);
-
- CodeInspector inspector = new CodeInspector(app);
- ClassSubject clazz = inspector.clazz(CLASS);
- MethodSubject method =
- clazz.method(new MethodSignature("inlineMe", "int", ImmutableList.of("int")));
- assertEquals(enableInlining, !method.isPresent());
- }
-}
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
index 4dc81f4..1415b49 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
@@ -6,6 +6,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
@@ -20,22 +21,30 @@
Method runMethod = o.getClass().getMethod("run");
runMethod.invoke(o);
}
+
+ static Serializable getLambda() {
+ return (Runnable & Serializable) () -> System.out.println("base lambda");
+ }
}
class KeepDeserializeLambdaMethodTestDex extends KeepDeserializeLambdaMethodTest {
public static void main(String[] args) throws Exception {
- Serializable myLambda =
+ invokeLambda(getLambda());
+ invokeLambda(
(Runnable & Serializable)
- () -> System.out.println(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE);
- invokeLambda(myLambda);
+ () -> System.out.println(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE));
}
}
class KeepDeserializeLambdaMethodTestCf extends KeepDeserializeLambdaMethodTest {
public static void main(String[] args) throws Exception {
- Serializable myLambda = (Runnable & Serializable) () -> System.out.println(LAMBDA_MESSAGE);
+ invokeLambda(roundtrip(getLambda()));
+ invokeLambda(roundtrip((Runnable & Serializable) () -> System.out.println(LAMBDA_MESSAGE)));
+ }
+ private static Object roundtrip(Serializable myLambda)
+ throws IOException, ClassNotFoundException {
byte[] bytes;
{
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
@@ -51,10 +60,6 @@
o = in.readObject();
in.close();
}
- String name = o.getClass().getName();
- if (!name.contains("KeepDeserializeLambdaMethodTestCf")) {
- throw new RuntimeException("Unexpected class name " + name);
- }
- invokeLambda(o);
+ return o;
}
}
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
index 9c5d05d..8bb8e36 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -4,19 +4,19 @@
package com.android.tools.r8.cf;
-import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8CompatTestBuilder;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,9 +31,12 @@
private static final Class TEST_CLASS_DEX =
com.android.tools.r8.cf.KeepDeserializeLambdaMethodTestDex.class;
+ private static final String EXPECTED =
+ StringUtils.lines("base lambda", KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE);
+
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withCfRuntimes().withDexRuntime(Version.last()).build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -42,48 +45,66 @@
this.parameters = parameters;
}
+ private Class<?> getMainClass() {
+ return parameters.isCfRuntime() ? TEST_CLASS_CF : TEST_CLASS_DEX;
+ }
+
+ private List<Class<?>> getClasses() {
+ return ImmutableList.of(KeepDeserializeLambdaMethodTest.class, getMainClass());
+ }
+
@Test
- public void testNoTreeShakingCf() throws Exception {
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .run(parameters.getRuntime(), getMainClass())
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkPresenceOfDeserializedLambdas);
+ }
+
+ @Test
+ public void testDontKeepDeserializeLambdaR8() throws Exception {
test(false);
}
@Test
- public void testKeepRuleCf() throws Exception {
- // Only run for CF runtimes, since the keep rule does not match anything when compiling for the
- // DEX.
- assumeTrue(parameters.isCfRuntime());
+ public void testKeepDeserializedLambdaR8() throws Exception {
test(true);
}
- private void test(boolean keepRule)
+ private void test(boolean addKeepDeserializedLambdaRule)
throws IOException, CompilationFailedException, ExecutionException {
- Class testClass = parameters.isCfRuntime() ? TEST_CLASS_CF : TEST_CLASS_DEX;
R8CompatTestBuilder builder =
testForR8Compat(parameters.getBackend())
- .addProgramClasses(
- com.android.tools.r8.cf.KeepDeserializeLambdaMethodTest.class, testClass)
- .setMinApi(parameters.getRuntime())
- .addKeepMainRule(testClass)
- .noMinification();
- if (keepRule) {
+ .addProgramClasses(getClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(getMainClass());
+ if (addKeepDeserializedLambdaRule) {
+ builder.allowUnusedProguardConfigurationRules(parameters.isDexRuntime());
builder.addKeepRules(
"-keepclassmembers class * {",
"private static synthetic java.lang.Object "
+ "$deserializeLambda$(java.lang.invoke.SerializedLambda);",
- "}");
+ "}",
+ // TODO(b/148836254): Support deserialized lambdas without the need of additional rules.
+ "-keep class * { private static synthetic void lambda$*(); }");
} else {
+ builder.noMinification();
builder.noTreeShaking();
}
- R8TestRunResult result =
- builder
- .run(parameters.getRuntime(), testClass)
- .assertSuccessWithOutputThatMatches(
- containsString(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE))
- .inspect(
- inspector -> {
- MethodSubject method =
- inspector.clazz(testClass).uniqueMethodWithName("$deserializeLambda$");
- assertEquals(parameters.isCfRuntime(), method.isPresent());
- });
+ builder
+ .run(parameters.getRuntime(), getMainClass())
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkPresenceOfDeserializedLambdas);
+ }
+
+ private void checkPresenceOfDeserializedLambdas(CodeInspector inspector) {
+ for (Class<?> clazz : getClasses()) {
+ MethodSubject method = inspector.clazz(clazz).uniqueMethodWithName("$deserializeLambda$");
+ assertEquals(
+ "Unexpected status for $deserializedLambda$ on " + clazz.getSimpleName(),
+ parameters.isCfRuntime(),
+ method.isPresent());
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 3d55195..26e5a16 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -98,7 +98,6 @@
private void configure(InternalOptions options) {
options.enableSideEffectAnalysis = false;
options.enableUnusedInterfaceRemoval = false;
- options.testing.nondeterministicCycleElimination = true;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -236,12 +235,6 @@
}
// This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
- // When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
- // before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will
- // detect the cycle upon the call edge A.<init> to B.<init>, or B.<init> to A.<init>. In order
- // increase the likelihood that this test encounters both orderings, the test is repeated 5 times.
- // Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
- // approximately 3% chance of succeeding although there is an issue.
@Test
public void testCallGraphCycle() throws Throwable {
String main = "classmerging.CallGraphCycleTest";
diff --git a/src/test/java/com/android/tools/r8/debug/Regress148661132Test.java b/src/test/java/com/android/tools/r8/debug/Regress148661132Test.java
new file mode 100644
index 0000000..ae64de7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/Regress148661132Test.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class Regress148661132Test extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public Regress148661132Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForD8().setMinApi(AndroidApiLevel.B).addProgramClassFileData(FlafKtDump.dump()).compile();
+ }
+
+ static class FlafKtDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+ cw.visitSource("Flaf.kt", null);
+ cw.visitInnerClass("FlafKt$inlineFun$1", null, null, ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+ MethodVisitor mv =
+ cw.visitMethod(
+ ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC,
+ "inlineFun$default",
+ "(LA;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)LA;",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ILOAD, 2);
+ mv.visitInsn(ICONST_2);
+ mv.visitInsn(IAND);
+ Label label0 = new Label();
+ mv.visitJumpInsn(IFEQ, label0);
+ mv.visitTypeInsn(NEW, "FlafKt$inlineFun$1");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "FlafKt$inlineFun$1", "<init>", "(LA;)V", false);
+ mv.visitTypeInsn(CHECKCAST, "kotlin/jvm/functions/Function0");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(label0);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ASTORE, 4);
+ Label label1 = new Label();
+ // The delayed introduction of the local variable causes a write after the phi.
+ // That write should not be replaced as it invalidates the assumption of local info
+ // associated with phis.
+ mv.visitLabel(label1);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ISTORE, 5);
+ Label label2 = new Label();
+ mv.visitLabel(label2);
+ mv.visitLineNumber(13, label2);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitMethodInsn(
+ INVOKEINTERFACE,
+ "kotlin/jvm/functions/Function0",
+ "invoke",
+ "()Ljava/lang/Object;",
+ true);
+ mv.visitTypeInsn(CHECKCAST, "A");
+ Label label3 = new Label();
+ mv.visitLabel(label3);
+ mv.visitInsn(ARETURN);
+ mv.visitLocalVariable("$i$f$inlineFun", "I", null, label2, label3, 5);
+ mv.visitLocalVariable(
+ "lambda$iv", "Lkotlin/jvm/functions/Function0;", null, label1, label3, 4);
+ mv.visitMaxs(3, 6);
+ mv.visitEnd();
+
+ cw.visitEnd();
+ return cw.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
new file mode 100644
index 0000000..7e969c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
@@ -0,0 +1,30 @@
+// 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.debuginfo;
+
+public class DexPcWithDebugInfoForOverloadedMethodsTest {
+
+ private static void inlinee(String message) {
+ if (System.currentTimeMillis() > 0) {
+ throw new RuntimeException(message);
+ }
+ }
+
+ public static void overloaded(int x) {
+ inlinee("overloaded(int)" + x);
+ }
+
+ public static void overloaded(String y) {
+ inlinee("overloaded(String)" + y);
+ }
+
+ public static void main(String[] args) {
+ if (args.length > 0) {
+ overloaded(42);
+ } else {
+ overloaded("42");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
new file mode 100644
index 0000000..b344944
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -0,0 +1,130 @@
+// 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.debuginfo;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 DexPcWithDebugInfoForOverloadedMethodsTestRunner extends TestBase {
+
+ private static final Class<?> MAIN = DexPcWithDebugInfoForOverloadedMethodsTest.class;
+ private static final int MINIFIED_LINE_POSITION = 6;
+ private static final String EXPECTED = "java.lang.RuntimeException: overloaded(String)42";
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.O)
+ .build();
+ }
+
+ public DexPcWithDebugInfoForOverloadedMethodsTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEmittedDebugInfoForOverloads()
+ throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MAIN)
+ .addKeepMainRule(MAIN)
+ .addKeepMethodRules(MAIN, "void overloaded(...)")
+ .addKeepAttributeLineNumberTable()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ internalOptions -> {
+ // TODO(b/37830524): Remove when activated.
+ internalOptions.enablePcDebugInfoOutput = true;
+ })
+ .run(parameters.getRuntime(), MAIN)
+ .assertFailureWithErrorThatMatches(containsString(EXPECTED))
+ .inspectOriginalStackTrace(
+ (stackTrace, inspector) -> {
+ assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
+ assertEquals("overloaded", stackTrace.get(0).methodName);
+ assertThat(stackTrace.get(0).fileName, not("Unknown Source"));
+ inspect(inspector);
+ })
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject throwingSubject =
+ codeInspector.clazz(MAIN).method("void", "overloaded", "java.lang.String");
+ assertThat(throwingSubject, isPresent());
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ Reference.methodFromMethod(
+ MAIN.getDeclaredMethod("inlinee", String.class)),
+ MINIFIED_LINE_POSITION,
+ 11),
+ InlinePosition.create(
+ Reference.methodFromMethod(
+ MAIN.getDeclaredMethod("overloaded", String.class)),
+ MINIFIED_LINE_POSITION,
+ 20));
+ RetraceMethodResult retraceResult =
+ throwingSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isThrow)
+ .collect(toSingle())
+ .retraceLinePosition(codeInspector.retrace());
+ assertThat(retraceResult, isInlineFrame());
+ assertThat(retraceResult, isInlineStack(inlineStack));
+ assertThat(
+ retraceResult,
+ isTopOfStackTrace(
+ stackTrace,
+ ImmutableList.of(MINIFIED_LINE_POSITION, MINIFIED_LINE_POSITION)));
+ });
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(MAIN);
+ assertThat(clazz, isPresent());
+ assertEquals(3, clazz.allMethods().size());
+ for (FoundMethodSubject method : clazz.allMethods()) {
+ if (method.getOriginalName().equals("main")) {
+ assertNull(method.getMethod().getCode().asDexCode().getDebugInfo());
+ } else {
+ assertEquals("overloaded", method.getOriginalName());
+ assertNotNull(method.getMethod().getCode().asDexCode().getDebugInfo());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTest.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTest.java
new file mode 100644
index 0000000..bca9629
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTest.java
@@ -0,0 +1,32 @@
+// 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.debuginfo;
+
+public class EnsureNoDebugInfoEmittedForPcOnlyTest {
+
+ public static void a() {
+ if (System.currentTimeMillis() > 0) {
+ throw new RuntimeException("Hello World!");
+ }
+ System.out.println("Foo");
+ System.out.println("Bar");
+ }
+
+ public static void b() {
+ a();
+ }
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ b();
+ } else {
+ other();
+ }
+ }
+
+ public static void other() {
+ System.out.println("FOO");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
new file mode 100644
index 0000000..c47f16f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -0,0 +1,114 @@
+// 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.debuginfo;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+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.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 EnsureNoDebugInfoEmittedForPcOnlyTestRunner extends TestBase {
+
+ private static final Class<?> MAIN = EnsureNoDebugInfoEmittedForPcOnlyTest.class;
+ private static final int INLINED_DEX_PC = 32;
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public EnsureNoDebugInfoEmittedForPcOnlyTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoEmittedDebugInfo()
+ throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MAIN)
+ .addKeepMainRule(MAIN)
+ .addKeepAttributeLineNumberTable()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ internalOptions -> {
+ // TODO(b/37830524): Remove when activated.
+ internalOptions.enablePcDebugInfoOutput = true;
+ })
+ .run(parameters.getRuntime(), MAIN)
+ .inspectOriginalStackTrace(
+ (stackTrace, inspector) -> {
+ assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
+ assertEquals("main", stackTrace.get(0).methodName);
+ inspect(inspector);
+ })
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ Reference.methodFromMethod(MAIN.getDeclaredMethod("a")),
+ INLINED_DEX_PC,
+ 11),
+ InlinePosition.create(
+ Reference.methodFromMethod(MAIN.getDeclaredMethod("b")),
+ INLINED_DEX_PC,
+ 18),
+ InlinePosition.create(
+ mainSubject.asFoundMethodSubject().asMethodReference(),
+ INLINED_DEX_PC,
+ 23));
+ RetraceMethodResult retraceResult =
+ mainSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isThrow)
+ .collect(toSingle())
+ .retracePcPosition(codeInspector.retrace(), mainSubject);
+ assertThat(retraceResult, isInlineFrame());
+ assertThat(retraceResult, isInlineStack(inlineStack));
+ assertThat(
+ retraceResult,
+ isTopOfStackTrace(
+ stackTrace,
+ ImmutableList.of(INLINED_DEX_PC, INLINED_DEX_PC, INLINED_DEX_PC)));
+ });
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(MAIN);
+ assertEquals(1, clazz.allMethods().size());
+ MethodSubject main = clazz.uniqueMethodWithName("main");
+ assertNull(main.getMethod().getCode().asDexCode().getDebugInfo());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/CharSequenceBackportJava11Test.java b/src/test/java/com/android/tools/r8/desugar/backports/CharSequenceBackportJava11Test.java
new file mode 100644
index 0000000..65f2f55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/CharSequenceBackportJava11Test.java
@@ -0,0 +1,37 @@
+// 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.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+@RunWith(Parameterized.class)
+public final class CharSequenceBackportJava11Test extends AbstractBackportTest {
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+ .build();
+ }
+
+ private static final Path TEST_JAR =
+ Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("backport" + JAR_EXTENSION);
+
+ public CharSequenceBackportJava11Test(TestParameters parameters) {
+ super(parameters, Character.class, TEST_JAR, "backport.CharSequenceBackportJava11Main");
+ // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+ // an actual API level, migrate these tests to CharacterBackportTest.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 2c7cbe5..83682b2 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -13,6 +13,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -34,8 +36,11 @@
// Check that the backported methods for each API level are are not present in the
// android.jar for that level.
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
+ InternalOptions options = new InternalOptions();
+ options.minApiLevel = apiLevel.getLevel();
List<DexMethod> backportedMethods =
- BackportedMethodRewriter.generateListOfBackportedMethods(apiLevel);
+ BackportedMethodRewriter.generateListOfBackportedMethods(
+ null, options, ThreadUtils.getExecutorService(options));
for (DexMethod method : backportedMethods) {
// Two different DexItemFactories are in play, but as toSourceString is used for lookup
// that is not an issue.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index eb22ea9..0097b3f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import java.util.ArrayList;
@@ -52,6 +53,26 @@
assertLines2By2Correct(d8TestRunResult.getStdOut());
}
+ @Test
+ public void testCustomCollectionSuperCallsR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ R8TestRunResult r8TestRunResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(CustomCollectionSuperCallsTest.class)
+ .addKeepMainRule(Executor.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess();
+ assertLines2By2Correct(r8TestRunResult.getStdOut());
+ }
+
static class Executor {
// ArrayList spliterator is Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 38e8bac..04630af 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -7,96 +7,137 @@
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
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.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
+import org.junit.BeforeClass;
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 CallBackConversionTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT = StringUtils.lines("0", "1", "0", "1");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public CallBackConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .setMinApi(MIN_SUPPORTED)
+ .addProgramClasses(CustomLibClass.class)
+ .compile()
+ .writeToZip();
+ }
+
+ private static void assertDuplicatedAPI(CodeInspector i) {
+ List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
+ assertEquals(2, virtualMethods.size());
+ assertTrue(
+ virtualMethods.stream()
+ .anyMatch(
+ m ->
+ m.getMethod()
+ .method
+ .proto
+ .parameters
+ .values[0]
+ .toString()
+ .equals("j$.util.function.Consumer")));
+ assertTrue(
+ virtualMethods.stream()
+ .anyMatch(
+ m ->
+ m.getMethod()
+ .method
+ .proto
+ .parameters
+ .values[0]
+ .toString()
+ .equals("java.util.function.Consumer")));
+ }
+
@Test
public void testCallBack() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Impl.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .inspect(
- i -> {
- // foo(j$) and foo(java)
- List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
- assertEquals(2, virtualMethods.size());
- assertTrue(
- virtualMethods.stream()
- .anyMatch(
- m ->
- m.getMethod()
- .method
- .proto
- .parameters
- .values[0]
- .toString()
- .equals("j$.util.function.Consumer")));
- assertTrue(
- virtualMethods.stream()
- .anyMatch(
- m ->
- m.getMethod()
- .method
- .proto
- .parameters
- .values[0]
- .toString()
- .equals("java.util.function.Consumer")));
- })
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
- .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+ .inspect(CallBackConversionTest::assertDuplicatedAPI)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testCallBackR8() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(Backend.DEX)
.addKeepMainRule(Impl.class)
.noMinification()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Impl.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::assertLibraryOverridesThere)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
- .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+ .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testCallBackR8Minifying() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(Backend.DEX)
.addKeepMainRule(Impl.class)
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Impl.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::assertLibraryOverridesThere)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
- .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
private void assertLibraryOverridesThere(CodeInspector i) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
index d4c7f53..4581211 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
@@ -4,30 +4,87 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
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.StringUtils;
import java.nio.file.Path;
import java.time.Clock;
+import java.util.List;
+import org.junit.BeforeClass;
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 ClockAPIConversionTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+ private static final String EXPECTED_RESULT = StringUtils.lines("Z", "Z", "true");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public ClockAPIConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testClock() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testClockD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(StringUtils.lines("Z", "Z", "true"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testClockR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Executor.class)
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
static class Executor {
@@ -47,6 +104,7 @@
// This class is convenient for easy testing. Each method plays the role of methods in the
// platform APIs for which argument/return values need conversion.
static class CustomLibClass {
+
@SuppressWarnings("all")
public static Clock getClock() {
return Clock.systemUTC();
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 b13ade6..835d1cb 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
@@ -4,42 +4,145 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import static com.android.tools.r8.Collectors.toSingle;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
+import org.junit.BeforeClass;
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 ConversionIntroduceInterfaceMethodTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "action called from j$ consumer",
+ "forEach called",
+ "action called from java consumer",
+ "forEach called");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public ConversionIntroduceInterfaceMethodTest(
+ TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .setMinApi(MIN_SUPPORTED)
+ .addProgramClasses(CustomLibClass.class)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testNoInterfaceMethods() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testNoInterfaceMethodsD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
- .addProgramClasses(
- MyCollectionInterface.class,
- MyCollectionInterfaceAbstract.class,
- MyCollection.class,
- Executor.class)
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(
- StringUtils.lines(
- "action called from j$ consumer",
- "forEach called",
- "action called from java consumer",
- "forEach called"));
+ .inspect(this::assertDoubleForEach)
+ .inspect(this::assertWrapperMethodsPresent)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private void assertDoubleForEach(CodeInspector inspector) {
+ System.out.println(inspector.allClasses().size());
+ FoundClassSubject myCollection =
+ inspector.allClasses().stream()
+ .filter(
+ c ->
+ c.getOriginalName()
+ .startsWith(
+ "com.android.tools.r8.desugar.desugaredlibrary.conversiontests")
+ && !c.getOriginalName().contains("Executor")
+ && !c.getOriginalName().contains("$-CC")
+ && !c.getDexClass().isInterface())
+ .collect(toSingle());
+ assertEquals(
+ "Missing duplicated forEach",
+ 2,
+ myCollection.getDexClass().virtualMethods().stream()
+ .filter(m -> m.method.name.toString().equals("forEach"))
+ .count());
+ }
+
+ private void assertWrapperMethodsPresent(CodeInspector inspector) {
+ List<FoundClassSubject> wrappers =
+ inspector.allClasses().stream()
+ .filter(
+ c ->
+ !c.getFinalName()
+ .startsWith(
+ "com.android.tools.r8.desugar.desugaredlibrary.conversiontests"))
+ .collect(Collectors.toList());
+ for (FoundClassSubject wrapper : wrappers) {
+ assertTrue(wrapper.virtualMethods().size() > 0);
+ }
+ }
+
+ @Test
+ public void testNoInterfaceMethodsR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
+ .addKeepMainRule(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertDoubleForEach)
+ .inspect(this::assertWrapperMethodsPresent)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
static class CustomLibClass {
@@ -91,7 +194,6 @@
return false;
}
- @NotNull
@Override
public Iterator<E> iterator() {
return null;
@@ -142,12 +244,4 @@
@Override
public void clear() {}
}
-
- interface MyCollectionInterfaceAbstract<E> extends Collection<E> {
-
- // The following method override a method from Iterable and use a desugared type.
- // API conversion is required.
- @Override
- void forEach(Consumer<? super E> action);
- }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
index 2699bd3..b62045b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -6,33 +6,94 @@
import static junit.framework.TestCase.assertEquals;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
+import org.junit.BeforeClass;
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 DuplicateAPIProgramTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public DuplicateAPIProgramTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testMap() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testMapD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
String stdOut =
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Executor.class, MyMap.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::assertDupMethod)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdOut);
+ }
+
+ @Test
+ public void testMapR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ String stdOut =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Executor.class)
+ .addProgramClasses(Executor.class, MyMap.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertDupMethod)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
.assertSuccess()
.getStdOut();
assertLines2By2Correct(stdOut);
@@ -72,6 +133,7 @@
// This class is convenient for easy testing. Each method plays the role of methods in the
// platform APIs for which argument/return values need conversion.
static class CustomLibClass {
+
@SuppressWarnings("WeakerAccess")
public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
map.forEach(consumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 1dfbc73..66cdcbc 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -6,11 +6,12 @@
import static junit.framework.TestCase.assertEquals;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -27,26 +28,84 @@
import java.util.function.LongSupplier;
import java.util.stream.Collectors;
import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
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 FunctionConversionTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(" true true true", "2", "false", "3", "true", "5", "42.0");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public FunctionConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testFunctionComposition() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testFunctionCompositionD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(
Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::assertSingleWrappers)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(
- StringUtils.lines("Object1 Object2 Object3", "2", "false", "3", "true", "5", "42.0"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testFunctionCompositionR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Executor.class)
+ .addProgramClasses(
+ Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
private void assertSingleWrappers(CodeInspector i) {
@@ -71,12 +130,14 @@
@Test
public void testWrapperWithChecksum() throws Exception {
+ Assume.assumeTrue(
+ shrinkDesugaredLibrary && parameters.getApiLevel().getLevel() <= MIN_SUPPORTED.getLevel());
testForD8()
.addProgramClasses(
Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
.addLibraryClasses(CustomLibClass.class)
- .setMinApi(AndroidApiLevel.B)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
.setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
.compile()
.inspect(
@@ -145,11 +206,12 @@
@Override
public String toString() {
- return field.field.getClass().getSimpleName()
+ return " "
+ + (field.field.getClass() == Object1.class)
+ " "
- + field.getClass().getSimpleName()
+ + (field.getClass() == Object2.class)
+ " "
- + getClass().getSimpleName();
+ + (getClass() == Object3.class);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
index eea96ae..10823c3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
@@ -7,38 +7,95 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
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.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.google.common.collect.Sets;
import java.nio.file.Path;
+import java.util.List;
import java.util.Set;
import java.util.function.Function;
+import org.junit.BeforeClass;
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 MoreFunctionConversionTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT = StringUtils.lines("6", "6", "6", "6", "6");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public MoreFunctionConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testFunction() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testFunctionD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
D8TestCompileResult compileResult =
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile();
Path program = compileResult.writeToZip();
- assertNoDuplicateLambdas(program, customLib);
+ assertNoDuplicateLambdas(program, CUSTOM_LIB);
compileResult
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(StringUtils.lines("6", "6", "6", "6", "6"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testFunctionR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class)
+ .addKeepMainRule(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
// If we have the exact same lambda in both, but one implements j$..Function and the other
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index bebefd1..af4088e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -5,15 +5,18 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.IntSummaryStatistics;
+import java.util.List;
import java.util.LongSummaryStatistics;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -23,40 +26,72 @@
public class SummaryStatisticsConversionTest extends DesugaredLibraryTestBase {
private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0");
+ private static Path CUSTOM_LIB;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- // Below 7 XXXSummaryStatistics are not present and conversions are pointless.
- return getTestParameters()
- .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
- .withAllApiLevels()
- .build();
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
}
- public SummaryStatisticsConversionTest(TestParameters parameters) {
+ public SummaryStatisticsConversionTest(
+ TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
this.parameters = parameters;
}
- @Test
- public void testStats() throws Exception {
- Path customLib =
- testForD8()
- .setMinApi(parameters.getApiLevel())
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
.addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
.compile()
.writeToZip();
+ }
+
+ @Test
+ public void testStatsD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramClasses(Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
- .addRunClasspathFiles(customLib)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutput(
- StringUtils.lines(
- "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0"));
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testStatsR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class)
+ .addKeepMainRule(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
index 3589bb3..4dbcccb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
@@ -4,46 +4,130 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.SummaryStatisticsConversionTest.CustomLibClass;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
import java.time.ZoneId;
+import java.util.List;
+import org.junit.BeforeClass;
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 TryCatchTimeConversionTest extends DesugaredLibraryTestBase {
- @Test
- public void testBaseline() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
- testForD8()
- .setMinApi(AndroidApiLevel.B)
- .addProgramClasses(BaselineExecutor.class)
- .addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), BaselineExecutor.class)
- .assertSuccessWithOutput(StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT"));
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT");
+ private static final String EXPECTED_RESULT_EXCEPTION =
+ StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT", "Exception caught");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public TryCatchTimeConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
}
@Test
- public void testTryCatch() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testBaselineD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(BaselineExecutor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), BaselineExecutor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testBaselineR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(BaselineExecutor.class)
+ .addKeepMainRule(BaselineExecutor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), BaselineExecutor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testTryCatchD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(TryCatchExecutor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), TryCatchExecutor.class)
- .assertSuccessWithOutput(
- StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT", "Exception caught"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), TryCatchExecutor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testTryCatchR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TryCatchExecutor.class)
+ .addKeepMainRule(TryCatchExecutor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), TryCatchExecutor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT_EXCEPTION);
}
@SuppressWarnings("WeakerAccess")
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
index 646d20f..2f10f3a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
@@ -4,31 +4,89 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.SummaryStatisticsConversionTest.CustomLibClass;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
+import org.junit.BeforeClass;
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 UnwrapConversionTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT = StringUtils.lines("true", "true");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public UnwrapConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(MIN_SUPPORTED)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testUnwrap() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testUnwrapD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(StringUtils.lines("true", "true"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testUnwrapR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class)
+ .addKeepMainRule(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
static class Executor {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..ddcb725
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.enumunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public ComparisonEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class)
+ .addKeepMainRules(INPUTS)
+ .addKeepRules(KEEP_ENUM)
+ .enableInliningAnnotations()
+ .addOptionsModification(this::enableEnumOptions)
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ assertThat(
+ inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck"),
+ isPresent());
+ assertThat(
+ inspector.clazz(EnumComparison.class).uniqueMethodWithName("check"),
+ isPresent());
+ });
+ for (Class<?> input : INPUTS) {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
+ .run(parameters.getRuntime(), input)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ static class NullCheck {
+
+ enum MyEnum {
+ A,
+ B
+ }
+
+ public static void main(String[] args) {
+ System.out.println(nullCheck(MyEnum.A));
+ System.out.println(false);
+ System.out.println(nullCheck(MyEnum.B));
+ System.out.println(false);
+ System.out.println(nullCheck(null));
+ System.out.println(true);
+ }
+
+ // Do not resolve the == with constants after inlining.
+ @NeverInline
+ static boolean nullCheck(MyEnum e) {
+ return e == null;
+ }
+ }
+
+ static class EnumComparison {
+
+ enum MyEnum {
+ A,
+ B
+ }
+
+ public static void main(String[] args) {
+ System.out.println(check(MyEnum.A));
+ System.out.println(false);
+ System.out.println(check(MyEnum.B));
+ System.out.println(true);
+ }
+
+ // Do not resolve the == with constants after inlining.
+ @NeverInline
+ static boolean check(MyEnum e) {
+ return e == MyEnum.B;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
index e4f7bd1..c7ad117 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -23,8 +23,6 @@
public class FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
private static final Class<?>[] FAILURES = {
- NullCheck.class,
- Check.class,
InstanceFieldPutObject.class,
StaticFieldPutObject.class,
ToString.class,
@@ -74,8 +72,6 @@
private void assertEnumsAsExpected(CodeInspector inspector) {
// Check all as expected (else we test nothing)
- assertTrue(inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck").isPresent());
- assertTrue(inspector.clazz(Check.class).uniqueMethodWithName("check").isPresent());
assertEquals(
1, inspector.clazz(InstanceFieldPutObject.class).getDexClass().instanceFields().size());
@@ -85,50 +81,6 @@
assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
}
- @SuppressWarnings("ConstantConditions")
- static class NullCheck {
-
- enum MyEnum {
- A,
- B,
- C
- }
-
- public static void main(String[] args) {
- System.out.println(nullCheck(MyEnum.A));
- System.out.println(false);
- System.out.println(nullCheck(null));
- System.out.println(true);
- }
-
- // Do not resolve the == with constants after inlining.
- @NeverInline
- static boolean nullCheck(MyEnum e) {
- return e == null;
- }
- }
-
- static class Check {
-
- enum MyEnum {
- A,
- B,
- C
- }
-
- public static void main(String[] args) {
- MyEnum e1 = MyEnum.A;
- System.out.println(check(e1));
- System.out.println(false);
- }
-
- // Do not resolve the == with constants after inlining.
- @NeverInline
- static boolean check(MyEnum e) {
- return e == MyEnum.B;
- }
- }
-
static class InstanceFieldPutObject {
enum MyEnum {
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index 90218e0..1b5ffa9 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -24,7 +24,7 @@
@BeforeClass
public static void makeAppInfo() throws Exception {
InternalOptions options = new InternalOptions();
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(
AndroidApp.builder()
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
index 44b676a..642c263 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.ByteDataView;
@@ -63,6 +64,7 @@
@Test
public void testDXBehavior() throws Exception {
+ assumeTrue(ToolHelper.artSupported());
testForDX()
.addProgramFiles(inputJar)
.run(Main.class)
diff --git a/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java b/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
new file mode 100644
index 0000000..34d0e01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
@@ -0,0 +1,69 @@
+// 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.internal;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Iosched2019Test extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public Iosched2019Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ assumeTrue(ToolHelper.isLocalDevelopment());
+
+ List<Path> programFiles =
+ Files.list(Paths.get(ToolHelper.THIRD_PARTY_DIR, "iosched_2019"))
+ .filter(path -> path.toString().endsWith(".jar"))
+ .collect(Collectors.toList());
+ assertEquals(155, programFiles.size());
+
+ testForR8(parameters.getBackend())
+ .addProgramFiles(programFiles)
+ .addKeepRuleFiles(
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "iosched_2019", "proguard-rules.pro"))
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .allowDiagnosticMessages()
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString("Ignoring option: "),
+ containsString("Proguard configuration rule does not match anything: ")))
+ .assertAllWarningMessagesMatch(
+ anyOf(
+ containsString("Missing class: "),
+ containsString("required for default or static interface methods desugaring"),
+ equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
+ .assertNoErrorMessages();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 78585b8..0924546 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -60,13 +60,15 @@
// Check lookup targets with include method.
Set<DexEncodedMethod> targets =
- appInfo.resolveMethodOnClass(clazz, method.method).lookupVirtualTargets(appInfo);
+ appInfo.resolveMethodOnClass(clazz, method.method).lookupVirtualDispatchTargets(appInfo);
assertTrue(targets.contains(method));
}
private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
Set<DexEncodedMethod> targets =
- appInfo.resolveMethodOnInterface(clazz, method.method).lookupInterfaceTargets(appInfo);
+ appInfo
+ .resolveMethodOnInterface(clazz, method.method)
+ .lookupVirtualDispatchTargets(appInfo);
if (appInfo.subtypes(method.method.holder).stream()
.allMatch(t -> appInfo.definitionFor(t).isInterface())) {
assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 34d7c61..a2b9dce 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -48,7 +48,7 @@
List<IRCode> additionalCode)
throws ExecutionException {
AppView<AppInfoWithSubtyping> appView =
- AppView.createForR8(new AppInfoWithSubtyping(application), options);
+ AppView.createForR8(new AppInfoWithSubtyping(application.asDirect()), options);
appView.setAppServices(AppServices.builder(appView).build());
ExecutorService executorService = ThreadUtils.getExecutorService(options);
appView.setRootSet(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index 7321e79..ebfb64f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.utils.AndroidApp;
@@ -64,7 +64,7 @@
@Before
public void setup() throws Exception {
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(app, options, Timing.empty()).read().toDirect();
appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 979de40..6a4bd17 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -18,11 +18,11 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -122,7 +122,7 @@
? ClassFileConsumer.emptyConsumer()
: DexIndexedConsumer.emptyConsumer();
Timing timing = Timing.empty();
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(
AndroidApp.builder()
.addClassProgramData(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index e0465b3..cac6ef5 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -17,9 +17,9 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -50,7 +50,7 @@
D8Command.Builder d8CommandBuilder = D8Command.builder();
d8CommandBuilder.addProgramFiles(testClassPaths);
AndroidApp testClassApp = ToolHelper.runD8(d8CommandBuilder);
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(
AndroidApp.builder(testClassApp)
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
index f38629f..c9ae9fd 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
-import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
@@ -55,15 +54,15 @@
method.addCallerConcurrently(forceInlinedMethod);
// Check that the cycle eliminator finds the cycle.
- CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
- assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+ CycleEliminator cycleEliminator = new CycleEliminator();
+ assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
// The edge from method to forceInlinedMethod should be removed to ensure that force inlining
// will work.
assertTrue(forceInlinedMethod.isLeaf());
// Check that the cycle eliminator agrees that there are no more cycles left.
- assertEquals(0, cycleEliminator.breakCycles().numberOfRemovedEdges());
+ assertEquals(0, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
}
}
@@ -76,11 +75,10 @@
forceInlinedMethod.addCallerConcurrently(method);
method.addCallerConcurrently(forceInlinedMethod);
- CycleEliminator cycleEliminator =
- new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
+ CycleEliminator cycleEliminator = new CycleEliminator();
try {
- cycleEliminator.breakCycles();
+ cycleEliminator.breakCycles(ImmutableList.of(method, forceInlinedMethod));
fail("Force inlining should fail");
} catch (CompilationError e) {
assertThat(e.toString(), containsString(CycleEliminator.CYCLIC_FORCE_INLINING_MESSAGE));
@@ -170,9 +168,9 @@
}
// Check that the cycle eliminator finds the cycles.
- CycleEliminator cycleEliminator =
- new CycleEliminator(configuration.nodes, new InternalOptions());
- int numberOfCycles = cycleEliminator.breakCycles().numberOfRemovedEdges();
+ CycleEliminator cycleEliminator = new CycleEliminator();
+ int numberOfCycles =
+ cycleEliminator.breakCycles(configuration.nodes).numberOfRemovedCallEdges();
if (numberOfCycles == 1) {
// If only one cycle was removed, then it must be the edge from n1 -> n2 that was removed.
assertTrue(n1.isLeaf());
@@ -182,7 +180,7 @@
}
// Check that the cycle eliminator agrees that there are no more cycles left.
- assertEquals(0, cycleEliminator.breakCycles().numberOfRemovedEdges());
+ assertEquals(0, cycleEliminator.breakCycles(configuration.nodes).numberOfRemovedCallEdges());
// Check that force inlining is guaranteed to succeed.
if (configuration.test != null) {
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index 2d1f0e5..4088693 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -11,16 +11,12 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
-import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
import java.util.Set;
import java.util.TreeSet;
import org.junit.Test;
public class NodeExtractionTest extends CallGraphTestBase {
- private InternalOptions options = new InternalOptions();
-
// Note that building a test graph is intentionally repeated to avoid race conditions and/or
// non-deterministic test results due to cycle elimination.
@@ -50,24 +46,21 @@
nodes.add(n5);
nodes.add(n6);
- Set<Node> wave = Sets.newIdentityHashSet();
-
- PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+ CallGraph cg = new CallGraph(nodes);
+ Set<DexEncodedMethod> wave = cg.extractLeaves();
assertEquals(3, wave.size());
- assertThat(wave, hasItem(n3));
- assertThat(wave, hasItem(n4));
- assertThat(wave, hasItem(n6));
- wave.clear();
+ assertThat(wave, hasItem(n3.method));
+ assertThat(wave, hasItem(n4.method));
+ assertThat(wave, hasItem(n6.method));
- PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+ wave = cg.extractLeaves();
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n2));
- assertThat(wave, hasItem(n5));
- wave.clear();
+ assertThat(wave, hasItem(n2.method));
+ assertThat(wave, hasItem(n5.method));
- PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+ wave = cg.extractLeaves();
assertEquals(1, wave.size());
- assertThat(wave, hasItem(n1));
+ assertThat(wave, hasItem(n1.method));
assertTrue(nodes.isEmpty());
}
@@ -99,27 +92,26 @@
n1.addCallerConcurrently(n3);
n3.method.getMutableOptimizationInfo().markForceInline();
- CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
- assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+ CycleEliminator cycleEliminator = new CycleEliminator();
+ assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
- Set<Node> wave = Sets.newIdentityHashSet();
-
- PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+ CallGraph cg = new CallGraph(nodes);
+ Set<DexEncodedMethod> wave = cg.extractLeaves();
assertEquals(3, wave.size());
- assertThat(wave, hasItem(n3));
- assertThat(wave, hasItem(n4));
- assertThat(wave, hasItem(n6));
+ assertThat(wave, hasItem(n3.method));
+ assertThat(wave, hasItem(n4.method));
+ assertThat(wave, hasItem(n6.method));
wave.clear();
- PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+ wave = cg.extractLeaves();
assertEquals(2, wave.size());
- assertThat(wave, hasItem(n2));
- assertThat(wave, hasItem(n5));
+ assertThat(wave, hasItem(n2.method));
+ assertThat(wave, hasItem(n5.method));
wave.clear();
- PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+ wave = cg.extractLeaves();
assertEquals(1, wave.size());
- assertThat(wave, hasItem(n1));
+ assertThat(wave, hasItem(n1.method));
assertTrue(nodes.isEmpty());
}
@@ -150,21 +142,17 @@
nodes.add(n6);
CallGraph callGraph = new CallGraph(nodes, null);
- Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-
- wave.addAll(callGraph.extractRoots());
+ Set<DexEncodedMethod> wave = callGraph.extractRoots();
assertEquals(2, wave.size());
assertThat(wave, hasItem(n1.method));
assertThat(wave, hasItem(n5.method));
- wave.clear();
- wave.addAll(callGraph.extractRoots());
+ wave = callGraph.extractRoots();
assertEquals(2, wave.size());
assertThat(wave, hasItem(n2.method));
assertThat(wave, hasItem(n6.method));
- wave.clear();
- wave.addAll(callGraph.extractRoots());
+ wave = callGraph.extractRoots();
assertEquals(2, wave.size());
assertThat(wave, hasItem(n3.method));
assertThat(wave, hasItem(n4.method));
@@ -199,25 +187,21 @@
n1.addCallerConcurrently(n3);
n3.method.getMutableOptimizationInfo().markForceInline();
- CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
- assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+ CycleEliminator cycleEliminator = new CycleEliminator();
+ assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
CallGraph callGraph = new CallGraph(nodes, null);
- Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-
- wave.addAll(callGraph.extractRoots());
+ Set<DexEncodedMethod> wave = callGraph.extractRoots();
assertEquals(2, wave.size());
assertThat(wave, hasItem(n1.method));
assertThat(wave, hasItem(n5.method));
- wave.clear();
- wave.addAll(callGraph.extractRoots());
+ wave = callGraph.extractRoots();
assertEquals(2, wave.size());
assertThat(wave, hasItem(n2.method));
assertThat(wave, hasItem(n6.method));
- wave.clear();
- wave.addAll(callGraph.extractRoots());
+ wave = callGraph.extractRoots();
assertEquals(2, wave.size());
assertThat(wave, hasItem(n3.method));
assertThat(wave, hasItem(n4.method));
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 6b18b6b..1955c47 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -14,9 +14,9 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.Enqueuer;
@@ -42,7 +42,8 @@
public PartialCallGraphTest() throws Exception {
Timing timing = Timing.empty();
AndroidApp app = testForD8().addProgramClasses(TestClass.class).compile().app;
- DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+ DirectMappedDexApplication application =
+ new ApplicationReader(app, options, timing).read().toDirect();
AppView<AppInfoWithSubtyping> appView =
AppView.createForR8(new AppInfoWithSubtyping(application), options);
appView.setAppServices(AppServices.builder(appView).build());
@@ -80,24 +81,20 @@
assertNotNull(m5);
assertNotNull(m6);
- Set<Node> wave = Sets.newIdentityHashSet();
-
- PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
+ Set<DexEncodedMethod> wave = cg.extractLeaves();
assertEquals(4, wave.size()); // including <init>
- assertThat(wave, hasItem(m3));
- assertThat(wave, hasItem(m4));
- assertThat(wave, hasItem(m6));
- wave.clear();
+ assertThat(wave, hasItem(m3.method));
+ assertThat(wave, hasItem(m4.method));
+ assertThat(wave, hasItem(m6.method));
- PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
+ wave = cg.extractLeaves();
assertEquals(2, wave.size());
- assertThat(wave, hasItem(m2));
- assertThat(wave, hasItem(m5));
- wave.clear();
+ assertThat(wave, hasItem(m2.method));
+ assertThat(wave, hasItem(m5.method));
- PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
+ wave = cg.extractLeaves();
assertEquals(1, wave.size());
- assertThat(wave, hasItem(m1));
+ assertThat(wave, hasItem(m1.method));
assertTrue(cg.nodes.isEmpty());
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CharSequenceMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CharSequenceMethods.java
new file mode 100644
index 0000000..5030c6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CharSequenceMethods.java
@@ -0,0 +1,25 @@
+// 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.desugar.backports;
+
+public final class CharSequenceMethods {
+
+ public static int compare(CharSequence a, CharSequence b) {
+ int aLen = a.length(); // implicit null check
+ int bLen = b.length(); // implicit null check
+ if (a == b) {
+ return 0;
+ }
+ for (int i = 0, stop = Math.min(aLen, bLen); i < stop; i++) {
+ char aChar = a.charAt(i);
+ char bChar = b.charAt(i);
+ if (aChar != bChar) {
+ return (int) aChar - (int) bChar; // See CharacterMethods.compare
+ }
+ }
+ // Prefixes match so we need to return a value based on which is longer.
+ return aLen - bLen;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index e5c7948..306c93a 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -71,6 +71,7 @@
ImmutableList.of(
BooleanMethods.class,
ByteMethods.class,
+ CharSequenceMethods.class,
CharacterMethods.class,
CloseResourceMethod.class,
CollectionMethods.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java b/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
new file mode 100644
index 0000000..e89e6cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
@@ -0,0 +1,388 @@
+// 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;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class B148366506 extends TestBase implements Opcodes {
+
+ private final TestParameters parameters;
+ private final CompilationMode compilationMode;
+
+ @Parameterized.Parameters(name = "{0}, {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), CompilationMode.values());
+ }
+
+ public B148366506(TestParameters parameters, CompilationMode compilationMode) {
+ this.parameters = parameters;
+ this.compilationMode = compilationMode;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForD8()
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .setMode(compilationMode)
+ .compile();
+ }
+
+ // Code from b/148366506.
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_7,
+ ACC_PUBLIC | ACC_SUPER,
+ "d/b/c/e/e/a/b/a",
+ null,
+ "java/lang/Object",
+ new String[] {"com/microsoft/identity/common/adal/internal/cache/IStorageHelper"});
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_SYNCHRONIZED, "e", "()Ljavax/crypto/SecretKey;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Exception");
+ Label label3 = new Label();
+ Label label4 = new Label();
+ Label label5 = new Label();
+ methodVisitor.visitTryCatchBlock(label3, label4, label5, null);
+ Label label6 = new Label();
+ methodVisitor.visitTryCatchBlock(label6, label5, label2, "java/lang/Exception");
+ Label label7 = new Label();
+ Label label8 = new Label();
+ Label label9 = new Label();
+ methodVisitor.visitTryCatchBlock(label7, label8, label9, null);
+ Label label10 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label10);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"java/lang/Exception"});
+ methodVisitor.visitInsn(ATHROW);
+ Label label11 = new Label();
+ methodVisitor.visitLabel(label11);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitInsn(ICONST_0);
+ Label label12 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label12);
+ Label label13 = new Label();
+ methodVisitor.visitLabel(label13);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"d/b/c/e/e/a/b/a"}, 0, null);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD, "d/b/c/e/e/a/b/a", "mContext", "Landroid/content/Context;");
+ methodVisitor.visitLabel(label1);
+ Label label14 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label14);
+ Label label15 = new Label();
+ methodVisitor.visitLabel(label15);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"android/content/Context"});
+ methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 111);
+ methodVisitor.visitInsn(IADD);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitIntInsn(SIPUSH, 128);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ Label label16 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label16);
+ Label label17 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label17);
+ methodVisitor.visitLabel(label16);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"android/content/Context"});
+ methodVisitor.visitJumpInsn(GOTO, label3);
+ Label label18 = new Label();
+ methodVisitor.visitLabel(label18);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 0,
+ new Object[] {},
+ 2,
+ new Object[] {"d/b/c/e/e/a/b/a", "java/lang/String"});
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitJumpInsn(GOTO, label6);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"d/b/c/e/e/a/b/a"},
+ 1,
+ new Object[] {"android/content/Context"});
+ methodVisitor.visitLdcInsn("android.content.Context");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/lang/Class",
+ "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;",
+ false);
+ methodVisitor.visitLdcInsn("getPackageName");
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/Class",
+ "getMethod",
+ "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
+ false);
+ methodVisitor.visitInsn(SWAP);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/reflect/Method",
+ "invoke",
+ "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "d/b/c/e/e/a/b/a", "getSecretKeyData", "(Ljava/lang/String;)[B", false);
+ methodVisitor.visitVarInsn(ASTORE, 2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ Label label19 = new Label();
+ methodVisitor.visitJumpInsn(IFNONNULL, label19);
+ Label label20 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label20);
+ methodVisitor.visitLabel(label19);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ Label label21 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label21);
+ Label label22 = new Label();
+ methodVisitor.visitLabel(label22);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitLdcInsn("A001");
+ methodVisitor.visitJumpInsn(GOTO, label6);
+ methodVisitor.visitLabel(label12);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 2, new Object[] {"d/b/c/e/e/a/b/a", Opcodes.INTEGER});
+ Label label23 = new Label();
+ methodVisitor.visitTableSwitchInsn(0, 1, label22, new Label[] {label22, label23});
+ Label label24 = new Label();
+ methodVisitor.visitLabel(label24);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitJumpInsn(GOTO, label12);
+ methodVisitor.visitLabel(label9);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 0,
+ new Object[] {},
+ 2,
+ new Object[] {"d/b/c/e/e/a/b/a", "java/lang/String"});
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "d/b/c/e/e/a/b/a",
+ "loadSecretKeyForEncryption",
+ "(Ljava/lang/String;)Ljavax/crypto/SecretKey;",
+ false);
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;", false);
+ methodVisitor.visitInsn(DUP);
+ Label label25 = new Label();
+ methodVisitor.visitJumpInsn(IFNULL, label25);
+ Label label26 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label26);
+ methodVisitor.visitLabel(label25);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 0,
+ new Object[] {},
+ 2,
+ new Object[] {"java/lang/Throwable", "java/lang/Throwable"});
+ Label label27 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label27);
+ Label label28 = new Label();
+ methodVisitor.visitLabel(label28);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"d/b/c/e/e/a/b/a"},
+ 1,
+ new Object[] {"android/content/Context"});
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitJumpInsn(GOTO, label15);
+ Label label29 = new Label();
+ methodVisitor.visitLabel(label29);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitLdcInsn("U001");
+ methodVisitor.visitJumpInsn(GOTO, label18);
+ methodVisitor.visitLabel(label10);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"d/b/c/e/e/a/b/a"}, 0, null);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitInsn(POP);
+ Label label30 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label30);
+ methodVisitor.visitLabel(label20);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitInsn(ICONST_1);
+ Label label31 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label31);
+ Label label32 = new Label();
+ methodVisitor.visitLabel(label32);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"d/b/c/e/e/a/b/a"},
+ 1,
+ new Object[] {"android/content/Context"});
+ methodVisitor.visitJumpInsn(GOTO, label28);
+ methodVisitor.visitLabel(label31);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 2, new Object[] {"d/b/c/e/e/a/b/a", Opcodes.INTEGER});
+ Label label33 = new Label();
+ methodVisitor.visitTableSwitchInsn(0, 1, label29, new Label[] {label29, label33});
+ methodVisitor.visitLabel(label26);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 0,
+ new Object[] {},
+ 2,
+ new Object[] {"java/lang/Throwable", "java/lang/Throwable"});
+ methodVisitor.visitInsn(SWAP);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label21);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitJumpInsn(GOTO, label31);
+ methodVisitor.visitLabel(label17);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"d/b/c/e/e/a/b/a"},
+ 1,
+ new Object[] {"android/content/Context"});
+ methodVisitor.visitJumpInsn(GOTO, label3);
+ methodVisitor.visitLabel(label30);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 47);
+ methodVisitor.visitInsn(IADD);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitIntInsn(SIPUSH, 128);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ Label label34 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label34);
+ methodVisitor.visitJumpInsn(GOTO, label8);
+ methodVisitor.visitLabel(label34);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitJumpInsn(GOTO, label13);
+ methodVisitor.visitLabel(label33);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 35);
+ methodVisitor.visitInsn(IADD);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitIntInsn(SIPUSH, 128);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ Label label35 = new Label();
+ methodVisitor.visitJumpInsn(IFEQ, label35);
+ methodVisitor.visitJumpInsn(GOTO, label24);
+ methodVisitor.visitLabel(label35);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitJumpInsn(GOTO, label11);
+ methodVisitor.visitLabel(label14);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"d/b/c/e/e/a/b/a"},
+ 1,
+ new Object[] {"android/content/Context"});
+ methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 45);
+ methodVisitor.visitInsn(IADD);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitIntInsn(SIPUSH, 128);
+ methodVisitor.visitInsn(IREM);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitInsn(IREM);
+ Label label36 = new Label();
+ methodVisitor.visitJumpInsn(IFEQ, label36);
+ methodVisitor.visitJumpInsn(GOTO, label32);
+ methodVisitor.visitLabel(label36);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"android/content/Context"});
+ methodVisitor.visitJumpInsn(GOTO, label28);
+ methodVisitor.visitLabel(label27);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 0,
+ new Object[] {},
+ 2,
+ new Object[] {"java/lang/Throwable", "java/lang/Throwable"});
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label23);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+ methodVisitor.visitLdcInsn("A001");
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitInsn(ARRAYLENGTH);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitJumpInsn(GOTO, label6);
+ methodVisitor.visitLabel(label8);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"d/b/c/e/e/a/b/a"}, 0, null);
+ methodVisitor.visitJumpInsn(GOTO, label13);
+ methodVisitor.visitMaxs(4, 3);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
index a24de2f..41eba43 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -9,9 +9,9 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.utils.AndroidApp;
@@ -27,7 +27,7 @@
@BeforeClass
public static void makeAppInfo() throws Exception {
InternalOptions options = new InternalOptions();
- DexApplication application =
+ DirectMappedDexApplication application =
new ApplicationReader(
AndroidApp.builder().addLibraryFiles(ToolHelper.getDefaultAndroidJar()).build(),
options,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 838a681..15456be 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -20,7 +20,8 @@
Timing timing = Timing.empty();
AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
InternalOptions options = new InternalOptions();
- DexApplication dexApplication = new ApplicationReader(app, options, timing).read().toDirect();
+ DirectMappedDexApplication dexApplication =
+ new ApplicationReader(app, options, timing).read().toDirect();
AppView<?> appView = AppView.createForD8(new AppInfoWithSubtyping(dexApplication), options);
appView.setAppServices(AppServices.builder(appView).build());
return appView;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index 7db31b4..a963edf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -46,6 +47,7 @@
.withCfRuntimes()
// Objects#requireNonNull will be desugared VMs older than API level K.
.withDexRuntimesStartingFromExcluding(Version.V4_4_4)
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
.build();
}
@@ -111,7 +113,7 @@
testForD8()
.debug()
.addProgramClassesAndInnerClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 2, 1);
@@ -120,7 +122,7 @@
testForD8()
.release()
.addProgramClassesAndInnerClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 0, 1);
@@ -136,7 +138,7 @@
.enableMemberValuePropagationAnnotations()
.addKeepMainRule(MAIN)
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 0, 0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
index c00b563..d8b26ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -32,7 +33,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public InlineInvokeWithNullableReceiverTest(TestParameters parameters) {
@@ -45,7 +46,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(InlineInvokeWithNullableReceiverTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyMethodHasBeenInlined);
@@ -67,7 +68,21 @@
assertThat(methodSubject, isPresent());
// A `throw` instruction should have been synthesized into main().
- assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isThrow));
+ if (parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)) {
+ assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isInvokeStatic));
+ } else {
+ assertTrue(
+ methodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeVirtual)
+ .anyMatch(
+ method ->
+ method
+ .getMethod()
+ .toSourceString()
+ .equals("java.lang.Class java.lang.Object.getClass()")));
+ }
// Class A is still present because the instance flows into a phi that has a null-check.
ClassSubject otherClassSubject = inspector.clazz(A.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstClassMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstClassMemberValuePropagationTest.java
new file mode 100644
index 0000000..af4af19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstClassMemberValuePropagationTest.java
@@ -0,0 +1,98 @@
+// 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.InstructionSubject;
+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 ConstClassMemberValuePropagationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ConstClassMemberValuePropagationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ConstClassMemberValuePropagationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertThat(
+ testClassSubject.uniqueMethodWithName("deadDueToFieldValuePropagation"), not(isPresent()));
+ assertThat(
+ testClassSubject.uniqueMethodWithName("deadDueToReturnValuePropagation"), not(isPresent()));
+
+ // Verify that there are no more conditional instructions.
+ MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+ }
+
+ static class TestClass {
+
+ static Class<?> INSTANCE = TestClass.class;
+
+ public static void main(String[] args) {
+ if (INSTANCE == TestClass.class) {
+ System.out.print("Hello");
+ } else {
+ deadDueToFieldValuePropagation();
+ }
+ if (get() == TestClass.class) {
+ System.out.println(" world!");
+ } else {
+ deadDueToReturnValuePropagation();
+ }
+ }
+
+ @NeverInline
+ static Class<?> get() {
+ return TestClass.class;
+ }
+
+ @NeverInline
+ static void deadDueToFieldValuePropagation() {
+ throw new RuntimeException();
+ }
+
+ @NeverInline
+ static void deadDueToReturnValuePropagation() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
index 4b8ceff..d230621 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -61,8 +62,8 @@
assertTrue(readFieldsWaveIndex >= 0);
int writeFieldsWaveIndex =
IterableUtils.firstIndexMatching(waves, wavePredicate.apply("writeFields"));
- // TODO(b/144739580): Should be false.
- assertTrue(writeFieldsWaveIndex >= readFieldsWaveIndex);
+ assertTrue(writeFieldsWaveIndex >= 0);
+ assertTrue(writeFieldsWaveIndex < readFieldsWaveIndex);
};
})
.enableInliningAnnotations()
@@ -79,8 +80,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
- // TODO(b/144739580): Should be absent.
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/RequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/RequireNonNullTest.java
new file mode 100644
index 0000000..a6f8740
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/RequireNonNullTest.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.ir.optimize.nonnull;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Objects;
+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 RequireNonNullTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public RequireNonNullTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(RequireNonNullTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Live!", "Live!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Object a = System.currentTimeMillis() >= 0 ? new Object() : null;
+ Objects.requireNonNull(a);
+ if (a != null) {
+ live();
+ } else {
+ dead();
+ }
+
+ Object b = System.currentTimeMillis() >= 0 ? new Object() : null;
+ Object c = Objects.requireNonNull(b);
+ if (c != null) {
+ live();
+ } else {
+ dead();
+ }
+ }
+
+ @NeverInline
+ static void live() {
+ System.out.println("Live!");
+ }
+
+ @NeverInline
+ static void dead() {
+ System.out.println("Dead!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index cf4a324..c2fa7fb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.reflection;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -155,7 +156,7 @@
assertThat(mainMethod, isPresent());
int expectedCount = isR8 ? (isRelease ? 0 : 5) : 6;
assertEquals(expectedCount, countGetClass(mainMethod));
- expectedCount = isR8 ? (isRelease ? (parameters.isCfRuntime() ? 7 : 5) : 1) : 0;
+ expectedCount = isR8 ? (isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1) : 0;
assertEquals(expectedCount, countConstClass(mainMethod));
boolean expectToBeOptimized = isR8 && isRelease;
@@ -167,10 +168,14 @@
assertEquals(0, countConstClass(getMainClass));
MethodSubject call = mainClass.method("java.lang.Class", "call", ImmutableList.of());
- assertThat(call, isPresent());
- // Because of local, only R8 release mode can rewrite getClass() to const-class.
- assertEquals(expectToBeOptimized ? 0 : 1, countGetClass(call));
- assertEquals(expectToBeOptimized ? 1 : 0, countConstClass(call));
+ if (isR8 && isRelease) {
+ assertThat(call, not(isPresent()));
+ } else {
+ assertThat(call, isPresent());
+ // Because of local, only R8 release mode can rewrite getClass() to const-class.
+ assertEquals(expectToBeOptimized ? 0 : 1, countGetClass(call));
+ assertEquals(expectToBeOptimized ? 1 : 0, countConstClass(call));
+ }
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index c8d3f1c..f19fa6f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -10,14 +10,12 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.ForceInline;
-import com.android.tools.r8.JvmTestRunResult;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
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;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -49,11 +47,10 @@
@Test
public void testJVMOutput() throws Exception {
assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
- JvmTestRunResult run = testForJvm().addTestClasspath().run(parameters.getRuntime(), MAIN);
- // TODO(b/119097175): Fix test
- if (!ToolHelper.isWindows()) {
- run.assertSuccessWithOutput(JAVA_OUTPUT);
- }
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
}
private long countNonZeroConstNumber(MethodSubject method) {
@@ -102,12 +99,9 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), MAIN);
- // TODO(b/119097175): Fix test
- if (!ToolHelper.isWindows()) {
- result.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 0, parameters.isDexRuntime() ? 6 : 7);
- }
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 0, parameters.isDexRuntime() ? 6 : 7);
}
public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index f88783d..911479e 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -59,17 +59,13 @@
}
@Override
- public void replaceCurrentInstructionWithConstInt(
- AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+ public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
throw new Unimplemented();
}
@Override
public void replaceCurrentInstructionWithStaticGet(
- AppView<? extends AppInfoWithSubtyping> appView,
- IRCode code,
- DexField field,
- Set<Value> affectedValues) {
+ AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index 5059a1e..16385b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.Collection;
@@ -62,19 +63,15 @@
}
@Test
- public void testMetadataInCompanion_renamed() throws Exception {
+ public void testMetadataInCompanion_kept() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
.addProgramFiles(companionLibJarMap.get(targetVersion))
- // Keep the B class and its interface (which has the doStuff method).
- .addKeepRules("-keep class **.B")
- .addKeepRules("-keep class **.I { <methods>; }")
- // Keep getters for B$Companion.(singleton|foo) which will be referenced at the app.
- .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
- // No rule for Super, but will be kept and renamed.
+ // Keep everything
+ .addKeepRules("-keep class **.companion_lib.** { *; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
- .inspect(this::inspect)
+ .inspect(codeInspector -> inspect(codeInspector, true))
.writeToZip();
ProcessResult kotlinTestCompileResult =
@@ -87,17 +84,52 @@
// TODO(b/70169921): should be able to compile!
assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: singleton"));
+ assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2"));
+ }
+
+ @Test
+ public void testMetadataInCompanion_renamed() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(companionLibJarMap.get(targetVersion))
+ // Keep the B class and its interface (which has the doStuff method).
+ .addKeepRules("-keep class **.B")
+ .addKeepRules("-keep class **.I { <methods>; }")
+ // Keep getters for B$Companion.(eltN|foo) which will be referenced at the app.
+ .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
+ // No rule for Super, but will be kept and renamed.
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(codeInspector -> inspect(codeInspector, false))
+ .writeToZip();
+
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ // TODO(b/70169921): update to just .compile() once fixed.
+ .compileRaw();
+
+ // TODO(b/70169921): should be able to compile!
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
+ assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt1"));
+ assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2"));
assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo"));
}
- private void inspect(CodeInspector inspector) {
+ private void inspect(CodeInspector inspector, boolean keptAll) {
final String superClassName = PKG + ".companion_lib.Super";
final String bClassName = PKG + ".companion_lib.B";
final String companionClassName = PKG + ".companion_lib.B$Companion";
ClassSubject sup = inspector.clazz(superClassName);
- assertThat(sup, isRenamed());
+ if (keptAll) {
+ assertThat(sup, isPresent());
+ assertThat(sup, not(isRenamed()));
+ } else {
+ assertThat(sup, isRenamed());
+ }
ClassSubject impl = inspector.clazz(bClassName);
assertThat(impl, isPresent());
@@ -106,26 +138,60 @@
KmClassSubject kmClass = impl.getKmClass();
assertThat(kmClass, isPresent());
List<ClassSubject> superTypes = kmClass.getSuperTypes();
- assertTrue(superTypes.stream().noneMatch(
- supertype -> supertype.getFinalDescriptor().contains("Super")));
- assertTrue(superTypes.stream().anyMatch(
- supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+ if (!keptAll) {
+ assertTrue(superTypes.stream().noneMatch(
+ supertype -> supertype.getFinalDescriptor().contains("Super")));
+ assertTrue(superTypes.stream().anyMatch(
+ supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+ }
// Bridge for the property in the companion that needs a backing field.
- MethodSubject singletonBridge = impl.uniqueMethodWithName("access$getSingleton$cp");
- assertThat(singletonBridge, isRenamed());
+ MethodSubject elt1Bridge = impl.uniqueMethodWithName("access$getElt1$cp");
+ if (keptAll) {
+ assertThat(elt1Bridge, isPresent());
+ assertThat(elt1Bridge, not(isRenamed()));
+ } else {
+ assertThat(elt1Bridge, isRenamed());
+ }
+ // With @JvmField, no bridge is added.
+ MethodSubject elt2Bridge = impl.uniqueMethodWithName("access$getElt2$cp");
+ assertThat(elt2Bridge, not(isPresent()));
- // For B$Companion.foo, no backing field needed, hence no bridge.
+ // For B$Companion.foo, which is a simple computation, no backing field needed, hence no bridge.
MethodSubject fooBridge = impl.uniqueMethodWithName("access$getFoo$cp");
assertThat(fooBridge, not(isPresent()));
ClassSubject companion = inspector.clazz(companionClassName);
- assertThat(companion, isRenamed());
+ if (keptAll) {
+ assertThat(companion, isPresent());
+ assertThat(companion, not(isRenamed()));
+ } else {
+ assertThat(companion, isRenamed());
+ }
- MethodSubject singletonGetter = companion.uniqueMethodWithName("getSingleton");
- assertThat(singletonGetter, isPresent());
+ // TODO(b/70169921): Assert impl's KmClass points to the correct companion object and class.
+
+ kmClass = companion.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ KmPropertySubject kmProperty = kmClass.kmPropertyWithUniqueName("elt1");
+ assertThat(kmProperty, isPresent());
+ // TODO(b/70169921): property in companion with @JvmField is missing.
+ kmProperty = kmClass.kmPropertyWithUniqueName("elt2");
+ assertThat(kmProperty, not(isPresent()));
+ kmProperty = kmClass.kmPropertyWithUniqueName("foo");
+ assertThat(kmProperty, isPresent());
+
+ MethodSubject elt1Getter = companion.uniqueMethodWithName("getElt1");
+ assertThat(elt1Getter, isPresent());
+ assertThat(elt1Getter, not(isRenamed()));
+
+ // Note that there is no getter for property with @JvmField.
+ MethodSubject elt2Getter = companion.uniqueMethodWithName("getElt2");
+ assertThat(elt2Getter, not(isPresent()));
MethodSubject fooGetter = companion.uniqueMethodWithName("getFoo");
assertThat(fooGetter, isPresent());
+ assertThat(fooGetter, not(isRenamed()));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 917ede7..1f8fee7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -7,8 +7,10 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
@@ -20,6 +22,7 @@
import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
@@ -94,7 +97,6 @@
private void inspectMerged(CodeInspector inspector) {
String superClassName = PKG + ".extension_function_lib.Super";
String bClassName = PKG + ".extension_function_lib.B";
- String bKtClassName = PKG + ".extension_function_lib.BKt";
assertThat(inspector.clazz(superClassName), not(isPresent()));
@@ -108,15 +110,7 @@
assertTrue(superTypes.stream().noneMatch(
supertype -> supertype.getFinalDescriptor().contains("Super")));
- ClassSubject bKt = inspector.clazz(bKtClassName);
- assertThat(bKt, isPresent());
- assertThat(bKt, not(isRenamed()));
- // API entry is kept, hence the presence of Metadata.
- KmPackageSubject kmPackage = bKt.getKmPackage();
- assertThat(kmPackage, isPresent());
-
- KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
- assertThat(kmFunction, isExtensionFunction());
+ inspectExtensions(inspector);
}
@Test
@@ -154,7 +148,6 @@
private void inspectRenamed(CodeInspector inspector) {
String superClassName = PKG + ".extension_function_lib.Super";
String bClassName = PKG + ".extension_function_lib.B";
- String bKtClassName = PKG + ".extension_function_lib.BKt";
ClassSubject sup = inspector.clazz(superClassName);
assertThat(sup, isPresent());
@@ -172,6 +165,16 @@
assertTrue(superTypes.stream().anyMatch(
supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+ inspectExtensions(inspector);
+ }
+
+ private void inspectExtensions(CodeInspector inspector) {
+ String bClassName = PKG + ".extension_function_lib.B";
+ String bKtClassName = PKG + ".extension_function_lib.BKt";
+
+ ClassSubject impl = inspector.clazz(bClassName);
+ assertThat(impl, isPresent());
+
ClassSubject bKt = inspector.clazz(bKtClassName);
assertThat(bKt, isPresent());
assertThat(bKt, not(isRenamed()));
@@ -181,5 +184,29 @@
KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
assertThat(kmFunction, isExtensionFunction());
+ KmTypeSubject kmTypeSubject = kmFunction.receiverParameterType();
+ assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
+
+ kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("csHash");
+ assertThat(kmFunction, isExtensionFunction());
+ kmTypeSubject = kmFunction.receiverParameterType();
+ assertEquals("Lkotlin/CharSequence;", kmTypeSubject.descriptor());
+ kmTypeSubject = kmFunction.returnType();
+ assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+
+ kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("longArrayHash");
+ assertThat(kmFunction, isExtensionFunction());
+ kmTypeSubject = kmFunction.receiverParameterType();
+ assertEquals("Lkotlin/LongArray;", kmTypeSubject.descriptor());
+ kmTypeSubject = kmFunction.returnType();
+ assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+
+ kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("myApply");
+ assertThat(kmFunction, isExtensionFunction());
+ kmTypeSubject = kmFunction.receiverParameterType();
+ assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
+ // TODO(b/70169921): Check param[0] has type kotlin/Function1<(renamed) B, Unit>
+ String desc = kmFunction.signature().getDesc();
+ assertThat(desc, containsString("kotlin/jvm/functions/Function1"));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
new file mode 100644
index 0000000..ec9cdc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInFunctionWithDefaultValueTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteInFunctionWithDefaultValueTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> defaultValueLibJarMap = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String default_valueLibFolder = PKG_PREFIX + "/default_value_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path default_valueLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(default_valueLibFolder, "lib"))
+ .compile();
+ defaultValueLibJarMap.put(targetVersion, default_valueLibJar);
+ }
+ }
+
+ @Test
+ public void testMetadataInFunctionWithDefaultValue() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar(), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(defaultValueLibJarMap.get(targetVersion))
+ // Keep LibKt and applyMap function, along with applyMap$default
+ .addKeepRules("-keep class **.LibKt { *** applyMap*(...); }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/default_value_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ // TODO(b/70169921): update to just .compile() once fixed.
+ .compileRaw();
+
+ // TODO(b/70169921): should be able to compile!
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
+ assertThat(
+ kotlinTestCompileResult.stderr,
+ containsString("type mismatch: inferred type is kotlin.collections.Map<String, String"));
+ assertThat(
+ kotlinTestCompileResult.stderr, containsString("but java.util.Map<K, V> was expected"));
+ assertThat(
+ kotlinTestCompileResult.stderr, containsString("no value passed for parameter 'p2'"));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ String libClassName = PKG + ".default_value_lib.LibKt";
+
+ ClassSubject libKt = inspector.clazz(libClassName);
+ assertThat(libKt, isPresent());
+ assertThat(libKt, not(isRenamed()));
+
+ MethodSubject methodSubject = libKt.uniqueMethodWithName("applyMap");
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+
+ methodSubject = libKt.uniqueMethodWithName("applyMap$default");
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+ assertThat(kmPackage, isPresent());
+
+ KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("applyMap");
+ assertThat(kmFunction, isExtensionFunction());
+ // TODO(b/70169921): inspect 2nd arg has flag that says it has a default value.
+ // https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/src/kotlinx/metadata/Flag.kt#L455
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
new file mode 100644
index 0000000..f4e79f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInFunctionWithVarargTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteInFunctionWithVarargTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> varargLibJarMap = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String varargLibFolder = PKG_PREFIX + "/vararg_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path varargLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(varargLibFolder, "lib"))
+ .compile();
+ varargLibJarMap.put(targetVersion, varargLibJar);
+ }
+ }
+
+ @Test
+ public void testMetadataInFunctionWithVararg() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(varargLibJarMap.get(targetVersion))
+ // keep SomClass#foo, since there is a method reference in the app.
+ .addKeepRules("-keep class **.SomeClass { *** foo(...); }")
+ // Keep LibKt, along with bar function.
+ .addKeepRules("-keep class **.LibKt { *** bar(...); }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/vararg_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ // TODO(b/70169921): update to just .compile() once fixed.
+ .compileRaw();
+
+ // TODO(b/70169921): should be able to compile!
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
+ assertThat(
+ kotlinTestCompileResult.stderr,
+ containsString("type mismatch: inferred type is String but Array<T> was expected"));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ String className = PKG + ".vararg_lib.SomeClass";
+ String libClassName = PKG + ".vararg_lib.LibKt";
+
+ ClassSubject cls = inspector.clazz(className);
+ assertThat(cls, isPresent());
+ assertThat(cls, not(isRenamed()));
+
+ MethodSubject foo = cls.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ assertThat(foo, not(isRenamed()));
+
+ ClassSubject libKt = inspector.clazz(libClassName);
+ assertThat(libKt, isPresent());
+ assertThat(libKt, not(isRenamed()));
+
+ MethodSubject bar = libKt.uniqueMethodWithName("bar");
+ assertThat(bar, isPresent());
+ assertThat(bar, not(isRenamed()));
+
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+ assertThat(kmPackage, isPresent());
+
+ KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("bar");
+ assertThat(kmFunction, not(isExtensionFunction()));
+ // TODO(b/70169921): inspect 1st arg is `vararg`.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
new file mode 100644
index 0000000..7ecf894
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -0,0 +1,124 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInNestedClassTest extends KotlinMetadataTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteInNestedClassTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static final Map<KotlinTargetVersion, Path> nestedLibJarMap = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String nestedLibFolder = PKG_PREFIX + "/nested_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path nestedLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(nestedLibFolder, "lib"))
+ .compile();
+ nestedLibJarMap.put(targetVersion, nestedLibJar);
+ }
+ }
+
+ @Test
+ public void testMetadataInNestedClass() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(nestedLibJarMap.get(targetVersion))
+ // Keep the Outer class and delegations.
+ .addKeepRules("-keep class **.Outer { <init>(...); *** delegate*(...); }")
+ // Keep Inner to check the hierarchy.
+ .addKeepRules("-keep class **.*Inner")
+ // Keep Nested, but allow obfuscation.
+ .addKeepRules("-keep,allowobfuscation class **.*Nested")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+ .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/nested_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".nested_app.MainKt")
+ .assertSuccessWithOutputLines("Inner::inner", "42", "Nested::nested", "42");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ String outerClassName = PKG + ".nested_lib.Outer";
+ String innerClassName = outerClassName + "$Inner";
+ String nestedClassName = outerClassName + "$Nested";
+
+ ClassSubject inner = inspector.clazz(innerClassName);
+ assertThat(inner, isPresent());
+ assertThat(inner, not(isRenamed()));
+
+ ClassSubject nested = inspector.clazz(nestedClassName);
+ assertThat(nested, isRenamed());
+
+ ClassSubject outer = inspector.clazz(outerClassName);
+ assertThat(outer, isPresent());
+ assertThat(outer, not(isRenamed()));
+
+ KmClassSubject kmClass = outer.getKmClass();
+ assertThat(kmClass, isPresent());
+
+ assertFalse(kmClass.getNestedClassDescriptors().isEmpty());
+ kmClass.getNestedClassDescriptors().forEach(nestedClassDescriptor -> {
+ ClassSubject nestedClass = inspector.clazz(descriptorToJavaType(nestedClassDescriptor));
+ if (nestedClass.getOriginalName().contains("Inner")) {
+ assertThat(nestedClass, not(isRenamed()));
+ } else {
+ assertThat(nestedClass, isRenamed());
+ }
+ assertEquals(nestedClassDescriptor, nestedClass.getFinalDescriptor());
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
new file mode 100644
index 0000000..6bfafc4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -0,0 +1,138 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInRenamedTypeTest extends KotlinMetadataTestBase {
+ private static final String OBFUSCATE_RENAMED = "-keep,allowobfuscation class **.Renamed { *; }";
+ private static final String KEEP_KEPT = "-keep class **.Kept { *; }";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteInRenamedTypeTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static final Map<KotlinTargetVersion, Path> annoJarMap = new HashMap<>();
+ private static final Map<KotlinTargetVersion, Path> inputJarMap = new HashMap<>();
+
+ @BeforeClass
+ public static void createInputJar() throws Exception {
+ String inputFolder = PKG_PREFIX + "/anno";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path annoJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(inputFolder, "Anno"))
+ .compile();
+ Path inputJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addClasspathFiles(annoJar)
+ .addSourceFiles(getKotlinFileInTest(inputFolder, "main"))
+ .compile();
+ annoJarMap.put(targetVersion, annoJar);
+ inputJarMap.put(targetVersion, inputJar);
+ }
+ }
+
+ @Test
+ public void testR8_kotlinStdlibAsLib() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(
+ annoJarMap.get(targetVersion),
+ ToolHelper.getJava8RuntimeJar(),
+ ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(inputJarMap.get(targetVersion))
+ .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testR8_kotlinStdlibAsClassPath() throws Exception {
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(annoJarMap.get(targetVersion), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(inputJarMap.get(targetVersion))
+ .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testR8_kotlinStdlibAsProgramFile() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(annoJarMap.get(targetVersion), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(inputJarMap.get(targetVersion))
+ .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
+ .addKeepRules("-keep class **.Anno")
+ .addKeepRules("-keep class kotlin.Metadata")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .assertWarningMessageThatMatches(
+ equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ String pkg = getClass().getPackage().getName();
+ ClassSubject kept = inspector.clazz(pkg + ".anno.Kept");
+ assertThat(kept, isPresent());
+ assertThat(kept, not(isRenamed()));
+ // API entry is kept, hence @Metadata exists.
+ KmClassSubject kmClass = kept.getKmClass();
+ assertThat(kmClass, isPresent());
+ assertEquals(kept.getFinalDescriptor(), getDescriptorFromKotlinClassifier(kmClass.getName()));
+ // @Anno is kept.
+ String annoName = pkg + ".anno.Anno";
+ AnnotationSubject anno = kept.annotation(annoName);
+ assertThat(anno, isPresent());
+
+ ClassSubject renamed = inspector.clazz(pkg + ".anno.Renamed");
+ assertThat(renamed, isRenamed());
+ // @Anno is kept.
+ anno = renamed.annotation(annoName);
+ assertThat(anno, isPresent());
+ // @Metadata is kept even though the class is renamed.
+ kmClass = renamed.getKmClass();
+ assertThat(kmClass, isPresent());
+ assertEquals(
+ renamed.getFinalDescriptor(), getDescriptorFromKotlinClassifier(kmClass.getName()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 6cd1034..b643c4b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -12,6 +12,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
@@ -110,6 +111,7 @@
KmClassSubject kmClass = expr.getKmClass();
assertThat(kmClass, isPresent());
+ assertFalse(kmClass.getSealedSubclassDescriptors().isEmpty());
kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
ClassSubject sealedSubclass =
inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index f93179f..31cd07d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -52,6 +52,8 @@
.addKeepMainRule(mainClassName)
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.addKeepRules("-keep class kotlin.Metadata")
+ // TODO(b/70169921): if this option is settled down, this test is meaningless.
+ .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
.allowDiagnosticWarningMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anno/Anno.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/Anno.kt
new file mode 100644
index 0000000..3e37cc4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/Anno.kt
@@ -0,0 +1,7 @@
+// 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.kotlin.metadata.anno
+
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Anno
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anno/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/main.kt
new file mode 100644
index 0000000..c2443e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/main.kt
@@ -0,0 +1,14 @@
+// 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.kotlin.metadata.anno
+
+@Anno
+class Renamed {
+ var num: Int = 8
+}
+
+@Anno
+class Kept {
+ val answer: Int = 42
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
index 55c61e3..403021e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
@@ -7,6 +7,7 @@
fun main() {
B().doStuff()
- B.singleton.doStuff()
+ B.elt1.doStuff()
+ B.elt2.doStuff()
println(B.foo)
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
index 9e9f2eb..e3da633 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -19,7 +19,9 @@
}
companion object {
- val singleton: Super = B()
+ val elt1: Super = B()
+ @JvmField
+ val elt2: Super = B()
val foo: String
get() = "B.Companion:foo"
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
new file mode 100644
index 0000000..9cb6039
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
@@ -0,0 +1,12 @@
+// 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.kotlin.metadata.default_value_app
+
+import com.android.tools.r8.kotlin.metadata.default_value_lib.applyMap
+
+fun main() {
+ val m = mapOf("A" to "a", "B" to "b", "C" to "c")
+ val s = listOf("A", "B", "C").joinToString(separator = "\n")
+ println(s.applyMap(m))
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
new file mode 100644
index 0000000..c76b5be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.default_value_lib
+
+fun String.applyMap(map: Map<String, String>, separator: String = "\n") =
+ this.split(separator).joinToString(separator = separator) {
+ if (map.containsKey(it)) map.getValue(it) else it
+ }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
index 2196586..d85c992 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
@@ -5,8 +5,16 @@
import com.android.tools.r8.kotlin.metadata.extension_function_lib.B
import com.android.tools.r8.kotlin.metadata.extension_function_lib.extension
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.csHash
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.longArrayHash
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.myApply
fun main() {
B().doStuff()
B().extension()
+
+ "R8".csHash()
+ longArrayOf(42L).longArrayHash()
+ // TODO(b/70169921): Need to set arguments as type parameter.
+ // B().myApply { this.doStuff() }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
index 84c759a..4321ac1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
@@ -17,4 +17,20 @@
fun B.extension() {
doStuff()
-}
\ No newline at end of file
+}
+
+fun CharSequence.csHash(): Long {
+ var result = 0L
+ this.forEach { result = result * 8L + it.toLong() }
+ return result
+}
+
+fun LongArray.longArrayHash(): Long {
+ var result = 0L
+ this.forEach { result = result * 8L + it }
+ return result
+}
+
+fun B.myApply(apply: B.() -> Unit) {
+ apply.invoke(this)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/nested_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_app/main.kt
new file mode 100644
index 0000000..d4f91d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_app/main.kt
@@ -0,0 +1,12 @@
+// 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.kotlin.metadata.nested_app
+
+import com.android.tools.r8.kotlin.metadata.nested_lib.Outer
+
+fun main() {
+ val o = Outer()
+ println(o.delegateInner())
+ println(o.delegateNested(21))
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/nested_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_lib/lib.kt
new file mode 100644
index 0000000..13765fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_lib/lib.kt
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.nested_lib
+
+class Outer {
+ val prop1
+ get() = 42
+
+ fun delegateInner() = Inner().inner()
+
+ fun delegateNested(x: Int) = Nested().nested(x)
+
+ inner class Inner {
+ fun inner(): Int {
+ println("Inner::inner")
+ return this@Outer.prop1
+ }
+ }
+
+ class Nested {
+ fun nested(x: Int): Int {
+ println("Nested::nested")
+ return 2 * x
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_app/main.kt
new file mode 100644
index 0000000..417bfcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_app/main.kt
@@ -0,0 +1,12 @@
+// 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.kotlin.metadata.vararg_app
+
+import com.android.tools.r8.kotlin.metadata.vararg_lib.bar
+
+fun main() {
+ bar("R8") { instance, str ->
+ instance.foo(str)
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_lib/lib.kt
new file mode 100644
index 0000000..8cc6e19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_lib/lib.kt
@@ -0,0 +1,19 @@
+// 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.kotlin.metadata.vararg_lib
+
+class SomeClass {
+ fun foo(x : String) {
+ println("SomeClass::$x")
+ }
+}
+
+fun bar(vararg strs: String, f: (SomeClass, String) -> Unit) {
+ if (strs.isNotEmpty() && strs.any { it.startsWith("R8") }) {
+ val instance = SomeClass()
+ strs
+ .filter { it.startsWith("R8") }
+ .map { f.invoke(instance, it) }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 8bc0e79..d1ecf46 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -9,10 +9,18 @@
import static com.android.tools.r8.utils.FileUtils.withNativeFileSeparators;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tools.r8.GenerateMainDexList;
import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.references.Reference;
@@ -21,7 +29,6 @@
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -35,11 +42,9 @@
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import org.junit.Assert;
import org.junit.Test;
public class MainDexTracingTest extends TestBase {
@@ -61,9 +66,10 @@
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules-whyareyoukeeping.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
- AndroidApiLevel.I);
+ AndroidApiLevel.I,
+ TestCompilerBuilder::allowStdoutMessages);
String output = new String(baos.toByteArray(), Charset.defaultCharset());
- Assert.assertThat(output, containsString("is referenced in keep rule:"));
+ assertThat(output, containsString("is referenced in keep rule:"));
System.setOut(stdout);
}
@@ -78,10 +84,12 @@
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
AndroidApiLevel.I,
- options -> {
- options.enableInlining = false;
- options.mainDexKeptGraphConsumer = graphConsumer;
- });
+ builder ->
+ builder.addOptionsModification(
+ options -> {
+ options.enableInlining = false;
+ options.mainDexKeptGraphConsumer = graphConsumer;
+ }));
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
graphConsumer.printWhyAreYouKeeping(
@@ -92,7 +100,7 @@
"multidex001.MainActivity",
"|- is referenced in keep rule:",
withNativeFileSeparators("| src/test/examples/multidex/main-dex-rules.txt:14:1"));
- Assert.assertEquals(expected, output);
+ assertEquals(expected, output);
}
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -110,7 +118,7 @@
"|- is referenced in keep rule:",
withNativeFileSeparators(
"| src/test/examples/multidex/main-dex-rules.txt:14:1"));
- Assert.assertEquals(expected, output);
+ assertEquals(expected, output);
}
}
@@ -271,9 +279,7 @@
expectedR8MainDexList,
expectedMainDexList,
minSdk,
- (options) -> {
- options.enableInlining = false;
- });
+ builder -> builder.addOptionsModification(options -> options.enableInlining = false));
}
private void doTest(
@@ -284,7 +290,7 @@
Path expectedR8MainDexList,
Path expectedMainDexList,
AndroidApiLevel minSdk,
- Consumer<InternalOptions> optionsConsumer)
+ ThrowableConsumer<R8FullTestBuilder> configuration)
throws Throwable {
Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
@@ -328,7 +334,7 @@
.addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
.addKeepRules("-keepattributes *Annotation*")
.addMainDexRuleFiles(mainDexRules)
- .addOptionsModification(optionsConsumer)
+ .apply(configuration)
.allowDiagnosticWarningMessages()
.assumeAllMethodsMayHaveSideEffects()
.setMinApi(minSdk)
@@ -351,7 +357,7 @@
for (int i = 0; i < r8RefList.length; i++) {
String reference = r8RefList[i].trim();
if (r8MainDexList.size() <= i) {
- Assert.fail("R8 main dex list is missing '" + reference + "'");
+ fail("R8 main dex list is missing '" + reference + "'");
}
checkSameMainDexEntry(reference, r8MainDexList.get(i));
}
@@ -363,7 +369,7 @@
// The main dex list generator does not do any lambda desugaring.
if (!isLambda(reference)) {
if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
- Assert.fail("Main dex list generator is missing '" + reference + "'");
+ fail("Main dex list generator is missing '" + reference + "'");
}
checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
checkSameMainDexEntry(
@@ -389,9 +395,9 @@
continue;
}
if (index == 0) {
- Assert.assertEquals("classes.dex", entry.getName());
+ assertEquals("classes.dex", entry.getName());
} else {
- Assert.assertEquals("classes" + (index + 1) + ".dex", entry.getName());
+ assertEquals("classes" + (index + 1) + ".dex", entry.getName());
}
index++;
}
@@ -399,7 +405,7 @@
String[] entriesUnsorted = entryNames.toArray(StringUtils.EMPTY_ARRAY);
String[] entriesSorted = entryNames.toArray(StringUtils.EMPTY_ARRAY);
Arrays.sort(entriesSorted);
- Assert.assertArrayEquals(entriesUnsorted, entriesSorted);
+ assertArrayEquals(entriesUnsorted, entriesSorted);
}
private boolean isLambda(String mainDexEntry) {
@@ -407,7 +413,7 @@
}
private String mainDexStringToDescriptor(String mainDexString) {
- Assert.assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
+ assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
return DescriptorUtils.getDescriptorFromClassBinaryName(
mainDexString.substring(0, mainDexString.length() - FileUtils.CLASS_EXTENSION.length()));
}
@@ -421,6 +427,6 @@
reference = reference.substring(0, reference.lastIndexOf('$'));
computed = computed.substring(0, computed.lastIndexOf('$'));
}
- Assert.assertEquals(reference, computed);
+ assertEquals(reference, computed);
}
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
index cd80843..c4a616c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -90,14 +90,15 @@
.setMinApi(AndroidApiLevel.K)
.addProgramClasses(CLASSES)
.addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
- .setMainDexKeptGraphConsumer(consumer);
+ .setMainDexKeptGraphConsumer(consumer)
+ .allowStdoutMessages();
if (rule != null) {
builder.addMainDexRules(rule);
}
builder.compile();
}
- private String runTest(Class clazz) throws Exception {
+ private String runTest(Class<?> clazz) throws Exception {
PrintStream stdout = System.out;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String rule = null;
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index b03aeaa..f0b3c24 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -8,8 +8,8 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -41,7 +41,7 @@
private final Timing timing;
- private DexApplication program;
+ private DirectMappedDexApplication program;
protected DexItemFactory dexItemFactory;
protected NamingTestBase(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassFieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassFieldAccessTest.java
new file mode 100644
index 0000000..4cb3b94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassFieldAccessTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnrelatedClasspathClassFieldAccessTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnrelatedClasspathClassFieldAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class ClasspathClass {
+ public static int field = 42;
+ }
+
+ public static class ProgramClass {
+
+ public static void main(String[] args) {
+ System.out.println(ClasspathClass.field);
+ }
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(ProgramClass.class, ClasspathClass.class)
+ .run(parameters.getRuntime(), ProgramClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @Test
+ public void testApplyMapping() throws Exception {
+ R8TestCompileResult classpath =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ClasspathClass.class)
+ .addKeepAllClassesRuleWithAllowObfuscation()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ProgramClass.class)
+ .addClasspathClasses(ClasspathClass.class)
+ .addApplyMapping(classpath.getProguardMap())
+ .addKeepMainRule(ProgramClass.class)
+ .addRunClasspathFiles(classpath.writeToZip())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), ProgramClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassIndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassIndirectFieldAccessTest.java
new file mode 100644
index 0000000..f9397d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassIndirectFieldAccessTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnrelatedClasspathClassIndirectFieldAccessTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnrelatedClasspathClassIndirectFieldAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class ClasspathClassA {
+ public static int field = 42;
+ }
+
+ public static class ClasspathClassB extends ClasspathClassA {}
+
+ public static class ProgramClass {
+
+ public static void main(String[] args) {
+ System.out.println(ClasspathClassB.field);
+ }
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(ProgramClass.class, ClasspathClassA.class, ClasspathClassB.class)
+ .run(parameters.getRuntime(), ProgramClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @Test
+ public void testApplyMapping() throws Exception {
+ R8TestCompileResult classpath =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ClasspathClassA.class, ClasspathClassB.class)
+ .addKeepAllClassesRuleWithAllowObfuscation()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ProgramClass.class)
+ .addClasspathClasses(ClasspathClassA.class, ClasspathClassB.class)
+ .addApplyMapping(classpath.getProguardMap())
+ .addKeepMainRule(ProgramClass.class)
+ .addRunClasspathFiles(classpath.writeToZip())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), ProgramClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+}
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 b12b05f..d8f3910 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
@@ -8,12 +8,11 @@
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.ProguardTestBuilder;
-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.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -92,15 +91,21 @@
private final boolean enableClassMerging;
private final boolean onlyForceInlining;
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "enable class merging: {0}, only force inlining: {1}")
+ @Parameterized.Parameters(name = "{2}, enable class merging: {0}, only force inlining: {1}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), BooleanUtils.values());
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public B130791310(boolean enableClassMerging, boolean onlyForceInlining) {
+ public B130791310(
+ boolean enableClassMerging, boolean onlyForceInlining, TestParameters parameters) {
this.enableClassMerging = enableClassMerging;
this.onlyForceInlining = onlyForceInlining;
+ this.parameters = parameters;
}
private void inspect(CodeInspector inspector, boolean isR8) {
@@ -130,38 +135,39 @@
@Test
public void testProguard() throws Exception {
assumeFalse(onlyForceInlining);
- ProguardTestBuilder builder =
- testForProguard()
- .addProgramClasses(CLASSES)
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .addKeepClassAndMembersRules(MAIN)
- .addKeepRules(RULES)
- .addTestingAnnotationsAsProgramClasses();
- if (!enableClassMerging) {
- builder.addKeepRules("-optimizations !class/merging/*");
- }
- builder
+ assumeTrue(parameters.isCfRuntime());
+ testForProguard()
+ .addProgramClasses(CLASSES)
+ .addKeepClassAndMembersRules(MAIN)
+ .addKeepRules(RULES)
+ .addTestingAnnotationsAsProgramClasses()
+ .setMinApi(parameters.getApiLevel())
+ .apply(
+ builder -> {
+ if (!enableClassMerging) {
+ builder.addKeepRules("-optimizations !class/merging/*");
+ }
+ })
.compile()
.inspect(inspector -> inspect(inspector, false));
}
@Test
public void testR8() throws Exception {
- R8FullTestBuilder builder =
- testForR8(Backend.DEX)
- .addProgramClasses(CLASSES)
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .addKeepClassAndMembersRules(MAIN)
- .addKeepRules(RULES)
- .enableNeverClassInliningAnnotations();
- if (!enableClassMerging) {
- builder.addOptionsModification(o -> o.enableVerticalClassMerging = false);
- }
- if (onlyForceInlining) {
- builder.addOptionsModification(
- o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE));
- }
- builder
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addKeepClassAndMembersRules(MAIN)
+ .addKeepRules(RULES)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(o -> o.enableVerticalClassMerging = enableClassMerging)
+ .apply(
+ builder -> {
+ if (onlyForceInlining) {
+ builder.addOptionsModification(
+ o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE));
+ }
+ })
.compile()
.inspect(inspector -> inspect(inspector, true));
}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index c7f8ef9..8034ad7 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -36,7 +36,8 @@
.addLibraryFile(ToolHelper.getDefaultAndroidJar())
.addProgramFiles(ToolHelper.getClassFileForTestClass(Foo.class))
.build();
- DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+ DirectMappedDexApplication application =
+ new ApplicationReader(app, options, timing).read().toDirect();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
DexItemFactory factory = options.itemFactory;
DexType fooType =
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index b473a40..45a9a20 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -222,7 +222,7 @@
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
if (resolutionResult.isVirtualTarget()) {
- Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
+ Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualDispatchTargets(appInfo);
Set<DexType> targetHolders =
targets.stream().map(m -> m.method.holder).collect(Collectors.toSet());
Assert.assertEquals(allTargetHolders.size(), targetHolders.size());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index 609b506..b119874 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -55,7 +55,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index 37f346b..f926d4b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -53,7 +53,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
new file mode 100644
index 0000000..61e872e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -0,0 +1,160 @@
+// 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.interfacetargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 DefaultWithoutTopTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"J.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultWithoutTopTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testDynamicLookupTargets() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, J.class, Main.class)
+ .addClassProgramData(setAImplementsIAndJ())
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ Set<String> targets =
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, J.class, Main.class)
+ .addProgramClassFileData(setAImplementsIAndJ())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, Main.class)
+ .addProgramClassFileData(setAImplementsIAndJ())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testtestDynamicLookupTargetsWithIndirectDefault() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, J.class, K.class, Main.class)
+ .addClassProgramData(setAimplementsIandK())
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+ Set<String> targets =
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntimeWithIndirectDefault()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, J.class, K.class, Main.class)
+ .addProgramClassFileData(setAimplementsIandK())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8WithIndirectDefault()
+ throws IOException, CompilationFailedException, ExecutionException {
+ // TODO(b/148686556): Fix test expectation.
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, K.class, Main.class)
+ .addProgramClassFileData(setAimplementsIandK())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.AbstractMethodError"));
+ }
+
+ private byte[] setAImplementsIAndJ() throws IOException {
+ return transformer(A.class).setImplements(I.class, J.class).transform();
+ }
+
+ private byte[] setAimplementsIandK() throws IOException {
+ return transformer(A.class).setImplements(I.class, K.class).transform();
+ }
+
+ @NeverMerge
+ public interface I {
+ void foo();
+ }
+
+ @NeverMerge
+ public interface J {
+ @NeverInline
+ default void foo() {
+ System.out.println("J.foo");
+ }
+ }
+
+ public interface K extends J {}
+
+ @NeverClassInline
+ public static class A implements J /* I, J or I, K */ {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((I) new A()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
index 65448a0..a69a18b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -60,7 +60,7 @@
Assert.assertThrows(
AssertionError.class,
() -> {
- appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo);
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo);
});
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index ede5785..3b6022b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -50,7 +50,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
Assert.assertThrows(
AssertionError.class,
- () -> appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo));
+ () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 3a455cc..88fd988 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -54,7 +54,7 @@
DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
index 8b96a84..23c2215 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -54,7 +54,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
index 60ed217..03a6141 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -55,7 +55,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 4986840..e73a10e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -54,7 +54,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
index 3ccb04b..24916b0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -53,7 +53,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
index 4e4a494..a9b34f2 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -53,7 +53,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index 83831d1..8a52035 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -52,7 +52,7 @@
.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
Set<String> targets =
- appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
// TODO(b/148591377): Should we report B.foo()?
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index 988f1a3..34b536b 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -52,7 +52,7 @@
.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
Set<String> targets =
- appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
// TODO(b/148591377): I.foo() should ideally not be included in the set.
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 89f17da..399ad7d 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -52,7 +52,7 @@
.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
Set<String> targets =
- appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
new file mode 100644
index 0000000..4759dc3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -0,0 +1,111 @@
+// 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.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 DefaultWithoutTopTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"J.foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultWithoutTopTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testDynamicLookupTargets() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(I.class, J.class, Main.class)
+ .addClassProgramData(setAImplementsIAndJ())
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+ Set<String> targets =
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
+ .map(DexEncodedMethod::qualifiedName)
+ .collect(Collectors.toSet());
+ ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+ assertEquals(expected, targets);
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, J.class, Main.class)
+ .addProgramClassFileData(setAImplementsIAndJ())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, Main.class)
+ .addProgramClassFileData(setAImplementsIAndJ())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private byte[] setAImplementsIAndJ() throws IOException {
+ return transformer(A.class).setImplements(I.class, J.class).transform();
+ }
+
+ @NeverMerge
+ public interface I {
+ void foo();
+ }
+
+ @NeverMerge
+ public interface J {
+ @NeverInline
+ default void foo() {
+ System.out.println("J.foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements J /* I,J */ {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 2f979d9..ebbfcef 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -51,7 +51,7 @@
.appInfo();
DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
Set<String> targets =
- appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+ appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected = ImmutableSet.of(I.class.getTypeName() + ".foo");
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 5bef2ca..e4d1f55 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -54,7 +54,7 @@
DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
Set<String> targets =
- resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+ resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
.map(DexEncodedMethod::qualifiedName)
.collect(Collectors.toSet());
ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index c46d3ce..809a521 100644
--- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -16,9 +16,12 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Objects;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -120,7 +123,7 @@
assertThat(
stackTrace,
isSameExceptForFileNameAndLineNumber(
- StackTrace.builder()
+ createStackTraceBuilder()
.addWithoutFileNameAndLineNumber(
Result.class, "methodWhichAccessInstanceMethod")
.addWithoutFileNameAndLineNumber(
@@ -148,7 +151,7 @@
assertThat(
stackTrace,
isSameExceptForFileNameAndLineNumber(
- StackTrace.builder()
+ createStackTraceBuilder()
.addWithoutFileNameAndLineNumber(
Result.class, "methodWhichAccessInstanceField")
.addWithoutFileNameAndLineNumber(
@@ -178,7 +181,7 @@
assertThat(
stackTrace,
isSameExceptForFileNameAndLineNumber(
- StackTrace.builder()
+ createStackTraceBuilder()
.addWithoutFileNameAndLineNumber(
Result.class, "methodWhichAccessStaticField")
.addWithoutFileNameAndLineNumber(
@@ -188,6 +191,24 @@
.build())));
}
+ private StackTrace.Builder createStackTraceBuilder() {
+ StackTrace.Builder builder = StackTrace.builder();
+ if (canUseRequireNonNull()) {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+ builder.addWithoutFileNameAndLineNumber("java.base/java.util.Objects", "requireNonNull");
+ } else {
+ builder.addWithoutFileNameAndLineNumber(Objects.class, "requireNonNull");
+ }
+ }
+ return builder;
+ }
+
+ private boolean canUseRequireNonNull() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+ }
+
static class TestClassForInlineMethod {
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 13190d9..d70f22b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -122,7 +122,7 @@
.streamInstructions()
.filter(InstructionSubject::isThrow)
.collect(toSingle())
- .retracePosition(codeInspector.retrace());
+ .retraceLinePosition(codeInspector.retrace());
assertThat(retraceResult, isInlineFrame());
assertThat(retraceResult, isInlineStack(inlineStack));
assertThat(stackTrace, containsInlinePosition(inlineStack));
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 2d9f47b..b4d302b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -64,6 +65,20 @@
.compile();
}
+ private FoundMethodSubject inlineExceptionStatic(CodeInspector kotlinInspector) {
+ return kotlinInspector
+ .clazz("retrace.InlineFunctionKt")
+ .uniqueMethodWithName("inlineExceptionStatic")
+ .asFoundMethodSubject();
+ }
+
+ private FoundMethodSubject inlineExceptionInstance(CodeInspector kotlinInspector) {
+ return kotlinInspector
+ .clazz("retrace.InlineFunction")
+ .uniqueMethodWithName("inlineExceptionInstance")
+ .asFoundMethodSubject();
+ }
+
@Test
public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
testForRuntime(parameters)
@@ -78,9 +93,10 @@
public void testRetraceKotlinInlineStaticFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainKt";
+ Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+ CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
testForR8(parameters.getBackend())
- .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
.addKeepAttributes("SourceFile", "LineNumberTable")
.allowDiagnosticWarningMessages()
.setMode(CompilationMode.RELEASE)
@@ -95,8 +111,7 @@
MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
InlinePosition inlineStack =
InlinePosition.stack(
- InlinePosition.create(
- "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+ InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8),
InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
});
@@ -106,9 +121,10 @@
public void testRetraceKotlinInlineInstanceFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainInstanceKt";
+ Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+ CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
testForR8(parameters.getBackend())
- .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
.addKeepAttributes("SourceFile", "LineNumberTable")
.allowDiagnosticWarningMessages()
.setMode(CompilationMode.RELEASE)
@@ -123,8 +139,7 @@
MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
InlinePosition inlineStack =
InlinePosition.stack(
- InlinePosition.create(
- "retrace.InlineFunction", "inlineExceptionInstance", 2, 15),
+ InlinePosition.create(inlineExceptionInstance(kotlinInspector), 2, 15),
InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
});
@@ -134,9 +149,10 @@
public void testRetraceKotlinNestedInlineFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainNestedKt";
+ Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+ CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
testForR8(parameters.getBackend())
- .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
.addKeepAttributes("SourceFile", "LineNumberTable")
.allowDiagnosticWarningMessages()
.setMode(CompilationMode.RELEASE)
@@ -151,8 +167,7 @@
MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
InlinePosition inlineStack =
InlinePosition.stack(
- InlinePosition.create(
- "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
+ InlinePosition.create(inlineExceptionStatic(kotlinInspector), 3, 8),
// TODO(b/146399675): There should be a nested frame on
// retrace.NestedInlineFunctionKt.nestedInline(line 10).
InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
@@ -164,9 +179,10 @@
public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainNestedFirstLineKt";
+ Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+ CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
testForR8(parameters.getBackend())
- .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
.addKeepAttributes("SourceFile", "LineNumberTable")
.allowDiagnosticWarningMessages()
.setMode(CompilationMode.RELEASE)
@@ -181,8 +197,7 @@
MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
InlinePosition inlineStack =
InlinePosition.stack(
- InlinePosition.create(
- "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+ InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8),
// TODO(b/146399675): There should be a nested frame on
// retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15).
InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
@@ -201,7 +216,7 @@
.streamInstructions()
.filter(InstructionSubject::isThrow)
.collect(toSingle())
- .retracePosition(codeInspector.retrace());
+ .retraceLinePosition(codeInspector.retrace());
assertThat(retraceResult, isInlineFrame());
assertThat(retraceResult, isInlineStack(inlineStack));
assertThat(stackTrace, containsInlinePosition(inlineStack));
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 cfc4fba..174dd5e 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
@@ -165,6 +166,12 @@
runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
}
+ @Test
+ public void testBootLoaderAndNamedModulesStackTrace() {
+ assumeFalse(useRegExpParsing);
+ runRetraceTest(new NamedModuleStackTrace());
+ }
+
private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
new file mode 100644
index 0000000..8bd402f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is testing the string representation of stack trace elements with built-in class loaders and
+ * named/unnamed modules:
+ * https://docs.oracle.com/javase/10/docs/api/java/lang/StackTraceElement.html#toString()
+ */
+public class NamedModuleStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "SomeFakeException: this is a fake exception",
+ "\tat classloader.a.b.a/named_module@9.0/a.a(:101)",
+ "\tat classloader.a.b.a//a.b(App.java:12)",
+ "\tat named_module@2.1/a.c(Lib.java:80)",
+ "\tat named_module/a.d(Lib.java:81)",
+ "\tat a.e(MyClass.java:9)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.joinLines(
+ "com.android.tools.r8.Classloader -> classloader.a.b.a:",
+ "com.android.tools.r8.Main -> a:",
+ " 101:101:void main(java.lang.String[]):1:1 -> a",
+ " 12:12:void foo(java.lang.String[]):2:2 -> b",
+ " 80:80:void bar(java.lang.String[]):3:3 -> c",
+ " 81:81:void baz(java.lang.String[]):4:4 -> d",
+ " 9:9:void qux(java.lang.String[]):5:5 -> e");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ "SomeFakeException: this is a fake exception",
+ "\tat com.android.tools.r8.Classloader/named_module@9.0/com.android.tools.r8.Main.main(Main.java:1)",
+ "\tat com.android.tools.r8.Classloader//com.android.tools.r8.Main.foo(Main.java:2)",
+ "\tat named_module@2.1/com.android.tools.r8.Main.bar(Main.java:3)",
+ "\tat named_module/com.android.tools.r8.Main.baz(Main.java:4)",
+ "\tat com.android.tools.r8.Main.qux(Main.java:5)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
new file mode 100644
index 0000000..b201ce6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -0,0 +1,481 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.ThrowingConsumer;
+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.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class AssertionConfigurationKotlinTest extends KotlinTestBase implements Opcodes {
+
+ private static final Package pkg = AssertionConfigurationKotlinTest.class.getPackage();
+ private static final String kotlintestclasesPackage = pkg.getName() + ".kotlintestclasses";
+ private static final String testClassKt = kotlintestclasesPackage + ".TestClassKt";
+ private static final String class1 = kotlintestclasesPackage + ".Class1";
+ private static final String class2 = kotlintestclasesPackage + ".Class2";
+
+ private static final Map<KotlinTargetVersion, Path> kotlinClasses = new HashMap<>();
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0},{1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+ }
+
+ public AssertionConfigurationKotlinTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileKotlin() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path ktClasses =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFilesInTestPackage(pkg))
+ .compile();
+ kotlinClasses.put(targetVersion, ktClasses);
+ }
+ }
+
+ private void runD8Test(
+ ThrowableConsumer<D8TestBuilder> builderConsumer,
+ ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+ List<String> outputLines)
+ throws Exception {
+ testForD8()
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinClasses.get(targetVersion))
+ .setMinApi(parameters.getApiLevel())
+ .apply(builderConsumer)
+ .run(
+ parameters.getRuntime(),
+ getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+ .inspect(inspector)
+ .assertSuccessWithOutputLines(outputLines);
+ }
+
+ public void runR8Test(
+ ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+ ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+ List<String> outputLines)
+ throws Exception {
+ runR8Test(builderConsumer, inspector, outputLines, false);
+ }
+
+ public void runR8Test(
+ ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+ ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+ List<String> outputLines,
+ boolean enableJvmAssertions)
+ throws Exception {
+
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addKeepMainRule(testClassKt)
+ .addKeepClassAndMembersRules(class1, class2)
+ .setMinApi(parameters.getApiLevel())
+ .apply(builderConsumer)
+ .noMinification()
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .enableRuntimeAssertions(enableJvmAssertions)
+ .run(parameters.getRuntime(), testClassKt)
+ .inspect(inspector)
+ .assertSuccessWithOutputLines(outputLines);
+ }
+
+ private List<String> allAssertionsExpectedLines() {
+ return ImmutableList.of("AssertionError in Class1", "AssertionError in Class2", "DONE");
+ }
+
+ private List<String> noAllAssertionsExpectedLines() {
+ return ImmutableList.of("DONE");
+ }
+
+ private void checkAssertionCodeRemoved(ClassSubject subject, boolean isR8) {
+ assertThat(subject, isPresent());
+ if (subject.getOriginalName().equals("kotlin._Assertions")) {
+ // With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as well,
+ // as is not used.
+ assertEquals(
+ (isR8 ? 0 : 1),
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .filter(InstructionSubject::isStaticPut)
+ .count());
+ assertFalse(
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isConstNumber));
+ } else {
+ // In R8 the false (default) value of kotlin._Assertions.ENABLED is propagated.
+ assertEquals(
+ !isR8,
+ subject
+ .uniqueMethodWithName("m")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isThrow));
+ }
+ }
+
+ private void checkAssertionCodeRemoved(CodeInspector inspector, String clazz, boolean isR8) {
+ checkAssertionCodeRemoved(inspector.clazz(clazz), isR8);
+ }
+
+ private void checkAssertionCodeEnabled(ClassSubject subject, boolean isR8) {
+ assertThat(subject, isPresent());
+ if (subject.getOriginalName().equals("kotlin._Assertions")) {
+ // With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as is not used.
+ assertEquals(
+ (isR8 ? 1 : 2),
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .filter(InstructionSubject::isStaticPut)
+ .count());
+ assertTrue(
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstNumber(1)));
+ } else {
+ assertTrue(
+ subject
+ .uniqueMethodWithName("m")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isThrow));
+ }
+ }
+
+ private void checkAssertionCodeEnabled(CodeInspector inspector, String clazz, boolean isR8) {
+
+ checkAssertionCodeEnabled(inspector.clazz(clazz), isR8);
+ }
+
+ private void checkAssertionCodeLeft(CodeInspector inspector, String clazz, boolean isR8) {
+ ClassSubject subject = inspector.clazz(clazz);
+ assertThat(subject, isPresent());
+ if (subject.getOriginalName().equals("kotlin._Assertions")) {
+ // With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as is not used.
+ assertEquals(
+ (isR8 ? 1 : 2),
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .filter(InstructionSubject::isStaticPut)
+ .count());
+ assertFalse(
+ subject
+ .uniqueMethodWithName("<clinit>")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isConstNumber));
+ } else {
+ assertTrue(
+ subject
+ .uniqueMethodWithName("m")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isThrow));
+ }
+ }
+
+ private void checkAssertionCodeRemoved(CodeInspector inspector, boolean isR8) {
+ if (isR8) {
+ assertThat(inspector.clazz("kotlin._Assertions"), not(isPresent()));
+ } else {
+ checkAssertionCodeRemoved(inspector, "kotlin._Assertions", isR8);
+ }
+ checkAssertionCodeRemoved(inspector, class1, isR8);
+ checkAssertionCodeRemoved(inspector, class2, isR8);
+ }
+
+ private void checkAssertionCodeEnabled(CodeInspector inspector, boolean isR8) {
+ if (isR8) {
+ assertThat(inspector.clazz("kotlin._Assertions"), not(isPresent()));
+ } else {
+ checkAssertionCodeEnabled(inspector, "kotlin._Assertions", isR8);
+ }
+ checkAssertionCodeEnabled(inspector, class1, isR8);
+ checkAssertionCodeEnabled(inspector, class2, isR8);
+ }
+
+ private void checkAssertionCodeLeft(CodeInspector inspector, boolean isR8) {
+ checkAssertionCodeLeft(inspector, "kotlin._Assertions", isR8);
+ checkAssertionCodeLeft(inspector, class1, isR8);
+ checkAssertionCodeLeft(inspector, class2, isR8);
+ }
+
+ @Test
+ public void testAssertionsForDex() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ // Leaving assertions in or disabling them on Dalvik/Art means no assertions.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions),
+ inspector -> checkAssertionCodeLeft(inspector, false),
+ noAllAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions),
+ inspector -> checkAssertionCodeLeft(inspector, true),
+ noAllAssertionsExpectedLines());
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::disableAllAssertions),
+ inspector -> checkAssertionCodeRemoved(inspector, false),
+ noAllAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::disableAllAssertions),
+ inspector -> checkAssertionCodeRemoved(inspector, true),
+ noAllAssertionsExpectedLines());
+ // Compile time enabling assertions gives assertions on Dalvik/Art.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::enableAllAssertions),
+ inspector -> checkAssertionCodeEnabled(inspector, false),
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::enableAllAssertions),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ // Enabling for the "kotlin._Assertions" class should enable all.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, false),
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ // Enabling for the "kotlin" package should enable all.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopePackage("kotlin").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, false),
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopePackage("kotlin").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ }
+
+ @Test
+ public void testAssertionsForCf() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
+ // Leaving assertion code means assertions are controlled by the -ea flag.
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions),
+ inspector -> checkAssertionCodeLeft(inspector, true),
+ noAllAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions),
+ inspector -> checkAssertionCodeLeft(inspector, true),
+ allAssertionsExpectedLines(),
+ true);
+ // Compile time enabling or disabling assertions means the -ea flag has no effect.
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::enableAllAssertions),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::enableAllAssertions),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines(),
+ true);
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::disableAllAssertions),
+ inspector -> checkAssertionCodeRemoved(inspector, true),
+ noAllAssertionsExpectedLines());
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::disableAllAssertions),
+ inspector -> checkAssertionCodeRemoved(inspector, true),
+ noAllAssertionsExpectedLines(),
+ true);
+ }
+
+ @Test
+ public void TestWithModifiedKotlinAssertions() throws Exception {
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClassFileData(dumpModifiedKotlinAssertions())
+ .addProgramFiles(kotlinClasses.get(targetVersion))
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
+ .run(
+ parameters.getRuntime(),
+ getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+ .assertSuccessWithOutputLines(noAllAssertionsExpectedLines());
+ testForD8()
+ .addProgramClassFileData(dumpModifiedKotlinAssertions())
+ .addProgramFiles(kotlinClasses.get(targetVersion))
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
+ .run(
+ parameters.getRuntime(),
+ getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+ .assertSuccessWithOutputLines(allAssertionsExpectedLines());
+ testForD8()
+ .addProgramClassFileData(dumpModifiedKotlinAssertions())
+ .addProgramFiles(kotlinClasses.get(targetVersion))
+ .setMinApi(parameters.getApiLevel())
+ .addAssertionsConfiguration(AssertionsConfiguration.Builder::disableAllAssertions)
+ .run(
+ parameters.getRuntime(),
+ getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+ .assertSuccessWithOutputLines(noAllAssertionsExpectedLines());
+ }
+
+ // Slightly modified version of kotlin._Assertions to hit all code paths in the assertion
+ // rewriter. See "Code added" below.
+ public static byte[] dumpModifiedKotlinAssertions() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_6,
+ ACC_PUBLIC | ACC_FINAL | ACC_SUPER,
+ "kotlin/_Assertions",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "ENABLED", "Z", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "INSTANCE", "Lkotlin/_Assertions;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC | ACC_DEPRECATED,
+ "ENABLED$annotations",
+ "()V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(0, 0);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, "kotlin/_Assertions");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "kotlin/_Assertions", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC, "kotlin/_Assertions", "INSTANCE", "Lkotlin/_Assertions;");
+
+ // Code added (added an additional call to getClass().desiredAssertionStatus() which
+ // result is not assigned to ENABLED).
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+ methodVisitor.visitInsn(POP);
+ // End code added.
+
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "kotlin/_Assertions", "ENABLED", "Z");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index 2d79d48..01c28c1 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -481,16 +481,33 @@
"AssertionError in TestClassForInnerClass.InnerClass",
"DONE");
}
+
/**
* Code for the following class in the unnamed package:
*
- * <p>public class Main { public static void main(String[] args) { try { Class1.m(); } catch
- * (AssertionError e) { System.out.println("AssertionError in Class1"); } try { Class2.m(); }
- * catch (AssertionError e) { System.out.println("AssertionError in Class2"); } try {
- * com.android.tools.r8.rewrite.assertions.Class1.m(); } catch (AssertionError e) {
- * System.out.println("AssertionError in Class1"); } try {
- * com.android.tools.r8.rewrite.assertions.Class2.m(); } catch (AssertionError e) {
- * System.out.println("AssertionError in Class2"); } System.out.println("DONE"); } }
+ * <pre>
+ * public class Main {
+ * public static void main(String[] args) {
+ * try {
+ * Class1.m();
+ * } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class1");
+ * } try {
+ * Class2.m();
+ * } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class2");
+ * } try {
+ * com.android.tools.r8.rewrite.assertions.Class1.m();
+ * } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class1");
+ * } try {
+ * com.android.tools.r8.rewrite.assertions.Class2.m();
+ * } catch (AssertionError e) {
+ * System.out.println("AssertionError in Class2");
+ * } System.out.println("DONE");
+ * }
+ * }
+ * </pre>
*/
public static byte[] testClassForUnknownPackage() {
@@ -642,7 +659,13 @@
/**
* Code for the following class in the unnamed package:
*
- * <p>public class <name> { public static void m() { assert false; } }
+ * <pre>
+ * public class <name> {
+ * public static void m() {
+ * assert false;
+ * }
+ * }
+ * </pre>
*/
public static byte[] classInUnnamedPackage(String name) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class1.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class1.kt
new file mode 100644
index 0000000..582c029
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class1.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions.kotlintestclasses
+
+class Class1 {
+ fun m() {
+ assert(hashCode() == 0)
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class2.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class2.kt
new file mode 100644
index 0000000..2936e6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class2.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions.kotlintestclasses
+
+class Class2 {
+ fun m() {
+ assert(false)
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/TestClass.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/TestClass.kt
new file mode 100644
index 0000000..990999c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/TestClass.kt
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions.kotlintestclasses
+
+fun main() {
+ try {
+ Class1().m()
+ } catch (e: AssertionError) {
+ println("AssertionError in Class1")
+ }
+ try {
+ Class2().m()
+ } catch (e: AssertionError) {
+ println("AssertionError in Class2")
+ }
+ println("DONE")
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index c1998bd..e64933b 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -9,9 +9,9 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@@ -21,7 +21,7 @@
public class R8Shaking2LookupTest {
static final String APP_FILE_NAME = ToolHelper.EXAMPLES_BUILD_DIR + "shaking2/classes.dex";
- private DexApplication program;
+ private DirectMappedDexApplication program;
private DexItemFactory dexItemFactory;
private AppInfoWithSubtyping appInfo;
diff --git a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
index f1d0cda..15fba7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
@@ -85,7 +85,6 @@
"-keep class " + Outer.class.getCanonicalName() + " {",
" onEvent(...);",
"}",
- "-printmapping",
"-keepattributes InnerClasses,EnclosingMethod,Signature",
"-allowaccessmodification");
test(config, compatMode);
@@ -106,7 +105,6 @@
" @**.Keep <fields>;",
" @**.Keep <methods>;",
"}",
- "-printmapping",
"-keepattributes InnerClasses,EnclosingMethod,Signature",
"-allowaccessmodification");
test(config, true);
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index ab46041..7752712 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -243,11 +243,11 @@
public void testStaticFieldWithoutInitializationStaticClassKept() throws Exception {
// An explicit keep rule keeps the default constructor.
Class<?> mainClass = MainGetStaticFieldNotInitialized.class;
- String proguardConfiguration = keepMainProguardConfiguration(
- mainClass,
- ImmutableList.of(
- "-keep class " + getJavacGeneratedClassName(StaticFieldNotInitialized.class) + " {",
- "}"));
+ String proguardConfiguration =
+ keepMainProguardConfiguration(
+ mainClass,
+ ImmutableList.of(
+ "-keep class " + StaticFieldNotInitialized.class.getTypeName() + " {", "}"));
runTest(
mainClass,
ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
@@ -259,11 +259,11 @@
public void testStaticFieldWithInitializationStaticClassKept() throws Exception {
// An explicit keep rule keeps the default constructor.
Class<?> mainClass = MainGetStaticFieldInitialized.class;
- String proguardConfiguration = keepMainProguardConfiguration(
- mainClass,
- ImmutableList.of(
- "-keep class " + getJavacGeneratedClassName(StaticFieldInitialized.class) + " {",
- "}"));
+ String proguardConfiguration =
+ keepMainProguardConfiguration(
+ mainClass,
+ ImmutableList.of(
+ "-keep class " + StaticFieldInitialized.class.getTypeName() + " {", "}"));
runTest(
mainClass,
ImmutableList.of(mainClass, StaticFieldInitialized.class),
@@ -275,11 +275,10 @@
public void testStaticMethodStaticClassKept() throws Exception {
// An explicit keep rule keeps the default constructor.
Class<?> mainClass = MainCallStaticMethod.class;
- String proguardConfiguration = keepMainProguardConfiguration(
- mainClass,
- ImmutableList.of(
- "-keep class " + getJavacGeneratedClassName(StaticMethod.class) + " {",
- "}"));
+ String proguardConfiguration =
+ keepMainProguardConfiguration(
+ mainClass,
+ ImmutableList.of("-keep class " + StaticMethod.class.getTypeName() + " {", "}"));
runTest(
mainClass,
ImmutableList.of(mainClass, StaticMethod.class),
diff --git a/src/test/java/com/android/tools/r8/testing/UnicodeUtf8Test.java b/src/test/java/com/android/tools/r8/testing/UnicodeUtf8Test.java
new file mode 100644
index 0000000..85264d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/UnicodeUtf8Test.java
@@ -0,0 +1,51 @@
+// 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.testing;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/* This test is ensuring that javac will compile expecting an UTF-8 encoding of the test. */
+@RunWith(Parameterized.class)
+public class UnicodeUtf8Test extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnicodeUtf8Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("\uD83C\uDCA1");
+ }
+
+ public static class Main {
+
+ private static final String MOTOR_HEAD = "🂡";
+
+ public static void main(String[] args) throws UnsupportedEncodingException {
+ (new PrintStream(System.out, true, "UTF-8")).println(MOTOR_HEAD);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/ThrowingOutputStream.java b/src/test/java/com/android/tools/r8/utils/ThrowingOutputStream.java
new file mode 100644
index 0000000..2542264
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ThrowingOutputStream.java
@@ -0,0 +1,42 @@
+// 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 java.io.OutputStream;
+import java.util.function.Supplier;
+
+public class ThrowingOutputStream<T extends Error> extends OutputStream {
+
+ private final Supplier<T> exceptionSupplier;
+
+ public ThrowingOutputStream(Supplier<T> exceptionSupplier) {
+ this.exceptionSupplier = exceptionSupplier;
+ }
+
+ @Override
+ public void write(int b) {
+ throw exceptionSupplier.get();
+ }
+
+ @Override
+ public void write(byte[] b) {
+ throw exceptionSupplier.get();
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ throw exceptionSupplier.get();
+ }
+
+ @Override
+ public void flush() {
+ throw exceptionSupplier.get();
+ }
+
+ @Override
+ public void close() {
+ throw exceptionSupplier.get();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 1a640ed..c467da4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -9,6 +9,11 @@
public class AbsentKmClassSubject extends KmClassSubject {
@Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
public DexClass getDexClass() {
return null;
}
@@ -99,6 +104,16 @@
}
@Override
+ public List<String> getNestedClassDescriptors() {
+ return null;
+ }
+
+ @Override
+ public List<ClassSubject> getNestedClasses() {
+ return null;
+ }
+
+ @Override
public List<String> getSealedSubclassDescriptors() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
index e869207..c72d305 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -31,4 +31,14 @@
public JvmMethodSignature signature() {
return null;
}
+
+ @Override
+ public KmTypeSubject receiverParameterType() {
+ return null;
+ }
+
+ @Override
+ public KmTypeSubject returnType() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 6c65c2d..7b57397 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -24,6 +24,11 @@
}
@Override
+ public String getName() {
+ return kmClass.getName();
+ }
+
+ @Override
public DexClass getDexClass() {
return clazz;
}
@@ -58,7 +63,7 @@
@Override
public List<String> getSuperTypeDescriptors() {
return kmClass.getSupertypes().stream()
- .map(this::getDescriptorFromKmType)
+ .map(KmTypeSubject::getDescriptorFromKmType)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@@ -71,6 +76,26 @@
.collect(Collectors.toList());
}
+ private String nestClassDescriptor(String nestClassName) {
+ return DescriptorUtils.getDescriptorFromClassBinaryName(
+ kmClass.name + DescriptorUtils.INNER_CLASS_SEPARATOR + nestClassName);
+ }
+
+ @Override
+ public List<String> getNestedClassDescriptors() {
+ return kmClass.getNestedClasses().stream()
+ .map(this::nestClassDescriptor)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassSubject> getNestedClasses() {
+ return kmClass.getNestedClasses().stream()
+ .map(this::nestClassDescriptor)
+ .map(this::getClassSubjectFromDescriptor)
+ .collect(Collectors.toList());
+ }
+
@Override
public List<String> getSealedSubclassDescriptors() {
return kmClass.getSealedSubclasses().stream()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 9fece50..b9fa99a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -3,10 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
-import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+import static com.android.tools.r8.utils.codeinspector.KmTypeSubject.getDescriptorFromKmType;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.Box;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -19,7 +18,6 @@
import kotlinx.metadata.KmPropertyExtensionVisitor;
import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -30,26 +28,6 @@
CodeInspector codeInspector();
KmDeclarationContainer getKmDeclarationContainer();
- // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
- default String getDescriptorFromKmType(KmType kmType) {
- if (kmType == null) {
- return null;
- }
- Box<String> descriptor = new Box<>(null);
- kmType.accept(new KmTypeVisitor() {
- @Override
- public void visitClass(String name) {
- descriptor.set(getDescriptorFromKotlinClassifier(name));
- }
-
- @Override
- public void visitTypeAlias(String name) {
- descriptor.set(getDescriptorFromKotlinClassifier(name));
- }
- });
- return descriptor.get();
- }
-
@Override
default List<String> getParameterTypeDescriptorsInFunctions() {
return getKmDeclarationContainer().getFunctions().stream()
@@ -97,6 +75,8 @@
};
}
});
+ // We don't check Kotlin types in tests, but be aware of the relocation issue.
+ // See b/70169921#comment57 for more details.
}
}
@@ -202,6 +182,8 @@
};
}
});
+ // We don't check Kotlin types in tests, but be aware of the relocation issue.
+ // See b/70169921#comment57 for more details.
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index 7c970c2..3bbf235 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmFunctionProcessor;
import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmType;
import kotlinx.metadata.jvm.JvmMethodSignature;
public class FoundKmFunctionSubject extends KmFunctionSubject {
@@ -46,4 +47,16 @@
public JvmMethodSignature signature() {
return signature;
}
+
+ @Override
+ public KmTypeSubject receiverParameterType() {
+ KmType kmType = kmFunction.getReceiverParameterType();
+ assert !isExtension() || kmType != null;
+ return kmType == null ? null : new KmTypeSubject(kmType);
+ }
+
+ @Override
+ public KmTypeSubject returnType() {
+ return new KmTypeSubject(kmFunction.getReturnType());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index ebe2f02..9a48dfa 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -115,15 +115,22 @@
MethodSubject getMethodSubject();
default int getLineNumber() {
- return getMethodSubject().getLineNumberTable().getLineForInstruction(this);
+ LineNumberTable lineNumberTable = getMethodSubject().getLineNumberTable();
+ return lineNumberTable == null ? -1 : lineNumberTable.getLineForInstruction(this);
}
- default RetraceMethodResult retracePosition(RetraceBase retraceBase) {
+ default RetraceMethodResult retrace(RetraceBase retraceBase) {
MethodSubject methodSubject = getMethodSubject();
assert methodSubject.isPresent();
- int lineNumber = getLineNumber();
- return retraceBase
- .retrace(methodSubject.asFoundMethodSubject().asMethodReference())
- .narrowByLine(lineNumber);
+ return retraceBase.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
+ }
+
+ default RetraceMethodResult retraceLinePosition(RetraceBase retraceBase) {
+ return retrace(retraceBase).narrowByLine(getLineNumber());
+ }
+
+ default RetraceMethodResult retracePcPosition(
+ RetraceBase retraceBase, MethodSubject methodSubject) {
+ return retrace(retraceBase).narrowByLine(getOffset(methodSubject).offset);
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 97b233d..7fb1f9f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -7,12 +7,18 @@
import java.util.List;
public abstract class KmClassSubject extends Subject implements KmDeclarationContainerSubject {
+ public abstract String getName();
+
public abstract DexClass getDexClass();
public abstract List<String> getSuperTypeDescriptors();
public abstract List<ClassSubject> getSuperTypes();
+ public abstract List<String> getNestedClassDescriptors();
+
+ public abstract List<ClassSubject> getNestedClasses();
+
public abstract List<String> getSealedSubclassDescriptors();
public abstract List<ClassSubject> getSealedSubclasses();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
index 2e9eeda..d0e0a37 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -15,4 +15,8 @@
public abstract boolean isExtension();
public abstract JvmMethodSignature signature();
+
+ public abstract KmTypeSubject receiverParameterType();
+
+ public abstract KmTypeSubject returnType();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
new file mode 100644
index 0000000..aa52c67
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.Box;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
+
+public class KmTypeSubject extends Subject {
+ private final KmType kmType;
+
+ KmTypeSubject(KmType kmType) {
+ assert kmType != null;
+ this.kmType = kmType;
+ }
+
+ // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
+ static String getDescriptorFromKmType(KmType kmType) {
+ if (kmType == null) {
+ return null;
+ }
+ Box<String> descriptor = new Box<>(null);
+ kmType.accept(new KmTypeVisitor() {
+ @Override
+ public void visitClass(String name) {
+ // We don't check Kotlin types in tests, but be aware of the relocation issue.
+ // See b/70169921#comment25 for more details.
+ assert descriptor.get() == null;
+ descriptor.set(getDescriptorFromKotlinClassifier(name));
+ }
+
+ @Override
+ public void visitTypeAlias(String name) {
+ assert descriptor.get() == null;
+ descriptor.set(getDescriptorFromKotlinClassifier(name));
+ }
+ });
+ return descriptor.get();
+ }
+
+ public String descriptor() {
+ return getDescriptorFromKmType(kmType);
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ throw new Unreachable("Cannot determine if a type is renamed");
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ throw new Unreachable("Cannot determine if a type is synthetic");
+ }
+}
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 d8d219e..3fb684f 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
@@ -12,7 +12,6 @@
import com.android.tools.r8.retrace.RetraceMethodResult;
import com.android.tools.r8.retrace.RetraceMethodResult.Element;
import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.stream.Collectors;
@@ -443,21 +442,7 @@
returnValue.set(false);
return;
}
- if (currentInline.hasMethodSubject()) {
- sameMethod =
- element
- .getMethodReference()
- .equals(
- currentInline.methodSubject.asFoundMethodSubject().asMethodReference());
- } else {
- MethodReference methodReference = element.getMethodReference();
- sameMethod =
- methodReference.getMethodName().equals(currentInline.methodName)
- || methodReference
- .getHolderClass()
- .getTypeName()
- .equals(currentInline.holder);
- }
+ sameMethod = element.getMethodReference().equals(currentInline.methodReference);
boolean samePosition =
element.getOriginalLineNumber(currentInline.minifiedPosition)
== currentInline.originalPosition;
@@ -476,29 +461,33 @@
};
}
- public static Matcher<RetraceMethodResult> isInlinedInto(InlinePosition inlinePosition) {
+ public static Matcher<RetraceMethodResult> isTopOfStackTrace(
+ StackTrace stackTrace, List<Integer> minifiedPositions) {
return new TypeSafeMatcher<RetraceMethodResult>() {
@Override
protected boolean matchesSafely(RetraceMethodResult item) {
- if (item.isAmbiguous() || !inlinePosition.methodSubject.isPresent()) {
+ List<Element> retraceElements = item.stream().collect(Collectors.toList());
+ if (retraceElements.size() > stackTrace.size()
+ || retraceElements.size() != minifiedPositions.size()) {
return false;
}
- List<Element> references = item.stream().collect(Collectors.toList());
- if (references.size() < 2) {
- return false;
+ for (int i = 0; i < retraceElements.size(); i++) {
+ Element retraceElement = retraceElements.get(i);
+ StackTraceLine stackTraceLine = stackTrace.get(i);
+ MethodReference methodReference = retraceElement.getMethodReference();
+ if (!stackTraceLine.methodName.equals(methodReference.getMethodName())
+ || !stackTraceLine.className.equals(methodReference.getHolderClass().getTypeName())
+ || stackTraceLine.lineNumber
+ != retraceElement.getOriginalLineNumber(minifiedPositions.get(i))) {
+ return false;
+ }
}
- Element lastElement = ListUtils.last(references);
- if (!lastElement
- .getMethodReference()
- .equals(inlinePosition.methodSubject.asFoundMethodSubject().asMethodReference())) {
- return false;
- }
- return lastElement.getFirstLineNumberOfOriginalRange() == inlinePosition.originalPosition;
+ return true;
}
@Override
public void describeTo(Description description) {
- description.appendText("is not inlined into " + inlinePosition.getMethodName());
+ description.appendText("is not matching the stack trace");
}
};
}
@@ -538,37 +527,27 @@
}
public static class InlinePosition {
- private final FoundMethodSubject methodSubject;
- private final String holder;
- private final String methodName;
+ private final MethodReference methodReference;
private final int minifiedPosition;
private final int originalPosition;
private InlinePosition caller;
private InlinePosition(
- FoundMethodSubject methodSubject,
- String holder,
- String methodName,
- int minifiedPosition,
- int originalPosition) {
- this.methodSubject = methodSubject;
- this.holder = holder;
- this.methodName = methodName;
+ MethodReference methodReference, int minifiedPosition, int originalPosition) {
+ this.methodReference = methodReference;
this.minifiedPosition = minifiedPosition;
this.originalPosition = originalPosition;
- assert methodSubject != null || holder != null;
- assert methodSubject != null || methodName != null;
+ }
+
+ public static InlinePosition create(
+ MethodReference methodReference, int minifiedPosition, int originalPosition) {
+ return new InlinePosition(methodReference, minifiedPosition, originalPosition);
}
public static InlinePosition create(
FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
- return new InlinePosition(methodSubject, null, null, minifiedPosition, originalPosition);
- }
-
- public static InlinePosition create(
- String holder, String methodName, int minifiedPosition, int originalPosition) {
- return new InlinePosition(null, holder, methodName, minifiedPosition, originalPosition);
+ return create(methodSubject.asMethodReference(), minifiedPosition, originalPosition);
}
public static InlinePosition stack(InlinePosition... stack) {
@@ -585,18 +564,12 @@
setCaller(index + 1, stack);
}
- boolean hasMethodSubject() {
- return methodSubject != null;
- }
-
String getMethodName() {
- return hasMethodSubject() ? methodSubject.getOriginalName(false) : methodName;
+ return methodReference.getMethodName();
}
String getClassName() {
- return hasMethodSubject()
- ? methodSubject.asMethodReference().getHolderClass().getTypeName()
- : holder;
+ return methodReference.getHolderClass().getTypeName();
}
}
}
diff --git a/src/test/sampleApks/simple/res/layout/main.xml b/src/test/sampleApks/simple/res/layout/main.xml
index 7859435..ab673af 100644
--- a/src/test/sampleApks/simple/res/layout/main.xml
+++ b/src/test/sampleApks/simple/res/layout/main.xml
@@ -14,5 +14,5 @@
android:layout_height="wrap_content"
android:layout_width="match_parent" android:id="@+id/PressButton"
android:layout_margin="2dip"
- android:text="Do something"/>
+ android:text="@string/referenced_from_layout"/>
</LinearLayout>
diff --git a/src/test/sampleApks/simple/res/values/strings.xml b/src/test/sampleApks/simple/res/values/strings.xml
index 8bae26c..5bf4844 100644
--- a/src/test/sampleApks/simple/res/values/strings.xml
+++ b/src/test/sampleApks/simple/res/values/strings.xml
@@ -6,4 +6,7 @@
-->
<resources>
<string name="app_name">R8 simple app</string>
+ <string name="referenced_from_layout">Push it</string>
+ <string name="referenced_from_code">code txt</string>
+ <string name="not_used">not used</string>
</resources>
diff --git a/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
index 4a09fd4..67aca2b 100644
--- a/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
+++ b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
@@ -7,11 +7,40 @@
import android.app.Activity;
import android.os.Bundle;
import com.android.tools.r8.sample.simple.R;
+import java.util.ArrayList;
+import android.content.res.Resources;
+
public class R8Activity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_Light);
setContentView(R.layout.main);
+ System.out.println(R.string.referenced_from_code);
+ useResources();
+ }
+
+
+ public void useResources() {
+ // Add usage of resource identifiers with ID's that would never
+ // exist in aapt generated R class (so that we can swap them out).
+ ArrayList<Integer> resources = new ArrayList();
+ resources.add(0x7fDEAD01);
+ resources.add(0x7fDEAD02);
+ resources.add(0x7fDEAD03);
+ resources.add(0x7fDEAD04);
+ resources.add(0x7fDEAD05);
+ resources.add(0x7fDEAD06);
+ resources.add(0x7fDEAD07);
+ resources.add(0x7fDEAD08);
+ resources.add(0x7fDEAD09);
+ resources.add(0x7fDEAD0a);
+ for (int id : resources) {
+ try {
+ getResources().getResourceName(id);
+ } catch (Resources.NotFoundException e) {
+ // Do nothing
+ }
+ }
}
}
diff --git a/tools/archive.py b/tools/archive.py
index 3eb0200..a3e15ba 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -13,7 +13,6 @@
except ImportError:
# Not a Unix system. Do what Gandalf tells you not to.
pass
-import resource
import shutil
import subprocess
import sys
diff --git a/tools/gradle.py b/tools/gradle.py
index 0e926e6..a52e3f3 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -41,6 +41,7 @@
def GetJavaEnv(env):
java_env = dict(env if env else os.environ, JAVA_HOME = jdk.GetJdkHome())
java_env['PATH'] = java_env['PATH'] + os.pathsep + os.path.join(jdk.GetJdkHome(), 'bin')
+ java_env['GRADLE_OPTS'] = '-Xmx1g'
return java_env
def PrintCmd(s):