blob: 683cf92d31965fee961bc3f72faf28a58b0616e4 [file] [log] [blame]
// 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();
}
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);
}
}