Revert "Extend and disable-by-default Enum reflection tracing"
This reverts commit cb8bff39f56cea0196ab1163d7ffe08334104b85.
It's possible that some people rely on Enum.valueOf() reflection, since
support for that has existed for some time.
Bug: b/204939965
Change-Id: Iffb606cc6224eb9ab857bf31741cd03d650257a8
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 1aa0120..27f31fa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -640,6 +640,9 @@
createStaticallyKnownType("Ljava/util/logging/Level;");
public final DexType javaUtilLoggingLoggerType =
createStaticallyKnownType("Ljava/util/logging/Logger;");
+ public final DexType javaUtilEnumMapType = createStaticallyKnownType("Ljava/util/EnumMap;");
+ public final DexType javaUtilEnumSetType = createStaticallyKnownType("Ljava/util/EnumSet;");
+
public final DexType androidAppActivity = createStaticallyKnownType("Landroid/app/Activity;");
public final DexType androidAppFragment = createStaticallyKnownType("Landroid/app/Fragment;");
public final DexType androidAppZygotePreload =
@@ -761,6 +764,8 @@
public final JavaUtilLocaleMembers javaUtilLocaleMembers = new JavaUtilLocaleMembers();
public final JavaUtilLoggingLevelMembers javaUtilLoggingLevelMembers =
new JavaUtilLoggingLevelMembers();
+ public final JavaUtilEnumMapMembers javaUtilEnumMapMembers = new JavaUtilEnumMapMembers();
+ public final JavaUtilEnumSetMembers javaUtilEnumSetMembers = new JavaUtilEnumSetMembers();
public final List<LibraryMembers> libraryMembersCollection =
ImmutableList.of(
@@ -1609,6 +1614,28 @@
}
}
+ public class JavaUtilEnumMapMembers {
+ public final DexMethod constructor =
+ createMethod(javaUtilEnumMapType, createProto(voidType, classType), constructorMethodName);
+ }
+
+ public class JavaUtilEnumSetMembers {
+ private final DexString allOfString = createString("allOf");
+ private final DexString noneOfString = createString("noneOf");
+ private final DexString rangeString = createString("range");
+
+ public boolean isFactoryMethod(DexMethod invokedMethod) {
+ if (!invokedMethod.getHolderType().equals(javaUtilEnumSetType)) {
+ return false;
+ }
+ DexString name = invokedMethod.getName();
+ return name.isIdenticalTo(allOfString)
+ || name.isIdenticalTo(noneOfString)
+ || name.isIdenticalTo(ofMethodName)
+ || name.isIdenticalTo(rangeString);
+ }
+ }
+
public class LongMembers extends BoxedPrimitiveMembers {
public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
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 d179979..fdc7fb5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -99,7 +99,9 @@
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstantValueUtils;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -551,10 +553,6 @@
ProtoEnqueuerExtension.register(appView, analysesBuilder);
ResourceAccessAnalysis.register(appView, this, analysesBuilder);
RuntimeTypeCheckInfo.register(runtimeTypeCheckInfoBuilder, analysesBuilder);
- if (options.experimentalTraceEnumReflection) {
- EnqueuerEnumReflectionSupport.register(
- appView, this::markEnumValuesAsReachable, analysesBuilder);
- }
}
analyses = analysesBuilder.build();
@@ -1550,6 +1548,11 @@
MethodResolutionResult resolutionResult =
handleInvokeOfDirectTarget(invokedMethod, context, reason);
analyses.traceInvokeDirect(invokedMethod, resolutionResult, context);
+
+ if (invokedMethod.equals(appView.dexItemFactory().javaUtilEnumMapMembers.constructor)) {
+ // EnumMap uses reflection.
+ pendingReflectiveUses.add(context);
+ }
}
void traceInvokeInterface(
@@ -1604,6 +1607,10 @@
identifierNameStrings.add(invokedMethod);
// Revisit the current method to implicitly add -keep rule for items with reflective access.
pendingReflectiveUses.add(context);
+ } else if (invokedMethod == dexItemFactory.enumMembers.valueOf
+ || dexItemFactory.javaUtilEnumSetMembers.isFactoryMethod(invokedMethod)) {
+ // See comment in handleEnumValueOfOrCollectionInstantiation.
+ pendingReflectiveUses.add(context);
} else if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) {
pendingReflectiveUses.add(context);
} else if (dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
@@ -1640,6 +1647,7 @@
invokedMethod, context, registry, KeepReason.invokedFromLambdaCreatedIn(context));
}
+ @SuppressWarnings("ReferenceEquality")
private void traceInvokeVirtual(
DexMethod invokedMethod,
ProgramMethod context,
@@ -1648,11 +1656,10 @@
if (registry != null && !registry.markInvokeVirtualAsSeen(invokedMethod)) {
return;
}
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- if (invokedMethod.isIdenticalTo(dexItemFactory.classMethods.newInstance)
- || invokedMethod.isIdenticalTo(dexItemFactory.constructorMethods.newInstance)) {
+ if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
+ || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
pendingReflectiveUses.add(context);
- } else if (dexItemFactory.classMethods.isReflectiveMemberLookup(invokedMethod)) {
+ } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
// Implicitly add -identifiernamestring rule for the Java reflection in use.
identifierNameStrings.add(invokedMethod);
// Revisit the current method to implicitly add -keep rule for items with reflective access.
@@ -5217,24 +5224,33 @@
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
- if (instruction.isInvokeMethod()) {
- handleReflectiveBehavior(method, instruction.asInvokeMethod());
- }
+ handleReflectiveBehavior(method, instruction);
}
}
- private void handleReflectiveBehavior(ProgramMethod method, InvokeMethod invoke) {
+ @SuppressWarnings("ReferenceEquality")
+ private void handleReflectiveBehavior(ProgramMethod method, Instruction instruction) {
+ if (!instruction.isInvokeMethod()) {
+ return;
+ }
+ InvokeMethod invoke = instruction.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
DexItemFactory dexItemFactory = appView.dexItemFactory();
- if (invokedMethod.isIdenticalTo(dexItemFactory.classMethods.newInstance)) {
+ if (invokedMethod == dexItemFactory.classMethods.newInstance) {
handleJavaLangClassNewInstance(method, invoke);
return;
}
- if (invokedMethod.isIdenticalTo(dexItemFactory.constructorMethods.newInstance)) {
+ if (invokedMethod == dexItemFactory.constructorMethods.newInstance) {
handleJavaLangReflectConstructorNewInstance(method, invoke);
return;
}
- if (invokedMethod.isIdenticalTo(dexItemFactory.proxyMethods.newProxyInstance)) {
+ if (invokedMethod == dexItemFactory.enumMembers.valueOf
+ || invokedMethod == dexItemFactory.javaUtilEnumMapMembers.constructor
+ || dexItemFactory.javaUtilEnumSetMembers.isFactoryMethod(invokedMethod)) {
+ handleEnumValueOfOrCollectionInstantiation(method, invoke);
+ return;
+ }
+ if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) {
handleJavaLangReflectProxyNewProxyInstance(method, invoke);
return;
}
@@ -5557,6 +5573,47 @@
}
}
+ private void handleEnumValueOfOrCollectionInstantiation(
+ ProgramMethod context, InvokeMethod invoke) {
+ if (invoke.inValues().isEmpty()) {
+ // Should never happen.
+ return;
+ }
+
+ // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
+ // access the values() method of the enum class passed as the first argument. The method
+ // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
+ // call this method.
+ // Likewise, EnumSet and EnumMap call values() on the passed in Class.
+ Value firstArg = invoke.getFirstNonReceiverArgument();
+ if (firstArg.isPhi()) {
+ return;
+ }
+ DexType type;
+ if (invoke
+ .getInvokedMethod()
+ .getParameter(0)
+ .isIdenticalTo(appView.dexItemFactory().classType)) {
+ // EnumMap.<init>(), EnumSet.noneOf(), EnumSet.allOf(), Enum.valueOf().
+ ConstClass constClass = firstArg.definition.asConstClass();
+ if (constClass == null || !constClass.getType().isClassType()) {
+ return;
+ }
+ type = constClass.getType();
+ } else {
+ // EnumSet.of(), EnumSet.range()
+ ClassTypeElement typeElement = firstArg.getType().asClassType();
+ if (typeElement == null) {
+ return;
+ }
+ type = typeElement.getClassType();
+ }
+ DexProgramClass clazz = getProgramClassOrNull(type, context);
+ if (clazz != null && clazz.isEnum()) {
+ markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(context));
+ }
+ }
+
private void handleServiceLoaderInvocation(ProgramMethod method, InvokeMethod invoke) {
if (invoke.inValues().isEmpty()) {
// Should never happen.
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionSupport.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionSupport.java
deleted file mode 100644
index 376a723..0000000
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionSupport.java
+++ /dev/null
@@ -1,336 +0,0 @@
-// Copyright (c) 2025, 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.shaking;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
-import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
-import com.android.tools.r8.graph.analysis.TraceInvokeEnqueuerAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
-
-/**
- * Finds Enum.valueOf() methods that must be kept due to uses of OS APIs that use it via reflection.
- *
- * <p>Looks for:
- *
- * <ul>
- * <li>Enum.valueOf()
- * <li>EnumSet.allOf()
- * <li>EnumSet.noneOf()
- * <li>EnumSet.range()
- * <li>new EnumMap()
- * <li>Parcel.readSerializable()
- * <li>Bundle.getSerializable()
- * <li>Intent.getSerializableExtra()
- * <li>ObjectInputStream.readObject()
- * </ul>
- *
- * <p>Similar to non-enum reflection calls, tracing is best-effort. E.g.:
- *
- * <ul>
- * <li>For the overloads that accept a Class, it must be a const-class
- * <li>For others, the return value must be directly used by a checked-cast of the Enum type.
- * </ul>
- */
-public class EnqueuerEnumReflectionSupport
- implements TraceInvokeEnqueuerAnalysis, FixpointEnqueuerAnalysis {
- private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final BiConsumer<DexProgramClass, KeepReason> markAsReachableCallback;
-
- private final DexString enumSetAllOfString;
- private final DexString enumSetNoneOfString;
- private final DexString enumSetRangeString;
- private final DexString enumSetOfString;
- private final DexType androidContentIntentType;
- private final DexType androidOsParcelType;
- private final DexType javaIoObjectInputStreamType;
- private final DexType javaUtilEnumMapType;
- private final DexType javaUtilEnumSetType;
-
- private final DexMethod intentGetSerializableExtra1;
- private final DexMethod intentGetSerializableExtra2;
- private final DexMethod bundleGetSerializable1;
- private final DexMethod bundleGetSerializable2;
- private final DexMethod parcelReadSerializable1;
- private final DexMethod parcelReadSerializable2;
-
- private final DexMethod objectInputStreamReadObject;
- private final DexMethod enumValueOfMethod;
- private final DexMethod enumMapConstructor;
- private final ProgramMethodSet pendingReflectiveUses = ProgramMethodSet.createLinked();
-
- public EnqueuerEnumReflectionSupport(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- BiConsumer<DexProgramClass, KeepReason> markAsReachableCallback) {
- this.appView = appView;
- this.markAsReachableCallback = markAsReachableCallback;
-
- DexItemFactory dexItemFactory = appView.dexItemFactory();
-
- enumSetAllOfString = dexItemFactory.createString("allOf");
- enumSetNoneOfString = dexItemFactory.createString("noneOf");
- enumSetRangeString = dexItemFactory.createString("range");
- enumSetOfString = dexItemFactory.ofMethodName;
-
- androidContentIntentType = dexItemFactory.createType("Landroid/content/Intent;");
- androidOsParcelType = dexItemFactory.createType("Landroid/os/Parcel;");
- javaIoObjectInputStreamType = dexItemFactory.createType("Ljava/io/ObjectInputStream;");
- javaUtilEnumMapType = dexItemFactory.createType("Ljava/util/EnumMap;");
- javaUtilEnumSetType = dexItemFactory.createType("Ljava/util/EnumSet;");
-
- intentGetSerializableExtra1 =
- dexItemFactory.createMethod(
- androidContentIntentType,
- dexItemFactory.createProto(dexItemFactory.serializableType, dexItemFactory.stringType),
- "getSerializableExtra");
- intentGetSerializableExtra2 =
- dexItemFactory.createMethod(
- androidContentIntentType,
- dexItemFactory.createProto(
- dexItemFactory.serializableType,
- dexItemFactory.stringType,
- dexItemFactory.classType),
- "getSerializableExtra");
- bundleGetSerializable1 =
- dexItemFactory.createMethod(
- dexItemFactory.androidOsBundleType,
- dexItemFactory.createProto(dexItemFactory.serializableType, dexItemFactory.stringType),
- "getSerializable");
- bundleGetSerializable2 =
- dexItemFactory.createMethod(
- dexItemFactory.androidOsBundleType,
- dexItemFactory.createProto(
- dexItemFactory.serializableType,
- dexItemFactory.stringType,
- dexItemFactory.classType),
- "getSerializable");
- parcelReadSerializable1 =
- dexItemFactory.createMethod(
- androidOsParcelType,
- dexItemFactory.createProto(dexItemFactory.serializableType),
- "readSerializable");
- parcelReadSerializable2 =
- dexItemFactory.createMethod(
- androidOsParcelType,
- dexItemFactory.createProto(
- dexItemFactory.serializableType,
- dexItemFactory.classLoaderType,
- dexItemFactory.classType),
- "readSerializable");
- objectInputStreamReadObject =
- dexItemFactory.createMethod(
- javaIoObjectInputStreamType,
- dexItemFactory.createProto(dexItemFactory.objectType),
- "readObject");
- enumValueOfMethod = dexItemFactory.enumMembers.valueOf;
- enumMapConstructor =
- dexItemFactory.createMethod(
- javaUtilEnumMapType,
- dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.classType),
- dexItemFactory.constructorMethodName);
- }
-
- public static void register(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- BiConsumer<DexProgramClass, KeepReason> markAsReachableCallback,
- EnqueuerAnalysisCollection.Builder builder) {
- EnqueuerEnumReflectionSupport instance =
- new EnqueuerEnumReflectionSupport(appView, markAsReachableCallback);
- builder.addTraceInvokeAnalysis(instance);
- builder.addFixpointAnalysis(instance);
- }
-
- private DexProgramClass maybeGetProgramEnumType(DexType type, boolean checkSuper) {
- // Arrays can be used for serialization-related methods.
- if (type.isArrayType()) {
- type = type.toBaseType(appView.dexItemFactory());
- if (!type.isClassType()) {
- return null;
- }
- }
-
- DexClass dexClass = appView.definitionFor(type);
- if (dexClass == null || !dexClass.isProgramClass() || !dexClass.hasSuperType()) {
- return null;
- }
- DexType superType = dexClass.getSuperType();
- if (superType.isIdenticalTo(appView.dexItemFactory().enumType)) {
- return dexClass.asProgramClass();
- }
- // Cannot have a sub-sub-type of an Enum.
- return checkSuper ? maybeGetProgramEnumType(superType, false) : null;
- }
-
- private void handleEnumCastFromSerializable(ProgramMethod context, Value value) {
- if (value == null || value.isPhi()) {
- return;
- }
- for (Instruction user : value.aliasedUsers()) {
- CheckCast castInstr = user.asCheckCast();
- if (castInstr == null) {
- continue;
- }
- DexProgramClass enumClass = maybeGetProgramEnumType(castInstr.getType(), true);
- if (enumClass != null) {
- markAsReachableCallback.accept(enumClass, KeepReason.invokedFrom(context));
- }
- }
- }
-
- private void handleReflectiveEnumInvoke(
- ProgramMethod context, InvokeMethod invoke, int classParamIndex) {
- if (invoke.inValues().size() <= classParamIndex) {
- // Should never happen.
- return;
- }
-
- // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
- // access the values() method of the enum class passed as the first argument. The method
- // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
- // call this method.
- // Likewise, EnumSet and EnumMap call values() on the passed in Class.
- Value classArg = invoke.getArgumentForParameter(classParamIndex);
- if (classArg.isPhi()) {
- return;
- }
- DexType type;
- if (invoke
- .getInvokedMethod()
- .getParameter(classParamIndex)
- .isIdenticalTo(appView.dexItemFactory().classType)) {
- // EnumMap.<init>(), EnumSet.noneOf(), EnumSet.allOf(), Enum.valueOf().
- ConstClass constClass = classArg.definition.asConstClass();
- if (constClass == null) {
- return;
- }
- type = constClass.getType();
- } else {
- // EnumSet.of(), EnumSet.range()
- ClassTypeElement typeElement = classArg.getType().asClassType();
- if (typeElement == null) {
- return;
- }
- type = typeElement.getClassType();
- }
- // Arrays can be used for serialization-related methods.
- if (type.isArrayType()) {
- type = type.toBaseType(appView.dexItemFactory());
- }
- DexClass clazz = appView.definitionFor(type, context);
- if (clazz != null && clazz.isProgramClass() && clazz.isEnum()) {
- markAsReachableCallback.accept(clazz.asProgramClass(), KeepReason.invokedFrom(context));
- }
- }
-
- private void handleReflectiveBehavior(ProgramMethod method) {
- IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting());
- InstructionIterator iterator = code.instructionIterator();
- while (iterator.hasNext()) {
- Instruction instruction = iterator.next();
- if (!instruction.isInvokeMethod()) {
- continue;
- }
- InvokeMethod invoke = instruction.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod.isIdenticalTo(enumValueOfMethod)
- || invokedMethod.isIdenticalTo(enumMapConstructor)
- || isEnumSetFactoryMethod(invokedMethod)) {
- handleReflectiveEnumInvoke(method, invoke, 0);
- } else if (invokedMethod.isIdenticalTo(bundleGetSerializable2)
- || invokedMethod.isIdenticalTo(parcelReadSerializable2)
- || invokedMethod.isIdenticalTo(intentGetSerializableExtra2)) {
- handleReflectiveEnumInvoke(method, invoke, 1);
- } else if (invokedMethod.isIdenticalTo(bundleGetSerializable1)
- || invokedMethod.isIdenticalTo(parcelReadSerializable1)
- || invokedMethod.isIdenticalTo(intentGetSerializableExtra1)
- || invokedMethod.isIdenticalTo(objectInputStreamReadObject)) {
- // These forms do not take a Class parameter, but are often followed by a checked-cast.
- // Get the enum type from the checked-cast.
- // There is no checked-cast when the return value is used as an Object (e.g. toString(), or
- // inserted into a List<Object>).
- // This also fails to identify when the above methods are wrapped in a helper method
- // (e.g. a "IntentUtils.safeGetSerializableExtra()").
- handleEnumCastFromSerializable(method, invoke.outValue());
- }
- }
- }
-
- private boolean isEnumSetFactoryMethod(DexMethod invokedMethod) {
- if (!invokedMethod.getHolderType().equals(javaUtilEnumSetType)) {
- return false;
- }
- DexString name = invokedMethod.getName();
- return name.isIdenticalTo(enumSetAllOfString)
- || name.isIdenticalTo(enumSetNoneOfString)
- || name.isIdenticalTo(enumSetOfString)
- || name.isIdenticalTo(enumSetRangeString);
- }
-
- @Override
- public void traceInvokeStatic(
- DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
- if (invokedMethod.isIdenticalTo(enumValueOfMethod) || isEnumSetFactoryMethod(invokedMethod)) {
- pendingReflectiveUses.add(context);
- }
- }
-
- @Override
- public void traceInvokeDirect(
- DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
- // EnumMap uses reflection.
- if (invokedMethod.isIdenticalTo(enumMapConstructor)) {
- pendingReflectiveUses.add(context);
- }
- }
-
- @Override
- public void traceInvokeVirtual(
- DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
- if (invokedMethod.isIdenticalTo(bundleGetSerializable1)
- || invokedMethod.isIdenticalTo(bundleGetSerializable2)
- || invokedMethod.isIdenticalTo(parcelReadSerializable1)
- || invokedMethod.isIdenticalTo(parcelReadSerializable2)
- || invokedMethod.isIdenticalTo(intentGetSerializableExtra1)
- || invokedMethod.isIdenticalTo(intentGetSerializableExtra2)
- || invokedMethod.isIdenticalTo(objectInputStreamReadObject)) {
- pendingReflectiveUses.add(context);
- }
- }
-
- @Override
- public void notifyFixpoint(
- Enqueuer enqueuer,
- EnqueuerWorklist worklist,
- ExecutorService executorService,
- Timing timing) {
- if (!pendingReflectiveUses.isEmpty()) {
- timing.begin("Handle reflective Enum operations");
- pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
- pendingReflectiveUses.clear();
- timing.end();
- }
- }
-}
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 44d0ad2..64de864 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -770,8 +770,6 @@
System.getProperty("com.android.tools.r8.ignoreBootClasspathEnumsForMaindexTracing") != null;
public boolean pruneNonVissibleAnnotationClasses =
System.getProperty("com.android.tools.r8.pruneNonVissibleAnnotationClasses") != null;
- public boolean experimentalTraceEnumReflection =
- System.getProperty("com.android.tools.r8.experimentalTraceEnumReflection") != null;
// Flag to turn on/offLoad/store optimization in the Cf back-end.
public boolean enableLoadStoreOptimization = true;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index dfd0f1a..01a5e94 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -51,14 +51,7 @@
.addInnerClasses(FailingMethodEnumUnboxingTest.class)
.addKeepMainRules(TESTS)
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- opt -> {
- enableEnumOptions(opt, enumValueOptimization);
- if (enumKeepRules == EnumKeepRules.NONE) {
- // Look for Enum.valueOf() when tracing rather than rely on -keeps.
- opt.experimentalTraceEnumReflection = true;
- }
- })
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.addEnumUnboxingInspector(
inspector ->
inspector.assertNotUnboxed(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
index 4662a90..e75d109 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
@@ -39,14 +39,7 @@
.addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(Main.Enum.class))
.enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- opt -> {
- enableEnumOptions(opt, enumValueOptimization);
- if (enumKeepRules == EnumKeepRules.NONE) {
- // Look for Enum.valueOf() when tracing rather than rely on -keeps.
- opt.experimentalTraceEnumReflection = true;
- }
- })
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters)
.compile()
.run(parameters.getRuntime(), success)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
index e73cda0..f976adb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
@@ -46,14 +46,7 @@
inspector -> inspector.assertUnboxed(EnumValueOf.MyEnum.class))
.enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- opt -> {
- enableEnumOptions(opt, enumValueOptimization);
- if (enumKeepRules == EnumKeepRules.NONE) {
- // Look for Enum.valueOf() when tracing rather than rely on -keeps.
- opt.experimentalTraceEnumReflection = true;
- }
- })
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters)
.compile();
for (Class<?> main : TESTS) {
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index c72b68e..5b1d6f5 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase.EnumKeepRules;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
@@ -51,7 +50,6 @@
.addProgramClassFileData(enumClassFile)
.addKeepMainRule(mainClass)
.addKeepRules("-neverinline enum * extends java.lang.Enum { valueOf(...); }")
- .addKeepRules(EnumKeepRules.STUDIO.getKeepRules())
.enableProguardTestOptions()
.setMinApi(parameters)
.compile();
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
index 117154a..fe575ed 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
@@ -49,13 +49,6 @@
.addInnerClasses(EnumValueOfOptimizationTest.class)
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- opt -> {
- if (enumKeepRules == EnumKeepRules.NONE) {
- // Look for Enum.valueOf() when tracing rather than rely on -keeps.
- opt.experimentalTraceEnumReflection = true;
- }
- })
.applyIf(
enableNoVerticalClassMergingAnnotations,
R8TestBuilder::enableNoVerticalClassMergingAnnotations,
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumCollectionsTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumCollectionsTest.java
new file mode 100644
index 0000000..2a9872c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumCollectionsTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2024, 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.shaking.enums;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumCollectionsTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build();
+ }
+
+ private static final List<String> EXPECTED_OUTPUT =
+ Arrays.asList(
+ "none: [A, B]",
+ "all: [B, C]",
+ "of: [C]",
+ "of: [D, E]",
+ "of: [E, F, G]",
+ "of: [F, G, H, I]",
+ "of: [G, H, I, J, K]",
+ "of: [H, I, J, K, L, M]",
+ "range: [I, J]",
+ "map: {J=1}",
+ "valueOf: K",
+ "phi: [B]");
+
+ public static class TestMain {
+ public enum EnumA {
+ A,
+ B
+ }
+
+ public enum EnumB {
+ B,
+ C
+ }
+
+ public enum EnumC {
+ C,
+ D
+ }
+
+ public enum EnumD {
+ D,
+ E
+ }
+
+ public enum EnumE {
+ E,
+ F,
+ G
+ }
+
+ public enum EnumF {
+ F,
+ G,
+ H,
+ I
+ }
+
+ public enum EnumG {
+ G,
+ H,
+ I,
+ J,
+ K
+ }
+
+ public enum EnumH {
+ H,
+ I,
+ J,
+ K,
+ L,
+ M
+ }
+
+ public enum EnumI {
+ I,
+ J
+ }
+
+ public enum EnumJ {
+ J,
+ K
+ }
+
+ public enum EnumK {
+ K,
+ L
+ }
+
+ @NeverInline
+ private static void noneOf() {
+ System.out.println("none: " + EnumSet.complementOf(EnumSet.noneOf(EnumA.class)));
+ }
+
+ @NeverInline
+ private static void allOf() {
+ System.out.println("all: " + EnumSet.allOf(EnumB.class));
+ }
+
+ @NeverInline
+ private static void of1() {
+ System.out.println("of: " + EnumSet.of(EnumC.C));
+ }
+
+ @NeverInline
+ private static void of2() {
+ System.out.println("of: " + EnumSet.of(EnumD.D, EnumD.E));
+ }
+
+ @NeverInline
+ private static void of3() {
+ System.out.println("of: " + EnumSet.of(EnumE.E, EnumE.F, EnumE.G));
+ }
+
+ @NeverInline
+ private static void of4() {
+ System.out.println("of: " + EnumSet.of(EnumF.F, EnumF.G, EnumF.H, EnumF.I));
+ }
+
+ @NeverInline
+ private static void of5() {
+ System.out.println("of: " + EnumSet.of(EnumG.G, EnumG.H, EnumG.I, EnumG.J, EnumG.K));
+ }
+
+ @NeverInline
+ private static void ofVarArgs() {
+ System.out.println("of: " + EnumSet.of(EnumH.H, EnumH.I, EnumH.J, EnumH.K, EnumH.L, EnumH.M));
+ }
+
+ @NeverInline
+ private static void range() {
+ System.out.println("range: " + EnumSet.range(EnumI.I, EnumI.J));
+ }
+
+ @NeverInline
+ private static void map() {
+ EnumMap<EnumJ, Integer> map = new EnumMap<>(EnumJ.class);
+ map.put(EnumJ.J, 1);
+ System.out.println("map: " + map);
+ }
+
+ @NeverInline
+ private static void valueOf() {
+ System.out.println("valueOf: " + EnumK.valueOf("K"));
+ }
+
+ public static void main(String[] args) {
+ // Use different methods to ensure Enqueuer.traceInvokeStatic() triggers for each one.
+ noneOf();
+ allOf();
+ of1();
+ of2();
+ of3();
+ of4();
+ of5();
+ ofVarArgs();
+ range();
+ map();
+ valueOf();
+ // Ensure phi as argument does not cause issues.
+ System.out.println(
+ "phi: " + EnumSet.of((Enum) (args.length > 10 ? (Object) EnumA.A : (Object) EnumB.B)));
+ }
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClassesAndInnerClasses(TestMain.class)
+ .run(parameters.getRuntime(), TestMain.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClassesAndInnerClasses(TestMain.class)
+ .enableInliningAnnotations()
+ .addKeepMainRule(TestMain.class)
+ .compile()
+ .run(parameters.getRuntime(), TestMain.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumReflectionTest.java
deleted file mode 100644
index 9e9b297..0000000
--- a/src/test/java/com/android/tools/r8/shaking/enums/EnumReflectionTest.java
+++ /dev/null
@@ -1,505 +0,0 @@
-// Copyright (c) 2025, 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.shaking.enums;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumA;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumB;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumC;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumD;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumE;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumF;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumG;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumH;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumI;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumJ;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumK;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumL;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumM;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumN;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumO;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumP;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumQ;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumR;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumS;
-import com.android.tools.r8.shaking.enums.EnumReflectionTest.Helpers.EnumT;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.lang.reflect.Array;
-import java.util.Arrays;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-/** Tests for the various APIs that cause an Enum type's values() method to be kept. */
-@RunWith(Parameterized.class)
-public class EnumReflectionTest extends TestBase {
-
- @Parameter(0)
- public TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build();
- }
-
- public static class FakeParcel {
- // Deserializing arrays yields UnsatisfiedLinkError for com.android.org.conscrypt.NativeCrypto
- // when running under ART.
- private Class arrayType;
- private ObjectInputStream objectInputStream;
-
- public static byte[] toBytes(Serializable thing) {
- try {
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
- objectOutputStream.writeObject(thing);
- return byteArrayOutputStream.toByteArray();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public static FakeParcel createWithSingleSerializable(Serializable thing) {
- try {
- FakeParcel ret = new FakeParcel();
- if (thing.getClass().isArray()) {
- ret.arrayType = thing.getClass();
- if (Array.getLength(thing) != 1) {
- throw new IllegalArgumentException();
- }
- thing = (Serializable) Array.get(thing, 0);
- }
- ret.objectInputStream = new ObjectInputStream(new ByteArrayInputStream(toBytes(thing)));
- return ret;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public Serializable readSerializable() {
- try {
- Serializable ret = (Serializable) objectInputStream.readObject();
- if (arrayType != null) {
- Object array = Array.newInstance(arrayType.getComponentType(), 1);
- Array.set(array, 0, ret);
- ret = (Serializable) array;
- }
- return ret;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public <T extends Serializable> T readSerializable(ClassLoader loader, Class<T> clazz) {
- return clazz.cast(readSerializable());
- }
- }
-
- public static class FakeBundle {
- private Object parcel;
-
- public static FakeBundle createWithSingleSerializable(Serializable thing) {
- FakeBundle ret = new FakeBundle();
- ret.parcel = FakeParcel.createWithSingleSerializable(thing);
- return ret;
- }
-
- public <T extends Serializable> T getSerializable(String key, Class<T> clazz) {
- return clazz.cast(getSerializable(key));
- }
-
- public Serializable getSerializable(String key) {
- return ((FakeParcel) parcel).readSerializable();
- }
- }
-
- public static class FakeIntent {
- private Object parcel;
-
- public static FakeIntent createWithSingleSerializable(Serializable thing) {
- FakeIntent ret = new FakeIntent();
- ret.parcel = FakeParcel.createWithSingleSerializable(thing);
- return ret;
- }
-
- public <T extends Serializable> T getSerializableExtra(String key, Class<T> clazz) {
- return clazz.cast(getSerializableExtra(key));
- }
-
- public Serializable getSerializableExtra(String key) {
- return ((FakeParcel) parcel).readSerializable();
- }
- }
-
- private static final List<String> EXPECTED_OUTPUT =
- Arrays.asList(
- "none: [A, B]",
- "all: [B, C]",
- "of: [C]",
- "of: [D, E]",
- "of: [E, F, G]",
- "of: [F, G, H, I]",
- "of: [G, H, I, J, K]",
- "of: [H, I, J, K, L, M]",
- "range: [I, J]",
- "map: {J=1}",
- "valueOf: K",
- "bundle: L",
- "bundle: M",
- "parcel: N",
- "parcel: O",
- "intent: P",
- "intent: Q",
- "stream: R",
- "array: [S]",
- "array: [T]",
- "phi: [B]");
-
- public static class Helpers {
-
- public enum EnumA {
- A,
- B
- }
-
- public enum EnumB {
- B,
- C
- }
-
- public enum EnumC {
- C,
- D
- }
-
- public enum EnumD {
- D,
- E
- }
-
- public enum EnumE {
- E,
- F,
- G
- }
-
- public enum EnumF {
- F,
- G,
- H,
- I
- }
-
- public enum EnumG {
- G,
- H,
- I,
- J,
- K
- }
-
- public enum EnumH {
- H,
- I,
- J,
- K,
- L,
- M
- }
-
- public enum EnumI {
- I,
- J
- }
-
- public enum EnumJ {
- J,
- K
- }
-
- public enum EnumK {
- K,
- L
- }
-
- public enum EnumL {
- L,
- M
- }
-
- public enum EnumM {
- M,
- N
- }
-
- public enum EnumN {
- N,
- O
- }
-
- public enum EnumO {
- O,
- P
- }
-
- public enum EnumP {
- P,
- Q
- }
-
- public enum EnumQ {
- Q,
- R
- }
-
- public enum EnumR {
- R {}, // Test anonymous enum subtype.
- S
- }
-
- public enum EnumS {
- S,
- T
- }
-
- public enum EnumT {
- T,
- U
- }
- }
-
- public static class TestMain {
- @NeverInline
- private static void noneOf() {
- System.out.println("none: " + EnumSet.complementOf(EnumSet.noneOf(EnumA.class)));
- }
-
- @NeverInline
- private static void allOf() {
- System.out.println("all: " + EnumSet.allOf(EnumB.class));
- }
-
- @NeverInline
- private static void of1() {
- System.out.println("of: " + EnumSet.of(EnumC.C));
- }
-
- @NeverInline
- private static void of2() {
- System.out.println("of: " + EnumSet.of(EnumD.D, EnumD.E));
- }
-
- @NeverInline
- private static void of3() {
- System.out.println("of: " + EnumSet.of(EnumE.E, EnumE.F, EnumE.G));
- }
-
- @NeverInline
- private static void of4() {
- System.out.println("of: " + EnumSet.of(EnumF.F, EnumF.G, EnumF.H, EnumF.I));
- }
-
- @NeverInline
- private static void of5() {
- System.out.println("of: " + EnumSet.of(EnumG.G, EnumG.H, EnumG.I, EnumG.J, EnumG.K));
- }
-
- @NeverInline
- private static void ofVarArgs() {
- System.out.println("of: " + EnumSet.of(EnumH.H, EnumH.I, EnumH.J, EnumH.K, EnumH.L, EnumH.M));
- }
-
- @NeverInline
- private static void range() {
- System.out.println("range: " + EnumSet.range(EnumI.I, EnumI.J));
- }
-
- @NeverInline
- private static void map() {
- EnumMap<EnumJ, Integer> map = new EnumMap<>(EnumJ.class);
- map.put(EnumJ.J, 1);
- System.out.println("map: " + map);
- }
-
- @NeverInline
- private static void valueOf() {
- System.out.println("valueOf: " + EnumK.valueOf("K"));
- }
-
- @NeverInline
- private static void androidBundle1() {
- FakeBundle b = FakeBundle.createWithSingleSerializable(EnumL.L);
- EnumL result = (EnumL) b.getSerializable("");
- System.out.println("bundle: " + result);
- }
-
- @NeverInline
- private static void androidBundle2() {
- FakeBundle b = FakeBundle.createWithSingleSerializable(EnumM.M);
- EnumM result = b.getSerializable("", EnumM.class);
- System.out.println("bundle: " + result);
- }
-
- @NeverInline
- private static void androidParcel1() {
- FakeParcel p = FakeParcel.createWithSingleSerializable(EnumN.N);
- EnumN result = (EnumN) p.readSerializable();
- System.out.println("parcel: " + result);
- }
-
- @NeverInline
- private static void androidParcel2() {
- FakeParcel p = FakeParcel.createWithSingleSerializable(EnumO.O);
- System.out.println("parcel: " + p.readSerializable(null, EnumO.class));
- }
-
- @NeverInline
- private static void androidIntent1() {
- FakeIntent i = FakeIntent.createWithSingleSerializable(EnumP.P);
- EnumP result = (EnumP) i.getSerializableExtra("");
- System.out.println("intent: " + result);
- }
-
- @NeverInline
- private static void androidIntent2() {
- FakeIntent i = FakeIntent.createWithSingleSerializable(EnumQ.Q);
- System.out.println("intent: " + i.getSerializableExtra("", EnumQ.class));
- }
-
- @NeverInline
- private static void array1() {
- FakeParcel p = FakeParcel.createWithSingleSerializable(new EnumS[] {EnumS.S});
- EnumS[] result = (EnumS[]) p.readSerializable();
- System.out.println("array: " + Arrays.toString(result));
- }
-
- @NeverInline
- private static void array2() {
- FakeParcel p = FakeParcel.createWithSingleSerializable(new EnumT[] {EnumT.T});
- System.out.println("array: " + Arrays.toString(p.readSerializable(null, EnumT[].class)));
- }
-
- @NeverInline
- private static void objectStream() {
- try {
- ObjectInputStream objectInputStream =
- new ObjectInputStream(new ByteArrayInputStream(FakeParcel.toBytes(EnumR.R)));
- EnumR result = (EnumR) objectInputStream.readObject();
- System.out.println("stream: " + result);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static void main(String[] args) {
- // Use different methods to ensure Enqueuer.traceInvokeStatic() triggers for each one.
- noneOf();
- allOf();
- of1();
- of2();
- of3();
- of4();
- of5();
- ofVarArgs();
- range();
- map();
- valueOf();
- androidBundle1();
- androidBundle2();
- androidParcel1();
- androidParcel2();
- androidIntent1();
- androidIntent2();
- objectStream();
- array1();
- array2();
- // Ensure phi as argument does not cause issues.
- System.out.println(
- "phi: " + EnumSet.of((Enum) (args.length > 10 ? (Object) EnumA.A : (Object) EnumB.B)));
- }
- }
-
- // EnumR.R references this class in its constructor.
- private static final String ENUM_SUBTYPE_BRIDGE_CLASS_NAME =
- EnumReflectionTest.class.getName() + "$1";
-
- private static final String PARCEL_DESCRIPTOR = "Landroid/os/Parcel;";
- private static final String BUNDLE_DESCRIPTOR = "Landroid/os/Bundle;";
- private static final String INTENT_DESCRIPTOR = "Landroid/content/Intent;";
-
- private static byte[] rewriteTestMain() throws IOException {
- return transformer(TestMain.class)
- .replaceClassDescriptorInMethodInstructions(descriptor(FakeParcel.class), PARCEL_DESCRIPTOR)
- .replaceClassDescriptorInMethodInstructions(descriptor(FakeBundle.class), BUNDLE_DESCRIPTOR)
- .replaceClassDescriptorInMethodInstructions(descriptor(FakeIntent.class), INTENT_DESCRIPTOR)
- .transform();
- }
-
- private static byte[] rewriteParcel() throws IOException {
- return transformer(FakeParcel.class).setClassDescriptor(PARCEL_DESCRIPTOR).transform();
- }
-
- private static byte[] rewriteBundle() throws IOException {
- return transformer(FakeBundle.class)
- .setClassDescriptor(BUNDLE_DESCRIPTOR)
- .replaceClassDescriptorInMethodInstructions(descriptor(FakeParcel.class), PARCEL_DESCRIPTOR)
- .transform();
- }
-
- private static byte[] rewriteIntent() throws IOException {
- return transformer(FakeIntent.class)
- .setClassDescriptor(INTENT_DESCRIPTOR)
- .replaceClassDescriptorInMethodInstructions(descriptor(FakeParcel.class), PARCEL_DESCRIPTOR)
- .transform();
- }
-
- @Test
- public void testRuntime() throws Exception {
- byte[] parcelBytes = rewriteParcel();
- byte[] bundleBytes = rewriteBundle();
- byte[] intentBytes = rewriteIntent();
- testForRuntime(parameters)
- .addProgramClassesAndInnerClasses(Helpers.class)
- .addProgramClassFileData(rewriteTestMain())
- .addProgramClasses(Class.forName(ENUM_SUBTYPE_BRIDGE_CLASS_NAME))
- .addClasspathClassFileData(parcelBytes, bundleBytes, intentBytes)
- .addRunClasspathFiles(buildOnDexRuntime(parameters, parcelBytes, bundleBytes, intentBytes))
- .run(parameters.getRuntime(), TestMain.class)
- .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
- }
-
- @Test
- public void testR8() throws Exception {
- byte[] parcelBytes = rewriteParcel();
- byte[] bundleBytes = rewriteBundle();
- byte[] intentBytes = rewriteIntent();
- testForR8(parameters.getBackend())
- .setMinApi(parameters)
- .addOptionsModification(options -> options.experimentalTraceEnumReflection = true)
- .addProgramClassesAndInnerClasses(Helpers.class)
- .addProgramClassFileData(rewriteTestMain())
- .addProgramClasses(Class.forName(ENUM_SUBTYPE_BRIDGE_CLASS_NAME))
- .addClasspathClassFileData(parcelBytes, bundleBytes, intentBytes)
- .enableInliningAnnotations()
- .addKeepMainRule(TestMain.class)
- .compile()
- .addRunClasspathClassFileData(parcelBytes, bundleBytes, intentBytes)
- .run(parameters.getRuntime(), TestMain.class)
- .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
- }
-}