blob: c4884a57e5b54f70a48687e61586cbd3f83cfdfe [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.ClassFileResourceProvider;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.StringResource;
import com.android.tools.r8.dex.ApplicationReader;
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.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.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.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter;
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.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
public class LegacyToHumanSpecificationConverter {
private AndroidApiLevel legacyHackLevel = AndroidApiLevel.N_MR1;
public void convertAllAPILevels(
StringResource inputSpecification, Path androidLib, StringConsumer output)
throws IOException {
InternalOptions options = new InternalOptions();
MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec =
new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
options.dexItemFactory(), options.reporter)
.parseMultiLevelConfiguration(inputSpecification);
MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
convertAllAPILevels(legacySpec, androidLib, options);
MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.deduplicateFlags(
humanSpec, options.dexItemFactory(), options.reporter);
MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(humanSpec, output);
}
public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec,
Path androidLib,
InternalOptions options)
throws IOException {
Origin origin = legacySpec.getOrigin();
AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build();
DexApplication app = readApp(androidApp, options);
LibraryValidator.validate(app, legacySpec.getTopLevelFlags().getRequiredCompilationAPILevel());
HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
Int2ObjectArrayMap<HumanRewritingFlags> commonFlags =
convertRewritingFlagMap(legacySpec.getCommonFlags(), app, origin);
Int2ObjectArrayMap<HumanRewritingFlags> programFlags =
convertRewritingFlagMap(legacySpec.getProgramFlags(), app, origin);
Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags =
convertRewritingFlagMap(legacySpec.getLibraryFlags(), app, origin);
legacyLibraryFlagHacks(libraryFlags, app, origin);
return new MultiAPILevelHumanDesugaredLibrarySpecification(
origin, humanTopLevelFlags, commonFlags, libraryFlags, programFlags);
}
public HumanDesugaredLibrarySpecification convert(
LegacyDesugaredLibrarySpecification legacySpec,
List<ClassFileResourceProvider> library,
InternalOptions options)
throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
for (ClassFileResourceProvider classFileResourceProvider : library) {
builder.addLibraryResourceProvider(classFileResourceProvider);
}
return internalConvert(legacySpec, builder.build(), options);
}
public HumanDesugaredLibrarySpecification convert(
LegacyDesugaredLibrarySpecification legacySpec, Path androidLib, InternalOptions options)
throws IOException {
AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build();
return internalConvert(legacySpec, androidApp, options);
}
public HumanDesugaredLibrarySpecification internalConvert(
LegacyDesugaredLibrarySpecification legacySpec, AndroidApp inputApp, InternalOptions options)
throws IOException {
DexApplication app = readApp(inputApp, options);
LibraryValidator.validate(app, 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 (options.getMinApiLevel().isLessThanOrEqualTo(legacyHackLevel)
&& legacySpec.isLibraryCompilation()) {
HumanRewritingFlags.Builder builder =
humanRewritingFlags.newBuilder(app.dexItemFactory(), app.options.reporter, origin);
legacyLibraryFlagHacks(app.dexItemFactory(), builder);
humanRewritingFlags = builder.build();
}
return new HumanDesugaredLibrarySpecification(
humanTopLevelFlags, humanRewritingFlags, legacySpec.isLibraryCompilation());
}
private void legacyLibraryFlagHacks(
Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags, DexApplication app, Origin origin) {
int level = legacyHackLevel.getLevel();
HumanRewritingFlags humanRewritingFlags = libraryFlags.get(level);
HumanRewritingFlags.Builder builder =
humanRewritingFlags.newBuilder(app.dexItemFactory(), app.options.reporter, origin);
legacyLibraryFlagHacks(app.dexItemFactory(), builder);
libraryFlags.put(level, builder.build());
}
private void legacyLibraryFlagHacks(
DexItemFactory itemFactory, HumanRewritingFlags.Builder builder) {
// 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.putRetargetCoreLibMember(source, target);
// 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.putRetargetCoreLibMember(source, target);
}
private DexApplication readApp(AndroidApp inputApp, InternalOptions options) throws IOException {
ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
ExecutorService executorService = ThreadUtils.getExecutorService(options);
return applicationReader.read(executorService).toDirect();
}
private Int2ObjectArrayMap<HumanRewritingFlags> convertRewritingFlagMap(
Int2ObjectMap<LegacyRewritingFlags> libFlags, DexApplication app, Origin origin) {
Int2ObjectArrayMap<HumanRewritingFlags> map = new Int2ObjectArrayMap<>();
libFlags.forEach((key, flags) -> map.put((int) key, convertRewritingFlags(flags, app, origin)));
return map;
}
private HumanRewritingFlags convertRewritingFlags(
LegacyRewritingFlags flags, DexApplication app, Origin origin) {
HumanRewritingFlags.Builder builder =
HumanRewritingFlags.builder(app.dexItemFactory(), app.options.reporter, origin);
flags.getRewritePrefix().forEach(builder::putRewritePrefix);
flags.getEmulateLibraryInterface().forEach(builder::putEmulateLibraryInterface);
flags.getBackportCoreLibraryMember().forEach(builder::putBackportCoreLibraryMember);
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));
return builder.build();
}
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);
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);
assert dexClass != null;
List<DexClassAndMethod> methodsWithName = findMethodsWithName(name, dexClass);
for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
builder.putRetargetCoreLibMember(dexClassAndMethod.getReference(), rewrittenType);
}
});
}
private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
List<DexClassAndMethod> found = new ArrayList<>();
clazz.forEachClassMethodMatching(definition -> definition.getName() == methodName, found::add);
assert !found.isEmpty()
: "Should have found a method (library specifications) for "
+ clazz.toSourceString()
+ "."
+ methodName
+ ". Maybe the library used for the compilation should be newer.";
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();
}
}