Disable backported methods for API levels higher than known Android release

If the min API level is set higher than the last known Android
release, the methods implemented by the platform is not known, and the
compiler cannot know which methods to backport. The backporting of
methods is then turned off and a warning is issued.

For the "magic" API level of 10000 the warning is not issued, as that
is the API level used when building the core library in the Android
Platform. Here disabling of backported methods is the expected
behaviour.

Bug: 147480264
Change-Id: I59b96fc0543a243861c7a0e3b0b9428504e96555
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 5f65f6a..9dc1cc9 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -540,6 +540,16 @@
         }
         reporter.error(builder.toString());
       }
+      if (getMinApiLevel() > AndroidApiLevel.LATEST.getLevel()) {
+        if (getMinApiLevel() != AndroidApiLevel.magicApiLevelUsedByAndroidPlatformBuild) {
+          reporter.warning(
+              "An API level of "
+                  + getMinApiLevel()
+                  + " is not supported by this compiler. Please use an API level of "
+                  + AndroidApiLevel.LATEST.getLevel()
+                  + " or earlier");
+        }
+      }
       super.validate();
     }
   }
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 acdad7d..1d9c597 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
@@ -70,6 +70,7 @@
   private final IRConverter converter;
   private final DexItemFactory factory;
   private final RewritableMethods rewritableMethods;
+  private final boolean enable;
 
   private final Set<DexType> holders = Sets.newConcurrentHashSet();
   private final Map<DexMethod, MethodProvider> methodProviders = new ConcurrentHashMap<>();
@@ -79,6 +80,13 @@
     this.converter = converter;
     this.factory = appView.dexItemFactory();
     this.rewritableMethods = new RewritableMethods(appView.options(), appView);
+    // Disable rewriting if there are no methods to rewrite or if the API level is higher than
+    // the highest known API level when the compiler is built. This ensures that when this is used
+    // 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.enable =
+        !this.rewritableMethods.isEmpty()
+            && appView.options().minApiLevel <= AndroidApiLevel.LATEST.getLevel();
   }
 
   public static List<DexMethod> generateListOfBackportedMethods(AndroidApiLevel apiLevel) {
@@ -93,7 +101,7 @@
   }
 
   public void desugar(IRCode code) {
-    if (rewritableMethods.isEmpty()) {
+    if (!enable) {
       return; // Nothing to do!
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 1a21ffd..8c5c3b2 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -45,6 +45,8 @@
 
   public static final AndroidApiLevel LATEST = Q;
 
+  public static final int magicApiLevelUsedByAndroidPlatformBuild = 10000;
+
   private final int level;
 
   AndroidApiLevel(int level) {
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 2541f47..ef17f80 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -38,6 +38,10 @@
     clientHandler.warning(warning);
   }
 
+  public void warning(String message) {
+    warning(new StringDiagnostic(message));
+  }
+
   @Override
   public synchronized void error(Diagnostic error) {
     clientHandler.error(error);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index cc8d46a..dbad10b 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -78,6 +78,8 @@
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
     builder.setMainDexListConsumer(mainDexListConsumer);
     if (backend == Backend.DEX && defaultMinApiLevel != null) {
+      assert builder.getMinApiLevel() == 0
+          : "Don't set the API level directly through BaseCompilerCommand.Builder in tests";
       builder.setMinApiLevel(defaultMinApiLevel.getLevel());
     }
     if (useDefaultRuntimeLibrary) {
@@ -168,6 +170,9 @@
   }
 
   public T setMinApi(AndroidApiLevel minApiLevel) {
+    assert builder.getMinApiLevel() > 0 || this.defaultMinApiLevel != null
+        : "Tests must use this method to set min API level, and not"
+            + " BaseCompilerCommand.Builder.setMinApiLevel()";
     if (backend == Backend.DEX) {
       this.defaultMinApiLevel = null;
       builder.setMinApiLevel(minApiLevel.getLevel());
@@ -175,6 +180,14 @@
     return self();
   }
 
+  public T setMinApi(int minApiLevel) {
+    if (backend == Backend.DEX) {
+      this.defaultMinApiLevel = null;
+      builder.setMinApiLevel(minApiLevel);
+    }
+    return self();
+  }
+
   /** @deprecated use {@link #setMinApi(AndroidApiLevel)} instead. */
   @Deprecated
   public T setMinApi(TestRuntime runtime) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/NoBackportForAndroidPlatform.java b/src/test/java/com/android/tools/r8/desugar/backports/NoBackportForAndroidPlatform.java
new file mode 100644
index 0000000..47081ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/NoBackportForAndroidPlatform.java
@@ -0,0 +1,128 @@
+// 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 org.hamcrest.core.StringContains.containsString;
+
+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 NoBackportForAndroidPlatform extends TestBase implements Opcodes {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // The use of high API level will produce dex files with high DEX version, so only run on high
+    // API level VMs.
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
+        .build();
+  }
+
+  public NoBackportForAndroidPlatform(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void backportSucceedsOnSupportedApiLevel() throws Exception {
+    testForD8()
+        .addProgramClassFileData(mainWithMathMultiplyExactLongInt())
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), "Test")
+        .assertSuccessWithOutputLines("4");
+  }
+
+  @Test
+  public void warningForNonPlatformBuild() throws Exception {
+    testForD8()
+        .addProgramClassFileData(mainWithMathMultiplyExactLongInt())
+        .setMinApi(30)
+        .compile()
+        .assertOnlyWarnings()
+        .assertWarningMessageThatMatches(
+            containsString("An API level of 30 is not supported by this compiler"))
+        .run(parameters.getRuntime(), "Test")
+        .assertFailureWithErrorThatMatches(
+            containsString("java.lang.NoSuchMethodError: No static method multiplyExact(JI)J"));
+  }
+
+  @Test
+  public void noWarningForPlatformBuild() throws Exception {
+    testForD8()
+        .addProgramClassFileData(mainWithMathMultiplyExactLongInt())
+        .setMinApi(AndroidApiLevel.magicApiLevelUsedByAndroidPlatformBuild)
+        .run(parameters.getRuntime(), "Test")
+        .assertFailureWithErrorThatMatches(
+            containsString("java.lang.NoSuchMethodError: No static method multiplyExact(JI)J"));
+  }
+
+  // Code for:
+  //
+  // class Test {
+  //   public static void main(String[] args) {
+  //     // Call Math.multiplyExact(long, int), which is not in Android Q.
+  //     System.out.println(Math.multiplyExact(2L, 2));
+  //   }
+  // }
+  //
+  private byte[] mainWithMathMultiplyExactLongInt() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "Test", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Test.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, label0);
+      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_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn(Long.valueOf(2L));
+      methodVisitor.visitLdcInsn(Integer.valueOf(2));
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/lang/Math", "multiplyExact", "(JI)J", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(5, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}