Version 1.6.87
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 17a1e17..c96fa6f 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 = "1.6.86";
+ public static final String LABEL = "1.6.87";
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 2a01243..dbd2778 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -537,8 +537,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 11f8a6e..87f4d0a 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
@@ -33,6 +33,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 =
@@ -63,6 +66,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 c7776f2..3bda4b2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.NestUtils;
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;
@@ -533,6 +534,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 3fb0d45..085dfbc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1028,6 +1028,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..91fa37b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -0,0 +1,131 @@
+// 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.
+ CodeInspector inspector = testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClassFileData(getDowngradedClass(Runner.class))
+ .addProgramClassFileData(getDowngradedClass(TestClass.class))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .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();
+ }
+ }
+}