Merge "CF backend: Implement InvokePolymorphic for VarHandle"
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 42982d5..9a260ea 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -103,7 +103,7 @@
     private OutputMode outputMode = OutputMode.DexIndexed;
 
     private CompilationMode mode;
-    private int minApiLevel = AndroidApiLevel.getDefault().getLevel();
+    private int minApiLevel = 0;
     private boolean disableDesugaring = false;
 
     Builder() {}
@@ -228,13 +228,20 @@
 
     /** Get the minimum API level (aka SDK version). */
     public int getMinApiLevel() {
-      return minApiLevel;
+      return isMinApiLevelSet() ? minApiLevel : AndroidApiLevel.getDefault().getLevel();
+    }
+
+    boolean isMinApiLevelSet() {
+      return minApiLevel != 0;
     }
 
     /** Set the minimum required API level (aka SDK version). */
     public B setMinApiLevel(int minApiLevel) {
-      assert minApiLevel > 0;
-      this.minApiLevel = minApiLevel;
+      if (minApiLevel <= 0) {
+        getReporter().error("Invalid minApiLevel: " + minApiLevel);
+      } else {
+        this.minApiLevel = minApiLevel;
+      }
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4615fa5..5f27356 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -256,6 +256,9 @@
               "R8 does not support compiling DEX inputs", new PathOrigin(file)));
         }
       }
+      if (getProgramConsumer() instanceof ClassFileConsumer && isMinApiLevelSet()) {
+        reporter.error("R8 does not support --min-api when compiling to class files");
+      }
       super.validate();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 718d23c..01cd5f5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokePolymorphicRange;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -14,7 +13,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.conversion.JarSourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -87,15 +85,12 @@
   public void buildCf(CfBuilder builder) {
     DexMethod dexMethod = getInvokedMethod();
     DexItemFactory factory = builder.getFactory();
-
-    if (dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_METHOD_HANDLE)) {
-      DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
-      builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
-    } else {
-      assert dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_VAR_HANDLE);
-      // VarHandle is new in Java 9
-      throw new Unimplemented();
-    }
+    // When we translate InvokeVirtual on MethodHandle/VarHandle into InvokePolymorphic,
+    // we translate the invoked prototype into a generic prototype that simply accepts Object[].
+    // To translate InvokePolymorphic back into InvokeVirtual, use the original prototype
+    // that is stored in getProto().
+    DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
+    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 26a94d2..b584448 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -2658,8 +2658,8 @@
     Handle bsmHandle = insn.bsm;
     if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC &&
         bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
-      throw new Unreachable(
-          "Bootstrap handle is not yet supported: tag == " + bsmHandle.getTag());
+      // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
+      throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
     }
     // Resolve the bootstrap method.
     DexMethodHandle bootstrapMethod = getMethodHandle(application, bsmHandle);
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 1073cf9..cc8c9ce 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -401,50 +401,54 @@
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
-    return minApiLevel >= AndroidApiLevel.P.getLevel();
+    return hasMinApi(AndroidApiLevel.P);
   }
 
   public boolean canUseInvokePolymorphic() {
-    return minApiLevel >= AndroidApiLevel.O.getLevel();
+    return hasMinApi(AndroidApiLevel.O);
   }
 
   public boolean canUseConstantMethodHandle() {
-    return minApiLevel >= AndroidApiLevel.P.getLevel();
+    return hasMinApi(AndroidApiLevel.P);
+  }
+
+  private boolean hasMinApi(AndroidApiLevel level) {
+    return isGeneratingClassFiles() || minApiLevel >= level.getLevel();
   }
 
   public boolean canUseConstantMethodType() {
-    return minApiLevel >= AndroidApiLevel.P.getLevel();
+    return hasMinApi(AndroidApiLevel.P);
   }
 
   public boolean canUseInvokeCustom() {
-    return minApiLevel >= AndroidApiLevel.O.getLevel();
+    return hasMinApi(AndroidApiLevel.O);
   }
 
   public boolean canUseDefaultAndStaticInterfaceMethods() {
-    return minApiLevel >= AndroidApiLevel.N.getLevel();
+    return hasMinApi(AndroidApiLevel.N);
   }
 
   public boolean canUsePrivateInterfaceMethods() {
-    return minApiLevel >= AndroidApiLevel.N.getLevel();
+    return hasMinApi(AndroidApiLevel.N);
   }
 
   public boolean canUseMultidex() {
-    return intermediate || minApiLevel >= AndroidApiLevel.L.getLevel();
+    return intermediate || hasMinApi(AndroidApiLevel.L);
   }
 
   public boolean canUseLongCompareAndObjectsNonNull() {
-    return minApiLevel >= AndroidApiLevel.K.getLevel();
+    return hasMinApi(AndroidApiLevel.K);
   }
 
   public boolean canUseSuppressedExceptions() {
-    return minApiLevel >= AndroidApiLevel.K.getLevel();
+    return hasMinApi(AndroidApiLevel.K);
   }
 
   // APIs for accessing parameter names annotations are not available before Android O, thus does
   // not emit them to avoid wasting space in Dex files because runtimes before Android O will ignore
   // them.
   public boolean canUseParameterNameAnnotations() {
-    return minApiLevel >= AndroidApiLevel.O.getLevel();
+    return hasMinApi(AndroidApiLevel.O);
   }
 
   // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
