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;
   }