Only unbox enums where instance field puts are inside enum constructors
This is a somewhat coarse-grained fix to ensure that we do not try to unbox enums that has field put references outside their constructors. A better fix would be to unbox instance-put on enums if possible.
Bug: b/247146910
Bug: b/249752942
Change-Id: If2f870944573e64ec980cf48137fd9420d733635
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 2412ecc..b4926fe 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -690,9 +690,6 @@
}
public boolean validateUnboxedEnumsHaveBeenPruned() {
- if (app().options.testing.allowNotPrunedUnboxedEnums) {
- return true;
- }
for (DexType unboxedEnum : unboxedEnums.getUnboxedEnums()) {
assert appInfo.definitionForWithoutExistenceAssert(unboxedEnum) == null
: "Enum " + unboxedEnum + " has been unboxed but is still in the program.";
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index b1ea459..7f0fd6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1155,12 +1155,17 @@
if (field == null) {
return Reason.INVALID_FIELD_PUT;
}
- DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context());
- if (dexClass == null) {
+ DexProgramClass holderClass =
+ appView.programDefinitionFor(field.getHolderType(), code.context());
+ if (holderClass == null) {
return Reason.INVALID_FIELD_PUT;
}
if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) {
- return Reason.ELIGIBLE;
+ // TODO(b/249752942): The requirement to be inside an initializer of the enum can be relaxed
+ // if we support puts.
+ return context.getHolder() == enumClass && context.getDefinition().isInstanceInitializer()
+ ? Reason.ELIGIBLE
+ : Reason.ASSIGNMENT_OUTSIDE_INIT;
}
// The put value has to be of the field type.
if (field.getReference().type.toBaseType(factory) != enumClass.type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 63fc7fd..3e17a0a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -34,6 +34,7 @@
public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
+ public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER =
new StringReason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 5823c33..0c40434 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2060,8 +2060,7 @@
|| appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
: "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
- assert appView.options().testing.allowNotPrunedUnboxedEnums
- || !appView.unboxedEnums().isUnboxedEnum(clazz)
+ assert !appView.unboxedEnums().isUnboxedEnum(clazz)
: "Enum " + clazz.toSourceString() + " has been unboxed but is still in the program.";
if (options.isGeneratingClassFiles() && clazz.hasPermittedSubclassAttributes()) {
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 f7e258e..2269e25 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1980,7 +1980,6 @@
System.getProperty("com.android.tools.r8.disableMarkingClassesFinal") != null;
public boolean testEnableTestAssertions = false;
public boolean keepMetadataInR8IfNotRewritten = true;
- public boolean allowNotPrunedUnboxedEnums = false;
// If set, pruned record fields are not used in hashCode/equals/toString and toString prints
// minified field names instead of original field names.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
index 632b24a..66e7ee5 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
@@ -4,13 +4,7 @@
package com.android.tools.r8.enumunboxing;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DiagnosticsMatcher;
import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestParameters;
import java.util.List;
import org.junit.Test;
@@ -48,36 +42,16 @@
@Test
public void testEnumUnboxing() throws Exception {
- // TODO(b/247146910): Should not throw compilation error.
- assertThrows(CompilationFailedException.class, setupTest()::compile);
- }
-
- @Test
- public void testEnumUnboxingAllowingTypeErrors() throws Exception {
- // TODO(b/247146910): Should not insert throw null.
- setupTest()
- .addOptionsModification(options -> options.testing.allowTypeErrors = true)
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertAllWarningsMatch(
- DiagnosticsMatcher.diagnosticMessage(
- containsString(
- "does not type check and will be assumed to be unreachable."))))
- .run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatThrows(NullPointerException.class);
- }
-
- private R8FullTestBuilder setupTest() throws Exception {
- return testForR8(parameters.getBackend())
+ testForR8(parameters.getBackend())
.addInnerClasses(EnumInEnumFieldTest.class)
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
- .addEnumUnboxingInspector(
- inspector -> inspector.assertUnboxed(MyEnum.class, OtherEnum.class))
+ .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(OtherEnum.class))
.addNeverClassInliningAnnotations()
- .setMinApi(parameters.getApiLevel());
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0");
}
@NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
index 8be968b..55cdede 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
@@ -4,13 +4,8 @@
package com.android.tools.r8.enumunboxing;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,54 +44,17 @@
}
@Test
- public void testEnumUnboxing() throws Exception {
- // TODO(b/247146910): Should not throw.
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class)
- .addKeepMainRule(Main.class)
- .addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
- .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
- .addNeverClassInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .addOptionsModification(options -> options.testing.allowTypeErrors = true)
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "Enum "
- + typeName(MyEnum.class)
- + " has been unboxed but is still in the program"))));
- }
-
- private boolean hasVerifyError() {
- return parameters.getDexRuntimeVersion().isDalvik()
- || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0);
- }
-
- @Test
public void testEnumUnboxingAllowNotPrunedEnums() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class)
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- opt -> {
- enableEnumOptions(opt, enumValueOptimization);
- opt.testing.allowNotPrunedUnboxedEnums = true;
- })
- .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.addNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(options -> options.testing.allowTypeErrors = true)
+ .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
.run(parameters.getRuntime(), Main.class)
- // TODO(b/247146910): Should not fail with runtime error.
- .assertFailureWithErrorThatThrowsIf(hasVerifyError(), VerifyError.class)
- .assertFailureWithErrorThatThrowsIf(!hasVerifyError(), NoSuchFieldError.class);
+ .assertSuccessWithOutputLines("42");
}
@NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
index ff0b642..4dac1af 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
@@ -4,13 +4,8 @@
package com.android.tools.r8.enumunboxing;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,54 +44,17 @@
}
@Test
- public void testEnumUnboxing() throws Exception {
- // TODO(b/247146910): Should not throw.
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class)
- .addKeepMainRule(Main.class)
- .addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
- .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
- .addNeverClassInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .addOptionsModification(options -> options.testing.allowTypeErrors = true)
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "Enum "
- + typeName(MyEnum.class)
- + " has been unboxed but is still in the program"))));
- }
-
- private boolean hasVerifyError() {
- return parameters.getDexRuntimeVersion().isDalvik()
- || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0);
- }
-
- @Test
public void testEnumUnboxingAllowNotPrunedEnums() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class)
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- opt -> {
- enableEnumOptions(opt, enumValueOptimization);
- opt.testing.allowNotPrunedUnboxedEnums = true;
- })
- .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.addNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(options -> options.testing.allowTypeErrors = true)
.run(parameters.getRuntime(), Main.class)
- // TODO(b/247146910): Should not fail with runtime error.
- .assertFailureWithErrorThatThrowsIf(hasVerifyError(), VerifyError.class)
- .assertFailureWithErrorThatThrowsIf(!hasVerifyError(), NoSuchFieldError.class);
+ .assertSuccessWithOutputLines("42");
}
@NeverClassInline