blob: 5d00719588dc61a82517caeea8f5f8e93cfff13c [file] [log] [blame]
// Copyright (c) 2021, 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.itf;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.cf.code.CfInitClass;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.cf.code.CfStaticFieldRead;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
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.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.DexValueInt;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.InvalidCode;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ThrowNullCode;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.ClasspathEmulatedInterfaceSynthesizerEventConsumer;
import com.android.tools.r8.synthesis.SyntheticClassBuilder;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.ImmutableList;
import java.util.function.Predicate;
public class InterfaceDesugaringSyntheticHelper {
// Any interface method desugared code can be version 1.7 at the most.
// Note: we always desugar both default/static (v1.8) and private (v9) when targeting api < N.
public static final CfVersion MAX_INTERFACE_DESUGARED_CF_VERSION = CfVersion.V1_7;
public static CfVersion getInterfaceDesugaredCfVersion(CfVersion existing) {
return Ordered.min(existing, MAX_INTERFACE_DESUGARED_CF_VERSION);
}
// Use InterfaceDesugaringForTesting for public accesses in tests.
static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
static final String DEFAULT_METHOD_PREFIX = "$default$";
static final String PRIVATE_METHOD_PREFIX = "$private$";
private final AppView<?> appView;
private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
public InterfaceDesugaringSyntheticHelper(AppView<?> appView) {
this.appView = appView;
this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
}
boolean isEmulatedInterface(DexType itf) {
return appView
.options()
.machineDesugaredLibrarySpecification
.getEmulatedInterfaces()
.containsKey(itf);
}
boolean isRewrittenEmulatedInterface(DexType itf) {
return appView
.options()
.machineDesugaredLibrarySpecification
.isEmulatedInterfaceRewrittenType(itf);
}
DexType getEmulatedInterface(DexType type) {
EmulatedInterfaceDescriptor interfaceDescriptor =
appView.options().machineDesugaredLibrarySpecification.getEmulatedInterfaces().get(type);
return interfaceDescriptor == null ? null : interfaceDescriptor.getRewrittenType();
}
boolean isInDesugaredLibrary(DexClass clazz) {
assert clazz.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
if (isEmulatedInterface(clazz.type)) {
return true;
}
return appView.typeRewriter.hasRewrittenType(clazz.type, appView);
}
final boolean isCompatibleDefaultMethod(DexEncodedMethod method) {
assert !method.accessFlags.isConstructor();
assert !method.accessFlags.isStatic();
if (method.accessFlags.isAbstract()) {
return false;
}
if (method.accessFlags.isNative()) {
throw new Unimplemented("Native default interface methods are not yet supported.");
}
if (!method.accessFlags.isPublic()) {
// NOTE: even though the class is allowed to have non-public interface methods
// with code, for example private methods, all such methods we are aware of are
// created by the compiler for stateful lambdas and they must be converted into
// static methods by lambda desugaring by this time.
throw new Unimplemented("Non public default interface methods are not yet supported.");
}
return true;
}
DexMethod emulatedInterfaceDispatchMethod(DerivedMethod method, DexType holder) {
assert method.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
}
DexMethod emulatedInterfaceInterfaceMethod(DerivedMethod method) {
assert method.getHolderKind() == null;
return method.getMethod();
}
public static String getCompanionClassDescriptor(String descriptor) {
return descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";";
}
// Gets the companion class for the interface `type`.
public static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
assert type.isClassType();
String descriptor = type.descriptor.toString();
String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
return factory.createSynthesizedType(ccTypeDescriptor);
}
// Checks if `type` is a companion class.
public static boolean isCompanionClassType(DexType type) {
return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
}
// Gets the interface class for a companion class `type`.
DexType getInterfaceClassType(DexType type) {
return getInterfaceClassType(type, appView.dexItemFactory());
}
// Gets the interface class for a companion class `type`.
public static DexType getInterfaceClassType(DexType type, DexItemFactory factory) {
assert isCompanionClassType(type);
String descriptor = type.descriptor.toString();
String interfaceTypeDescriptor =
descriptor.substring(0, descriptor.length() - 1 - COMPANION_CLASS_NAME_SUFFIX.length())
+ ";";
return factory.createType(interfaceTypeDescriptor);
}
// TODO(b/198273164): This should take the context class and not just a type.
DexClasspathClass ensureEmulatedInterfaceMarkerInterface(DexType type) {
return appView
.getSyntheticItems()
.ensureFixedClasspathClassFromType(
SyntheticKind.EMULATED_INTERFACE_MARKER_CLASS,
type,
appView,
SyntheticClassBuilder::setInterface,
ignored -> {});
}
DexClassAndMethod lookupMaximallySpecificIncludingSelf(
DexClass initialResolutionHolder, DexClassAndMethod method) {
assert method.getHolderType().isClassType();
if (method.getHolder().isInterface()) {
return method;
}
return appView
.appInfoForDesugaring()
.lookupMaximallySpecificMethod(initialResolutionHolder, method.getReference());
}
EmulatedDispatchMethodDescriptor getEmulatedDispatchDescriptor(
DexClass initialResolutionHolder, DexClassAndMethod method) {
if (method == null) {
return null;
}
assert initialResolutionHolder != null;
if (!requiresEmulatedDispatch(method)) {
return null;
}
DexClassAndMethod maximallySpecificMethod =
lookupMaximallySpecificIncludingSelf(initialResolutionHolder, method);
if (maximallySpecificMethod == null) {
return null;
}
return appView
.options()
.machineDesugaredLibrarySpecification
.getEmulatedInterfaceEmulatedDispatchMethodDescriptor(
maximallySpecificMethod.getReference());
}
private boolean requiresEmulatedDispatch(DexClassAndMethod method) {
return method.isLibraryMethod()
|| isEmulatedInterface(method.getHolderType())
|| appView
.options()
.machineDesugaredLibrarySpecification
.getEmulatedVirtualRetargetThroughEmulatedInterface()
.containsKey(method.getReference());
}
DerivedMethod computeEmulatedInterfaceDispatchMethod(MethodResolutionResult resolutionResult) {
EmulatedDispatchMethodDescriptor descriptor =
getEmulatedDispatchDescriptor(
resolutionResult.getInitialResolutionHolder(), resolutionResult.getResolutionPair());
return descriptor == null ? null : descriptor.getEmulatedDispatchMethod();
}
DerivedMethod computeEmulatedInterfaceForwardingMethod(
DexClass initialResolutionHolder, DexClassAndMethod method) {
if (method == null) {
return null;
}
DexMethod retarget =
appView
.options()
.machineDesugaredLibrarySpecification
.getEmulatedVirtualRetargetThroughEmulatedInterface()
.get(method.getReference());
if (retarget != null) {
return new DerivedMethod(retarget);
}
EmulatedDispatchMethodDescriptor descriptor =
getEmulatedDispatchDescriptor(initialResolutionHolder, method);
return descriptor == null ? null : descriptor.getForwardingMethod();
}
DexMethod ensureEmulatedInterfaceForwardingMethod(DerivedMethod method) {
if (method.getHolderKind() == null) {
return method.getMethod();
}
assert method.getHolderKind() == SyntheticKind.COMPANION_CLASS;
DexClassAndMethod resolvedMethod =
appView.appInfoForDesugaring().resolveMethod(method.getMethod(), true).getResolutionPair();
return ensureDefaultAsMethodOfCompanionClassStub(resolvedMethod).getReference();
}
DexClassAndMethod ensureEmulatedInterfaceDispatchMethod(
DerivedMethod emulatedDispatchMethod,
ClasspathEmulatedInterfaceSynthesizerEventConsumer eventConsumer) {
assert emulatedDispatchMethod.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
DexClassAndMethod method =
appView
.appInfoForDesugaring()
.resolveMethod(emulatedDispatchMethod.getMethod(), true)
.getResolutionPair();
assert emulatedDispatchMethod.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
if (method.isProgramMethod()) {
assert appView.options().isDesugaredLibraryCompilation();
DexProgramClass emulatedInterface =
appView
.getSyntheticItems()
.getExistingFixedClass(
SyntheticKind.EMULATED_INTERFACE_CLASS,
method.asProgramMethod().getHolder(),
appView);
DexMethod emulatedInterfaceMethod =
emulatedInterfaceDispatchMethod(emulatedDispatchMethod, emulatedInterface.type);
assert emulatedInterface.lookupProgramMethod(emulatedInterfaceMethod) != null;
return emulatedInterface.lookupProgramMethod(emulatedInterfaceMethod);
}
// The holder is not used.
DexMethod emulatedInterfaceMethod =
emulatedInterfaceDispatchMethod(
emulatedDispatchMethod, appView.dexItemFactory().objectType);
return appView
.getSyntheticItems()
.ensureFixedClasspathClassMethod(
emulatedInterfaceMethod.getName(),
emulatedInterfaceMethod.getProto(),
SyntheticKind.EMULATED_INTERFACE_CLASS,
method.getHolder().asClasspathOrLibraryClass(),
appView,
classBuilder -> {},
eventConsumer::acceptClasspathEmulatedInterface,
methodBuilder ->
methodBuilder
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(ignore -> ThrowNullCode.get()));
}
DexClassAndMethod ensureDefaultAsMethodOfCompanionClassStub(DexClassAndMethod method) {
if (method.isProgramMethod()) {
return ensureDefaultAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
}
ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
DexMethod companionMethodReference =
defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
}
DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(
DexClassAndMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
if (method.isProgramMethod()) {
return ensureStaticAsMethodOfProgramCompanionClassStub(
method.asProgramMethod(), eventConsumer);
} else {
ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
}
}
ProgramMethod ensureDefaultAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
DexEncodedMethod virtual = method.getDefinition();
DexMethod companionMethod =
defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
return InterfaceProcessor.ensureCompanionMethod(
method.getHolder(),
companionMethod.getName(),
companionMethod.getProto(),
appView,
methodBuilder -> {
MethodAccessFlags newFlags = method.getAccessFlags().copy();
newFlags.promoteToStatic();
methodBuilder
.setAccessFlags(newFlags)
.setGenericSignature(MethodTypeSignature.noSignature())
// Will be traced by the enqueuer.
.disableAndroidApiLevelCheck()
.setAnnotations(
virtual
.annotations()
.methodParametersWithFakeThisArguments(appView.dexItemFactory()))
.setParameterAnnotationsList(
virtual.getParameterAnnotations().withFakeThisParameter())
.setCode(ignored -> InvalidCode.getInstance());
},
ignored -> {});
}
ProgramMethod ensurePrivateAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
DexMethod companionMethod =
privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
DexEncodedMethod definition = method.getDefinition();
return InterfaceProcessor.ensureCompanionMethod(
method.getHolder(),
companionMethod.getName(),
companionMethod.getProto(),
appView,
methodBuilder -> {
MethodAccessFlags newFlags = definition.getAccessFlags().copy();
assert newFlags.isPrivate();
newFlags.promoteToPublic();
newFlags.promoteToStatic();
methodBuilder
.setAccessFlags(newFlags)
.setGenericSignature(definition.getGenericSignature())
.setAnnotations(definition.annotations())
// Will be traced by the enqueuer.
.disableAndroidApiLevelCheck()
// TODO(b/200938394): Should this not also be updating with a fake 'this'
.setParameterAnnotationsList(definition.getParameterAnnotations())
.setCode(ignored -> InvalidCode.getInstance());
},
ignored -> {});
}
// Represent a static interface method as a method of companion class.
private DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
return rewritten;
}
private static DexMethod instanceAsMethodOfCompanionClass(
DexMethod method, String prefix, DexItemFactory factory) {
// Add an implicit argument to represent the receiver.
DexType[] params = method.proto.parameters.values;
DexType[] newParams = new DexType[params.length + 1];
newParams[0] = method.holder;
System.arraycopy(params, 0, newParams, 1, params.length);
// Add prefix to avoid name conflicts.
return factory.createMethod(
getCompanionClassType(method.holder, factory),
factory.createProto(method.proto.returnType, newParams),
factory.createString(prefix + method.name.toString()));
}
// Represent a default interface method as a method of companion class.
public static DexMethod defaultAsMethodOfCompanionClass(
DexMethod method, DexItemFactory factory) {
return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX, factory);
}
// Represent a private instance interface method as a method of companion class.
static DexMethod privateAsMethodOfCompanionClass(DexMethod method, DexItemFactory factory) {
// Add an implicit argument to represent the receiver.
return instanceAsMethodOfCompanionClass(method, PRIVATE_METHOD_PREFIX, factory);
}
DexMethod privateAsMethodOfCompanionClass(DexClassAndMethod method) {
return privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
}
private static DexClassAndMethod ensureMethodOfClasspathCompanionClassStub(
DexMethod companionMethodReference, ClasspathOrLibraryClass context, AppView<?> appView) {
return appView
.getSyntheticItems()
.ensureFixedClasspathClassMethod(
companionMethodReference.getName(),
companionMethodReference.getProto(),
SyntheticKind.COMPANION_CLASS,
context,
appView,
classBuilder -> {},
ignored -> {},
methodBuilder ->
methodBuilder
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(ignore -> ThrowNullCode.get()));
}
ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(
ProgramMethod method, InterfaceMethodDesugaringBaseEventConsumer eventConsumer) {
assert !method.getDefinition().isClassInitializer();
if (method.getHolder().hasClassInitializer()) {
ensureCompanionClassInitializesInterface(method.getHolder(), eventConsumer);
}
DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
DexEncodedMethod definition = method.getDefinition();
return InterfaceProcessor.ensureCompanionMethod(
method.getHolder(),
companionMethodReference.getName(),
companionMethodReference.getProto(),
appView,
methodBuilder -> {
MethodAccessFlags newFlags = definition.getAccessFlags().copy();
newFlags.promoteToPublic();
methodBuilder
.setAccessFlags(newFlags)
.setGenericSignature(definition.getGenericSignature())
.setAnnotations(definition.annotations())
.setParameterAnnotationsList(definition.getParameterAnnotations())
// Will be traced by the enqueuer.
.disableAndroidApiLevelCheck()
.setCode(ignored -> InvalidCode.getInstance());
},
companion -> eventConsumer.acceptCompanionMethod(method, companion));
}
public ProgramMethod ensureMethodOfProgramCompanionClassStub(
ProgramMethod method, InterfaceMethodDesugaringBaseEventConsumer eventConsumer) {
DexEncodedMethod definition = method.getDefinition();
assert method.getHolder().isInterface();
assert definition.isNonAbstractNonNativeMethod();
assert definition.getCode() != null;
assert !InvalidCode.isInvalidCode(definition.getCode());
if (definition.isStatic()) {
return ensureStaticAsMethodOfProgramCompanionClassStub(method, eventConsumer);
}
if (definition.isPrivate()) {
return ensurePrivateAsMethodOfProgramCompanionClassStub(method);
}
return ensureDefaultAsMethodOfProgramCompanionClassStub(method);
}
private void ensureCompanionClassInitializesInterface(
DexProgramClass iface, InterfaceMethodDesugaringBaseEventConsumer eventConsumer) {
assert hasStaticMethodThatTriggersNonTrivialClassInitializer(iface);
InterfaceProcessor.ensureCompanionMethod(
iface,
appView.dexItemFactory().classConstructorMethodName,
appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
appView,
methodBuilder -> createCompanionClassInitializer(iface, methodBuilder),
eventConsumer::acceptCompanionClassClinit);
}
private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
DexProgramClass iface) {
DexEncodedField clinitField =
findExistingStaticClinitFieldToTriggerInterfaceInitialization(iface);
if (clinitField == null) {
clinitField = createStaticClinitFieldToTriggerInterfaceInitialization(iface);
iface.appendStaticField(clinitField);
}
return clinitField;
}
private boolean hasStaticMethodThatTriggersNonTrivialClassInitializer(DexProgramClass iface) {
return iface.hasClassInitializer()
&& iface
.getMethodCollection()
.hasDirectMethods(method -> method.isStatic() && !method.isClassInitializer());
}
private DexEncodedField findExistingStaticClinitFieldToTriggerInterfaceInitialization(
DexProgramClass iface) {
// Don't select a field that has been marked dead, since we'll assert later that these fields
// have been dead code eliminated.
for (DexEncodedField field :
iface.staticFields(field -> !field.isPrivate() && !field.getOptimizationInfo().isDead())) {
return field;
}
return null;
}
private DexEncodedField createStaticClinitFieldToTriggerInterfaceInitialization(
DexProgramClass iface) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexField clinitFieldReference =
dexItemFactory.createFreshFieldNameWithoutHolder(
iface.getType(),
dexItemFactory.intType,
"$desugar$clinit",
candidate -> iface.lookupField(candidate) == null);
return DexEncodedField.syntheticBuilder()
.setField(clinitFieldReference)
.setAccessFlags(
FieldAccessFlags.builder().setPackagePrivate().setStatic().setSynthetic().build())
.setStaticValue(DexValueInt.DEFAULT)
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build();
}
private void createCompanionClassInitializer(
DexProgramClass iface, SyntheticMethodBuilder methodBuilder) {
methodBuilder
.setAccessFlags(
MethodAccessFlags.builder().setConstructor().setPackagePrivate().setStatic().build())
.setClassFileVersion(getInterfaceDesugaredCfVersion(iface.getInitialClassFileVersion()))
.setCode(
method -> {
if (appView.canUseInitClass()) {
return new CfCode(
method.holder,
1,
0,
ImmutableList.of(
new CfInitClass(iface.getType()),
new CfStackInstruction(Opcode.Pop),
new CfReturnVoid()));
}
DexEncodedField clinitField =
ensureStaticClinitFieldToTriggerInterfaceInitialization(iface);
boolean isWide = clinitField.getType().isWideType();
return new CfCode(
method.holder,
isWide ? 2 : 1,
0,
ImmutableList.of(
new CfStaticFieldRead(clinitField.getReference(), clinitField.getReference()),
isWide
? new CfStackInstruction(Opcode.Pop2)
: new CfStackInstruction(Opcode.Pop),
new CfReturnVoid()));
});
}
private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
DexString companionClassNameDescriptorSuffix =
dexItemFactory.createString(
InterfaceDesugaringSyntheticHelper.COMPANION_CLASS_NAME_SUFFIX + ";");
return type -> {
DexString descriptor = type.getDescriptor();
return appView.typeRewriter.hasRewrittenType(type, appView)
|| descriptor.endsWith(companionClassNameDescriptorSuffix)
|| isRewrittenEmulatedInterface(type)
|| options.machineDesugaredLibrarySpecification.isCustomConversionRewrittenType(type)
|| appView.getDontWarnConfiguration().matches(type);
};
}
boolean shouldIgnoreFromReports(DexType missing) {
return shouldIgnoreFromReportsPredicate.test(missing);
}
public void warnMissingInterface(
DexClass classToDesugar, DexClass implementing, DexType missing) {
// We use contains() on non hashed collection, but we know it's a 8 cases collection.
// j$ interfaces won't be missing, they are in the desugared library.
if (shouldIgnoreFromReports(missing)) {
return;
}
appView.options().warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
}
}