blob: ac5addd1576c819f9f65acc14e128a68140e36b0 [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 static com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer.emptyForcefullyMovedLambdaMethodConsumer;
import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
import static com.android.tools.r8.utils.DesugarUtils.appendFullyQualifiedHolderToMethodName;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
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.DexMethodHandle;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
import com.android.tools.r8.graph.DexProgramClass;
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.DexValue.DexValueNull;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring.DesugarInvoke;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
/**
* Represents lambda class generated for a lambda descriptor in context of lambda instantiation
* point.
*
* <p>Even though call sites, and thus lambda descriptors, are canonicalized across the application,
* the context may require several lambda classes to be generated for the same lambda descriptor.
*
* <p>One reason is that we always generate a lambda class in the same package lambda instantiation
* point is located in, so if same call site is used in two classes from different packages (which
* can happen if same public method is being references via method reference expression) we generate
* separate lambda classes in those packages.
*
* <p>Another reason is that if we generate an accessor, we generate it in the class referencing the
* call site, and thus two such classes will require two separate lambda classes.
*/
public final class LambdaClass {
public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
public static final String JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
public static final String R8_LAMBDA_ACCESSOR_METHOD_PREFIX = "$r8$lambda$";
private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
final AppView<?> appView;
final LambdaInstructionDesugaring desugaring;
public final DexType type;
public LambdaDescriptor descriptor;
public final DexMethod constructor;
final DexMethod classConstructor;
private final DexMethod factoryMethod;
public final DexField lambdaField;
public final Target target;
// Considered final but is set after due to circularity in allocation.
private DexProgramClass clazz = null;
public LambdaClass(
SyntheticProgramClassBuilder builder,
AppView<?> appView,
LambdaInstructionDesugaring desugaring,
ProgramMethod accessedFrom,
LambdaDescriptor descriptor,
DesugarInvoke desugarInvoke) {
assert desugaring != null;
assert descriptor != null;
this.type = builder.getType();
this.appView = appView;
this.desugaring = desugaring;
this.descriptor = descriptor;
DexItemFactory factory = builder.getFactory();
DexProto constructorProto = factory.createProto(
factory.voidType, descriptor.captures.values);
this.constructor = factory.createMethod(type, constructorProto, factory.constructorMethodName);
this.target = createTarget(accessedFrom);
boolean statelessSingleton = isStatelessSingleton();
this.classConstructor =
statelessSingleton
? factory.createMethod(type, constructorProto, factory.classConstructorMethodName)
: null;
this.lambdaField =
statelessSingleton
? factory.createField(type, type, factory.lambdaInstanceFieldName)
: null;
this.factoryMethod =
appView.options().testing.alwaysGenerateLambdaFactoryMethods
? factory.createMethod(
type,
factory.createProto(type, descriptor.captures.values),
factory.createString("create"))
: null;
// Synthesize the program class once all fields are set.
synthesizeLambdaClass(builder, desugarInvoke);
}
public final DexProgramClass getLambdaProgramClass() {
assert clazz != null;
return clazz;
}
public DexType getType() {
return type;
}
public void setClass(DexProgramClass clazz) {
assert this.clazz == null;
assert clazz != null;
assert type == clazz.type;
this.clazz = clazz;
}
private void synthesizeLambdaClass(
SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
builder.setInterfaces(descriptor.interfaces);
synthesizeStaticFields(builder);
synthesizeInstanceFields(builder);
synthesizeDirectMethods(builder);
synthesizeVirtualMethods(builder, desugarInvoke);
}
final DexField getCaptureField(int index) {
return appView
.dexItemFactory()
.createField(
this.type,
descriptor.captures.values[index],
appView.dexItemFactory().createString("f$" + index));
}
public final boolean isStatelessSingleton() {
return appView.options().createSingletonsForStatelessLambdas && descriptor.isStateless();
}
public boolean hasFactoryMethod() {
return factoryMethod != null;
}
public DexMethod getFactoryMethod() {
assert hasFactoryMethod();
return factoryMethod;
}
// Synthesize virtual methods.
private void synthesizeVirtualMethods(
SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
DexMethod mainMethod =
appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
List<DexEncodedMethod> methods = new ArrayList<>(1 + descriptor.bridges.size());
// Synthesize main method.
methods.add(
DexEncodedMethod.syntheticBuilder()
.setMethod(mainMethod)
.setAccessFlags(
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false))
.setCode(LambdaMainMethodSourceCode.build(this, mainMethod, desugarInvoke))
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
DexMethod bridgeMethod =
appView.dexItemFactory().createMethod(type, bridgeProto, descriptor.name);
methods.add(
DexEncodedMethod.syntheticBuilder()
.setMethod(bridgeMethod)
.setAccessFlags(
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC
| Constants.ACC_FINAL
| Constants.ACC_SYNTHETIC
| Constants.ACC_BRIDGE,
false))
.setCode(LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod))
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
}
builder.setVirtualMethods(methods);
}
// Synthesize direct methods.
private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
boolean statelessSingleton = isStatelessSingleton();
List<DexEncodedMethod> methods = new ArrayList<>(statelessSingleton ? 2 : 1);
// Constructor.
MethodAccessFlags accessFlags =
MethodAccessFlags.fromSharedAccessFlags(
(statelessSingleton ? Constants.ACC_PRIVATE : Constants.ACC_PUBLIC)
| Constants.ACC_SYNTHETIC,
true);
methods.add(
DexEncodedMethod.syntheticBuilder()
.setMethod(constructor)
.setAccessFlags(accessFlags)
.setCode(LambdaConstructorSourceCode.build(this))
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
// Class constructor for stateless lambda classes.
if (statelessSingleton) {
methods.add(
DexEncodedMethod.syntheticBuilder()
.setMethod(classConstructor)
.setAccessFlags(
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true))
.setCode(LambdaClassConstructorSourceCode.build(this))
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
feedback.classInitializerMayBePostponed(methods.get(1));
}
if (hasFactoryMethod()) {
methods.add(
DexEncodedMethod.syntheticBuilder()
.setMethod(factoryMethod)
.setAccessFlags(
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_STATIC | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
.setCode(LambdaClassFactorySourceCode.build(this))
.disableAndroidApiLevelCheck()
.build());
}
builder.setDirectMethods(methods);
}
// Synthesize instance fields to represent captured values.
private void synthesizeInstanceFields(SyntheticProgramClassBuilder builder) {
DexType[] fieldTypes = descriptor.captures.values;
int fieldCount = fieldTypes.length;
List<DexEncodedField> fields = new ArrayList<>(fieldCount);
for (int i = 0; i < fieldCount; i++) {
fields.add(
DexEncodedField.syntheticBuilder()
.setField(getCaptureField(i))
.setAccessFlags(
appView.options().desugarSpecificOptions().lambdaClassFieldsFinal
? FieldAccessFlags.createPublicFinalSynthetic()
: FieldAccessFlags.createPublicSynthetic())
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
}
builder.setInstanceFields(fields);
}
// Synthesize static fields to represent singleton instance.
private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
if (isStatelessSingleton()) {
// Create instance field for stateless lambda.
assert this.lambdaField != null;
builder.setStaticFields(
Collections.singletonList(
DexEncodedField.syntheticBuilder()
.setField(this.lambdaField)
.setAccessFlags(
FieldAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC
| Constants.ACC_FINAL
| Constants.ACC_SYNTHETIC
| Constants.ACC_STATIC))
.setStaticValue(DexValueNull.NULL)
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build()));
}
}
public static int getAsmOpcodeForInvokeType(MethodHandleType type) {
switch (type) {
case INVOKE_INTERFACE:
return Opcodes.INVOKEINTERFACE;
case INVOKE_STATIC:
return Opcodes.INVOKESTATIC;
case INVOKE_DIRECT:
return Opcodes.INVOKESPECIAL;
case INVOKE_INSTANCE:
return Opcodes.INVOKEVIRTUAL;
default:
throw new Unreachable("Unexpected method handle type: " + type);
}
}
// Creates a delegation target for this particular lambda class. Note that we
// should always be able to create targets for the lambdas we support.
private Target createTarget(ProgramMethod accessedFrom) {
switch (descriptor.implHandle.type) {
case INVOKE_SUPER:
throw new Unimplemented("Method references to super methods are not yet supported");
case INVOKE_INTERFACE:
return createInterfaceMethodTarget(accessedFrom);
case INVOKE_CONSTRUCTOR:
return createConstructorTarget(accessedFrom);
case INVOKE_STATIC:
return canAccessModifyLambdaImplMethod()
? createLambdaImplMethodTarget(accessedFrom)
: createStaticMethodTarget(accessedFrom);
case INVOKE_DIRECT:
return canAccessModifyLambdaImplMethod()
? createLambdaImplMethodTarget(accessedFrom)
: createInstanceMethodTarget(accessedFrom);
case INVOKE_INSTANCE:
return createInstanceMethodTarget(accessedFrom);
default:
throw new Unreachable("Unexpected method handle type in " + descriptor.implHandle);
}
}
private boolean doesNotNeedAccessor(ProgramMethod accessedFrom) {
return canAccessModifyLambdaImplMethod()
|| isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared()
|| !descriptor.needsAccessor(accessedFrom);
}
private boolean isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared() {
return appView.options().isInterfaceMethodDesugaringEnabled()
&& descriptor.implHandle.isInterface
&& (descriptor.implHandle.type.isInvokeDirect()
|| descriptor.implHandle.type.isInvokeStatic());
}
private boolean canAccessModifyLambdaImplMethod() {
MethodHandleType invokeType = descriptor.implHandle.type;
return appView.options().canAccessModifyLambdaImplementationMethods(appView)
&& !isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared()
&& (invokeType.isInvokeDirect() || invokeType.isInvokeStatic())
&& descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory())
&& !desugaring.isDirectTargetedLambdaImplementationMethod(descriptor.implHandle);
}
private Target createLambdaImplMethodTarget(ProgramMethod accessedFrom) {
DexMethodHandle implHandle = descriptor.implHandle;
assert implHandle != null;
DexMethod implMethod = implHandle.asMethod();
// Lambda$ method. We should always find it. If not found an ICCE can be expected to be thrown.
assert descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory());
assert implMethod.holder == accessedFrom.getHolderType();
assert descriptor.verifyTargetFoundInClass(accessedFrom.getHolderType());
if (implHandle.type.isInvokeStatic()) {
MethodResolutionResult resolution =
appView.appInfoForDesugaring().resolveMethodLegacy(implMethod, implHandle.isInterface);
if (resolution.isFailedResolution()) {
return new InvalidLambdaImplTarget(
implMethod,
Type.STATIC,
appView.dexItemFactory().icceType,
descriptor.implHandle.isInterface);
}
SingleResolutionResult<?> result = resolution.asSingleResolution();
assert result.getResolvedMethod().isStatic();
assert result.getResolvedHolder().isProgramClass();
return new StaticLambdaImplTarget(
new ProgramMethod(
result.getResolvedHolder().asProgramClass(), result.getResolvedMethod()),
descriptor.implHandle.isInterface);
}
assert implHandle.type.isInvokeDirect();
// If the lambda$ method is an instance-private method on an interface we convert it into a
// public static method so it is accessible.
if (appView.definitionFor(implMethod.holder).isInterface()) {
DexProto implProto = implMethod.proto;
DexType[] implParams = implProto.parameters.values;
DexType[] newParams = new DexType[implParams.length + 1];
newParams[0] = implMethod.holder;
System.arraycopy(implParams, 0, newParams, 1, implParams.length);
DexProto newProto = appView.dexItemFactory().createProto(implProto.returnType, newParams);
return new InterfaceLambdaImplTarget(
descriptor.implHandle.asMethod(),
descriptor.implHandle.isInterface,
appView.dexItemFactory().createMethod(implMethod.holder, newProto, implMethod.name),
appView);
} else {
// Otherwise we need to ensure the method can be reached publicly by virtual dispatch.
// To avoid potential conflicts on the name of the lambda method once dispatch becomes virtual
// we add the fully qualified method-holder name as suffix to the lambda-method name.
return new InstanceLambdaImplTarget(
descriptor.implHandle.asMethod(),
descriptor.implHandle.isInterface,
appView
.dexItemFactory()
.createMethod(
implMethod.holder,
implMethod.proto,
appendFullyQualifiedHolderToMethodName(implMethod, appView.dexItemFactory())),
appView);
}
}
// Create targets for instance method referenced directly without
// lambda$ methods. It may require creation of accessors in some cases.
private Target createInstanceMethodTarget(ProgramMethod accessedFrom) {
assert descriptor.implHandle.type.isInvokeInstance() ||
descriptor.implHandle.type.isInvokeDirect();
if (doesNotNeedAccessor(accessedFrom)) {
return new NoAccessorMethodTarget(
descriptor.implHandle.asMethod(),
descriptor.implHandle.type.isInvokeDirect()
? Type.DIRECT
: descriptor.implHandle.type.isInvokeStatic() ? Type.STATIC : Type.VIRTUAL,
descriptor.implHandle.isInterface);
}
// We need to generate an accessor method in `accessedFrom` class/interface
// for accessing the original instance impl-method. Note that impl-method's
// holder does not have to be the same as `accessedFrom`.
DexMethod implMethod = descriptor.implHandle.asMethod();
DexProto implProto = implMethod.proto;
DexType[] implParams = implProto.parameters.values;
// The accessor method will be static, package private, and take the
// receiver as the first argument. The receiver must be captured and
// be the first captured value in case there are more than one.
DexType[] accessorParams = new DexType[1 + implParams.length];
accessorParams[0] = descriptor.getImplReceiverType();
System.arraycopy(implParams, 0, accessorParams, 1, implParams.length);
DexProto accessorProto =
appView.dexItemFactory().createProto(implProto.returnType, accessorParams);
DexMethod accessorMethod =
appView
.dexItemFactory()
.createMethod(
accessedFrom.getHolderType(), accessorProto, generateUniqueLambdaMethodName());
return new ClassMethodWithAccessorTarget(
descriptor.implHandle.asMethod(),
descriptor.implHandle.isInterface,
descriptor.implHandle.type,
accessorMethod,
appView);
}
// Create targets for static method referenced directly without
// lambda$ methods. It may require creation of accessors in some cases.
private Target createStaticMethodTarget(ProgramMethod accessedFrom) {
assert descriptor.implHandle.type.isInvokeStatic();
if (doesNotNeedAccessor(accessedFrom)) {
return new NoAccessorMethodTarget(
descriptor.implHandle.asMethod(), Type.STATIC, descriptor.implHandle.isInterface);
}
// We need to generate an accessor method in `accessedFrom` class/interface
// for accessing the original static impl-method. The accessor method will be
// static, package private with exactly same signature and the original method.
DexMethod accessorMethod =
appView
.dexItemFactory()
.createMethod(
accessedFrom.getHolderType(),
descriptor.implHandle.asMethod().proto,
generateUniqueLambdaMethodName());
return new ClassMethodWithAccessorTarget(
descriptor.implHandle.asMethod(),
descriptor.implHandle.isInterface,
descriptor.implHandle.type,
accessorMethod,
appView);
}
// Create targets for constructor referenced directly without lambda$ methods.
// It may require creation of accessors in some cases.
private Target createConstructorTarget(ProgramMethod accessedFrom) {
DexMethodHandle implHandle = descriptor.implHandle;
assert implHandle != null;
assert implHandle.type.isInvokeConstructor();
if (doesNotNeedAccessor(accessedFrom)) {
return new NoAccessorMethodTarget(
descriptor.implHandle.asMethod(), Type.DIRECT, descriptor.implHandle.isInterface);
}
// We need to generate an accessor method in `accessedFrom` class/interface for
// instantiating the class and calling constructor on it. The accessor method will
// be static, package private with exactly same parameters as the constructor,
// and return the newly created instance.
DexMethod implMethod = implHandle.asMethod();
DexType returnType = implMethod.holder;
DexProto accessorProto =
appView.dexItemFactory().createProto(returnType, implMethod.proto.parameters.values);
DexMethod accessorMethod =
appView
.dexItemFactory()
.createMethod(
accessedFrom.getHolderType(), accessorProto, generateUniqueLambdaMethodName());
return new ClassMethodWithAccessorTarget(
descriptor.implHandle.asMethod(),
descriptor.implHandle.isInterface,
descriptor.implHandle.type,
accessorMethod,
appView);
}
// Create targets for interface methods.
private Target createInterfaceMethodTarget(ProgramMethod accessedFrom) {
assert descriptor.implHandle.type.isInvokeInterface();
assert doesNotNeedAccessor(accessedFrom);
return new NoAccessorMethodTarget(
descriptor.implHandle.asMethod(), Type.INTERFACE, descriptor.implHandle.isInterface);
}
private DexString generateUniqueLambdaMethodName() {
return appView
.dexItemFactory()
.createString(R8_LAMBDA_ACCESSOR_METHOD_PREFIX + descriptor.uniqueId);
}
// Represents information about the method lambda class need to delegate the call to. It may
// be the same method as specified in lambda descriptor or a newly synthesized accessor.
// Also provides action for ensuring accessibility of the referenced symbols.
public abstract static class Target {
final DexMethod callTarget;
final Invoke.Type invokeType;
final boolean isInterface;
private boolean hasEnsuredAccessibility;
Target(DexMethod callTarget, Type invokeType, boolean isInterface) {
assert callTarget != null;
assert invokeType != null;
this.callTarget = callTarget;
this.invokeType = invokeType;
this.isInterface = isInterface;
}
// Ensure access of the referenced symbol(s).
abstract ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer);
public final void ensureAccessibilityIfNeeded() {
ensureAccessibilityIfNeeded(emptyForcefullyMovedLambdaMethodConsumer(), emptyConsumer());
}
// Ensure access of the referenced symbol(s).
public final void ensureAccessibilityIfNeeded(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
if (!hasEnsuredAccessibility) {
ensureAccessibility(forcefullyMovedLambdaMethodConsumer, needsProcessingConsumer);
hasEnsuredAccessibility = true;
}
}
boolean isInterface() {
return isInterface;
}
}
public abstract static class D8SpecificTarget extends Target {
D8SpecificTarget(DexMethod callTarget, Type invokeType, boolean isInterface) {
super(callTarget, invokeType, isInterface);
}
}
// Used for targeting methods referenced directly without creating accessors.
public static final class NoAccessorMethodTarget extends Target {
NoAccessorMethodTarget(DexMethod method, Type invokeType, boolean isInterface) {
super(method, invokeType, isInterface);
}
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
return null;
}
}
// Used for static private lambda$ methods. Only needs access relaxation.
private static final class StaticLambdaImplTarget extends D8SpecificTarget {
final ProgramMethod target;
StaticLambdaImplTarget(ProgramMethod target, boolean isInterface) {
super(target.getReference(), Invoke.Type.STATIC, isInterface);
this.target = target;
}
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// We already found the static method to be called, just relax its accessibility.
MethodAccessFlags flags = target.getAccessFlags();
flags.unsetPrivate();
if (target.getHolder().isInterface()) {
flags.setPublic();
}
return null;
}
}
// Used for instance private lambda$ methods on interfaces which need to be converted to public
// static methods. They can't remain instance methods as they will end up on the companion class.
private static final class InterfaceLambdaImplTarget extends D8SpecificTarget {
private final AppView<?> appView;
private final DexMethod implMethod;
InterfaceLambdaImplTarget(
DexMethod implMethod, boolean isInterface, DexMethod staticMethod, AppView<?> appView) {
super(staticMethod, Type.STATIC, isInterface);
this.implMethod = implMethod;
this.appView = appView;
}
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexProgramClass implMethodHolder = appView.definitionFor(implMethod.holder).asProgramClass();
DexEncodedMethod replacement =
implMethodHolder
.getMethodCollection()
.replaceDirectMethod(
implMethod,
encodedMethod -> {
// We need to create a new static method with the same code to be able to safely
// relax its accessibility without making it virtual.
MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
newAccessFlags.setStatic();
newAccessFlags.unsetPrivate();
// Always make the method public to provide access.
newAccessFlags.setPublic();
DexEncodedMethod newMethod =
DexEncodedMethod.syntheticBuilder()
.setMethod(callTarget)
.setAccessFlags(newAccessFlags)
.setGenericSignature(encodedMethod.getGenericSignature())
.setAnnotations(encodedMethod.annotations())
.setParameterAnnotations(encodedMethod.parameterAnnotationsList)
.setCode(encodedMethod.getCode())
.setApiLevelForDefinition(encodedMethod.getApiLevelForDefinition())
.setApiLevelForCode(encodedMethod.getApiLevelForCode())
.build();
newMethod.copyMetadata(appView, encodedMethod);
forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
encodedMethod.getReference(), callTarget);
DexEncodedMethod.setDebugInfoWithFakeThisParameter(
newMethod.getCode(), callTarget.getArity(), appView);
return newMethod;
});
if (replacement != null) {
// Since we've copied the code object from an existing method, the code should already be
// processed, and thus we don't need to schedule it for processing in D8.
assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement);
if (appView.options().isDesugaredLibraryCompilation()) {
assert appView.options().isGeneratingClassFiles();
needsProcessingConsumer.accept(newMethod);
}
return newMethod;
}
// The method might already have been moved by another invoke-dynamic targeting it.
// If so, it must be defined on the holder.
ProgramMethod modified = implMethodHolder.lookupProgramMethod(callTarget);
assert modified != null;
assert modified.getDefinition().isNonPrivateVirtualMethod();
return modified;
}
}
static final class InvalidLambdaImplTarget extends Target {
final DexType exceptionType;
public InvalidLambdaImplTarget(
DexMethod callTarget, Type invokeType, DexType exceptionType, boolean isInterface) {
super(callTarget, invokeType, isInterface);
this.exceptionType = exceptionType;
}
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
return null;
}
}
// Used for instance private lambda$ methods which need to be converted to public methods.
private static final class InstanceLambdaImplTarget extends D8SpecificTarget {
private final DexMethod implMethod;
private final AppView<?> appView;
InstanceLambdaImplTarget(
DexMethod implMethod, boolean isInterface, DexMethod staticMethod, AppView<?> appView) {
super(staticMethod, Type.VIRTUAL, isInterface);
this.implMethod = implMethod;
this.appView = appView;
}
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// When compiling with whole program optimization, check that we are not inplace modifying.
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexProgramClass implMethodHolder = appView.definitionFor(implMethod.holder).asProgramClass();
DexEncodedMethod replacement =
implMethodHolder
.getMethodCollection()
.replaceDirectMethodWithVirtualMethod(
implMethod,
encodedMethod -> {
assert encodedMethod.isDirectMethod();
// We need to create a new method with the same code to be able to safely relax
// its accessibility and make it virtual.
MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
newAccessFlags.unsetPrivate();
DexEncodedMethod newMethod =
DexEncodedMethod.syntheticBuilder()
.setMethod(callTarget)
.setAccessFlags(newAccessFlags)
.setGenericSignature(encodedMethod.getGenericSignature())
.setAnnotations(encodedMethod.annotations())
.setParameterAnnotations(encodedMethod.parameterAnnotationsList)
.setCode(encodedMethod.getCode())
.setApiLevelForDefinition(encodedMethod.getApiLevelForDefinition())
.setApiLevelForCode(encodedMethod.getApiLevelForCode())
.build();
newMethod.copyMetadata(appView, encodedMethod);
forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
encodedMethod.getReference(), callTarget);
return newMethod;
});
if (replacement != null) {
// Since we've copied the code object from an existing method, the code should already be
// processed, and thus we don't need to schedule it for processing in D8.
assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement);
if (appView.options().isDesugaredLibraryCompilation()) {
assert appView.options().isGeneratingClassFiles();
needsProcessingConsumer.accept(newMethod);
}
return newMethod;
}
// The method might already have been moved by another invoke-dynamic targeting it.
// If so, it must be defined on the holder.
ProgramMethod modified = implMethodHolder.lookupProgramMethod(callTarget);
assert modified != null;
assert modified.getDefinition().isNonPrivateVirtualMethod();
return modified;
}
}
// Used for instance/static methods or constructors accessed via
// synthesized accessor method. Needs accessor method to be created.
private static class ClassMethodWithAccessorTarget extends Target {
private final AppView<?> appView;
private final DexMethod implMethod;
private final MethodHandleType type;
ClassMethodWithAccessorTarget(
DexMethod implMethod,
boolean isInterface,
MethodHandleType type,
DexMethod accessorMethod,
AppView<?> appView) {
super(accessorMethod, Invoke.Type.STATIC, isInterface);
this.appView = appView;
this.implMethod = implMethod;
this.type = type;
}
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// Create a static accessor with proper accessibility.
DexProgramClass accessorClass = appView.definitionForProgramType(callTarget.holder);
assert accessorClass != null;
// The accessor might already have been created by another invoke-dynamic targeting it.
ProgramMethod existing = accessorClass.lookupProgramMethod(callTarget);
if (existing != null) {
assert existing.getAccessFlags().isSynthetic();
assert existing.getAccessFlags().isPublic();
assert existing.getAccessFlags().isStatic();
return existing;
}
// Always make the method public to provide access when r8 minification is allowed to move
// the lambda class accessing this method to another package (-allowaccessmodification).
ProgramMethod accessorMethod =
new ProgramMethod(
accessorClass,
DexEncodedMethod.syntheticBuilder()
.setMethod(callTarget)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(
AccessorMethodSourceCode.build(
implMethod, isInterface, type, callTarget, appView))
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
accessorClass.addDirectMethod(accessorMethod.getDefinition());
if (appView.options().isDesugaredLibraryCompilation()
|| appView.options().isGeneratingDex()) {
needsProcessingConsumer.accept(accessorMethod);
}
return accessorMethod;
}
}
}