@@ -456,7 +460,7 @@
   //
   // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
   public boolean canUseFilledNewArrayOfObjects() {
-    return minApiLevel >= AndroidApiLevel.K.getLevel();
+    return hasMinApi(AndroidApiLevel.K);
   }
 
   // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
@@ -492,7 +496,7 @@
   // we can only use not instructions if we are targeting Art-based
   // phones.
   public boolean canUseNotInstruction() {
-    return minApiLevel >= AndroidApiLevel.L.getLevel();
+    return hasMinApi(AndroidApiLevel.L);
   }
 
   // Art before M has a verifier bug where the type of the contents of the receiver register is
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
new file mode 100644
index 0000000..f4d3da5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -0,0 +1,127 @@
+// 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;
+
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
+
+public class R8CFRunExamplesJava9Test extends RunExamplesJava9Test<R8Command.Builder> {
+
+  class R8CFTestRunner extends TestRunner<R8CFTestRunner> {
+
+    R8CFTestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    R8CFTestRunner withMinApiLevel(int minApiLevel) {
+      return self();
+    }
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      R8Command.Builder builder = R8Command.builder();
+      for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+        builder = transformation.apply(builder);
+      }
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+      R8Command command =
+          builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
+      ToolHelper.runR8(command, this::combinedOptionConsumer);
+    }
+
+    @Override
+    void run() throws Throwable {
+      boolean expectedToThrow = minSdkErrorExpectedCf(testName);
+      if (expectedToThrow) {
+        thrown.expect(ApiLevelException.class);
+      }
+
+      String qualifiedMainClass = packageName + "." + mainClass;
+      Path inputFile = getInputJar();
+      Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+      build(inputFile, out);
+
+      if (!ToolHelper.isJava9Runtime()) {
+        System.out.println("No Java 9 support; skip execution tests");
+        return;
+      }
+
+      if (!dexInspectorChecks.isEmpty()) {
+        DexInspector inspector = new DexInspector(out);
+        for (Consumer<DexInspector> check : dexInspectorChecks) {
+          check.accept(inspector);
+        }
+      }
+
+      execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out});
+
+      if (expectedToThrow) {
+        System.out.println("Did not throw ApiLevelException as expected");
+      }
+    }
+
+    @Override
+    R8CFTestRunner self() {
+      return this;
+    }
+  }
+
+  @Override
+  R8CFTestRunner test(String testName, String packageName, String mainClass) {
+    return new R8CFTestRunner(testName, packageName, mainClass);
+  }
+
+  void execute(String testName, String qualifiedMainClass, Path[] inputJars, Path[] outputJars)
+      throws IOException {
+    boolean expectedToFail = expectedToFailCf(testName);
+    if (expectedToFail) {
+      thrown.expect(Throwable.class);
+    }
+    ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), qualifiedMainClass);
+    ToolHelper.ProcessResult inputResult =
+        ToolHelper.runJava(ImmutableList.copyOf(inputJars), qualifiedMainClass);
+    assertEquals(inputResult.toString(), outputResult.toString());
+    if (inputResult.exitCode != 0) {
+      System.out.println(inputResult);
+    }
+    assertEquals(0, inputResult.exitCode);
+    if (expectedToFail) {
+      System.out.println("Did not fail as expected");
+    }
+  }
+
+  private static List<String> expectedFailures =
+      ImmutableList.of(
+          "native-private-interface-methods",
+          "desugared-private-interface-methods"
+      );
+
+  private boolean expectedToFailCf(String testName) {
+    System.out.println(testName + " " + expectedFailures.contains(testName));
+    return expectedFailures.contains(testName);
+  }
+
+  private static List<String> minSdkErrorExpected =
+      ImmutableList.of(
+      );
+
+  private boolean minSdkErrorExpectedCf(String testName) {
+    System.out.println(testName);
+    return minSdkErrorExpected.contains(testName);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ebb8100..4d7240e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1488,7 +1488,6 @@
           AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
           if (minSdkVersion != null) {
             builder.setMinApiLevel(minSdkVersion.getLevel());
-            r8builder.setMinApiLevel(minSdkVersion.getLevel());
             builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
             r8builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
           } else {
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index ba25396..d92ff90 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -85,10 +85,12 @@
     AndroidApiLevel apiLevel = AndroidApiLevel.P;
     Builder cfBuilder =
         R8Command.builder()
-            .setMinApiLevel(apiLevel.getLevel())
             .setMode(CompilationMode.DEBUG)
             .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
             .setProgramConsumer(programConsumer);
+    if (!(programConsumer instanceof ClassFileConsumer)) {
+      cfBuilder.setMinApiLevel(apiLevel.getLevel());
+    }
     for (Class<?> c : inputClasses) {
       byte[] classAsBytes = getClassAsBytes(c);
       cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index 4916e1d..e4a88f0 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -25,7 +25,6 @@
       AndroidAppConsumers sink = new AndroidAppConsumers();
       R8.run(
           R8Command.builder()
-              .setMinApiLevel(minApi.getLevel())
               .setMode(CompilationMode.DEBUG)
               .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
               .setProgramConsumer(sink.wrapClassFileConsumer(null))