| // Copyright (c) 2019, 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.dex; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexString; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.google.common.collect.Sets; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| public abstract class CodeToKeep { |
| |
| static CodeToKeep createCodeToKeep(InternalOptions options, NamingLens namingLens) { |
| if ((!namingLens.hasPrefixRewritingLogic() |
| && !options.desugaredLibraryConfiguration.hasEmulatedLibraryInterfaces()) |
| || options.isDesugaredLibraryCompilation() |
| || options.testing.enableExperimentalDesugaredLibraryKeepRuleGenerator) { |
| return new NopCodeToKeep(); |
| } |
| return new DesugaredLibraryCodeToKeep(namingLens, options); |
| } |
| |
| abstract void recordMethod(DexMethod method); |
| |
| abstract void recordField(DexField field); |
| |
| abstract void recordClass(DexType type); |
| |
| abstract void recordClassAllAccesses(DexType type); |
| |
| abstract void recordHierarchyOf(DexProgramClass clazz); |
| |
| abstract boolean isNop(); |
| |
| abstract void generateKeepRules(InternalOptions options); |
| |
| public static class DesugaredLibraryCodeToKeep extends CodeToKeep { |
| |
| private static class KeepStruct { |
| |
| Set<DexField> fields = Sets.newConcurrentHashSet(); |
| Set<DexMethod> methods = Sets.newConcurrentHashSet(); |
| boolean all = false; |
| } |
| |
| private final NamingLens namingLens; |
| private final Set<DexType> potentialTypesToKeep = Sets.newIdentityHashSet(); |
| private final Map<DexType, KeepStruct> toKeep = new ConcurrentHashMap<>(); |
| private final InternalOptions options; |
| |
| public DesugaredLibraryCodeToKeep(NamingLens namingLens, InternalOptions options) { |
| this.namingLens = namingLens; |
| this.options = options; |
| potentialTypesToKeep.addAll( |
| options.desugaredLibraryConfiguration.getEmulateLibraryInterface().values()); |
| potentialTypesToKeep.addAll( |
| options.desugaredLibraryConfiguration.getCustomConversions().values()); |
| } |
| |
| private boolean shouldKeep(DexType type) { |
| return namingLens.prefixRewrittenType(type) != null |
| || potentialTypesToKeep.contains(type) |
| // TODO(b/158632510): This should prefix match on DexString. |
| || type.toDescriptorString() |
| .startsWith( |
| "L" |
| + options.desugaredLibraryConfiguration |
| .getSynthesizedLibraryClassesPackagePrefix()); |
| } |
| |
| @Override |
| void recordMethod(DexMethod method) { |
| DexType baseType = method.holder.toBaseType(options.dexItemFactory()); |
| if (shouldKeep(baseType)) { |
| keepClass(baseType); |
| if (!method.holder.isArrayType()) { |
| toKeep.get(method.holder).methods.add(method); |
| } |
| } |
| if (shouldKeep(method.proto.returnType)) { |
| keepClass(method.proto.returnType); |
| } |
| for (DexType type : method.proto.parameters.values) { |
| if (shouldKeep(type)) { |
| keepClass(type); |
| } |
| } |
| } |
| |
| @Override |
| void recordField(DexField field) { |
| DexType baseType = field.holder.toBaseType(options.dexItemFactory()); |
| if (shouldKeep(baseType)) { |
| keepClass(baseType); |
| if (!field.holder.isArrayType()) { |
| toKeep.get(field.holder).fields.add(field); |
| } |
| } |
| if (shouldKeep(field.type)) { |
| keepClass(field.type); |
| } |
| } |
| |
| @Override |
| void recordClass(DexType type) { |
| if (shouldKeep(type)) { |
| keepClass(type); |
| } |
| } |
| |
| @Override |
| void recordClassAllAccesses(DexType type) { |
| if (shouldKeep(type)) { |
| keepClass(type); |
| toKeep.get(type).all = true; |
| } |
| } |
| |
| @Override |
| void recordHierarchyOf(DexProgramClass clazz) { |
| recordClassAllAccesses(clazz.superType); |
| for (DexType itf : clazz.interfaces.values) { |
| recordClassAllAccesses(itf); |
| } |
| } |
| |
| private void keepClass(DexType type) { |
| DexType baseType = type.lookupBaseType(options.itemFactory); |
| toKeep.putIfAbsent(baseType, new KeepStruct()); |
| } |
| |
| @Override |
| boolean isNop() { |
| return false; |
| } |
| |
| private String convertType(DexType type) { |
| DexString rewriteType = namingLens.prefixRewrittenType(type); |
| DexString descriptor = rewriteType != null ? rewriteType : type.descriptor; |
| return DescriptorUtils.descriptorToJavaType(descriptor.toString()); |
| } |
| |
| @Override |
| void generateKeepRules(InternalOptions options) { |
| // TODO(b/134734081): Stream the consumer instead of building the String. |
| StringBuilder sb = new StringBuilder(); |
| String cr = System.lineSeparator(); |
| for (DexType type : toKeep.keySet()) { |
| KeepStruct keepStruct = toKeep.get(type); |
| sb.append("-keep class ").append(convertType(type)); |
| if (keepStruct.all) { |
| sb.append(" { *; }").append(cr); |
| continue; |
| } |
| if (keepStruct.fields.isEmpty() && keepStruct.methods.isEmpty()) { |
| sb.append(cr); |
| continue; |
| } |
| sb.append(" {").append(cr); |
| for (DexField field : keepStruct.fields) { |
| sb.append(" ") |
| .append(convertType(field.type)) |
| .append(" ") |
| .append(field.name) |
| .append(";") |
| .append(cr); |
| } |
| for (DexMethod method : keepStruct.methods) { |
| sb.append(" ") |
| .append(convertType(method.proto.returnType)) |
| .append(" ") |
| .append(method.name) |
| .append("("); |
| for (int i = 0; i < method.getArity(); i++) { |
| if (i != 0) { |
| sb.append(", "); |
| } |
| sb.append(convertType(method.proto.parameters.values[i])); |
| } |
| sb.append(");").append(cr); |
| } |
| sb.append("}").append(cr); |
| } |
| options.desugaredLibraryKeepRuleConsumer.accept(sb.toString(), options.reporter); |
| options.desugaredLibraryKeepRuleConsumer.finished(options.reporter); |
| } |
| } |
| |
| public static class NopCodeToKeep extends CodeToKeep { |
| |
| @Override |
| void recordMethod(DexMethod method) {} |
| |
| @Override |
| void recordField(DexField field) {} |
| |
| @Override |
| void recordClass(DexType type) {} |
| |
| @Override |
| void recordClassAllAccesses(DexType type) {} |
| |
| @Override |
| void recordHierarchyOf(DexProgramClass clazz) {} |
| |
| @Override |
| boolean isNop() { |
| return true; |
| } |
| |
| @Override |
| void generateKeepRules(InternalOptions options) { |
| throw new Unreachable("Has no keep rules to generate"); |
| } |
| } |
| } |