Version 2.0.62

Cherry-pick: Upgrade CF version when transforming to const class instructions.
CL: https://r8-review.googlesource.com/c/r8/+/50240
Bug: 152941143

Change-Id: I7f2b9a91173a8686ea23c986077d2edf3ed6273e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 19cf4b9..e53349e 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.0.61";
+  public static final String LABEL = "2.0.62";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index ecdf5ce..ea851e7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -688,8 +688,7 @@
   public void upgradeClassFileVersion(int version) {
     checkIfObsolete();
     assert version >= 0;
-    assert !hasClassFileVersion() || version >= getClassFileVersion();
-    classFileVersion = version;
+    classFileVersion = Math.max(classFileVersion, version);
   }
 
   public String qualifiedName() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index fa41f21..984d701 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -35,6 +35,9 @@
   // Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
   public static void rewriteGetClassOrForNameToConstClass(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
+    if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
+      return;
+    }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     DexType context = code.method.method.holder;
     ClassInitializationAnalysis classInitializationAnalysis =
@@ -65,6 +68,10 @@
           Value value = code.createValue(typeLattice, current.getLocalInfo());
           ConstClass constClass = new ConstClass(value, type);
           it.replaceCurrentInstruction(constClass);
+          if (appView.options().isGeneratingClassFiles()) {
+            code.method.upgradeClassFileVersion(
+                appView.options().requiredCfVersionForConstClassInstructions());
+          }
         }
       }
     }
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 9b5029f..21103b5d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -558,6 +559,31 @@
     previous.markObsolete();
   }
 
+  private int largestInputCfVersion = -1;
+
+  public boolean canUseConstClassInstructions(InternalOptions options) {
+    if (!options.isGeneratingClassFiles()) {
+      return true;
+    }
+    if (largestInputCfVersion == -1) {
+      computeLargestCfVersion();
+    }
+    return options.canUseConstClassInstructions(largestInputCfVersion);
+  }
+
+  private synchronized void computeLargestCfVersion() {
+    if (largestInputCfVersion != -1) {
+      return;
+    }
+    for (DexProgramClass clazz : classes()) {
+      // Skip synthetic classes which may not have a specified version.
+      if (clazz.hasClassFileVersion()) {
+        largestInputCfVersion = Math.max(largestInputCfVersion, clazz.getInitialClassFileVersion());
+      }
+    }
+    assert largestInputCfVersion != -1;
+  }
+
   public boolean isLiveProgramClass(DexProgramClass clazz) {
     return liveTypes.contains(clazz.type);
   }
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 21ff710..5a666e6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1119,6 +1119,16 @@
     return minApiLevel >= level.getLevel();
   }
 
+  public boolean canUseConstClassInstructions(int cfVersion) {
+    assert isGeneratingClassFiles();
+    return cfVersion >= requiredCfVersionForConstClassInstructions();
+  }
+
+  public int requiredCfVersionForConstClassInstructions() {
+    assert isGeneratingClassFiles();
+    return Opcodes.V1_5;
+  }
+
   public boolean canUseInvokePolymorphicOnVarHandle() {
     return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.P);
   }
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
new file mode 100644
index 0000000..a083cdd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -0,0 +1,133 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class GetClassLdcClassTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines(Runner.class.getName());
+
+  private final TestParameters parameters;
+  private final int version;
+
+  @Parameterized.Parameters(name = "{0}, cf:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        new Integer[] {Opcodes.V1_4, Opcodes.V1_5});
+  }
+
+  public GetClassLdcClassTest(TestParameters parameters, int version) {
+    this.parameters = parameters;
+    this.version = version;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    // Check the program works with the code as-is and the version downgraded.
+    testForRuntime(parameters)
+        .addProgramClassFileData(getDowngradedClass(Runner.class))
+        .addProgramClassFileData(getDowngradedClass(TestClass.class))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                checkVersion(inspector, TestClass.class, version);
+                checkVersion(inspector, Runner.class, version);
+              }
+            });
+  }
+
+  @Test
+  public void testNoVersionUpgrade() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getDowngradedClass(Runner.class))
+        .addProgramClassFileData(getDowngradedClass(TestClass.class))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Runner.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                checkVersion(inspector, TestClass.class, version);
+                checkVersion(inspector, Runner.class, version);
+              }
+            });
+  }
+
+  @Test
+  public void testWithVersionUpgrade() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getDowngradedClass(Runner.class))
+        // Here the main class is not downgraded, thus the output may upgrade to that version.
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Runner.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                // We are assuming the runtimes we are testing are post CF SE 1.4 (version 48).
+                int cfVersionForRuntime = getVersion(inspector, TestClass.class);
+                assertNotEquals(Opcodes.V1_4, cfVersionForRuntime);
+                // Check that the downgraded class has been bumped to at least SE 1.5 (version 49).
+                int cfVersionAfterUpgrade = getVersion(inspector, Runner.class);
+                assertTrue(cfVersionAfterUpgrade >= Opcodes.V1_5);
+              }
+              // Check that the method uses a const class instruction.
+              assertTrue(
+                  inspector
+                      .clazz(Runner.class)
+                      .uniqueMethodWithName("run")
+                      .streamInstructions()
+                      .anyMatch(i -> i.isConstClass(Runner.class.getTypeName())));
+            });
+  }
+
+  private static int getVersion(CodeInspector inspector, Class<?> clazz) {
+    return inspector.clazz(clazz).getDexClass().asProgramClass().getInitialClassFileVersion();
+  }
+
+  private static void checkVersion(CodeInspector inspector, Class<?> clazz, int version) {
+    assertEquals(version, getVersion(inspector, clazz));
+  }
+
+  private byte[] getDowngradedClass(Class<?> clazz) throws IOException {
+    return transformer(clazz).setVersion(version).transform();
+  }
+
+  static class Runner {
+
+    public void run() {
+      System.out.println(getClass().getName());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Runner().run();
+    }
+  }
+}