blob: 00c8bffdaa7f341d340cb6cb0217d0da5b9dc8e0 [file] [log] [blame]
// Copyright (c) 2019, 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.dex.Constants;
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.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
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.DexType;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
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.BooleanBox;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
// I am responsible for the generation of wrappers used to call library APIs when desugaring
// libraries. Wrappers can be both ways, wrapping the desugarType as a type, or the type as
// a desugar type.
// This file use the vivifiedType -> type, type -> desugarType convention described in the
// DesugaredLibraryAPIConverter class.
// Wrappers contain the following:
// - a single static method convert, which is used by the DesugaredLibraryAPIConverter for
// conversion, it's the main public API (public).
// - a constructor setting the wrappedValue (private).
// - a getter for the wrappedValue (public unwrap()).
// - a single instance field holding the wrapped value (private final).
// - a copy of all implemented methods in the class/interface wrapped. Such methods only do type
// conversions and forward the call to the wrapped type. Parameters and return types are also
// converted.
// Generation of the conversion method in the wrappers is postponed until the compiler knows if the
// reversed wrapper is needed.
// Example of the type wrapper ($-WRP) of java.util.BiFunction at the end of the compilation. I
// omitted
// generic values for simplicity and wrote .... instead of .util.function. Note the difference
// between $-WRP and $-V-WRP wrappers:
// public class j$....BiFunction$-WRP implements java....BiFunction {
// private final j$....BiFunction wrappedValue;
// private BiFunction (j$....BiFunction wrappedValue) {
// this.wrappedValue = wrappedValue;
// }
// public R apply(T t, U u) {
// return wrappedValue.apply(t, u);
// }
// public BiFunction andThen(java....Function after) {
// j$....BiFunction afterConverted = j$....BiFunction$-V-WRP.convert(after);
// return wrappedValue.andThen(afterConverted);
// }
// public static convert(j$....BiFunction function){
// if (function == null) {
// return null;
// }
// if (function instanceof j$....BiFunction$-V-WRP) {
// return ((j$....BiFunction$-V-WRP) function).wrappedValue;
// }
// return new j$....BiFunction$-WRP(wrappedValue);
// }
// }
public class DesugaredLibraryWrapperSynthesizer {
private final AppView<?> appView;
private final Set<DexType> wrappersToGenerate = Sets.newConcurrentHashSet();
// The invalidWrappers are wrappers with incorrect behavior because of final methods that could
// not be overridden. Such wrappers are awful because the runtime behavior is undefined and does
// not raise explicit errors. So we register them here and conversion methods for such wrappers
// raise a runtime exception instead of generating the wrapper.
private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
private final DexItemFactory factory;
private final DesugaredLibraryAPIConverter converter;
DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.converter = converter;
}
public boolean isSyntheticWrapper(DexType type) {
return appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.WRAPPER)
|| appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.VIVIFIED_WRAPPER);
}
boolean canGenerateWrapper(DexType type) {
return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
}
DexType ensureTypeWrapper(DexType type) {
return ensureWrappers(type).getWrapper().type;
}
DexType ensureVivifiedTypeWrapper(DexType type) {
return ensureWrappers(type).getVivifiedWrapper().type;
}
public void registerWrapper(DexType type) {
wrappersToGenerate.add(type);
assert getValidClassToWrap(type) != null;
}
private DexClass getValidClassToWrap(DexType type) {
DexClass dexClass = appView.definitionFor(type);
// The dexClass should be a library class, so it cannot be null.
assert dexClass != null;
assert dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
assert !dexClass.accessFlags.isFinal();
return dexClass;
}
private DexType vivifiedTypeFor(DexType type) {
return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
}
static class Wrappers {
private final DexClass wrapper;
private final DexClass vivifiedWrapper;
Wrappers(DexClass wrapper, DexClass vivifiedWrapper) {
this.wrapper = wrapper;
this.vivifiedWrapper = vivifiedWrapper;
}
public DexClass getWrapper() {
return wrapper;
}
public DexClass getVivifiedWrapper() {
return vivifiedWrapper;
}
}
private Wrappers ensureWrappers(DexType type) {
assert canGenerateWrapper(type) : type;
DexClass dexClass = getValidClassToWrap(type);
return ensureWrappers(dexClass, ignored -> {});
}
private Wrappers ensureWrappers(DexClass context, Consumer<DexClasspathClass> creationCallback) {
DexType type = context.type;
DexClass wrapper;
DexClass vivifiedWrapper;
if (context.isProgramClass()) {
assert appView.options().isDesugaredLibraryCompilation();
DexProgramClass programContext = context.asProgramClass();
wrapper =
ensureProgramWrapper(
SyntheticKind.WRAPPER,
vivifiedTypeFor(type),
type,
programContext,
wrapperField -> synthesizeVirtualMethodsForTypeWrapper(programContext, wrapperField));
vivifiedWrapper =
ensureProgramWrapper(
SyntheticKind.VIVIFIED_WRAPPER,
type,
vivifiedTypeFor(type),
programContext,
wrapperField ->
synthesizeVirtualMethodsForVivifiedTypeWrapper(programContext, wrapperField));
DexField wrapperField = getWrapperUniqueField(wrapper);
DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
ensureProgramConversionMethod(
SyntheticKind.WRAPPER, programContext, wrapperField, vivifiedWrapperField);
ensureProgramConversionMethod(
SyntheticKind.VIVIFIED_WRAPPER, programContext, vivifiedWrapperField, wrapperField);
} else {
assert context.isNotProgramClass();
ClasspathOrLibraryClass classpathOrLibraryContext = context.asClasspathOrLibraryClass();
wrapper =
ensureClasspathWrapper(
SyntheticKind.WRAPPER,
vivifiedTypeFor(type),
type,
classpathOrLibraryContext,
creationCallback,
wrapperField -> synthesizeVirtualMethodsForTypeWrapper(context, wrapperField));
vivifiedWrapper =
ensureClasspathWrapper(
SyntheticKind.VIVIFIED_WRAPPER,
type,
vivifiedTypeFor(type),
classpathOrLibraryContext,
creationCallback,
wrapperField ->
synthesizeVirtualMethodsForVivifiedTypeWrapper(context, wrapperField));
DexField wrapperField = getWrapperUniqueField(wrapper);
DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
ensureClasspathConversionMethod(
SyntheticKind.WRAPPER, classpathOrLibraryContext, wrapperField, vivifiedWrapperField);
ensureClasspathConversionMethod(
SyntheticKind.VIVIFIED_WRAPPER,
classpathOrLibraryContext,
vivifiedWrapperField,
wrapperField);
}
return new Wrappers(wrapper, vivifiedWrapper);
}
private DexEncodedField getWrapperUniqueEncodedField(DexClass wrapper) {
assert wrapper.instanceFields().size() == 1;
return wrapper.instanceFields().get(0);
}
private DexField getWrapperUniqueField(DexClass wrapper) {
return getWrapperUniqueEncodedField(wrapper).getReference();
}
private DexProgramClass ensureProgramWrapper(
SyntheticKind kind,
DexType wrappingType,
DexType wrappedType,
DexProgramClass programContext,
Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
return appView
.getSyntheticItems()
.ensureFixedClass(
kind,
programContext,
appView,
builder -> buildWrapper(wrappingType, wrappedType, programContext, builder),
// The creation of virtual methods may require new wrappers, this needs to happen
// once the wrapper is created to avoid infinite recursion.
wrapper ->
wrapper.setVirtualMethods(
virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper))));
}
private DexClasspathClass ensureClasspathWrapper(
SyntheticKind kind,
DexType wrappingType,
DexType wrappedType,
ClasspathOrLibraryClass classpathOrLibraryContext,
Consumer<DexClasspathClass> creationCallback,
Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
return appView
.getSyntheticItems()
.ensureFixedClasspathClass(
kind,
classpathOrLibraryContext,
appView,
builder ->
buildWrapper(
wrappingType, wrappedType, classpathOrLibraryContext.asDexClass(), builder),
// The creation of virtual methods may require new wrappers, this needs to happen
// once the wrapper is created to avoid infinite recursion.
wrapper -> {
wrapper.setVirtualMethods(
virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
creationCallback.accept(wrapper);
});
}
private ProgramMethod ensureProgramConversionMethod(
SyntheticKind kind,
DexProgramClass context,
DexField wrapperField,
DexField reverseWrapperField) {
return appView
.getSyntheticItems()
.ensureFixedClassMethod(
factory.convertMethodName,
factory.createProto(reverseWrapperField.type, wrapperField.type),
kind,
context,
appView,
ignored -> {},
methodBuilder ->
buildConversionMethod(
methodBuilder, wrapperField, reverseWrapperField, context.type));
}
private DexClassAndMethod ensureClasspathConversionMethod(
SyntheticKind kind,
ClasspathOrLibraryClass context,
DexField wrapperField,
DexField reverseWrapperField) {
return appView
.getSyntheticItems()
.ensureFixedClasspathClassMethod(
factory.convertMethodName,
factory.createProto(reverseWrapperField.type, wrapperField.type),
kind,
context,
appView,
ignored -> {},
methodBuilder ->
buildConversionMethod(
methodBuilder, wrapperField, reverseWrapperField, context.getType()));
}
private void buildConversionMethod(
SyntheticMethodBuilder methodBuilder,
DexField wrapperField,
DexField reverseWrapperField,
DexType reportingType) {
CfCode cfCode;
if (invalidWrappers.contains(wrapperField.holder)) {
cfCode =
new APIConverterThrowRuntimeExceptionCfCodeProvider(
appView,
factory.createString(
"Unsupported conversion for "
+ reportingType
+ ". See compilation time warnings for more details."),
wrapperField.holder)
.generateCfCode();
} else {
cfCode =
new APIConverterWrapperConversionCfCodeProvider(
appView, reverseWrapperField, wrapperField)
.generateCfCode();
}
methodBuilder
.setAccessFlags(
MethodAccessFlags.fromCfAccessFlags(
Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false))
.setCode(methodSignature -> cfCode);
}
private void buildWrapper(
DexType wrappingType,
DexType wrappedType,
DexClass clazz,
SyntheticClassBuilder<?, ?> builder) {
boolean isItf = clazz.isInterface();
DexType superType = isItf ? factory.objectType : wrappingType;
List<DexType> interfaces =
isItf ? Collections.singletonList(wrappingType) : Collections.emptyList();
DexEncodedField wrapperField =
synthesizeWrappedValueEncodedField(builder.getType(), wrappedType);
builder
.setInterfaces(interfaces)
.setSuperType(superType)
.setInstanceFields(Collections.singletonList(wrapperField))
.addMethod(methodBuilder -> buildWrapperConstructor(wrapperField, methodBuilder));
}
private void buildWrapperConstructor(
DexEncodedField wrappedValueField, SyntheticMethodBuilder methodBuilder) {
methodBuilder
.setName(factory.constructorMethodName)
.setProto(factory.createProto(factory.voidType, wrappedValueField.getType()))
.setAccessFlags(
MethodAccessFlags.fromCfAccessFlags(
Constants.ACC_PRIVATE | Constants.ACC_SYNTHETIC, true))
.setCode(
codeSynthesizor ->
new APIConverterConstructorCfCodeProvider(appView, wrappedValueField.getReference())
.generateCfCode());
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
DexClass dexClass, DexEncodedField wrapperField) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only types in their signature, but each method the wrapper forwards
// to should used only vivified types.
// Generated method looks like:
// long foo (type, int)
// v0 <- arg0;
// v1 <- arg1;
// v2 <- convertTypeToVivifiedType(v0);
// v3 <- wrappedValue.foo(v2,v1);
// return v3;
Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
for (DexEncodedMethod dexEncodedMethod : dexMethods) {
DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
boolean isInterface;
if (holderClass == null) {
assert appView
.options()
.desugaredLibraryConfiguration
.getEmulateLibraryInterface()
.containsValue(dexEncodedMethod.getHolderType());
isInterface = true;
} else {
isInterface = holderClass.isInterface();
}
DexMethod methodToInstall =
factory.createMethod(
wrapperField.getHolderType(),
dexEncodedMethod.getReference().proto,
dexEncodedMethod.getReference().name);
CfCode cfCode;
if (dexEncodedMethod.isFinal()) {
invalidWrappers.add(wrapperField.getHolderType());
finalMethods.add(dexEncodedMethod.getReference());
continue;
} else {
cfCode =
new APIConverterVivifiedWrapperCfCodeProvider(
appView, methodToInstall, wrapperField.getReference(), converter, isInterface)
.generateCfCode();
}
DexEncodedMethod newDexEncodedMethod =
newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
generatedMethods.add(newDexEncodedMethod);
}
return finalizeWrapperMethods(generatedMethods, finalMethods);
}
private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
DexClass dexClass, DexEncodedField wrapperField) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only vivified types in their signature, but each method the wrapper
// forwards
// to should used only types.
// Generated method looks like:
// long foo (type, int)
// v0 <- arg0;
// v1 <- arg1;
// v2 <- convertVivifiedTypeToType(v0);
// v3 <- wrappedValue.foo(v2,v1);
// return v3;
Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
for (DexEncodedMethod dexEncodedMethod : dexMethods) {
DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
boolean isInterface = holderClass == null || holderClass.isInterface();
DexMethod methodToInstall =
DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
dexEncodedMethod.getReference(), wrapperField.getHolderType(), appView);
CfCode cfCode;
if (dexEncodedMethod.isFinal()) {
invalidWrappers.add(wrapperField.getHolderType());
finalMethods.add(dexEncodedMethod.getReference());
continue;
} else {
cfCode =
new APIConverterWrapperCfCodeProvider(
appView,
dexEncodedMethod.getReference(),
wrapperField.getReference(),
converter,
isInterface)
.generateCfCode();
}
DexEncodedMethod newDexEncodedMethod =
newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
generatedMethods.add(newDexEncodedMethod);
}
return finalizeWrapperMethods(generatedMethods, finalMethods);
}
private DexEncodedMethod[] finalizeWrapperMethods(
List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
if (finalMethods.isEmpty()) {
return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
}
// Wrapper is invalid, no need to add the virtual methods.
reportFinalMethodsInWrapper(finalMethods);
return DexEncodedMethod.EMPTY_ARRAY;
}
private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
String[] methodArray =
methods.stream().map(method -> method.holder + "#" + method.name).toArray(String[]::new);
appView
.options()
.reporter
.warning(
new StringDiagnostic(
"Desugared library API conversion: cannot wrap final methods "
+ Arrays.toString(methodArray)
+ ". "
+ methods.iterator().next().holder
+ " is marked as invalid and will throw a runtime exception upon conversion."));
}
DexEncodedMethod newSynthesizedMethod(
DexMethod methodToInstall, DexEncodedMethod template, Code code) {
MethodAccessFlags newFlags = template.accessFlags.copy();
assert newFlags.isPublic();
if (code == null) {
newFlags.setAbstract();
} else {
newFlags.unsetAbstract();
}
// TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
newFlags.unsetBridge();
newFlags.setSynthetic();
return new DexEncodedMethod(
methodToInstall,
newFlags,
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
code,
true);
}
private List<DexEncodedMethod> allImplementedMethods(DexClass libraryClass) {
LinkedList<DexClass> workList = new LinkedList<>();
List<DexEncodedMethod> implementedMethods = new ArrayList<>();
workList.add(libraryClass);
while (!workList.isEmpty()) {
DexClass dexClass = workList.removeFirst();
for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
if (!virtualMethod.isPrivateMethod()) {
boolean alreadyAdded = false;
// This looks quadratic but given the size of the collections met in practice for
// desugared libraries (Max ~15) it does not matter.
for (DexEncodedMethod alreadyImplementedMethod : implementedMethods) {
if (alreadyImplementedMethod.getReference().match(virtualMethod.getReference())) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
implementedMethods.add(virtualMethod);
}
}
}
for (DexType itf : dexClass.interfaces.values) {
DexClass itfClass = appView.definitionFor(itf);
// Cannot be null in program since we started from a LibraryClass.
assert itfClass != null || appView.options().isDesugaredLibraryCompilation();
if (itfClass != null) {
workList.add(itfClass);
}
}
if (dexClass.superType != factory.objectType) {
DexClass superClass = appView.definitionFor(dexClass.superType);
assert superClass != null; // Cannot be null since we started from a LibraryClass.
workList.add(superClass);
}
}
return implementedMethods;
}
private DexField wrappedValueField(DexType holder, DexType fieldType) {
return factory.createField(holder, fieldType, factory.wrapperFieldName);
}
private DexEncodedField synthesizeWrappedValueEncodedField(DexType holder, DexType fieldType) {
DexField field = wrappedValueField(holder, fieldType);
// Field is package private to be accessible from convert methods without a getter.
FieldAccessFlags fieldAccessFlags =
FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
return new DexEncodedField(
field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
}
void finalizeWrappersForL8() {
DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
for (DexType type : conf.getWrapperConversions()) {
assert !conf.getCustomConversions().containsKey(type);
DexClass validClassToWrap = getValidClassToWrap(type);
// In broken set-ups we can end up having a json files containing wrappers of non desugared
// classes. Such wrappers are not required since the class won't be rewritten.
if (validClassToWrap.isProgramClass()) {
ensureWrappers(validClassToWrap, ignored -> {});
}
}
}
void synthesizeWrappersForClasspath(Consumer<DexClasspathClass> synthesizedCallback) {
BooleanBox changed = new BooleanBox(true);
while (changed.get()) {
changed.set(false);
Set<DexType> copy = new HashSet<>(wrappersToGenerate);
for (DexType type : copy) {
DexClass validClassToWrap = getValidClassToWrap(type);
ensureWrappers(
validClassToWrap,
classpathWrapper -> {
changed.set(true);
synthesizedCallback.accept(classpathWrapper);
});
}
}
}
}