| // Copyright (c) 2018, 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; |
| |
| import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; |
| |
| import com.android.tools.r8.features.ClassToFeatureSplitMap; |
| import com.android.tools.r8.graph.AccessControl; |
| 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.DexType; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.analysis.type.TypeAnalysis; |
| import com.android.tools.r8.ir.analysis.type.TypeElement; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.BasicBlockIterator; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.InitClass; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.InvokeMethod; |
| import com.android.tools.r8.ir.code.InvokeStatic; |
| import com.android.tools.r8.ir.code.InvokeVirtual; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.google.common.collect.Sets; |
| import java.util.Set; |
| import java.util.function.BiConsumer; |
| |
| public class ReflectionOptimizer { |
| |
| // Rewrite getClass() to const-class if the type of the given instance is effectively final. |
| // Rewrite forName() to const-class if the type is resolvable, accessible and already initialized. |
| public static void rewriteGetClassOrForNameToConstClass( |
| AppView<AppInfoWithLiveness> appView, IRCode code) { |
| if (!appView.appInfo().canUseConstClassInstructions(appView.options())) { |
| return; |
| } |
| Set<Value> affectedValues = Sets.newIdentityHashSet(); |
| ProgramMethod context = code.context(); |
| BasicBlockIterator blockIterator = code.listIterator(); |
| while (blockIterator.hasNext()) { |
| BasicBlock block = blockIterator.next(); |
| InstructionListIterator it = block.listIterator(code); |
| while (it.hasNext()) { |
| InvokeMethod invoke = it.nextUntil(x -> x.isInvokeStatic() || x.isInvokeVirtual()); |
| if (invoke == null) { |
| continue; |
| } |
| |
| if (invoke.isInvokeStatic()) { |
| applyTypeForClassForNameTo( |
| appView, |
| context, |
| invoke.asInvokeStatic(), |
| rewriteSingleGetClassOrForNameToConstClass( |
| appView, code, blockIterator, it, invoke, affectedValues)); |
| } else { |
| applyTypeForGetClassTo( |
| appView, |
| context, |
| invoke.asInvokeVirtual(), |
| rewriteSingleGetClassOrForNameToConstClass( |
| appView, code, blockIterator, it, invoke, affectedValues)); |
| } |
| } |
| } |
| // Newly introduced const-class is not null, and thus propagate that information. |
| if (!affectedValues.isEmpty()) { |
| new TypeAnalysis(appView).narrowing(affectedValues); |
| } |
| assert code.isConsistentSSA(appView); |
| } |
| |
| private static BiConsumer<DexType, DexClass> rewriteSingleGetClassOrForNameToConstClass( |
| AppView<AppInfoWithLiveness> appView, |
| IRCode code, |
| BasicBlockIterator blockIterator, |
| InstructionListIterator instructionIterator, |
| InvokeMethod invoke, |
| Set<Value> affectedValues) { |
| return (type, baseClass) -> { |
| InitClass initClass = null; |
| if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) { |
| // Bail-out if the optimization could increase the size of the main dex. |
| if (baseClass.isProgramClass() |
| && !appView |
| .appInfo() |
| .getMainDexInfo() |
| .canRebindReference( |
| code.context(), baseClass.getType(), appView.getSyntheticItems())) { |
| return; |
| } |
| |
| // We need to initialize the type if it may have observable side effects. |
| if (type.isClassType() |
| && baseClass.classInitializationMayHaveSideEffectsInContext(appView, code.context())) { |
| if (!baseClass.isProgramClass() || !appView.canUseInitClass()) { |
| // No way to trigger the class initialization of the given class without |
| // Class.forName(), so skip. |
| return; |
| } |
| |
| initClass = |
| InitClass.builder() |
| .setFreshOutValue(code, TypeElement.getInt()) |
| .setType(type) |
| .setPosition(invoke) |
| .build(); |
| } |
| } |
| |
| // If there are no users of the const-class then simply remove the instruction. |
| if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) { |
| if (initClass != null) { |
| instructionIterator.replaceCurrentInstruction(initClass); |
| } else { |
| instructionIterator.removeOrReplaceByDebugLocalRead(); |
| } |
| return; |
| } |
| |
| // Otherwise insert a const-class instruction. |
| BasicBlock block = invoke.getBlock(); |
| affectedValues.addAll(invoke.outValue().affectedValues()); |
| instructionIterator.replaceCurrentInstructionWithConstClass( |
| appView, code, type, invoke.getLocalInfo()); |
| |
| if (initClass != null) { |
| if (block.hasCatchHandlers()) { |
| instructionIterator |
| .splitCopyCatchHandlers(code, blockIterator, appView.options()) |
| .listIterator(code) |
| .add(initClass); |
| } else { |
| instructionIterator.add(initClass); |
| } |
| } |
| |
| if (appView.options().isGeneratingClassFiles()) { |
| code.method() |
| .upgradeClassFileVersion( |
| appView.options().requiredCfVersionForConstClassInstructions()); |
| } |
| }; |
| } |
| |
| private static void applyTypeForGetClassTo( |
| AppView<AppInfoWithLiveness> appView, |
| ProgramMethod context, |
| InvokeVirtual invoke, |
| BiConsumer<DexType, ? super DexClass> consumer) { |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| DexMethod invokedMethod = invoke.getInvokedMethod(); |
| // Class<?> Object#getClass() is final and cannot be overridden. |
| if (invokedMethod != dexItemFactory.objectMembers.getClass) { |
| return; |
| } |
| Value in = invoke.getReceiver(); |
| if (in.hasLocalInfo()) { |
| return; |
| } |
| TypeElement inType = in.getType(); |
| // Check the receiver is either class type or array type. Also make sure it is not |
| // nullable. |
| if (!(inType.isClassType() || inType.isArrayType()) |
| || inType.isNullable()) { |
| return; |
| } |
| DexType type = |
| inType.isClassType() |
| ? inType.asClassType().getClassType() |
| : inType.asArrayType().toDexType(dexItemFactory); |
| DexType baseType = type.toBaseType(dexItemFactory); |
| // Make sure base type is a class type. |
| if (!baseType.isClassType()) { |
| return; |
| } |
| // Only consider program class, e.g., platform can introduce subtypes in different |
| // versions. |
| DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(baseType)); |
| if (clazz == null) { |
| return; |
| } |
| // Only consider effectively final class. Exception: new Base().getClass(). |
| if (!clazz.isEffectivelyFinal(appView) |
| && (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) { |
| return; |
| } |
| // Make sure the target (base) type is visible. |
| ConstraintWithTarget constraints = |
| ConstraintWithTarget.classIsVisible(context, baseType, appView); |
| if (constraints == ConstraintWithTarget.NEVER) { |
| return; |
| } |
| |
| consumer.accept(type, clazz); |
| } |
| |
| private static void applyTypeForClassForNameTo( |
| AppView<AppInfoWithLiveness> appView, |
| ProgramMethod context, |
| InvokeStatic invoke, |
| BiConsumer<DexType, ? super DexClass> consumer) { |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| DexMethod invokedMethod = invoke.getInvokedMethod(); |
| // Class<?> Class#forName(String) is final and cannot be overridden. |
| if (invokedMethod != dexItemFactory.classMethods.forName) { |
| return; |
| } |
| assert invoke.arguments().size() == 1; |
| Value in = invoke.getArgument(0).getAliasedValue(); |
| // Only consider const-string input without locals. |
| if (in.hasLocalInfo() || in.isPhi()) { |
| return; |
| } |
| // Also, check if the result of forName() is updatable via locals. |
| if (invoke.hasOutValue() && invoke.outValue().hasLocalInfo()) { |
| return; |
| } |
| DexType type = null; |
| if (in.definition.isDexItemBasedConstString()) { |
| if (in.definition.asDexItemBasedConstString().getItem().isDexType()) { |
| type = in.definition.asDexItemBasedConstString().getItem().asDexType(); |
| } |
| } else if (in.definition.isConstString()) { |
| String name = in.definition.asConstString().getValue().toString(); |
| // Convert the name into descriptor if the given name is a valid java type. |
| String descriptor = DescriptorUtils.javaTypeToDescriptorIfValidJavaType(name); |
| // Otherwise, it may be an array's fully qualified name from Class<?>#getName(). |
| if (descriptor == null && name.startsWith("[") && name.endsWith(";")) { |
| // E.g., [Lx.y.Z; -> [Lx/y/Z; |
| descriptor = name.replace( |
| DescriptorUtils.JAVA_PACKAGE_SEPARATOR, |
| DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR); |
| } |
| if (descriptor == null |
| || descriptor.indexOf(DescriptorUtils.JAVA_PACKAGE_SEPARATOR) > 0) { |
| return; |
| } |
| type = dexItemFactory.createType(descriptor); |
| // Check if the given name refers to a reference type. |
| if (!type.isReferenceType()) { |
| return; |
| } |
| } else { |
| // Bail out for non-deterministic input to Class<?>#forName(name). |
| return; |
| } |
| if (type == null) { |
| return; |
| } |
| // Make sure the (base) type is resolvable. |
| DexType baseType = type.toBaseType(dexItemFactory); |
| DexClass baseClass = appView.appInfo().definitionForWithoutExistenceAssert(baseType); |
| if (baseClass == null || !baseClass.isResolvable(appView)) { |
| return; |
| } |
| |
| // Make sure the (base) type is visible. |
| ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap(); |
| if (AccessControl.isClassAccessible( |
| baseClass, context, classToFeatureSplitMap, appView.getSyntheticItems()) |
| .isPossiblyFalse()) { |
| return; |
| } |
| |
| // If the type is guaranteed to be visible, it must be in the same feature as the current method |
| // or in the base. |
| assert !baseClass.isProgramClass() |
| || classToFeatureSplitMap.isInBaseOrSameFeatureAs( |
| baseClass.asProgramClass(), context, appView.getSyntheticItems()); |
| |
| consumer.accept(type, baseClass); |
| } |
| } |