blob: bd26b562f28ba60d46ff17896b3a18ba5ad3af2b [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.StringConsumer;
import com.android.tools.r8.StringResource;
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.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.InternalOptions;
import com.android.tools.r8.utils.Pair;
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.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;
public class LegacyToHumanSpecificationConverter {
private static final String WRAPPER_PREFIX = "__wrapper__.";
private static final AndroidApiLevel LEGACY_HACK_LEVEL = AndroidApiLevel.N_MR1;
private final Timing timing;
public LegacyToHumanSpecificationConverter(Timing timing) {
this.timing = timing;
}
public void convertAllAPILevels(
StringResource inputSpecification,
Path desugaredJDKLib,
Path androidLib,
StringConsumer output)
throws IOException {
InternalOptions options = new InternalOptions();
MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec =
new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
options.dexItemFactory(), options.reporter)
.parseMultiLevelConfiguration(inputSpecification);
MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
convertAllAPILevels(legacySpec, desugaredJDKLib, androidLib, options);
MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(humanSpec, output);
}
public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec,
Path desugaredJDKLib,
Path androidLib,
InternalOptions options)
throws IOException {
timing.begin("Legacy to human all API convert");
Origin origin = legacySpec.getOrigin();
DexApplication app =
AppForSpecConversion.readAppForTesting(desugaredJDKLib, androidLib, options, true, timing);
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);
MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
new MultiAPILevelHumanDesugaredLibrarySpecification(
origin, humanTopLevelFlags, commonFlags, libraryFlags, programFlags);
MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.deduplicateFlags(
humanSpec, options.reporter);
timing.end();
return humanSpec;
}
public HumanDesugaredLibrarySpecification convertForTesting(
LegacyDesugaredLibrarySpecification legacySpec,
Path desugaredJDKLib,
Path androidLib,
InternalOptions options)
throws IOException {
DexApplication app =
AppForSpecConversion.readAppForTesting(
desugaredJDKLib, androidLib, options, legacySpec.isLibraryCompilation(), timing);
return convert(legacySpec, app);
}
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 (app.options.getMinApiLevel().isLessThanOrEqualTo(LEGACY_HACK_LEVEL)
&& legacySpec.isLibraryCompilation()) {
timing.begin("Legacy hacks");
HumanRewritingFlags.Builder builder =
humanRewritingFlags.newBuilder(app.options.reporter, origin);
legacyLibraryFlagHacks(app.dexItemFactory(), builder);
humanRewritingFlags = builder.build();
timing.end();
}
timing.end();
return new HumanDesugaredLibrarySpecification(
humanTopLevelFlags, humanRewritingFlags, legacySpec.isLibraryCompilation());
}
private void legacyLibraryFlagHacks(
Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags, DexApplication app, Origin origin) {
int level = LEGACY_HACK_LEVEL.getLevel();
HumanRewritingFlags humanRewritingFlags = libraryFlags.get(level);
if (humanRewritingFlags == null) {
// Skip CHM only configuration.
return;
}
HumanRewritingFlags.Builder builder =
humanRewritingFlags.newBuilder(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.retargetMethod(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.retargetMethod(source, target);
}
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) {
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);
assert dexClass != null;
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.retargetMethod(dexClassAndMethod.getReference(), rewrittenType);
} else {
builder.retargetMethodEmulatedDispatch(
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));
}
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();
}
}