Enum unboxing: Disable if missing static methods
If the program includes a call to a missing static method on a present
enum, do not unbox that enum to keep the correct NoSuchMethod error in
the resulting program.
Bug: 160535628
Change-Id: Id19cde7e049a33476cfcccbaf6944568d2037a75
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index e03bc6b..1ffca3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
@@ -162,7 +163,7 @@
analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
break;
case Opcodes.INVOKE_STATIC:
- analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums);
+ analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context());
break;
case Opcodes.STATIC_GET:
case Opcodes.INSTANCE_GET:
@@ -210,11 +211,17 @@
}
}
- private void analyzeInvokeStatic(InvokeStatic invokeStatic, Set<DexType> eligibleEnums) {
+ private void analyzeInvokeStatic(
+ InvokeStatic invokeStatic, Set<DexType> eligibleEnums, ProgramMethod context) {
DexMethod invokedMethod = invokeStatic.getInvokedMethod();
DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
if (enumClass != null) {
- eligibleEnums.add(enumClass.type);
+ DexEncodedMethod method = invokeStatic.lookupSingleTarget(appView, context);
+ if (method != null) {
+ eligibleEnums.add(enumClass.type);
+ } else {
+ markEnumAsUnboxable(Reason.INVALID_INVOKE, enumClass);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 56fa95c..f53e8b3 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -155,7 +155,7 @@
return addKeepMainRule(mainClass.getTypeName());
}
- public T addKeepMainRules(Class<?>[] mainClasses) {
+ public T addKeepMainRules(Class<?>... mainClasses) {
for (Class<?> mainClass : mainClasses) {
this.addKeepMainRule(mainClass);
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
new file mode 100644
index 0000000..5ccd903
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -0,0 +1,143 @@
+// 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.enumunboxing;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingB160535628Test extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean missingStaticMethods;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ public EnumUnboxingB160535628Test(TestParameters parameters, boolean missingStaticMethods) {
+ this.parameters = parameters;
+ this.missingStaticMethods = missingStaticMethods;
+ }
+
+ @Test
+ public void testCallToMissingStaticMethodInUnboxedEnum() throws Exception {
+ // Compile the lib cf to cf.
+ Path javaLibShrunk = compileLibrary();
+ // Compile the program with the lib.
+ // This should compile without error into code raising the correct NoSuchMethod errors.
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ProgramValueOf.class, ProgramStaticMethod.class)
+ .addProgramFiles(javaLibShrunk)
+ .addKeepMainRules(ProgramValueOf.class, ProgramStaticMethod.class)
+ .addOptionsModification(
+ options -> {
+ options.enableEnumUnboxing = true;
+ options.testing.enableEnumUnboxingDebugLogs = true;
+ })
+ .allowDiagnosticMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ // The enums cannot be unboxed if static methods are missing,
+ // but they should be unboxed otherwise.
+ this::assertEnumUnboxedIfStaticMethodsPresent);
+ if (missingStaticMethods) {
+ compile
+ .run(parameters.getRuntime(), ProgramValueOf.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
+ .assertFailureWithErrorThatMatches(containsString("valueOf"));
+ compile
+ .run(parameters.getRuntime(), ProgramStaticMethod.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
+ .assertFailureWithErrorThatMatches(containsString("staticMethod"));
+ } else {
+ compile.run(parameters.getRuntime(), ProgramValueOf.class).assertSuccessWithOutputLines("0");
+ compile
+ .run(parameters.getRuntime(), ProgramStaticMethod.class)
+ .assertSuccessWithOutputLines("42");
+ }
+ }
+
+ private Path compileLibrary() throws Exception {
+ return testForR8(Backend.CF)
+ .addProgramClasses(Lib.class, Lib.LibEnumStaticMethod.class, Lib.LibEnum.class)
+ .addKeepRules("-keep enum * { <fields>; }")
+ .addKeepRules(missingStaticMethods ? "" : "-keep enum * { static <methods>; }")
+ .addOptionsModification(
+ options -> {
+ options.enableEnumUnboxing = true;
+ options.testing.enableEnumUnboxingDebugLogs = true;
+ })
+ .addKeepClassRules(Lib.LibEnumStaticMethod.class)
+ .allowDiagnosticMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ msg -> {
+ assertEnumIsBoxed(
+ Lib.LibEnumStaticMethod.class,
+ Lib.LibEnumStaticMethod.class.getSimpleName(),
+ msg);
+ assertEnumIsBoxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
+ })
+ .writeToZip();
+ }
+
+ private void assertEnumUnboxedIfStaticMethodsPresent(TestDiagnosticMessages msg) {
+ if (missingStaticMethods) {
+ assertEnumIsBoxed(
+ Lib.LibEnumStaticMethod.class, Lib.LibEnumStaticMethod.class.getSimpleName(), msg);
+ assertEnumIsBoxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
+ } else {
+ assertEnumIsUnboxed(
+ Lib.LibEnumStaticMethod.class, Lib.LibEnumStaticMethod.class.getSimpleName(), msg);
+ assertEnumIsUnboxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
+ }
+ }
+
+ public static class Lib {
+
+ public enum LibEnumStaticMethod {
+ A,
+ B;
+
+ static int staticMethod() {
+ return 42;
+ }
+ }
+
+ public enum LibEnum {
+ A,
+ B
+ }
+ }
+
+ public static class ProgramValueOf {
+
+ public static void main(String[] args) {
+ System.out.println(Lib.LibEnumStaticMethod.valueOf(Lib.LibEnum.A.name()).ordinal());
+ }
+ }
+
+ public static class ProgramStaticMethod {
+
+ public static void main(String[] args) {
+ System.out.println(Lib.LibEnumStaticMethod.staticMethod());
+ }
+ }
+}