blob: ae3214b924a698cf3a1677abdf6244540423ec18 [file] [log] [blame]
// Copyright (c) 2022, 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.specificationconversion;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public class HumanToMachinePrefixConverter {
private final AppInfoWithClassHierarchy appInfo;
private final MachineRewritingFlags.Builder builder;
private final String synthesizedPrefix;
private final boolean libraryCompilation;
private final Map<DexString, DexString> descriptorPrefix;
private final Set<DexString> descriptorMaintainPrefix;
private final Set<DexString> descriptorDontRewritePrefix;
private final Map<DexString, Map<DexString, DexString>> descriptorDifferentPrefix;
private final Set<DexString> usedPrefix = Sets.newIdentityHashSet();
private final boolean jdk11Legacy;
public HumanToMachinePrefixConverter(
AppInfoWithClassHierarchy appInfo,
MachineRewritingFlags.Builder builder,
String synthesizedPrefix,
boolean libraryCompilation,
String identifier,
HumanRewritingFlags rewritingFlags) {
this.appInfo = appInfo;
this.builder = builder;
this.synthesizedPrefix = synthesizedPrefix;
this.libraryCompilation = libraryCompilation;
this.descriptorPrefix = convertRewritePrefix(rewritingFlags.getRewritePrefix());
this.descriptorDontRewritePrefix = convertPrefixSet(rewritingFlags.getDontRewritePrefix());
this.descriptorMaintainPrefix = convertPrefixSet(rewritingFlags.getMaintainPrefix());
this.descriptorDifferentPrefix =
convertRewriteDifferentPrefix(rewritingFlags.getRewriteDerivedPrefix());
this.jdk11Legacy = identifier.startsWith("com.tools.android:desugar_jdk_libs:1.2.");
}
public void convertPrefixFlags(
HumanRewritingFlags rewritingFlags, BiConsumer<String, Set<DexString>> warnConsumer) {
rewriteClasses();
rewriteValues(rewritingFlags.getRetargetMethodToType());
rewriteValues(rewritingFlags.getRetargetMethodEmulatedDispatchToType());
rewriteValues(rewritingFlags.getCustomConversions());
rewriteMethodValues(rewritingFlags.getRetargetMethodToMethod());
rewriteMethodValues(rewritingFlags.getRetargetMethodEmulatedDispatchToMethod());
rewriteEmulatedInterface(rewritingFlags.getEmulatedInterfaces());
rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatchToType());
rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatchToMethod());
rewriteApiConversions(rewritingFlags.getApiGenericConversion());
warnIfUnusedPrefix(warnConsumer);
}
private void warnIfUnusedPrefix(BiConsumer<String, Set<DexString>> warnConsumer) {
Set<DexString> unused = Sets.newIdentityHashSet();
unused.addAll(descriptorPrefix.keySet());
unused.addAll(descriptorMaintainPrefix);
unused.addAll(descriptorDifferentPrefix.keySet());
unused.removeAll(usedPrefix);
if (jdk11Legacy) {
// We have to allow duplicate because of the jdk11 legacy configuration, since there is no
// api_greater_or_equal in legacy, we're bound to have duplicates.
// If we have x.y. -> w.z and x. -> w., then if one is used the other is used.
List<DexString> duplicates = new ArrayList<>();
for (DexString prefix : unused) {
descriptorPrefix.forEach(
(k, v) -> {
if (!unused.contains(k) && (k.startsWith(prefix) || prefix.startsWith(k))) {
duplicates.add(prefix);
}
});
}
duplicates.forEach(unused::remove);
}
warnConsumer.accept("The following prefixes do not match any type: ", unused);
}
public DexType convertJavaNameToDesugaredLibrary(DexType type) {
String convertedPrefix = DescriptorUtils.getJavaTypeFromBinaryName(synthesizedPrefix);
String interfaceType = type.toString();
int firstPackage = interfaceType.indexOf('.');
return appInfo
.dexItemFactory()
.createType(
DescriptorUtils.javaTypeToDescriptor(
convertedPrefix + interfaceType.substring(firstPackage + 1)));
}
private void rewriteRetargetKeys(Map<DexMethod, ?> retarget) {
for (DexMethod dexMethod : retarget.keySet()) {
DexType type = convertJavaNameToDesugaredLibrary(dexMethod.holder);
builder.rewriteDerivedTypeOnly(dexMethod.holder, type);
}
}
private void rewriteApiConversions(Map<DexMethod, DexMethod[]> apiGenericConversions) {
apiGenericConversions.forEach(
(k, v) -> {
for (DexMethod dexMethod : v) {
if (dexMethod != null) {
registerType(dexMethod.getHolderType());
}
}
});
}
private void rewriteEmulatedInterface(Map<DexType, DexType> emulateLibraryInterface) {
emulateLibraryInterface.forEach(builder::rewriteDerivedTypeOnly);
}
private void rewriteValues(
Map<?, DexType> flags) {
for (DexType type : flags.values()) {
registerType(type);
}
}
private void rewriteMethodValues(Map<?, DexMethod> flags) {
for (DexMethod method : flags.values()) {
registerType(method.getHolderType());
}
}
private void rewriteClasses() {
appInfo.app().forEachLibraryType(this::registerClassType);
if (libraryCompilation) {
appInfo.app().forEachProgramType(this::registerClassType);
}
}
private void registerClassType(DexType type) {
registerType(type);
registerMaintainType(type);
registerDifferentType(type);
}
private void registerType(DexType type) {
DexType rewrittenType = rewrittenType(type);
if (rewrittenType != null) {
if (prefixMatching(type, descriptorDontRewritePrefix) != null) {
return;
}
builder.rewriteType(type, rewrittenType);
}
}
private void registerMaintainType(DexType type) {
DexString prefix = prefixMatching(type, descriptorMaintainPrefix);
if (prefix == null) {
return;
}
builder.maintainType(type);
// We cannot rely on the synthetic classes being on the same package anyway since the runtime
// may use a class from the Android framework which is considered in a different package even
// if the package name is identical.
// This is required since the Android framework also generates external synthetics for lambdas
// that are publicly visible.
builder.rewriteDerivedTypeOnly(type, convertJavaNameToDesugaredLibrary(type));
usedPrefix.add(prefix);
}
private void registerDifferentType(DexType type) {
DexString prefix = prefixMatching(type, descriptorDifferentPrefix.keySet());
if (prefix == null) {
return;
}
descriptorDifferentPrefix
.get(prefix)
.forEach(
(k, v) -> {
DexString typeDescriptor =
type.descriptor.withNewPrefix(prefix, k, appInfo.dexItemFactory());
DexString rewrittenTypeDescriptor =
type.descriptor.withNewPrefix(prefix, v, appInfo.dexItemFactory());
DexType newKey = appInfo.dexItemFactory().createType(typeDescriptor);
assert appInfo.definitionForWithoutExistenceAssert(newKey) == null
: "Trying to rewrite a type "
+ newKey
+ " with different prefix that already exists.";
builder.rewriteType(
newKey, appInfo.dexItemFactory().createType(rewrittenTypeDescriptor));
});
usedPrefix.add(prefix);
}
private DexString prefixMatching(DexType type, Set<DexString> prefixes) {
DexString prefixToMatch = type.descriptor.withoutArray(appInfo.dexItemFactory());
for (DexString prefix : prefixes) {
if (prefixToMatch.startsWith(prefix)) {
return prefix;
}
}
return null;
}
private DexType rewrittenType(DexType type) {
DexString prefix = prefixMatching(type, descriptorPrefix.keySet());
if (prefix == null) {
return null;
}
DexString rewrittenTypeDescriptor =
type.descriptor.withNewPrefix(
prefix, descriptorPrefix.get(prefix), appInfo.dexItemFactory());
usedPrefix.add(prefix);
return appInfo.dexItemFactory().createType(rewrittenTypeDescriptor);
}
private ImmutableMap<DexString, Map<DexString, DexString>> convertRewriteDifferentPrefix(
Map<String, Map<String, String>> rewriteDerivedPrefix) {
ImmutableMap.Builder<DexString, Map<DexString, DexString>> mapBuilder = ImmutableMap.builder();
for (String key : rewriteDerivedPrefix.keySet()) {
mapBuilder.put(toDescriptorPrefix(key), convertRewritePrefix(rewriteDerivedPrefix.get(key)));
}
return mapBuilder.build();
}
private ImmutableSet<DexString> convertPrefixSet(Set<String> maintainPrefix) {
ImmutableSet.Builder<DexString> builder = ImmutableSet.builder();
for (String prefix : maintainPrefix) {
builder.add(toDescriptorPrefix(prefix));
}
return builder.build();
}
private ImmutableMap<DexString, DexString> convertRewritePrefix(
Map<String, String> rewritePrefix) {
ImmutableMap.Builder<DexString, DexString> mapBuilder = ImmutableMap.builder();
for (String key : rewritePrefix.keySet()) {
mapBuilder.put(toDescriptorPrefix(key), toDescriptorPrefix(rewritePrefix.get(key)));
}
return mapBuilder.build();
}
private DexString toDescriptorPrefix(String prefix) {
return appInfo
.dexItemFactory()
.createString("L" + DescriptorUtils.getBinaryNameFromJavaType(prefix));
}
}