Upgrade CF version when transforming to const class instructions.
Const class was not valid prior to CF version 49.
Bug: 152941143
Change-Id: I7f2b9a91173a8686ea23c986077d2edf3ed6273e
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 bb1ee15..20337fd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -666,8 +666,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 1e19dea..eef169e 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.holder();
ClassInitializationAnalysis classInitializationAnalysis =
@@ -64,6 +67,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 f0189d3..f847e9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -43,6 +43,7 @@
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.Visibility;
@@ -545,6 +546,31 @@
return definition;
}
+ 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 7772f09..51c6934 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1202,6 +1202,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();
+ }
+ }
+}