Add a D8 option to only enable the backporting part of desugaring
Bug: 147139686
Change-Id: I0bed8207343a4b86a2d66ed023aeb02afc9aa282
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 9dc1cc9..8323666 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -166,7 +166,7 @@
private CompilationMode mode;
private int minApiLevel = 0;
- private DesugarState desugarState = DesugarState.ON;
+ protected DesugarState desugarState = DesugarState.ON;
private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
private boolean includeClassesChecksum = false;
private boolean lookupLibraryBeforeProgram = true;
diff --git a/src/main/java/com/android/tools/r8/CompatDxHelper.java b/src/main/java/com/android/tools/r8/CompatDxHelper.java
index fb186f4..beec3dc 100644
--- a/src/main/java/com/android/tools/r8/CompatDxHelper.java
+++ b/src/main/java/com/android/tools/r8/CompatDxHelper.java
@@ -6,15 +6,12 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
public class CompatDxHelper {
public static void run(D8Command command, Boolean minimalMainDex)
throws CompilationFailedException {
AndroidApp app = command.getInputApp();
InternalOptions options = command.getInternalOptions();
- // DX does not desugar.
- options.desugarState = DesugarState.OFF;
// DX allows --multi-dex without specifying a main dex list for legacy devices.
// That is broken, but for CompatDX we do the same to not break existing builds
// that are trying to transition.
@@ -28,4 +25,8 @@
public static void ignoreDexInArchive(BaseCommand.Builder builder) {
builder.setIgnoreDexInArchive(true);
}
+
+ public static void enableDesugarBackportStatics(D8Command.Builder builder) {
+ builder.enableDesugarBackportStatics();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 467570f..d421ec3 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -155,6 +155,12 @@
return self();
}
+ // Internal helper for compat tools to make them only desugar backports.
+ Builder enableDesugarBackportStatics() {
+ this.desugarState = DesugarState.ONLY_BACKPORT_STATICS;
+ return self();
+ }
+
@Override
Builder self() {
return this;
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
index f4f3c7e..38608a2 100644
--- a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
@@ -60,6 +60,7 @@
private String output = null;
private int numberOfThreads = 8;
private boolean noLocals = false;
+ private boolean backportStatics = true;
public static void main(String[] args)
throws IOException, InterruptedException, ExecutionException {
@@ -108,6 +109,9 @@
case "--nolocals":
noLocals = true;
break;
+ case "--desugar-backport-statics":
+ backportStatics = true;
+ break;
default:
System.err.println("Unsupported option: " + flag);
System.exit(1);
@@ -168,6 +172,9 @@
.setMode(noLocals ? CompilationMode.RELEASE : CompilationMode.DEBUG)
.setMinApiLevel(AndroidApiLevel.H_MR2.getLevel())
.setDisableDesugaring(true);
+ if (backportStatics) {
+ CompatDxHelper.enableDesugarBackportStatics(builder);
+ }
try (InputStream stream = zipFile.getInputStream(classEntry)) {
builder.addClassProgramData(
ByteStreams.toByteArray(stream),
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index a52c2ea..e98d5c0 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -90,6 +90,7 @@
public final String mainDexList;
public final boolean minimalMainDex;
public final int minApiLevel;
+ public final boolean backportStatics;
public final String inputList;
public final ImmutableList<String> inputs;
// Undocumented option
@@ -147,6 +148,7 @@
final OptionSpec<String> mainDexList;
final OptionSpec<Void> minimalMainDex;
final OptionSpec<Integer> minApiLevel;
+ final OptionSpec<Void> backportStatics;
final OptionSpec<String> inputList;
final OptionSpec<String> inputs;
final OptionSpec<Void> version;
@@ -226,6 +228,8 @@
minApiLevel = parser
.accepts("min-sdk-version", "Minimum Android API level compatibility.")
.withRequiredArg().ofType(Integer.class);
+ backportStatics =
+ parser.accepts("desugar-backport-statics", "Backport additional Java 8 APIs");
inputList = parser
.accepts("input-list", "File listing input files")
.withRequiredArg()
@@ -290,6 +294,7 @@
} else {
minApiLevel = AndroidApiLevel.getDefault().getLevel();
}
+ backportStatics = options.has(spec.backportStatics);
inputList = options.valueOf(spec.inputList);
inputs = ImmutableList.copyOf(options.valuesOf(spec.inputs));
maxIndexNumber = options.valueOf(spec.maxIndexNumber);
@@ -451,13 +456,16 @@
CompatDxHelper.ignoreDexInArchive(builder);
builder
.addProgramFiles(inputs)
- .setProgramConsumer(
- createConsumer(inputs, output, singleDexFile, dexArgs.keepClasses))
+ .setProgramConsumer(createConsumer(inputs, output, singleDexFile, dexArgs.keepClasses))
.setMode(mode)
+ .setDisableDesugaring(true) // DX does not desugar.
.setMinApiLevel(dexArgs.minApiLevel);
if (mainDexList != null) {
builder.addMainDexListFiles(mainDexList);
}
+ if (dexArgs.backportStatics) {
+ CompatDxHelper.enableDesugarBackportStatics(builder);
+ }
CompatDxHelper.run(builder.build(), dexArgs.minimalMainDex);
} finally {
executor.shutdown();
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 a1df648..ea6e851 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
@@ -86,7 +86,8 @@
// by the Android Platform build (which normally use an API level of 10000) there will be
// no rewriting of backported methods. See b/147480264.
this.enabled =
- appView.options().desugarState == DesugarState.ON
+ (appView.options().desugarState == DesugarState.ON
+ || appView.options().desugarState == DesugarState.ONLY_BACKPORT_STATICS)
&& !this.rewritableMethods.isEmpty()
&& appView.options().minApiLevel <= AndroidApiLevel.LATEST.getLevel();
}
@@ -115,6 +116,10 @@
}
InvokeMethod invoke = instruction.asInvokeMethod();
+ if (appView.options().desugarState == DesugarState.ONLY_BACKPORT_STATICS
+ && !invoke.isInvokeStatic()) {
+ continue;
+ }
MethodProvider provider = getMethodProviderOrNull(invoke.getInvokedMethod());
if (provider == null) {
if (!rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
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 6e83a6f..6b4f0dc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -76,6 +76,8 @@
public enum DesugarState {
OFF,
+ // This is for use when desugar has run before, and backports have still not been desugared.
+ ONLY_BACKPORT_STATICS,
ON
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java b/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
new file mode 100644
index 0000000..cd6d160
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
@@ -0,0 +1,136 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.Diagnostic;
+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.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.DexInstructionSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarStaticBackportsOnly extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public DesugarStaticBackportsOnly(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private void checkLongHashCodeDesugared(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ assertEquals(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+ classSubject
+ .uniqueMethodWithName("main")
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ instructionSubject.isInvokeStatic()
+ && instructionSubject
+ .toString()
+ .contains("$r8$backportedMethods$utility$Long$1$hashCode")));
+ assertEquals(
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+ classSubject
+ .uniqueMethodWithName("main")
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ instructionSubject.isInvokeStatic()
+ && instructionSubject.toString().contains("java/lang/Long")));
+ }
+
+ @Test
+ public void testBackportDesugared() throws Exception {
+ String expectedOutput = StringUtils.lines("1234");
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> options.desugarState = DesugarState.ONLY_BACKPORT_STATICS)
+ .compile()
+ .inspect(this::checkLongHashCodeDesugared)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ private void checkLambdaNotDesugared(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClassLambda.class);
+ assertThat(classSubject, isPresent());
+ assertTrue(
+ classSubject
+ .uniqueMethodWithName("main")
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ ((DexInstructionSubject) instructionSubject).isInvokeCustom()));
+ }
+
+ @Test
+ public void testLambdaNotDesugared() throws Exception {
+ D8TestBuilder builder =
+ testForD8()
+ .addProgramClasses(TestClassLambda.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> options.desugarState = DesugarState.ONLY_BACKPORT_STATICS);
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+ builder.compile().inspect(this::checkLambdaNotDesugared);
+ } else {
+ try {
+ builder.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsCount(1);
+ Diagnostic diagnostic = diagnostics.getErrors().get(0);
+ assertThat(
+ diagnostic.getDiagnosticMessage(),
+ containsString("Invoke-customs are only supported starting with Android O"));
+ });
+ } catch (CompilationFailedException e) {
+ // Expected compilation failed.
+ return;
+ }
+ fail("Expected test to fail with CompilationFailedException");
+ }
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println(Long.hashCode(1234));
+ }
+ }
+
+ static class TestClassLambda {
+ public static void main(String[] args) {
+ Arrays.asList(args).forEach(s -> System.out.println(s));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index af63ce8..e487726 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -46,6 +46,8 @@
import com.android.tools.r8.code.IgetWide;
import com.android.tools.r8.code.InstanceOf;
import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeCustom;
+import com.android.tools.r8.code.InvokeCustomRange;
import com.android.tools.r8.code.InvokeDirect;
import com.android.tools.r8.code.InvokeDirectRange;
import com.android.tools.r8.code.InvokeInterface;
@@ -197,6 +199,10 @@
return false;
}
+ public boolean isInvokeCustom() {
+ return instruction instanceof InvokeCustom || instruction instanceof InvokeCustomRange;
+ }
+
public boolean isInvokeSuper() {
return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
}