| // 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.ir.optimize.enums; |
| |
| import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; |
| |
| import com.android.tools.r8.graph.AccessFlags; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexCallSite; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexClassAndMethod; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMember; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| 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.DexType; |
| import com.android.tools.r8.graph.DirectMappedDexApplication; |
| import com.android.tools.r8.graph.FieldResolutionResult; |
| import com.android.tools.r8.graph.GraphLens; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.ProgramPackageCollection; |
| import com.android.tools.r8.graph.ResolutionResult; |
| import com.android.tools.r8.graph.UseRegistry; |
| import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues; |
| import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues; |
| import com.android.tools.r8.ir.analysis.type.ArrayTypeElement; |
| import com.android.tools.r8.ir.analysis.type.ClassTypeElement; |
| import com.android.tools.r8.ir.analysis.type.TypeElement; |
| import com.android.tools.r8.ir.analysis.value.AbstractValue; |
| import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState; |
| import com.android.tools.r8.ir.analysis.value.ObjectState; |
| import com.android.tools.r8.ir.code.ArrayPut; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.CheckCast; |
| import com.android.tools.r8.ir.code.ConstClass; |
| import com.android.tools.r8.ir.code.FieldInstruction; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.If; |
| import com.android.tools.r8.ir.code.InstanceGet; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InvokeMethod; |
| import com.android.tools.r8.ir.code.InvokeStatic; |
| import com.android.tools.r8.ir.code.MemberType; |
| import com.android.tools.r8.ir.code.Opcodes; |
| import com.android.tools.r8.ir.code.Phi; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.conversion.IRConverter; |
| import com.android.tools.r8.ir.conversion.PostMethodProcessor; |
| import com.android.tools.r8.ir.optimize.Inliner.Constraint; |
| import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData; |
| import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData; |
| import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData; |
| import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData; |
| import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData; |
| import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; |
| import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; |
| import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier; |
| import com.android.tools.r8.shaking.KeepInfoCollection; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; |
| import com.android.tools.r8.utils.collections.ProgramMethodSet; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.OptionalInt; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.function.Predicate; |
| |
| public class EnumUnboxer { |
| |
| private final AppView<AppInfoWithLiveness> appView; |
| private final DexItemFactory factory; |
| // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given |
| // enum if the optimization eventually decides to unbox it. |
| private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo; |
| private final ProgramPackageCollection enumsToUnboxWithPackageRequirement = |
| ProgramPackageCollection.createEmpty(); |
| private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap = |
| new ConcurrentHashMap<>(); |
| |
| private final DexEncodedField ordinalField; |
| |
| private EnumUnboxingRewriter enumUnboxerRewriter; |
| |
| private final boolean debugLogEnabled; |
| private final Map<DexType, Reason> debugLogs; |
| |
| public EnumUnboxer(AppView<AppInfoWithLiveness> appView) { |
| this.appView = appView; |
| this.factory = appView.dexItemFactory(); |
| if (appView.options().testing.enableEnumUnboxingDebugLogs) { |
| debugLogEnabled = true; |
| debugLogs = new ConcurrentHashMap<>(); |
| } else { |
| debugLogEnabled = false; |
| debugLogs = null; |
| } |
| assert !appView.options().debug; |
| enumUnboxingCandidatesInfo = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates(); |
| |
| ordinalField = |
| appView.appInfo().resolveField(factory.enumMembers.ordinalField).getResolvedField(); |
| if (ordinalField == null) { |
| // This can happen when compiling for non standard libraries, in that case, this effectively |
| // disables the enum unboxer. |
| enumUnboxingCandidatesInfo.clear(); |
| } |
| } |
| |
| public static int ordinalToUnboxedInt(int ordinal) { |
| return ordinal + 1; |
| } |
| |
| private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) { |
| assert enumClass.isEnum(); |
| reportFailure(enumClass.type, reason); |
| enumUnboxingCandidatesInfo.removeCandidate(enumClass.type); |
| } |
| |
| private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) { |
| if (lattice.isClassType()) { |
| DexType classType = lattice.asClassType().getClassType(); |
| return getEnumUnboxingCandidateOrNull(classType); |
| } |
| if (lattice.isArrayType()) { |
| ArrayTypeElement arrayType = lattice.asArrayType(); |
| if (arrayType.getBaseType().isClassType()) { |
| return getEnumUnboxingCandidateOrNull(arrayType.getBaseType()); |
| } |
| } |
| return null; |
| } |
| |
| private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) { |
| return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type); |
| } |
| |
| public void analyzeEnums(IRCode code) { |
| Set<DexType> eligibleEnums = Sets.newIdentityHashSet(); |
| for (BasicBlock block : code.blocks) { |
| for (Instruction instruction : block.getInstructions()) { |
| Value outValue = instruction.outValue(); |
| if (outValue != null) { |
| DexProgramClass enumClass = |
| getEnumUnboxingCandidateOrNull(outValue.getDynamicUpperBoundType(appView)); |
| if (enumClass != null) { |
| Reason reason = validateEnumUsages(code, outValue, enumClass); |
| if (reason == Reason.ELIGIBLE) { |
| eligibleEnums.add(enumClass.type); |
| } |
| } |
| if (outValue.getType().isNullType()) { |
| addNullDependencies(code, outValue.uniqueUsers(), eligibleEnums); |
| } |
| } else { |
| if (instruction.isInvokeMethod()) { |
| DexProgramClass enumClass = |
| getEnumUnboxingCandidateOrNull(instruction.asInvokeMethod().getReturnType()); |
| if (enumClass != null) { |
| eligibleEnums.add(enumClass.type); |
| } |
| } |
| } |
| switch (instruction.opcode()) { |
| case Opcodes.CONST_CLASS: |
| analyzeConstClass(instruction.asConstClass(), eligibleEnums, code.context()); |
| break; |
| case Opcodes.CHECK_CAST: |
| analyzeCheckCast(instruction.asCheckCast(), eligibleEnums); |
| break; |
| case Opcodes.INVOKE_STATIC: |
| analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context()); |
| break; |
| case Opcodes.STATIC_GET: |
| case Opcodes.INSTANCE_GET: |
| case Opcodes.STATIC_PUT: |
| case Opcodes.INSTANCE_PUT: |
| analyzeFieldInstruction(instruction.asFieldInstruction(), code); |
| break; |
| default: // Nothing to do for other instructions. |
| } |
| } |
| for (Phi phi : block.getPhis()) { |
| DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getType()); |
| if (enumClass != null) { |
| Reason reason = validateEnumUsages(code, phi, enumClass); |
| if (reason == Reason.ELIGIBLE) { |
| eligibleEnums.add(enumClass.type); |
| } |
| } |
| if (phi.getType().isNullType()) { |
| addNullDependencies(code, phi.uniqueUsers(), eligibleEnums); |
| } |
| } |
| } |
| if (!eligibleEnums.isEmpty()) { |
| for (DexType eligibleEnum : eligibleEnums) { |
| enumUnboxingCandidatesInfo.addMethodDependency(eligibleEnum, code.context()); |
| } |
| } |
| } |
| |
| private void analyzeFieldInstruction(FieldInstruction fieldInstruction, IRCode code) { |
| DexField field = fieldInstruction.getField(); |
| DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.holder); |
| if (enumClass != null) { |
| FieldResolutionResult resolutionResult = |
| appView.appInfo().resolveField(field, code.context()); |
| if (resolutionResult.isFailedOrUnknownResolution()) { |
| markEnumAsUnboxable(Reason.UNRESOLVABLE_FIELD, enumClass); |
| } |
| } |
| } |
| |
| private void analyzeInvokeStatic( |
| InvokeStatic invokeStatic, Set<DexType> eligibleEnums, ProgramMethod context) { |
| DexMethod invokedMethod = invokeStatic.getInvokedMethod(); |
| DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder); |
| if (enumClass != null) { |
| DexClassAndMethod method = invokeStatic.lookupSingleTarget(appView, context); |
| if (method != null) { |
| eligibleEnums.add(enumClass.type); |
| } else { |
| markEnumAsUnboxable(Reason.INVALID_INVOKE, enumClass); |
| } |
| } |
| } |
| |
| private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) { |
| // We are doing a type check, which typically means the in-value is of an upper |
| // type and cannot be dealt with. |
| // If the cast is on a dynamically typed object, the checkCast can be simply removed. |
| // This allows enum array clone and valueOf to work correctly. |
| DexProgramClass enumClass = |
| getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory)); |
| if (enumClass == null) { |
| return; |
| } |
| if (allowCheckCast(checkCast)) { |
| eligibleEnums.add(enumClass.type); |
| return; |
| } |
| markEnumAsUnboxable(Reason.DOWN_CAST, enumClass); |
| } |
| |
| private boolean allowCheckCast(CheckCast checkCast) { |
| TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView); |
| return objectType.equalUpToNullability( |
| TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView)); |
| } |
| |
| private void analyzeConstClass( |
| ConstClass constClass, Set<DexType> eligibleEnums, ProgramMethod context) { |
| // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed. |
| // We however allow unboxing if the ConstClass is used only: |
| // - as an argument to Enum#valueOf, to allow unboxing of: |
| // MyEnum a = Enum.valueOf(MyEnum.class, "A"); |
| // - as a receiver for a name method, to allow unboxing of: |
| // MyEnum.class.getName(); |
| DexType enumType = constClass.getValue(); |
| if (!enumUnboxingCandidatesInfo.isCandidate(enumType)) { |
| return; |
| } |
| if (constClass.outValue() == null) { |
| eligibleEnums.add(enumType); |
| return; |
| } |
| DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass(); |
| if (constClass.outValue().hasPhiUsers()) { |
| markEnumAsUnboxable(Reason.CONST_CLASS, enumClass); |
| return; |
| } |
| for (Instruction user : constClass.outValue().uniqueUsers()) { |
| if (user.isInvokeVirtual() |
| && isUnboxableNameMethod(user.asInvokeVirtual().getInvokedMethod())) { |
| continue; |
| } |
| if (user.isInvokeStatic()) { |
| DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context); |
| if (singleTarget != null && singleTarget.getReference() == factory.enumMembers.valueOf) { |
| // The name data is required for the correct mapping from the enum name to the ordinal in |
| // the valueOf utility method. |
| addRequiredNameData(enumType); |
| continue; |
| } |
| } |
| markEnumAsUnboxable(Reason.CONST_CLASS, enumClass); |
| return; |
| } |
| eligibleEnums.add(enumType); |
| } |
| |
| private void addRequiredNameData(DexType enumType) { |
| enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData( |
| enumType, factory.enumMembers.nameField); |
| } |
| |
| private boolean isUnboxableNameMethod(DexMethod method) { |
| return method == factory.classMethods.getName |
| || method == factory.classMethods.getCanonicalName |
| || method == factory.classMethods.getSimpleName; |
| } |
| |
| private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) { |
| for (Instruction use : uses) { |
| if (use.isInvokeMethod()) { |
| InvokeMethod invokeMethod = use.asInvokeMethod(); |
| DexMethod invokedMethod = invokeMethod.getInvokedMethod(); |
| for (DexType paramType : invokedMethod.proto.parameters.values) { |
| if (enumUnboxingCandidatesInfo.isCandidate(paramType)) { |
| eligibleEnums.add(paramType); |
| } |
| } |
| if (invokeMethod.isInvokeMethodWithReceiver()) { |
| DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder); |
| if (enumClass != null) { |
| markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass); |
| } |
| } |
| } else if (use.isFieldPut()) { |
| DexType type = use.asFieldInstruction().getField().type; |
| if (enumUnboxingCandidatesInfo.isCandidate(type)) { |
| eligibleEnums.add(type); |
| } |
| } else if (use.isReturn()) { |
| DexType returnType = code.method().method.proto.returnType; |
| if (enumUnboxingCandidatesInfo.isCandidate(returnType)) { |
| eligibleEnums.add(returnType); |
| } |
| } |
| } |
| } |
| |
| private Reason validateEnumUsages(IRCode code, Value value, DexProgramClass enumClass) { |
| for (Instruction user : value.uniqueUsers()) { |
| Reason reason = instructionAllowEnumUnboxing(user, code, enumClass, value); |
| if (reason != Reason.ELIGIBLE) { |
| markEnumAsUnboxable(reason, enumClass); |
| return reason; |
| } |
| } |
| for (Phi phi : value.uniquePhiUsers()) { |
| for (Value operand : phi.getOperands()) { |
| if (!operand.getType().isNullType() |
| && getEnumUnboxingCandidateOrNull(operand.getType()) != enumClass) { |
| markEnumAsUnboxable(Reason.INVALID_PHI, enumClass); |
| return Reason.INVALID_PHI; |
| } |
| } |
| } |
| return Reason.ELIGIBLE; |
| } |
| |
| public void unboxEnums( |
| PostMethodProcessor.Builder postBuilder, |
| ExecutorService executorService, |
| OptimizationFeedbackDelayed feedback) |
| throws ExecutionException { |
| EnumDataMap enumDataMap = finishAnalysis(); |
| // At this point the enum unboxing candidates are no longer candidates, they will all be |
| // unboxed. We extract the now immutable enums to unbox information and clear the candidate |
| // info. |
| if (enumUnboxingCandidatesInfo.isEmpty()) { |
| return; |
| } |
| ImmutableSet<DexType> enumsToUnbox = enumUnboxingCandidatesInfo.candidates(); |
| ImmutableSet<DexProgramClass> enumClassesToUnbox = |
| enumUnboxingCandidatesInfo.candidateClasses(); |
| ProgramMethodSet dependencies = enumUnboxingCandidatesInfo.allMethodDependencies(); |
| enumUnboxingCandidatesInfo.clear(); |
| // Update keep info on any of the enum methods of the removed classes. |
| updateKeepInfo(enumsToUnbox); |
| DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder(); |
| FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder = |
| FieldAccessInfoCollectionModifier.builder(); |
| UnboxedEnumMemberRelocator relocator = |
| UnboxedEnumMemberRelocator.builder(appView) |
| .synthesizeEnumUnboxingUtilityClasses( |
| enumClassesToUnbox, |
| enumsToUnboxWithPackageRequirement, |
| appBuilder, |
| fieldAccessInfoCollectionModifierBuilder) |
| .build(); |
| fieldAccessInfoCollectionModifierBuilder.build().modify(appView); |
| enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator); |
| EnumUnboxingLens enumUnboxingLens = |
| new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter) |
| .fixupTypeReferences(); |
| enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens); |
| appView.setUnboxedEnums(enumDataMap); |
| GraphLens previousLens = appView.graphLens(); |
| appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build()); |
| updateOptimizationInfos(executorService, feedback); |
| postBuilder.put(dependencies); |
| postBuilder.rewrittenWithLens(appView, previousLens); |
| } |
| |
| private void updateOptimizationInfos( |
| ExecutorService executorService, OptimizationFeedbackDelayed feedback) |
| throws ExecutionException { |
| feedback.fixupOptimizationInfos( |
| appView, |
| executorService, |
| new OptimizationInfoFixer() { |
| @Override |
| public void fixup(DexEncodedField field) { |
| FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo(); |
| if (optimizationInfo.isMutableFieldOptimizationInfo()) { |
| optimizationInfo |
| .asMutableFieldOptimizationInfo() |
| .fixupClassTypeReferences(appView.graphLens()::lookupType, appView) |
| .fixupAbstractValue(appView, appView.graphLens()); |
| } else { |
| assert optimizationInfo.isDefaultFieldOptimizationInfo(); |
| } |
| } |
| |
| @Override |
| public void fixup(DexEncodedMethod method) { |
| MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo(); |
| if (optimizationInfo.isUpdatableMethodOptimizationInfo()) { |
| UpdatableMethodOptimizationInfo updatableOptimizationInfo = |
| optimizationInfo.asUpdatableMethodOptimizationInfo(); |
| updatableOptimizationInfo |
| .fixupClassTypeReferences(appView.graphLens()::lookupType, appView) |
| .fixupAbstractReturnValue(appView, appView.graphLens()) |
| .fixupInstanceInitializerInfo(appView, appView.graphLens()); |
| } else { |
| assert optimizationInfo.isDefaultMethodOptimizationInfo(); |
| } |
| } |
| }); |
| } |
| |
| private void updateKeepInfo(Set<DexType> enumsToUnbox) { |
| KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo(); |
| keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(enumsToUnbox)); |
| } |
| |
| public EnumDataMap finishAnalysis() { |
| analyzeInitializers(); |
| analyzeAccessibility(); |
| EnumDataMap enumDataMap = analyzeEnumInstances(); |
| assert enumDataMap.getUnboxedEnums().size() == enumUnboxingCandidatesInfo.candidates().size(); |
| if (debugLogEnabled) { |
| reportEnumsAnalysis(); |
| } |
| return enumDataMap; |
| } |
| |
| private EnumDataMap analyzeEnumInstances() { |
| ImmutableMap.Builder<DexType, EnumData> builder = ImmutableMap.builder(); |
| enumUnboxingCandidatesInfo.forEachCandidateAndRequiredInstanceFieldData( |
| (enumClass, fields) -> { |
| EnumData data = buildData(enumClass, fields); |
| if (data == null) { |
| markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass); |
| return; |
| } |
| builder.put(enumClass.type, data); |
| }); |
| staticFieldValuesMap.clear(); |
| return new EnumDataMap(builder.build()); |
| } |
| |
| private EnumData buildData(DexProgramClass enumClass, Set<DexField> fields) { |
| // This map holds all the accessible fields to their unboxed value, so we can remap the field |
| // read to the unboxed value. |
| ImmutableMap.Builder<DexField, Integer> unboxedValues = ImmutableMap.builder(); |
| // This maps the ordinal to the object state, note that some fields may have been removed, |
| // hence the entry is in this map but not the enumToOrdinalMap. |
| Int2ReferenceMap<ObjectState> ordinalToObjectState = new Int2ReferenceArrayMap<>(); |
| // Any fields matching the expected $VALUES content can be recorded here, they have however |
| // all the same content. |
| ImmutableSet.Builder<DexField> valuesField = ImmutableSet.builder(); |
| EnumValuesObjectState valuesContents = null; |
| |
| EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type); |
| |
| // Step 1: We iterate over the field to find direct enum instance information and the values |
| // fields. |
| for (DexEncodedField staticField : enumClass.staticFields()) { |
| if (factory.enumMembers.isEnumField(staticField, enumClass.type)) { |
| ObjectState enumState = |
| enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field); |
| if (enumState == null) { |
| if (staticField.getOptimizationInfo().isDead()) { |
| // We don't care about unused field data. |
| continue; |
| } |
| // We could not track the content of that field. We bail out. |
| return null; |
| } |
| OptionalInt optionalOrdinal = getOrdinal(enumState); |
| if (!optionalOrdinal.isPresent()) { |
| return null; |
| } |
| int ordinal = optionalOrdinal.getAsInt(); |
| unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal)); |
| ordinalToObjectState.put(ordinal, enumState); |
| } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) { |
| ObjectState valuesState = |
| enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field); |
| if (valuesState == null) { |
| if (staticField.getOptimizationInfo().isDead()) { |
| // We don't care about unused field data. |
| continue; |
| } |
| // We could not track the content of that field. We bail out. |
| // We could not track the content of that field, and the field could be a values field. |
| // We conservatively bail out. |
| return null; |
| } |
| assert valuesState.isEnumValuesObjectState(); |
| assert valuesContents == null |
| || valuesContents.equals(valuesState.asEnumValuesObjectState()); |
| valuesContents = valuesState.asEnumValuesObjectState(); |
| valuesField.add(staticField.field); |
| } |
| } |
| |
| // Step 2: We complete the information based on the values content, since some enum instances |
| // may be reachable only though the $VALUES field. |
| if (valuesContents != null) { |
| for (int ordinal = 0; ordinal < valuesContents.getEnumValuesSize(); ordinal++) { |
| if (!ordinalToObjectState.containsKey(ordinal)) { |
| ObjectState enumState = valuesContents.getObjectStateForOrdinal(ordinal); |
| if (enumState.isEmpty()) { |
| // If $VALUES is used, we need data for all enums, at least the ordinal. |
| return null; |
| } |
| assert getOrdinal(enumState).isPresent(); |
| assert getOrdinal(enumState).getAsInt() == ordinal; |
| ordinalToObjectState.put(ordinal, enumState); |
| } |
| } |
| } |
| |
| // The ordinalToObjectState map may have holes at this point, if some enum instances are never |
| // used ($VALUES unused or removed, and enum instance field unused or removed), it contains |
| // only data for reachable enum instance, that is what we're interested in. |
| ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> instanceFieldBuilder = |
| ImmutableMap.builder(); |
| for (DexField instanceField : fields) { |
| EnumInstanceFieldData fieldData = |
| computeEnumFieldData(instanceField, enumClass, ordinalToObjectState); |
| if (fieldData.isUnknown()) { |
| return null; |
| } |
| instanceFieldBuilder.put(instanceField, fieldData.asEnumFieldKnownData()); |
| } |
| |
| return new EnumData( |
| instanceFieldBuilder.build(), |
| unboxedValues.build(), |
| valuesField.build(), |
| valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize()); |
| } |
| |
| private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) { |
| assert staticField.isFinal(); |
| return appView |
| .appInfo() |
| .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer()); |
| } |
| |
| private EnumInstanceFieldData computeEnumFieldData( |
| DexField instanceField, |
| DexProgramClass enumClass, |
| Int2ReferenceMap<ObjectState> ordinalToObjectState) { |
| DexEncodedField encodedInstanceField = |
| appView.appInfo().resolveFieldOn(enumClass, instanceField).getResolvedField(); |
| assert encodedInstanceField != null; |
| boolean canBeOrdinal = instanceField.type.isIntType(); |
| ImmutableInt2ReferenceSortedMap.Builder<AbstractValue> data = |
| ImmutableInt2ReferenceSortedMap.builder(); |
| for (Integer ordinal : ordinalToObjectState.keySet()) { |
| ObjectState state = ordinalToObjectState.get(ordinal); |
| AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField); |
| if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) { |
| return EnumInstanceFieldUnknownData.getInstance(); |
| } |
| data.put(ordinalToUnboxedInt(ordinal), fieldValue); |
| if (canBeOrdinal) { |
| assert fieldValue.isSingleNumberValue(); |
| int computedValue = fieldValue.asSingleNumberValue().getIntValue(); |
| if (computedValue != ordinal) { |
| canBeOrdinal = false; |
| } |
| } |
| } |
| if (canBeOrdinal) { |
| return new EnumInstanceFieldOrdinalData(); |
| } |
| return new EnumInstanceFieldMappingData(data.build()); |
| } |
| |
| private OptionalInt getOrdinal(ObjectState state) { |
| AbstractValue field = state.getAbstractFieldValue(ordinalField); |
| if (field.isSingleNumberValue()) { |
| return OptionalInt.of(field.asSingleNumberValue().getIntValue()); |
| } |
| return OptionalInt.empty(); |
| } |
| |
| private void analyzeAccessibility() { |
| // Unboxing an enum will require to move its methods to a different class, which may impact |
| // accessibility. For a quick analysis we simply reuse the inliner analysis. |
| enumUnboxingCandidatesInfo.forEachCandidate( |
| enumClass -> { |
| Constraint classConstraint = analyzeAccessibilityInClass(enumClass); |
| if (classConstraint == Constraint.NEVER) { |
| markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass); |
| } else if (classConstraint == Constraint.PACKAGE) { |
| enumsToUnboxWithPackageRequirement.addProgramClass(enumClass); |
| } |
| }); |
| } |
| |
| private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) { |
| Constraint classConstraint = Constraint.ALWAYS; |
| EnumAccessibilityUseRegistry useRegistry = null; |
| for (DexEncodedMethod method : enumClass.methods()) { |
| // Enum initializer are analyzed in analyzeInitializers instead. |
| if (!method.isInitializer()) { |
| if (useRegistry == null) { |
| useRegistry = new EnumAccessibilityUseRegistry(factory); |
| } |
| Constraint methodConstraint = constraintForEnumUnboxing(method, useRegistry); |
| classConstraint = classConstraint.meet(methodConstraint); |
| if (classConstraint == Constraint.NEVER) { |
| return classConstraint; |
| } |
| } |
| } |
| return classConstraint; |
| } |
| |
| public Constraint constraintForEnumUnboxing( |
| DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) { |
| return useRegistry.computeConstraint(method.asProgramMethod(appView)); |
| } |
| |
| public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) { |
| if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) { |
| return; |
| } |
| assert clazz.isEnum(); |
| EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues(); |
| if (getEnumUnboxingCandidateOrNull(clazz.type) != null) { |
| staticFieldValuesMap.put(clazz.type, enumStaticFieldValues); |
| } |
| } |
| |
| private class EnumAccessibilityUseRegistry extends UseRegistry { |
| |
| private ProgramMethod context; |
| private Constraint constraint; |
| |
| public EnumAccessibilityUseRegistry(DexItemFactory factory) { |
| super(factory); |
| } |
| |
| public Constraint computeConstraint(ProgramMethod method) { |
| constraint = Constraint.ALWAYS; |
| context = method; |
| method.registerCodeReferences(this); |
| return constraint; |
| } |
| |
| public Constraint deriveConstraint(DexType targetHolder, AccessFlags<?> flags) { |
| DexProgramClass contextHolder = context.getHolder(); |
| if (targetHolder == contextHolder.type) { |
| return Constraint.ALWAYS; |
| } |
| if (flags.isPublic()) { |
| return Constraint.ALWAYS; |
| } |
| if (flags.isPrivate()) { |
| // Enum unboxing is currently happening only cf to dex, and no class should be in a nest |
| // at this point. If that is the case, we just don't unbox the enum, or we would need to |
| // support Constraint.SAMENEST in the enum unboxer. |
| assert !contextHolder.isInANest(); |
| // Only accesses within the enum are allowed since all enum methods and fields will be |
| // moved to the same class, and the enum itself becomes an integer, which is |
| // accessible everywhere. |
| return Constraint.NEVER; |
| } |
| assert flags.isProtected() || flags.isPackagePrivate(); |
| // Protected is in practice equivalent to package private in this analysis since we are |
| // accessing the member from an enum context where subclassing is limited. |
| // At this point we don't support unboxing enums with subclasses, so we assume either |
| // same package access, or we just don't unbox. |
| // The only protected methods in java.lang.Enum are clone, finalize and the constructor. |
| // Besides calls to the constructor in the instance initializer, Enums with calls to such |
| // methods cannot be unboxed. |
| return targetHolder.isSamePackage(contextHolder.type) ? Constraint.PACKAGE : Constraint.NEVER; |
| } |
| |
| @Override |
| public void registerTypeReference(DexType type) { |
| if (type.isArrayType()) { |
| registerTypeReference(type.toBaseType(factory)); |
| return; |
| } |
| |
| if (type.isPrimitiveType()) { |
| return; |
| } |
| |
| DexClass definition = appView.definitionFor(type); |
| if (definition == null) { |
| constraint = Constraint.NEVER; |
| return; |
| } |
| constraint = constraint.meet(deriveConstraint(type, definition.accessFlags)); |
| } |
| |
| @Override |
| public void registerInitClass(DexType type) { |
| registerTypeReference(type); |
| } |
| |
| @Override |
| public void registerInstanceOf(DexType type) { |
| registerTypeReference(type); |
| } |
| |
| @Override |
| public void registerNewInstance(DexType type) { |
| registerTypeReference(type); |
| } |
| |
| @Override |
| public void registerInvokeVirtual(DexMethod method) { |
| registerVirtualInvoke(method, false); |
| } |
| |
| @Override |
| public void registerInvokeInterface(DexMethod method) { |
| registerVirtualInvoke(method, true); |
| } |
| |
| private void registerVirtualInvoke(DexMethod method, boolean isInterface) { |
| if (method.holder.isArrayType()) { |
| return; |
| } |
| // Perform resolution and derive unboxing constraints based on the accessibility of the |
| // resolution result. |
| ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, isInterface); |
| if (!resolutionResult.isVirtualTarget()) { |
| constraint = Constraint.NEVER; |
| return; |
| } |
| registerTarget( |
| resolutionResult.getInitialResolutionHolder(), resolutionResult.getSingleTarget()); |
| } |
| |
| private void registerTarget(DexClass initialResolutionHolder, DexEncodedMember<?, ?> target) { |
| if (target == null) { |
| // This will fail at runtime. |
| constraint = Constraint.NEVER; |
| return; |
| } |
| DexType resolvedHolder = target.getHolderType(); |
| if (initialResolutionHolder == null) { |
| constraint = Constraint.NEVER; |
| return; |
| } |
| Constraint memberConstraint = deriveConstraint(resolvedHolder, target.getAccessFlags()); |
| // We also have to take the constraint of the initial resolution holder into account. |
| Constraint classConstraint = |
| deriveConstraint(initialResolutionHolder.type, initialResolutionHolder.accessFlags); |
| Constraint instructionConstraint = memberConstraint.meet(classConstraint); |
| constraint = instructionConstraint.meet(constraint); |
| } |
| |
| @Override |
| public void registerInvokeDirect(DexMethod method) { |
| registerSingleTargetInvoke(method, DexEncodedMethod::isDirectMethod); |
| } |
| |
| @Override |
| public void registerInvokeStatic(DexMethod method) { |
| registerSingleTargetInvoke(method, DexEncodedMethod::isStatic); |
| } |
| |
| private void registerSingleTargetInvoke( |
| DexMethod method, Predicate<DexEncodedMethod> methodValidator) { |
| if (method.holder.isArrayType()) { |
| return; |
| } |
| ResolutionResult resolutionResult = |
| appView.appInfo().unsafeResolveMethodDueToDexFormat(method); |
| DexEncodedMethod target = resolutionResult.getSingleTarget(); |
| if (target == null || !methodValidator.test(target)) { |
| constraint = Constraint.NEVER; |
| return; |
| } |
| registerTarget(resolutionResult.getInitialResolutionHolder(), target); |
| } |
| |
| @Override |
| public void registerInvokeSuper(DexMethod method) { |
| // Invoke-super can only target java.lang.Enum methods since we do not unbox enums with |
| // subclasses. Calls to java.lang.Object methods would have resulted in the enum to be marked |
| // as unboxable. The methods of java.lang.Enum called are already analyzed in the enum |
| // unboxer analysis, so invoke-super is always valid. |
| assert method.holder == factory.enumType; |
| } |
| |
| @Override |
| public void registerCallSite(DexCallSite callSite) { |
| // This is reached after lambda desugaring, so this should not be a lambda call site. |
| // We do not unbox enums with invoke custom since it's not clear the accessibility |
| // constraints would be correct if the method holding the invoke custom is moved to |
| // another class. |
| assert !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod()); |
| constraint = Constraint.NEVER; |
| } |
| |
| private void registerFieldInstruction(DexField field) { |
| FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(field, context); |
| registerTarget( |
| fieldResolutionResult.getInitialResolutionHolder(), |
| fieldResolutionResult.getResolvedField()); |
| } |
| |
| @Override |
| public void registerInstanceFieldRead(DexField field) { |
| registerFieldInstruction(field); |
| } |
| |
| @Override |
| public void registerInstanceFieldWrite(DexField field) { |
| registerFieldInstruction(field); |
| } |
| |
| @Override |
| public void registerStaticFieldRead(DexField field) { |
| registerFieldInstruction(field); |
| } |
| |
| @Override |
| public void registerStaticFieldWrite(DexField field) { |
| registerFieldInstruction(field); |
| } |
| } |
| |
| private void analyzeInitializers() { |
| enumUnboxingCandidatesInfo.forEachCandidate( |
| enumClass -> { |
| boolean hasInstanceInitializer = false; |
| for (DexEncodedMethod directMethod : enumClass.directMethods()) { |
| if (directMethod.isInstanceInitializer()) { |
| hasInstanceInitializer = true; |
| if (directMethod |
| .getOptimizationInfo() |
| .getContextInsensitiveInstanceInitializerInfo() |
| .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) { |
| markEnumAsUnboxable(Reason.INVALID_INIT, enumClass); |
| break; |
| } |
| } |
| } |
| if (!hasInstanceInitializer) { |
| // This case typically happens when a programmer uses EnumSet/EnumMap without using the |
| // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work). |
| // We bail out. |
| markEnumAsUnboxable(Reason.NO_INIT, enumClass); |
| return; |
| } |
| |
| if (enumClass.classInitializationMayHaveSideEffects(appView)) { |
| enumClass.classInitializationMayHaveSideEffects(appView); |
| markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass); |
| } |
| }); |
| } |
| |
| private Reason instructionAllowEnumUnboxing( |
| Instruction instruction, IRCode code, DexProgramClass enumClass, Value enumValue) { |
| |
| // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal(). |
| if (instruction.isInvokeMethod()) { |
| InvokeMethod invokeMethod = instruction.asInvokeMethod(); |
| if (invokeMethod.getInvokedMethod().holder.isArrayType()) { |
| // The only valid methods is clone for values() to be correct. |
| if (invokeMethod.getInvokedMethod().name == factory.cloneMethodName) { |
| return Reason.ELIGIBLE; |
| } |
| return Reason.INVALID_INVOKE_ON_ARRAY; |
| } |
| DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, code.context()); |
| if (singleTarget == null) { |
| return Reason.INVALID_INVOKE; |
| } |
| DexClass dexClass = singleTarget.getHolder(); |
| if (dexClass.isProgramClass()) { |
| if (dexClass.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) { |
| if (code.method().getHolderType() == dexClass.type |
| && code.method().isClassInitializer()) { |
| // The enum instance initializer is allowed to be called only from the enum clinit. |
| return Reason.ELIGIBLE; |
| } else { |
| return Reason.INVALID_INIT; |
| } |
| } |
| // Check that the enum-value only flows into parameters whose type exactly matches the |
| // enum's type. |
| int offset = BooleanUtils.intValue(!singleTarget.getDefinition().isStatic()); |
| for (int i = 0; i < singleTarget.getReference().getParameters().size(); i++) { |
| if (invokeMethod.getArgument(offset + i) == enumValue) { |
| if (singleTarget.getReference().getParameter(i).toBaseType(factory) != enumClass.type) { |
| return Reason.GENERIC_INVOKE; |
| } |
| } |
| } |
| if (invokeMethod.isInvokeMethodWithReceiver()) { |
| Value receiver = invokeMethod.asInvokeMethodWithReceiver().getReceiver(); |
| if (receiver == enumValue && dexClass.isInterface()) { |
| return Reason.DEFAULT_METHOD_INVOKE; |
| } |
| } |
| return Reason.ELIGIBLE; |
| } |
| if (dexClass.isClasspathClass()) { |
| return Reason.INVALID_INVOKE; |
| } |
| assert dexClass.isLibraryClass(); |
| DexMethod singleTargetReference = singleTarget.getReference(); |
| if (dexClass.type != factory.enumType) { |
| // System.identityHashCode(Object) is supported for proto enums. |
| // Object#getClass without outValue and Objects.requireNonNull are supported since R8 |
| // rewrites explicit null checks to such instructions. |
| if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) { |
| return Reason.ELIGIBLE; |
| } |
| if (singleTargetReference == factory.stringMembers.valueOf) { |
| addRequiredNameData(enumClass.type); |
| return Reason.ELIGIBLE; |
| } |
| if (singleTargetReference == factory.objectMembers.getClass |
| && (!invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers())) { |
| // This is a hidden null check. |
| return Reason.ELIGIBLE; |
| } |
| if (singleTargetReference == factory.objectsMethods.requireNonNull |
| || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) { |
| return Reason.ELIGIBLE; |
| } |
| return Reason.UNSUPPORTED_LIBRARY_CALL; |
| } |
| // TODO(b/147860220): EnumSet and EnumMap may be interesting to model. |
| if (singleTargetReference == factory.enumMembers.compareTo) { |
| return Reason.ELIGIBLE; |
| } else if (singleTargetReference == factory.enumMembers.equals) { |
| return Reason.ELIGIBLE; |
| } else if (singleTargetReference == factory.enumMembers.nameMethod |
| || singleTargetReference == factory.enumMembers.toString) { |
| assert invokeMethod.asInvokeMethodWithReceiver().getReceiver() == enumValue; |
| addRequiredNameData(enumClass.type); |
| return Reason.ELIGIBLE; |
| } else if (singleTargetReference == factory.enumMembers.ordinalMethod) { |
| return Reason.ELIGIBLE; |
| } else if (singleTargetReference == factory.enumMembers.hashCode) { |
| return Reason.ELIGIBLE; |
| } else if (singleTargetReference == factory.enumMembers.constructor) { |
| // Enum constructor call is allowed only if called from an enum initializer. |
| if (code.method().isInstanceInitializer() |
| && code.method().getHolderType() == enumClass.type) { |
| return Reason.ELIGIBLE; |
| } |
| } |
| return Reason.UNSUPPORTED_LIBRARY_CALL; |
| } |
| |
| // A field put is valid only if the field is not on an enum, and the field type and the valuePut |
| // have identical enum type. |
| if (instruction.isFieldPut()) { |
| FieldInstruction fieldInstruction = instruction.asFieldInstruction(); |
| DexEncodedField field = |
| appView.appInfo().resolveField(fieldInstruction.getField()).getResolvedField(); |
| if (field == null) { |
| return Reason.INVALID_FIELD_PUT; |
| } |
| DexProgramClass dexClass = |
| appView.programDefinitionFor(field.getHolderType(), code.context()); |
| if (dexClass == null) { |
| return Reason.INVALID_FIELD_PUT; |
| } |
| if (fieldInstruction.isInstancePut() |
| && fieldInstruction.asInstancePut().object() == enumValue) { |
| return Reason.ELIGIBLE; |
| } |
| // The put value has to be of the field type. |
| if (field.field.type.toBaseType(factory) != enumClass.type) { |
| return Reason.TYPE_MISMATCH_FIELD_PUT; |
| } |
| return Reason.ELIGIBLE; |
| } |
| |
| if (instruction.isInstanceGet()) { |
| InstanceGet instanceGet = instruction.asInstanceGet(); |
| assert instanceGet.getField().holder == enumClass.type; |
| DexField field = instanceGet.getField(); |
| enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass.type, field); |
| return Reason.ELIGIBLE; |
| } |
| |
| // An If using enum as inValue is valid if it matches e == null |
| // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A). |
| if (instruction.isIf()) { |
| If anIf = instruction.asIf(); |
| assert (anIf.getType() == If.Type.EQ || anIf.getType() == If.Type.NE) |
| : "Comparing a reference with " + anIf.getType().toString(); |
| // e == null. |
| if (anIf.isZeroTest()) { |
| return Reason.ELIGIBLE; |
| } |
| // e == MyEnum.X |
| TypeElement leftType = anIf.lhs().getType(); |
| TypeElement rightType = anIf.rhs().getType(); |
| if (leftType.equalUpToNullability(rightType)) { |
| assert leftType.isClassType(); |
| assert leftType.asClassType().getClassType() == enumClass.type; |
| return Reason.ELIGIBLE; |
| } |
| return Reason.INVALID_IF_TYPES; |
| } |
| |
| if (instruction.isCheckCast()) { |
| if (allowCheckCast(instruction.asCheckCast())) { |
| return Reason.ELIGIBLE; |
| } |
| return Reason.DOWN_CAST; |
| } |
| |
| if (instruction.isArrayLength()) { |
| // MyEnum[] array = ...; array.length; is valid. |
| return Reason.ELIGIBLE; |
| } |
| |
| if (instruction.isArrayGet()) { |
| // MyEnum[] array = ...; array[0]; is valid. |
| return Reason.ELIGIBLE; |
| } |
| |
| if (instruction.isArrayPut()) { |
| // MyEnum[] array; array[0] = MyEnum.A; is valid. |
| // MyEnum[][] array2d; MyEnum[] array; array2d[0] = array; is valid. |
| // MyEnum[]^N array; MyEnum[]^(N-1) element; array[0] = element; is valid. |
| // We need to prove that the value to put in and the array have correct types. |
| ArrayPut arrayPut = instruction.asArrayPut(); |
| assert arrayPut.getMemberType() == MemberType.OBJECT; |
| TypeElement arrayType = arrayPut.array().getType(); |
| assert arrayType.isArrayType(); |
| assert arrayType.asArrayType().getBaseType().isClassType(); |
| ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType(); |
| TypeElement valueBaseType = arrayPut.value().getType(); |
| if (valueBaseType.isArrayType()) { |
| assert valueBaseType.asArrayType().getBaseType().isClassType(); |
| assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1; |
| valueBaseType = valueBaseType.asArrayType().getBaseType(); |
| } |
| if (arrayBaseType.equalUpToNullability(valueBaseType) |
| && arrayBaseType.getClassType() == enumClass.type) { |
| return Reason.ELIGIBLE; |
| } |
| return Reason.INVALID_ARRAY_PUT; |
| } |
| |
| if (instruction.isAssume()) { |
| Value outValue = instruction.outValue(); |
| return validateEnumUsages(code, outValue, enumClass); |
| } |
| |
| // Return is used for valueOf methods. |
| if (instruction.isReturn()) { |
| DexType returnType = code.method().method.proto.returnType; |
| if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) { |
| return Reason.IMPLICIT_UP_CAST_IN_RETURN; |
| } |
| return Reason.ELIGIBLE; |
| } |
| |
| return Reason.OTHER_UNSUPPORTED_INSTRUCTION; |
| } |
| |
| private void reportEnumsAnalysis() { |
| assert debugLogEnabled; |
| Reporter reporter = appView.options().reporter; |
| Set<DexType> candidates = enumUnboxingCandidatesInfo.candidates(); |
| reporter.info( |
| new StringDiagnostic( |
| "Unboxed enums (Unboxing succeeded " |
| + candidates.size() |
| + "): " |
| + Arrays.toString(candidates.toArray()))); |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n"); |
| for (DexType enumType : debugLogs.keySet()) { |
| sb.append("- ") |
| .append(enumType) |
| .append(": ") |
| .append(debugLogs.get(enumType).toString()) |
| .append('\n'); |
| } |
| reporter.info(new StringDiagnostic(sb.toString())); |
| } |
| |
| void reportFailure(DexType enumType, Reason reason) { |
| if (debugLogEnabled) { |
| debugLogs.put(enumType, reason); |
| } |
| } |
| |
| public Set<Phi> rewriteCode(IRCode code) { |
| // This has no effect during primary processing since the enumUnboxerRewriter is set |
| // in between primary and post processing. |
| if (enumUnboxerRewriter != null) { |
| return enumUnboxerRewriter.rewriteCode(code); |
| } |
| return Sets.newIdentityHashSet(); |
| } |
| |
| public void synthesizeUtilityMethods(IRConverter converter, ExecutorService executorService) |
| throws ExecutionException { |
| if (enumUnboxerRewriter != null) { |
| enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService); |
| } |
| } |
| |
| public enum Reason { |
| ELIGIBLE, |
| ACCESSIBILITY, |
| ANNOTATION, |
| PINNED, |
| DOWN_CAST, |
| SUBTYPES, |
| INTERFACE, |
| MANY_INSTANCE_FIELDS, |
| GENERIC_INVOKE, |
| DEFAULT_METHOD_INVOKE, |
| UNEXPECTED_STATIC_FIELD, |
| UNRESOLVABLE_FIELD, |
| CONST_CLASS, |
| INVALID_PHI, |
| NO_INIT, |
| INVALID_INIT, |
| INVALID_CLINIT, |
| INVALID_INVOKE, |
| INVALID_INVOKE_ON_ARRAY, |
| IMPLICIT_UP_CAST_IN_RETURN, |
| VALUE_OF_INVOKE, |
| VALUES_INVOKE, |
| COMPARE_TO_INVOKE, |
| UNSUPPORTED_LIBRARY_CALL, |
| MISSING_INSTANCE_FIELD_DATA, |
| INVALID_FIELD_READ, |
| INVALID_FIELD_PUT, |
| INVALID_ARRAY_PUT, |
| FIELD_PUT_ON_ENUM, |
| TYPE_MISMATCH_FIELD_PUT, |
| INVALID_IF_TYPES, |
| DYNAMIC_TYPE, |
| ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, |
| OTHER_UNSUPPORTED_INSTRUCTION; |
| } |
| } |