| // 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.desugaredlibrary.apiconversion; |
| |
| import com.android.tools.r8.androidapi.ComputedApiLevel; |
| import com.android.tools.r8.contexts.CompilationContext.ClassSynthesisDesugaringContext; |
| import com.android.tools.r8.contexts.CompilationContext.UniqueContext; |
| 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.ClasspathMethod; |
| import com.android.tools.r8.graph.ClasspathOrLibraryClass; |
| import com.android.tools.r8.graph.Code; |
| import com.android.tools.r8.graph.DexClass; |
| 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.DexType; |
| import com.android.tools.r8.graph.FieldAccessFlags; |
| import com.android.tools.r8.graph.MethodAccessFlags; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring; |
| import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.CustomConversionDescriptor; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; |
| import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider; |
| import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider; |
| import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.position.MethodPosition; |
| import com.android.tools.r8.position.Position; |
| import com.android.tools.r8.synthesis.SyntheticClassBuilder; |
| import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector; |
| import com.android.tools.r8.synthesis.SyntheticMethodBuilder; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.google.common.collect.ImmutableList; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.BiFunction; |
| import java.util.function.Supplier; |
| |
| // 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 implements CfClassSynthesizerDesugaring { |
| |
| private final AppView<?> appView; |
| private final DexItemFactory factory; |
| private final DesugaredLibraryEnumConversionSynthesizer enumConverter; |
| private final DesugaredLibraryConversionCfProvider conversionCfProvider; |
| |
| public DesugaredLibraryWrapperSynthesizer(AppView<?> appView) { |
| this.appView = appView; |
| this.factory = appView.dexItemFactory(); |
| this.enumConverter = new DesugaredLibraryEnumConversionSynthesizer(appView); |
| this.conversionCfProvider = new DesugaredLibraryConversionCfProvider(appView, this); |
| } |
| |
| enum WrapperKind { |
| WRAPPER, |
| VIVIFIED_WRAPPER; |
| |
| SyntheticKindSelector getKindSelector() { |
| if (this == WrapperKind.WRAPPER) { |
| return kinds1 -> kinds1.WRAPPER; |
| } else { |
| return kinds -> kinds.VIVIFIED_WRAPPER; |
| } |
| } |
| |
| DexType getWrappingType(DexType type, DexType vivifiedType) { |
| if (this == WrapperKind.WRAPPER) { |
| return vivifiedType; |
| } else { |
| return type; |
| } |
| } |
| |
| DexType getWrappedType(DexType type, DexType vivifiedType) { |
| if (this == WrapperKind.WRAPPER) { |
| return type; |
| } else { |
| return vivifiedType; |
| } |
| } |
| } |
| |
| public DesugaredLibraryConversionCfProvider getConversionCfProvider() { |
| return conversionCfProvider; |
| } |
| |
| public boolean isSyntheticWrapper(DexType type) { |
| return appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.WRAPPER) |
| || appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.VIVIFIED_WRAPPER); |
| } |
| |
| public boolean shouldConvert( |
| DexType type, DexMethod apiGenericTypesConversion, DexMethod method, ProgramMethod context) { |
| if (type.isArrayType()) { |
| assert apiGenericTypesConversion == null; |
| return shouldConvert( |
| type.toBaseType(appView.dexItemFactory()), apiGenericTypesConversion, method, context); |
| } |
| if (apiGenericTypesConversion != null) { |
| return true; |
| } |
| if (!appView.typeRewriter.hasRewrittenType(type, appView)) { |
| return false; |
| } |
| if (canConvert(type)) { |
| return true; |
| } |
| reportInvalidInvoke(type, method, context); |
| return false; |
| } |
| |
| private DexMethod ensureApiGenericConversion( |
| DexMethod conversion, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) { |
| assert !appView.options().isDesugaredLibraryCompilation(); |
| ClasspathMethod classpathMethod = |
| appView |
| .getSyntheticItems() |
| .ensureFixedClasspathMethodFromType( |
| conversion.getName(), |
| conversion.getProto(), |
| kinds -> kinds.GENERIC_API_CONVERSION_STUB, |
| conversion.getHolderType(), |
| appView, |
| ignored -> {}, |
| eventConsumer::acceptGenericApiConversionStub, |
| methodBuilder -> |
| methodBuilder |
| .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) |
| .setCode(null)); |
| assert classpathMethod.getReference() == conversion; |
| return conversion; |
| } |
| |
| public DexMethod ensureConversionMethod( |
| DexType type, |
| boolean destIsVivified, |
| DexMethod apiGenericTypesConversion, |
| DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, |
| Supplier<UniqueContext> contextSupplier) { |
| if (apiGenericTypesConversion != null) { |
| assert !type.isArrayType(); |
| return ensureApiGenericConversion(apiGenericTypesConversion, eventConsumer); |
| } |
| DexType srcType = destIsVivified ? type : vivifiedTypeFor(type); |
| DexType destType = destIsVivified ? vivifiedTypeFor(type) : type; |
| if (type.isArrayType()) { |
| return ensureArrayConversionMethod(type, srcType, destType, eventConsumer, contextSupplier); |
| } |
| DexMethod customConversion = getCustomConversion(type, srcType, destType); |
| if (customConversion != null) { |
| return customConversion; |
| } |
| DexClass clazz = getValidClassToWrap(type); |
| if (clazz.isEnum()) { |
| return enumConverter.ensureEnumConversionMethod(clazz, srcType, destType, eventConsumer); |
| } |
| assert canGenerateWrapper(type) : type; |
| WrapperConversions wrapperConversions = ensureWrappers(clazz, eventConsumer); |
| DexMethod conversion = |
| type == srcType |
| ? wrapperConversions.getConversion() |
| : wrapperConversions.getVivifiedConversion(); |
| assert srcType == conversion.getArgumentType(0, true); |
| assert destType == conversion.getReturnType(); |
| return conversion; |
| } |
| |
| private DexMethod ensureArrayConversionMethod( |
| DexType type, |
| DexType srcType, |
| DexType destType, |
| DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, |
| Supplier<UniqueContext> contextSupplier) { |
| DexMethod conversion = |
| ensureConversionMethod( |
| type.toDimensionMinusOneType(factory), |
| srcType == type, |
| null, |
| eventConsumer, |
| contextSupplier); |
| return ensureArrayConversionMethod( |
| srcType, destType, eventConsumer, contextSupplier, conversion); |
| } |
| |
| private DexMethod ensureArrayConversionMethodFromExistingBaseConversion( |
| DexType type, |
| DexType srcType, |
| DexType destType, |
| DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, |
| Supplier<UniqueContext> contextSupplier) { |
| DexMethod conversion = |
| getExistingProgramConversionMethod( |
| type.toDimensionMinusOneType(factory), |
| srcType == type, |
| null, |
| eventConsumer, |
| contextSupplier); |
| return ensureArrayConversionMethod( |
| srcType, destType, eventConsumer, contextSupplier, conversion); |
| } |
| |
| private DexMethod ensureArrayConversionMethod( |
| DexType srcType, |
| DexType destType, |
| DesugaredLibraryWrapperSynthesizerEventConsumer eventConsumer, |
| Supplier<UniqueContext> contextSupplier, |
| DexMethod conversion) { |
| ProgramMethod arrayConversion = |
| appView |
| .getSyntheticItems() |
| .createMethod( |
| kinds -> kinds.COLLECTION_CONVERSION, |
| contextSupplier.get(), |
| appView, |
| builder -> |
| builder |
| .setProto(factory.createProto(destType, srcType)) |
| .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) |
| .setCode( |
| codeSynthesizor -> |
| new ArrayConversionCfCodeProvider( |
| appView, |
| codeSynthesizor.getHolderType(), |
| srcType, |
| destType, |
| conversion) |
| .generateCfCode())); |
| eventConsumer.acceptCollectionConversion(arrayConversion); |
| return arrayConversion.getReference(); |
| } |
| |
| public DexMethod getExistingProgramConversionMethod( |
| DexType type, |
| boolean destIsVivified, |
| DexMethod apiGenericTypesConversion, |
| DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, |
| Supplier<UniqueContext> contextSupplier) { |
| if (apiGenericTypesConversion != null) { |
| assert !type.isArrayType(); |
| return apiGenericTypesConversion; |
| } |
| DexType srcType = destIsVivified ? type : vivifiedTypeFor(type); |
| DexType destType = destIsVivified ? vivifiedTypeFor(type) : type; |
| if (type.isArrayType()) { |
| return ensureArrayConversionMethodFromExistingBaseConversion( |
| type, srcType, destType, eventConsumer, contextSupplier); |
| } |
| DexMethod customConversion = getCustomConversion(type, srcType, destType); |
| if (customConversion != null) { |
| return customConversion; |
| } |
| DexClass clazz = getValidClassToWrap(type); |
| if (clazz.isEnum()) { |
| return enumConverter.getExistingProgramEnumConversionMethod(clazz, srcType, destType); |
| } |
| WrapperConversions wrapperConversions = getExistingProgramWrapperConversions(clazz); |
| DexMethod conversion = |
| type == srcType |
| ? wrapperConversions.getConversion() |
| : wrapperConversions.getVivifiedConversion(); |
| assert srcType == conversion.getArgumentType(0, true); |
| return conversion; |
| } |
| |
| private DexMethod getCustomConversion(DexType type, DexType srcType, DexType destType) { |
| // ConversionType holds the methods "rewrittenType convert(type)" and the other way around. |
| // But everything is going to be rewritten, so we need to use vivifiedType and type". |
| CustomConversionDescriptor descriptor = |
| appView.options().machineDesugaredLibrarySpecification.getCustomConversions().get(type); |
| if (descriptor == null) { |
| return null; |
| } |
| // Because the conversion have rewritten types instead of vivified type we cannot use the |
| // specification content directly until the rewriting is done upfront in the compilation. |
| DexMethod conversion = type == srcType ? descriptor.getTo() : descriptor.getFrom(); |
| assert type == srcType |
| ? type == conversion.getReturnType() |
| : type == conversion.getArgumentType(0, true); |
| return factory.createMethod( |
| conversion.getHolderType(), factory.createProto(destType, srcType), conversion.getName()); |
| } |
| |
| private boolean canConvert(DexType type) { |
| return appView |
| .options() |
| .machineDesugaredLibrarySpecification |
| .getCustomConversions() |
| .containsKey(type) |
| || canGenerateWrapper(type); |
| } |
| |
| private void reportInvalidInvoke(DexType type, DexMethod invokedMethod, ProgramMethod context) { |
| DexType desugaredType = appView.typeRewriter.rewrittenType(type, appView); |
| Origin origin = context != null ? context.getOrigin() : Origin.unknown(); |
| Position position = |
| context != null ? new MethodPosition(context.getMethodReference()) : Position.UNKNOWN; |
| StringDiagnostic diagnostic = |
| new StringDiagnostic( |
| "Invoke to " |
| + invokedMethod.holder |
| + "#" |
| + invokedMethod.name |
| + " may not work correctly at runtime (No conversion registered for type " |
| + desugaredType |
| + ").", |
| origin, |
| position); |
| if (appView.options().isDesugaredLibraryCompilation()) { |
| throw appView.options().reporter.fatalError(diagnostic); |
| } else { |
| appView.options().reporter.info(diagnostic); |
| } |
| } |
| |
| private boolean canGenerateWrapper(DexType type) { |
| return appView.options().machineDesugaredLibrarySpecification.getWrappers().containsKey(type); |
| } |
| |
| private DexClass getValidClassToWrap(DexType type) { |
| assert !type.isArrayType(); |
| 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() || dexClass.isEnum(); |
| return dexClass; |
| } |
| |
| private DexType vivifiedTypeFor(DexType type) { |
| return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView); |
| } |
| |
| static class WrapperConversions { |
| |
| private final DexMethod conversion; |
| private final DexMethod vivifiedConversion; |
| |
| WrapperConversions(DexMethod conversion, DexMethod vivifiedConversion) { |
| this.conversion = conversion; |
| this.vivifiedConversion = vivifiedConversion; |
| } |
| |
| public DexMethod getConversion() { |
| return conversion; |
| } |
| |
| public DexMethod getVivifiedConversion() { |
| return vivifiedConversion; |
| } |
| } |
| |
| private WrapperConversions ensureWrappers( |
| DexClass context, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) { |
| assert eventConsumer != null; |
| if (context.isProgramClass()) { |
| return getExistingProgramWrapperConversions(context); |
| } |
| assert context.isNotProgramClass(); |
| Iterable<DexMethod> methods = |
| appView |
| .options() |
| .machineDesugaredLibrarySpecification |
| .getWrappers() |
| .get(context.type) |
| .getMethods(); |
| assert methods != null; |
| ClasspathOrLibraryClass classpathOrLibraryContext = context.asClasspathOrLibraryClass(); |
| DexType type = context.type; |
| DexType vivifiedType = vivifiedTypeFor(type); |
| DexClass wrapper = |
| ensureClasspathWrapper( |
| WrapperKind.WRAPPER, |
| vivifiedType, |
| type, |
| classpathOrLibraryContext, |
| eventConsumer, |
| methods, |
| conversionCfProvider::generateWrapperConversionWithoutCode); |
| DexClass vivifiedWrapper = |
| ensureClasspathWrapper( |
| WrapperKind.VIVIFIED_WRAPPER, |
| type, |
| vivifiedType, |
| classpathOrLibraryContext, |
| eventConsumer, |
| methods, |
| conversionCfProvider::generateVivifiedWrapperConversionWithoutCode); |
| return new WrapperConversions( |
| getConversion(wrapper, vivifiedType, type), |
| getConversion(vivifiedWrapper, type, vivifiedType)); |
| } |
| |
| private WrapperConversions getExistingProgramWrapperConversions(DexClass context) { |
| DexClass vivifiedWrapper; |
| DexClass wrapper; |
| assert appView.options().isDesugaredLibraryCompilation(); |
| wrapper = getExistingProgramWrapper(context, WrapperKind.WRAPPER); |
| vivifiedWrapper = getExistingProgramWrapper(context, WrapperKind.VIVIFIED_WRAPPER); |
| DexField wrapperField = getWrapperUniqueField(wrapper); |
| DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper); |
| return new WrapperConversions( |
| getConversion(wrapper, vivifiedWrapperField.type, wrapperField.type), |
| getConversion(vivifiedWrapper, wrapperField.type, vivifiedWrapperField.type)); |
| } |
| |
| private DexProgramClass getExistingProgramWrapper(DexClass context, WrapperKind kind) { |
| if (context.isEnum()) { |
| return appView |
| .getSyntheticItems() |
| .getExistingFixedClass(kinds -> kinds.ENUM_CONVERSION, context, appView); |
| } |
| return appView |
| .getSyntheticItems() |
| .getExistingFixedClass(kind.getKindSelector(), getWrapperContext(context, kind), appView); |
| } |
| |
| private DexMethod getConversion(DexClass wrapper, DexType returnType, DexType argType) { |
| DexMethod convertMethod = |
| factory.createMethod( |
| wrapper.type, factory.createProto(returnType, argType), factory.convertMethodName); |
| return wrapper.lookupDirectMethod(convertMethod).getReference(); |
| } |
| |
| 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( |
| DexType type, |
| DexProgramClass context, |
| WrapperKind kind, |
| DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer) { |
| assert appView.options().isDesugaredLibraryCompilation(); |
| assert eventConsumer != null; |
| DexType vivifiedType = vivifiedTypeFor(type); |
| return appView |
| .getSyntheticItems() |
| .ensureFixedClass( |
| kind.getKindSelector(), |
| getWrapperContext(context, kind), |
| appView, |
| builder -> |
| buildWrapper( |
| kind.getWrappingType(type, vivifiedType), |
| kind.getWrappedType(type, vivifiedType), |
| context.isInterface(), |
| builder), |
| // The creation of virtual methods may require new wrappers, this needs to happen |
| // once the wrapper is created to avoid infinite recursion. |
| eventConsumer::acceptWrapperProgramClass); |
| } |
| |
| private DexClasspathClass ensureClasspathWrapper( |
| WrapperKind kind, |
| DexType wrappingType, |
| DexType wrappedType, |
| ClasspathOrLibraryClass classpathOrLibraryContext, |
| DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, |
| Iterable<DexMethod> methods, |
| BiFunction<DexMethod, DexField, DexEncodedMethod> methodGenerator) { |
| assert eventConsumer != null; |
| return appView |
| .getSyntheticItems() |
| .ensureFixedClasspathClass( |
| kind.getKindSelector(), |
| getWrapperContext(classpathOrLibraryContext.asDexClass(), kind) |
| .asClasspathOrLibraryClass(), |
| appView, |
| builder -> { |
| DexEncodedField wrapperField = |
| buildWrapper( |
| wrappingType, wrappedType, classpathOrLibraryContext.isInterface(), builder); |
| builder.addMethod( |
| methodBuilder -> |
| buildConversionMethod( |
| methodBuilder, factory.createProto(wrappingType, wrappedType), null)); |
| builder.setVirtualMethods( |
| synthesizeVirtualMethodsForWrapper(methods, wrapperField, methodGenerator)); |
| }, |
| eventConsumer::acceptWrapperClasspathClass); |
| } |
| |
| private void synthesizeProgramConversionMethod( |
| WrapperKind kind, |
| DexProgramClass context, |
| List<DexType> subwrappers, |
| DexClass wrapper, |
| DexClass reverseWrapper) { |
| DexField wrapperField = getWrapperUniqueField(wrapper); |
| DexField reverseWrapperField = getWrapperUniqueField(reverseWrapper); |
| List<DexMethod> subwrapperConvertList = new ArrayList<>(); |
| for (DexType subwrapper : subwrappers) { |
| DexClass subwrapperClass = appView.definitionFor(subwrapper); |
| assert subwrapperClass != null; |
| DexProgramClass subwrapperWrapper = getExistingProgramWrapper(subwrapperClass, kind); |
| subwrapperConvertList.add(getConversion(subwrapperWrapper)); |
| } |
| DexProto proto = factory.createProto(reverseWrapperField.type, wrapperField.type); |
| appView |
| .getSyntheticItems() |
| .ensureFixedClassMethod( |
| factory.convertMethodName, |
| proto, |
| kind.getKindSelector(), |
| getWrapperContext(context, kind).asProgramOrClasspathDefinition(), |
| appView, |
| ignored -> {}, |
| methodBuilder -> |
| buildConversionMethod( |
| methodBuilder, |
| proto, |
| computeProgramConversionMethodCode( |
| wrapperField, reverseWrapperField, context, subwrapperConvertList))); |
| } |
| |
| private DexMethod getConversion(DexProgramClass subwrapperWrapper) { |
| Iterator<DexEncodedMethod> iterator = subwrapperWrapper.directMethods().iterator(); |
| DexEncodedMethod method; |
| do { |
| method = iterator.next(); |
| } while (!method.isStatic()); |
| assert method.getName() == factory.convertMethodName; |
| return method.getReference(); |
| } |
| |
| private CfCode computeProgramConversionMethodCode( |
| DexField wrapperField, |
| DexField reverseWrapperField, |
| DexClass context, |
| List<DexMethod> subwrapperConvertList) { |
| assert context.isProgramClass(); |
| return new NullableConversionCfCodeProvider.WrapperConversionCfCodeProvider( |
| appView, reverseWrapperField, wrapperField, subwrapperConvertList) |
| .generateCfCode(); |
| } |
| |
| private void buildConversionMethod( |
| SyntheticMethodBuilder methodBuilder, DexProto proto, CfCode cfCode) { |
| methodBuilder |
| .setName(factory.convertMethodName) |
| .setProto(proto) |
| .setAccessFlags( |
| MethodAccessFlags.fromCfAccessFlags( |
| Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false)) |
| // Will be traced by the enqueuer. |
| .disableAndroidApiLevelCheck() |
| .setCode(methodSignature -> cfCode); |
| } |
| |
| private DexEncodedField buildWrapper( |
| DexType wrappingType, |
| DexType wrappedType, |
| boolean isItf, |
| SyntheticClassBuilder<?, ?> builder) { |
| 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, superType)); |
| return wrapperField; |
| } |
| |
| private void buildWrapperConstructor( |
| DexEncodedField wrappedValueField, SyntheticMethodBuilder methodBuilder, DexType superType) { |
| methodBuilder |
| .setName(factory.constructorMethodName) |
| .setProto(factory.createProto(factory.voidType, wrappedValueField.getType())) |
| .setAccessFlags( |
| MethodAccessFlags.fromCfAccessFlags( |
| Constants.ACC_PRIVATE | Constants.ACC_SYNTHETIC, true)) |
| // Will be traced by the enqueuer. |
| .disableAndroidApiLevelCheck() |
| .setCode( |
| codeSynthesizor -> |
| new WrapperConstructorCfCodeProvider( |
| appView, wrappedValueField.getReference(), superType) |
| .generateCfCode()); |
| } |
| |
| private Collection<DexEncodedMethod> synthesizeVirtualMethodsForWrapper( |
| Iterable<DexMethod> dexMethods, |
| DexEncodedField wrapperField, |
| BiFunction<DexMethod, DexField, DexEncodedMethod> methodGenerator) { |
| List<DexEncodedMethod> generatedMethods = new ArrayList<>(); |
| for (DexMethod method : dexMethods) { |
| DexEncodedMethod newDexEncodedMethod = |
| methodGenerator.apply(method, wrapperField.getReference()); |
| generatedMethods.add(newDexEncodedMethod); |
| } |
| return generatedMethods; |
| } |
| |
| private Collection<DexEncodedMethod> synthesizeHashCodeAndEquals( |
| DexProgramClass wrapper, DexEncodedField wrapperField) { |
| return ImmutableList.of( |
| conversionCfProvider.generateWrapperHashCode(wrapperField.getReference()), |
| conversionCfProvider.generateWrapperEquals(wrapperField.getReference())); |
| } |
| |
| DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) { |
| MethodAccessFlags newFlags = |
| MethodAccessFlags.fromSharedAccessFlags( |
| Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false); |
| ComputedApiLevel apiLevelForDefinition = |
| appView.enableWholeProgramOptimizations() |
| ? ComputedApiLevel.notSet() |
| : appView |
| .apiLevelCompute() |
| .computeApiLevelForDefinition(methodToInstall, factory, ComputedApiLevel.unknown()); |
| // Since the method is a forwarding method, the api level for code is the same as the |
| // definition. |
| ComputedApiLevel apiLevelForCode = apiLevelForDefinition; |
| return DexEncodedMethod.syntheticBuilder() |
| .setMethod(methodToInstall) |
| .setAccessFlags(newFlags) |
| .setCode(code) |
| .setApiLevelForDefinition(apiLevelForDefinition) |
| .setApiLevelForCode(code == null ? ComputedApiLevel.notSet() : apiLevelForCode) |
| .build(); |
| } |
| |
| 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_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC); |
| return DexEncodedField.syntheticBuilder() |
| .setField(field) |
| .setAccessFlags(fieldAccessFlags) |
| // The api level is computed when tracing. |
| .disableAndroidApiLevelCheck() |
| .build(); |
| } |
| |
| @Override |
| public String uniqueIdentifier() { |
| return "$wrapper"; |
| } |
| |
| // Program wrappers are harder to deal with than classpath wrapper because generating a method's |
| // code may require other wrappers. To keep it simple (This is L8 specific), we generate first |
| // the wrappers with the conversion methods only, then the virtual methods assuming the |
| // conversion methods are present. |
| @Override |
| public void synthesizeClasses( |
| ClassSynthesisDesugaringContext processingContext, |
| CfClassSynthesizerDesugaringEventConsumer eventConsumer) { |
| MachineDesugaredLibrarySpecification librarySpecification = |
| appView.options().machineDesugaredLibrarySpecification; |
| Map<DexProgramClass, Iterable<DexMethod>> validClassesToWrap = new IdentityHashMap<>(); |
| librarySpecification |
| .getWrappers() |
| .forEach( |
| (type, descriptor) -> { |
| 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()) { |
| if (validClassToWrap.isEnum()) { |
| enumConverter.ensureProgramEnumConversionClass(validClassToWrap, eventConsumer); |
| } else { |
| validClassesToWrap.put( |
| validClassToWrap.asProgramClass(), descriptor.getMethods()); |
| synthesizeProgramWrappersWithoutVirtualMethods( |
| validClassToWrap, descriptor.getSubwrappers(), eventConsumer); |
| } |
| } |
| }); |
| validClassesToWrap.forEach( |
| (clazz, methods) -> |
| synthesizeProgramWrappersVirtualMethods( |
| clazz, methods, eventConsumer, processingContext)); |
| } |
| |
| private DexClass getWrapperContext(DexClass context, WrapperKind kind) { |
| // A different context can be specified here, so that the wrapper is prefixed by a different |
| // class than the context. |
| return context; |
| } |
| |
| // We generate first the two wrappers with the constructor method and the fields, then we |
| // the two conversion methods which requires the wrappers to know both fields. |
| private void synthesizeProgramWrappersWithoutVirtualMethods( |
| DexClass context, |
| List<DexType> subwrappers, |
| DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer) { |
| assert eventConsumer != null; |
| assert context.isProgramClass(); |
| DexType type = context.type; |
| DexProgramClass programContext = context.asProgramClass(); |
| DexClass wrapper = |
| ensureProgramWrapper(type, programContext, WrapperKind.WRAPPER, eventConsumer); |
| DexClass vivifiedWrapper = |
| ensureProgramWrapper(type, programContext, WrapperKind.VIVIFIED_WRAPPER, eventConsumer); |
| synthesizeProgramConversionMethod( |
| WrapperKind.WRAPPER, programContext, subwrappers, wrapper, vivifiedWrapper); |
| synthesizeProgramConversionMethod( |
| WrapperKind.VIVIFIED_WRAPPER, programContext, subwrappers, vivifiedWrapper, wrapper); |
| } |
| |
| private void synthesizeProgramWrappersVirtualMethods( |
| DexProgramClass context, |
| Iterable<DexMethod> methods, |
| CfClassSynthesizerDesugaringEventConsumer eventConsumer, |
| ClassSynthesisDesugaringContext processingContext) { |
| DexProgramClass wrapper = getExistingProgramWrapper(context, WrapperKind.WRAPPER); |
| DexEncodedField wrapperField = getWrapperUniqueEncodedField(wrapper); |
| wrapper.addVirtualMethods( |
| synthesizeVirtualMethodsForWrapper( |
| methods, |
| wrapperField, |
| (method, field) -> |
| conversionCfProvider.generateWrapperConversion( |
| method, |
| field, |
| eventConsumer, |
| () -> processingContext.createUniqueContext(wrapper)))); |
| wrapper.addVirtualMethods(synthesizeHashCodeAndEquals(wrapper, wrapperField)); |
| DexProgramClass vivifiedWrapper = |
| getExistingProgramWrapper(context, WrapperKind.VIVIFIED_WRAPPER); |
| DexEncodedField vivifiedWrapperField = getWrapperUniqueEncodedField(vivifiedWrapper); |
| vivifiedWrapper.addVirtualMethods( |
| synthesizeVirtualMethodsForWrapper( |
| methods, |
| vivifiedWrapperField, |
| (method, field) -> |
| conversionCfProvider.generateVivifiedWrapperConversion( |
| method, |
| field, |
| eventConsumer, |
| () -> processingContext.createUniqueContext(wrapper)))); |
| vivifiedWrapper.addVirtualMethods( |
| synthesizeHashCodeAndEquals(vivifiedWrapper, vivifiedWrapperField)); |
| } |
| } |