blob: edfa9345381f206b3c303c1527b5191ee50e989d [file] [log] [blame]
// Copyright (c) 2017, 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.desugar;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
// Represents the lambda descriptor inferred from calls site.
public final class LambdaDescriptor {
private static final int LAMBDA_ALT_SERIALIZABLE = 1;
private static final int LAMBDA_ALT_HAS_EXTRA_INTERFACES = 2;
private static final int LAMBDA_ALT_HAS_BRIDGES = 4;
private static final int LAMBDA_ALT_MASK = LAMBDA_ALT_SERIALIZABLE
| LAMBDA_ALT_HAS_EXTRA_INTERFACES | LAMBDA_ALT_HAS_BRIDGES;
static final LambdaDescriptor MATCH_FAILED = new LambdaDescriptor();
final String uniqueId;
final DexMethod mainMethod;
public final DexString name;
final DexProto erasedProto;
final DexProto enforcedProto;
public final DexMethodHandle implHandle;
public final List<DexType> interfaces = new ArrayList<>();
final Set<DexProto> bridges = Sets.newIdentityHashSet();
public final DexTypeList captures;
// Used for accessibility analysis and few assertions only.
private final MethodAccessFlags targetAccessFlags;
private final DexType targetHolder;
private LambdaDescriptor() {
uniqueId = null;
name = null;
erasedProto = null;
enforcedProto = null;
implHandle = null;
captures = null;
targetAccessFlags = null;
targetHolder = null;
mainMethod = null;
}
public DexMethod getMainMethod() {
return mainMethod;
}
private LambdaDescriptor(
AppInfoWithClassHierarchy appInfo,
ProgramMethod context,
DexCallSite callSite,
DexString name,
DexProto erasedProto,
DexProto enforcedProto,
DexMethodHandle implHandle,
DexType mainInterface,
DexTypeList captures) {
assert appInfo != null;
assert callSite != null;
assert name != null;
assert erasedProto != null;
assert enforcedProto != null;
assert implHandle != null;
assert mainInterface != null;
assert captures != null;
this.mainMethod = appInfo.dexItemFactory().createMethod(mainInterface, erasedProto, name);
this.uniqueId = callSite.getHash();
this.name = name;
this.erasedProto = erasedProto;
this.enforcedProto = enforcedProto;
this.implHandle = implHandle;
this.captures = captures;
this.interfaces.add(mainInterface);
DexEncodedMethod targetMethod = context == null ? null : lookupTargetMethod(appInfo, context);
if (targetMethod != null) {
targetAccessFlags = targetMethod.accessFlags.copy();
targetHolder = targetMethod.getHolderType();
} else {
targetAccessFlags = null;
targetHolder = null;
}
}
final DexType getImplReceiverType() {
// The receiver of instance impl-method is captured as the first captured
// value or should be the first argument of the enforced method signature.
DexType[] params = enforcedProto.parameters.values;
DexType[] captures = this.captures.values;
assert captures.length > 0 || params.length > 0;
return captures.length > 0 ? captures[0] : params[0];
}
private DexEncodedMethod lookupTargetMethod(
AppInfoWithClassHierarchy appInfo, ProgramMethod context) {
assert context != null;
// Find the lambda's impl-method target.
DexMethod method = implHandle.asMethod();
switch (implHandle.type) {
case INVOKE_DIRECT:
case INVOKE_INSTANCE: {
DexEncodedMethod target =
appInfo
.resolveMethodOn(getImplReceiverType(), method, implHandle.isInterface)
.getSingleTarget();
if (target == null) {
target = appInfo.lookupDirectTarget(method, context);
}
assert target == null
|| (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
|| (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
|| (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
return target;
}
case INVOKE_STATIC: {
DexEncodedMethod target = appInfo.lookupStaticTarget(method, context);
assert target == null || target.accessFlags.isStatic();
return target;
}
case INVOKE_CONSTRUCTOR: {
DexEncodedMethod target = appInfo.lookupDirectTarget(method, context);
assert target == null || target.accessFlags.isConstructor();
return target;
}
case INVOKE_INTERFACE: {
DexEncodedMethod target =
appInfo.resolveMethodOnInterface(getImplReceiverType(), method).getSingleTarget();
assert target == null || isInstanceMethod(target);
return target;
}
default:
throw new Unreachable("Unexpected method handle kind in " + implHandle);
}
}
private boolean isInstanceMethod(DexEncodedMethod encodedMethod) {
assert encodedMethod != null;
return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.isStatic();
}
private boolean isPrivateInstanceMethod(DexEncodedMethod encodedMethod) {
assert encodedMethod != null;
return encodedMethod.isPrivateMethod() && isInstanceMethod(encodedMethod);
}
private boolean isPublicizedInstanceMethod(DexEncodedMethod encodedMethod) {
assert encodedMethod != null;
return encodedMethod.isPublicized() && isInstanceMethod(encodedMethod);
}
public final boolean verifyTargetFoundInClass(DexType type) {
return targetHolder == type;
}
/** If the lambda delegates to lambda$ method. */
public boolean delegatesToLambdaImplMethod(DexItemFactory factory) {
return implHandle.asMethod().getName().startsWith(factory.javacLambdaMethodPrefix);
}
/** Is a stateless lambda, i.e. lambda does not capture any values */
final boolean isStateless() {
return captures.isEmpty();
}
/** Checks if call site needs a accessor when referenced from `accessedFrom`. */
boolean needsAccessor(ProgramMethod accessedFrom) {
if (implHandle.type.isInvokeInterface()) {
// Interface methods must be public.
return false;
}
boolean staticTarget = implHandle.type.isInvokeStatic();
boolean instanceTarget = implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
boolean initTarget = implHandle.type.isInvokeConstructor();
assert instanceTarget || staticTarget || initTarget;
assert !implHandle.type.isInvokeDirect()
|| (targetAccessFlags.isPrivate()
&& !targetAccessFlags.isConstructor()
&& !targetAccessFlags.isStatic());
if (targetAccessFlags == null) {
// The target cannot be a private method, since otherwise it
// should have been found.
if (staticTarget || initTarget) {
// Create accessor only in case it is accessed from other
// package, since otherwise it can be called directly.
// NOTE: This case is different from regular instance method case
// because the method being called must be present in method holder,
// and not in one from its supertypes.
boolean accessedFromSamePackage =
accessedFrom
.getHolderType()
.getPackageDescriptor()
.equals(implHandle.asMethod().holder.getPackageDescriptor());
return !accessedFromSamePackage;
}
// Since instance method was not found, always generate an accessor
// since it may be a protected method located in another package.
return true;
}
MethodAccessFlags flags = targetAccessFlags;
// Private methods always need accessors.
if (flags.isPrivate()) {
return true;
}
if (flags.isPublic()) {
return false;
}
boolean accessedFromSamePackage =
accessedFrom
.getHolderType()
.getPackageDescriptor()
.equals(targetHolder.getPackageDescriptor());
assert flags.isProtected() || accessedFromSamePackage;
return flags.isProtected() && !accessedFromSamePackage;
}
/**
* Matches call site for lambda metafactory invocation pattern and returns extracted match
* information, or null if match failed.
*/
public static LambdaDescriptor tryInfer(
DexCallSite callSite, AppInfoWithClassHierarchy appInfo, ProgramMethod context) {
LambdaDescriptor descriptor = infer(callSite, appInfo, context);
return descriptor == MATCH_FAILED ? null : descriptor;
}
public static boolean isLambdaMetafactoryMethod(
DexCallSite callSite, DexDefinitionSupplier definitions) {
return callSite.bootstrapMethod.type.isInvokeStatic()
&& definitions
.dexItemFactory()
.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
}
/**
* Matches call site for lambda metafactory invocation pattern and returns extracted match
* information, or MATCH_FAILED if match failed.
*/
static LambdaDescriptor infer(
DexCallSite callSite, AppInfoWithClassHierarchy appInfo, ProgramMethod context) {
if (!isLambdaMetafactoryMethod(callSite, appInfo)) {
return LambdaDescriptor.MATCH_FAILED;
}
DexItemFactory factory = appInfo.dexItemFactory();
DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
// 'Method name' operand of the invoke-custom instruction represents
// the name of the functional interface main method.
DexString funcMethodName = callSite.methodName;
// Signature of main functional interface method.
DexValue.DexValueMethodType funcErasedSignature =
getBootstrapArgument(callSite.bootstrapArgs, 0, DexValue.DexValueMethodType.class);
// Method handle of the implementation method.
DexMethodHandle lambdaImplMethodHandle =
getBootstrapArgument(callSite.bootstrapArgs, 1, DexValue.DexValueMethodHandle.class).value;
// Even though there are some limitations on which method handle kinds are
// allowed for lambda impl-methods, there is no way to detect unsupported
// handle kinds after they are transformed into DEX method handle.
// Signature to be enforced on main method.
DexValue.DexValueMethodType funcEnforcedSignature =
getBootstrapArgument(callSite.bootstrapArgs, 2, DexValue.DexValueMethodType.class);
if (!isEnforcedSignatureValid(
factory, funcEnforcedSignature.value, funcErasedSignature.value)) {
throw new Unreachable(
"Enforced and erased signatures are inconsistent in " + callSite.toString());
}
// 'Method type' of the invoke-custom instruction represents the signature
// of the lambda method factory.
DexProto lambdaFactoryProto = callSite.methodProto;
// Main functional interface is the return type of the lambda factory method.
DexType mainFuncInterface = lambdaFactoryProto.returnType;
// Lambda captures are represented as parameters of the lambda factory method.
DexTypeList captures = lambdaFactoryProto.parameters;
// Create a match.
LambdaDescriptor match =
new LambdaDescriptor(
appInfo,
context,
callSite,
funcMethodName,
funcErasedSignature.value,
funcEnforcedSignature.value,
lambdaImplMethodHandle,
mainFuncInterface,
captures);
if (bootstrapMethod == factory.metafactoryMethod) {
if (callSite.bootstrapArgs.size() != 3) {
throw new Unreachable(
"Unexpected number of metafactory method arguments in " + callSite.toString());
}
} else {
extractAltMetafactory(
factory,
callSite.bootstrapArgs,
interfaceType -> {
if (!match.interfaces.contains(interfaceType)) {
match.interfaces.add(interfaceType);
}
},
match.bridges::add);
}
return match;
}
private static void extractAltMetafactory(
DexItemFactory dexItemFactory,
List<DexValue> bootstrapArgs,
Consumer<DexType> interfaceConsumer,
Consumer<DexProto> bridgeConsumer) {
int argIndex = 3;
int flagsArg =
getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueInt.class).value;
assert (flagsArg & ~LAMBDA_ALT_MASK) == 0;
// Load extra interfaces if any.
if ((flagsArg & LAMBDA_ALT_HAS_EXTRA_INTERFACES) != 0) {
int count = getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueInt.class).value;
for (int i = 0; i < count; i++) {
DexType interfaceType =
getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueType.class).value;
interfaceConsumer.accept(interfaceType);
}
}
// If the lambda is serializable, add it.
if ((flagsArg & LAMBDA_ALT_SERIALIZABLE) != 0) {
interfaceConsumer.accept(dexItemFactory.serializableType);
}
// Load bridges if any.
if ((flagsArg & LAMBDA_ALT_HAS_BRIDGES) != 0) {
int count = getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueInt.class).value;
for (int i = 0; i < count; i++) {
DexProto bridgeProto =
getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueMethodType.class)
.value;
bridgeConsumer.accept(bridgeProto);
}
}
if (bootstrapArgs.size() != argIndex) {
throw new Unreachable("Unexpected number of metafactory method arguments in DexCallSite");
}
}
public static List<DexType> getInterfaces(
DexCallSite callSite, AppInfoWithClassHierarchy appInfo) {
// No need for the invocationContext to figure out only the interfaces.
LambdaDescriptor descriptor = infer(callSite, appInfo, null);
if (descriptor == LambdaDescriptor.MATCH_FAILED) {
return null;
}
return descriptor.interfaces;
}
@SuppressWarnings("unchecked")
private static <T> T getBootstrapArgument(List<DexValue> bootstrapArgs, int i, Class<T> clazz) {
if (bootstrapArgs.size() < i) {
throw new Unreachable(
"Expected to find at least " + i + " bootstrap arguments in DexCallSite");
}
DexValue value = bootstrapArgs.get(i);
if (!clazz.isAssignableFrom(value.getClass())) {
throw new Unreachable("Unexpected type of bootstrap arguments #" + i + " in DexCallSite");
}
return (T) value;
}
private static boolean isEnforcedSignatureValid(
DexItemFactory factory, DexProto enforced, DexProto erased) {
if (!isSameOrDerived(factory, enforced.returnType, erased.returnType)) {
return false;
}
DexType[] enforcedValues = enforced.parameters.values;
DexType[] erasedValues = erased.parameters.values;
int count = enforcedValues.length;
if (count != erasedValues.length) {
return false;
}
for (int i = 0; i < count; i++) {
if (!isSameOrDerived(factory, enforcedValues[i], erasedValues[i])) {
return false;
}
}
return true;
}
// Checks if the types are the same OR both types are reference types and
// `subType` is derived from `b`. Note that in the latter case we only check if
// both types are class types, for the reasons mentioned in isSameOrAdaptableTo(...).
static boolean isSameOrDerived(
DexItemFactory factory, DexType subType, DexType superType) {
if (subType == superType || (subType.isClassType() && superType.isClassType())) {
return true;
}
if (subType.isArrayType()) {
if (superType.isArrayType()) {
// X[] -> Y[].
return isSameOrDerived(factory,
subType.toArrayElementType(factory), superType.toArrayElementType(factory));
}
return superType == factory.objectType; // T[] -> Object.
}
return false;
}
}