blob: 1f7fe89a1f7e155af8a736735f63d0a2adb0ca58 [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.desugaredlibrary.specificationconversion;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
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.MethodAccessFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class LegacyToHumanSpecificationConverter {
private static final String WRAPPER_PREFIX = "__wrapper__.";
private static final AndroidApiLevel LEGACY_HACK_LEVEL = AndroidApiLevel.N_MR1;
private final Timing timing;
private final Set<String> missingClasses = new HashSet<>();
private final Set<String> missingMethods = new HashSet<>();
public LegacyToHumanSpecificationConverter(Timing timing) {
this.timing = timing;
}
public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec, DexApplication app)
throws IOException {
timing.begin("Legacy to human all API convert");
Origin origin = legacySpec.getOrigin();
HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
Map<ApiLevelRange, HumanRewritingFlags> commonFlags =
convertRewritingFlagMap(legacySpec.getCommonFlags(), app, origin);
Map<ApiLevelRange, HumanRewritingFlags> programFlags =
convertRewritingFlagMap(legacySpec.getProgramFlags(), app, origin);
Map<ApiLevelRange, HumanRewritingFlags> libraryFlags =
convertRewritingFlagMap(legacySpec.getLibraryFlags(), app, origin);
legacyLibraryFlagHacks(humanTopLevelFlags.getIdentifier(), libraryFlags, app, origin);
reportWarnings(app.options.reporter);
MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
new MultiAPILevelHumanDesugaredLibrarySpecification(
origin, humanTopLevelFlags, commonFlags, libraryFlags, programFlags);
MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.deduplicateFlags(
humanSpec, app.options.reporter);
timing.end();
return humanSpec;
}
public HumanDesugaredLibrarySpecification convert(
LegacyDesugaredLibrarySpecification legacySpec, DexApplication app) throws IOException {
timing.begin("Legacy to Human convert");
LibraryValidator.validate(
app,
legacySpec.isLibraryCompilation(),
legacySpec.getTopLevelFlags().getRequiredCompilationAPILevel());
HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
// The origin is not maintained in non multi-level specifications.
// It should not matter since the origin is used to report invalid specifications, and
// converting non multi-level specifications should be performed only with *valid*
// specifications in practical cases.
Origin origin = Origin.unknown();
HumanRewritingFlags humanRewritingFlags =
convertRewritingFlags(legacySpec.getRewritingFlags(), app, origin);
if (legacySpec.isLibraryCompilation()) {
timing.begin("Legacy hacks");
HumanRewritingFlags.Builder builder =
humanRewritingFlags.newBuilder(app.options.reporter, origin);
legacyLibraryFlagHacks(
legacySpec.getIdentifier(), app.dexItemFactory(), app.options.getMinApiLevel(), builder);
humanRewritingFlags = builder.build();
timing.end();
}
reportWarnings(app.options.reporter);
timing.end();
return new HumanDesugaredLibrarySpecification(
humanTopLevelFlags, humanRewritingFlags, legacySpec.isLibraryCompilation());
}
private void reportWarnings(Reporter reporter) {
String errorSdk = "This usually means that the compilation SDK is absent or too old.";
if (!missingClasses.isEmpty()) {
reporter.warning(
"Cannot retarget core lib member for missing classes: "
+ missingClasses
+ ". "
+ errorSdk);
}
if (!missingMethods.isEmpty()) {
reporter.warning(
"Should have found a method (library specifications) for "
+ missingMethods
+ ". "
+ errorSdk);
}
}
private void legacyLibraryFlagHacks(
String identifier,
Map<ApiLevelRange, HumanRewritingFlags> libraryFlags,
DexApplication app,
Origin origin) {
ApiLevelRange range = new ApiLevelRange(LEGACY_HACK_LEVEL.getLevel());
HumanRewritingFlags humanRewritingFlags = libraryFlags.get(range);
if (humanRewritingFlags == null) {
// Skip CHM only configuration.
return;
}
HumanRewritingFlags.Builder builder =
humanRewritingFlags.newBuilder(app.options.reporter, origin);
legacyLibraryFlagHacks(identifier, app.dexItemFactory(), LEGACY_HACK_LEVEL, builder);
libraryFlags.put(range, builder.build());
}
private void legacyLibraryFlagHacks(
String identifier,
DexItemFactory itemFactory,
AndroidApiLevel apiLevel,
HumanRewritingFlags.Builder builder) {
if (apiLevel.isLessThanOrEqualTo(LEGACY_HACK_LEVEL)) {
// TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
// to j.u.DesugarArrays.deepEquals0.
DexString name = itemFactory.createString("deepEquals0");
DexProto proto =
itemFactory.createProto(
itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
DexMethod source =
itemFactory.createMethod(
itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
DexType target = itemFactory.createType("Ljava/util/DesugarArrays;");
builder.retargetMethodToType(source, target);
builder.amendLibraryMethod(
source,
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PRIVATE | Constants.ACC_STATIC, false));
// TODO(b/181629049): This is only a workaround rewriting invokes of
// j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
name = itemFactory.createString("getTimeZone");
proto =
itemFactory.createProto(
itemFactory.createType("Ljava/util/TimeZone;"),
itemFactory.createType("Ljava/time/ZoneId;"));
source =
itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
target = itemFactory.createType("Ljava/util/DesugarTimeZone;");
builder.retargetMethodToType(source, target);
}
// Required by
// https://github.com/google/desugar_jdk_libs/commit/485071cd09a3691549d065ba9e323d07edccf085.
if (identifier.contains(":1.2")) {
builder.putRewriteDerivedPrefix(
"sun.misc.Desugar", "jdk.internal.misc.", "j$.sun.misc.Desugar");
}
}
private Map<ApiLevelRange, HumanRewritingFlags> convertRewritingFlagMap(
Int2ObjectMap<LegacyRewritingFlags> libFlags, DexApplication app, Origin origin) {
Map<ApiLevelRange, HumanRewritingFlags> map = new HashMap<>();
libFlags.forEach(
(key, flags) -> map.put(new ApiLevelRange(key), convertRewritingFlags(flags, app, origin)));
return map;
}
private HumanRewritingFlags convertRewritingFlags(
LegacyRewritingFlags flags, DexApplication app, Origin origin) {
timing.begin("Convert rewriting flags");
HumanRewritingFlags.Builder builder = HumanRewritingFlags.builder(app.options.reporter, origin);
flags
.getRewritePrefix()
.forEach((prefix, rewritten) -> rewritePrefix(builder, prefix, rewritten));
flags.getEmulateLibraryInterface().forEach(builder::putEmulatedInterface);
flags.getBackportCoreLibraryMember().forEach(builder::putLegacyBackport);
flags.getCustomConversions().forEach(builder::putCustomConversion);
flags.getDontRetargetLibMember().forEach(builder::addDontRetargetLibMember);
flags.getWrapperConversions().forEach(builder::addWrapperConversion);
flags
.getRetargetCoreLibMember()
.forEach((name, typeMap) -> convertRetargetCoreLibMember(builder, app, name, typeMap));
flags
.getDontRewriteInvocation()
.forEach(pair -> convertDontRewriteInvocation(builder, app, pair));
HumanRewritingFlags humanFlags = builder.build();
timing.end();
return humanFlags;
}
private void rewritePrefix(HumanRewritingFlags.Builder builder, String prefix, String rewritten) {
// Legacy hacks: The human specification matches on class' types so we need different
// rewritings.
if (prefix.startsWith("j$")) {
assert rewritten.startsWith("java");
builder.putRewriteDerivedPrefix(rewritten, prefix, rewritten);
return;
}
if (prefix.equals(WRAPPER_PREFIX)) {
// We hard code here this applies to java.nio and java.io only.
ImmutableMap<String, String> map =
ImmutableMap.of("java.nio.", "j$.nio.", "java.io.", "j$.io.");
map.forEach(
(k, v) -> {
builder.putRewriteDerivedPrefix(k, WRAPPER_PREFIX + k, k);
builder.putRewriteDerivedPrefix(k, WRAPPER_PREFIX + v, v);
});
return;
}
builder.putRewritePrefix(prefix, rewritten);
}
private void convertDontRewriteInvocation(
HumanRewritingFlags.Builder builder, DexApplication app, Pair<DexType, DexString> pair) {
DexClass dexClass = app.definitionFor(pair.getFirst());
assert dexClass != null;
List<DexClassAndMethod> methodsWithName =
findMethodsWithName(pair.getSecond(), dexClass, builder, app);
for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
builder.addDontRewriteInvocation(dexClassAndMethod.getReference());
}
}
private void convertRetargetCoreLibMember(
HumanRewritingFlags.Builder builder,
DexApplication app,
DexString name,
Map<DexType, DexType> typeMap) {
typeMap.forEach(
(type, rewrittenType) -> {
DexClass dexClass = app.definitionFor(type);
if (dexClass == null) {
assert false : "Cannot retarget core lib member for missing class " + type;
missingClasses.add(type.toSourceString());
return;
}
List<DexClassAndMethod> methodsWithName =
findMethodsWithName(name, dexClass, builder, app);
for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
DexEncodedMethod definition = dexClassAndMethod.getDefinition();
if (definition.isStatic()
|| definition.isFinal()
|| dexClassAndMethod.getHolder().isFinal()) {
builder.retargetMethodToType(dexClassAndMethod.getReference(), rewrittenType);
} else {
builder.retargetMethodEmulatedDispatchToType(
dexClassAndMethod.getReference(), rewrittenType);
}
}
});
}
private List<DexClassAndMethod> findMethodsWithName(
DexString methodName,
DexClass clazz,
HumanRewritingFlags.Builder builder,
DexApplication app) {
List<DexClassAndMethod> found = new ArrayList<>();
clazz.forEachClassMethodMatching(definition -> definition.getName() == methodName, found::add);
if (found.isEmpty()
&& methodName.toString().equals("transferTo")
&& clazz.type.toString().equals("java.io.InputStream")) {
// Special hack for JDK11 java.io.InputStream#transferTo which could not be specified
// correctly.
DexItemFactory factory = app.dexItemFactory();
DexProto proto =
factory.createProto(factory.longType, factory.createType("Ljava/io/OutputStream;"));
DexMethod method = factory.createMethod(clazz.type, proto, methodName);
MethodAccessFlags flags =
MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false);
builder.amendLibraryMethod(method, flags);
DexEncodedMethod build =
DexEncodedMethod.builder().setMethod(method).setAccessFlags(flags).build();
return ImmutableList.of(DexClassAndMethod.create(clazz, build));
}
if (found.isEmpty()) {
String warning = clazz.toSourceString() + "." + methodName;
assert false : "Should have found a method (library specifications) for " + warning;
missingMethods.add(warning);
}
return found;
}
private HumanTopLevelFlags convertTopLevelFlags(LegacyTopLevelFlags topLevelFlags) {
return HumanTopLevelFlags.builder()
.setDesugaredLibraryIdentifier(topLevelFlags.getIdentifier())
.setExtraKeepRules(topLevelFlags.getExtraKeepRules())
.setJsonSource(topLevelFlags.getJsonSource())
.setRequiredCompilationAPILevel(topLevelFlags.getRequiredCompilationAPILevel())
.setSupportAllCallbacksFromLibrary(topLevelFlags.supportAllCallbacksFromLibrary())
.setSynthesizedLibraryClassesPackagePrefix(
topLevelFlags.getSynthesizedLibraryClassesPackagePrefix())
.build();
}
}