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 fa41f210d..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();
+ }
+ }
+}