| // 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.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) { |
| 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) { |
| 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(); |
| } |
| } |