|  | // Copyright (c) 2018, 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; | 
|  |  | 
|  | import com.android.tools.r8.dex.ApplicationReader; | 
|  | import com.android.tools.r8.graph.AppInfoWithSubtyping; | 
|  | import com.android.tools.r8.graph.DexAnnotation; | 
|  | import com.android.tools.r8.graph.DexApplication; | 
|  | import com.android.tools.r8.graph.DexCallSite; | 
|  | import com.android.tools.r8.graph.DexClass; | 
|  | import com.android.tools.r8.graph.DexEncodedField; | 
|  | import com.android.tools.r8.graph.DexEncodedMethod; | 
|  | import com.android.tools.r8.graph.DexField; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.graph.DexProgramClass; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.graph.DexValue; | 
|  | import com.android.tools.r8.graph.DexValue.DexValueArray; | 
|  | import com.android.tools.r8.graph.DexValue.DexValueType; | 
|  | import com.android.tools.r8.graph.ResolutionResult; | 
|  | import com.android.tools.r8.graph.UseRegistry; | 
|  | import com.android.tools.r8.ir.desugar.LambdaDescriptor; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.Timing; | 
|  | import com.google.common.collect.Maps; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.io.IOException; | 
|  | import java.nio.file.Path; | 
|  | import java.nio.file.Paths; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Comparator; | 
|  | import java.util.HashSet; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  |  | 
|  | /** | 
|  | * PrintUses prints the classes, interfaces, methods and fields used by a given program | 
|  | * <sample.jar>, restricted to classes and interfaces in a given library <r8.jar> that | 
|  | * are not in <sample.jar>. | 
|  | * | 
|  | * <p>The output is in the same format as what is printed when specifying {@code -printseeds} in a | 
|  | * ProGuard configuration file. See also the {@link PrintSeeds} program in R8. | 
|  | * | 
|  | * <p>Note that this tool is not related to the {@code -printusage} option of ProGuard configuration | 
|  | * files. | 
|  | */ | 
|  | public class PrintUses { | 
|  |  | 
|  | private static final String USAGE = | 
|  | "Arguments: [--keeprules, --keeprules-allowobfuscation] <rt.jar> <r8.jar> <sample.jar>\n" | 
|  | + "\n" | 
|  | + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n" | 
|  | + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n" | 
|  | + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n" | 
|  | + "\n" | 
|  | + "The output is in the same format as what is printed when specifying -printseeds in\n" | 
|  | + "a ProGuard configuration file. Use --keeprules or --keeprules-allowobfuscation for " | 
|  | + "outputting proguard keep rules. See also the " | 
|  | + PrintSeeds.class.getSimpleName() | 
|  | + " program in R8."; | 
|  |  | 
|  | private final Set<String> descriptors; | 
|  | private final Printer printer; | 
|  | private final boolean allowObfuscation; | 
|  | private Set<DexType> types = Sets.newIdentityHashSet(); | 
|  | private Map<DexType, Set<DexMethod>> methods = Maps.newIdentityHashMap(); | 
|  | private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap(); | 
|  | private Set<DexType> noObfuscationTypes = Sets.newIdentityHashSet(); | 
|  | private final DexApplication application; | 
|  | private final AppInfoWithSubtyping appInfo; | 
|  | private int errors; | 
|  |  | 
|  | class UseCollector extends UseRegistry { | 
|  |  | 
|  | UseCollector(DexItemFactory factory) { | 
|  | super(factory); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInvokeVirtual(DexMethod method) { | 
|  | DexEncodedMethod target = appInfo.lookupVirtualTarget(method.holder, method); | 
|  | if (target != null && target.method != method) { | 
|  | addType(method.holder); | 
|  | addMethod(target.method); | 
|  | } else { | 
|  | addMethod(method); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInvokeDirect(DexMethod method) { | 
|  | addMethod(method); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInvokeStatic(DexMethod method) { | 
|  | DexEncodedMethod target = appInfo.lookupStaticTarget(method); | 
|  | if (target != null && target.method != method) { | 
|  | addType(method.holder); | 
|  | addMethod(target.method); | 
|  | } else { | 
|  | addMethod(method); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInvokeInterface(DexMethod method) { | 
|  | return registerInvokeVirtual(method); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInvokeSuper(DexMethod method) { | 
|  | DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, method.holder); | 
|  | if (superTarget != null) { | 
|  | addMethod(superTarget.method); | 
|  | } else { | 
|  | addMethod(method); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInstanceFieldWrite(DexField field) { | 
|  | addField(field, false); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerInstanceFieldRead(DexField field) { | 
|  | addField(field, false); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerNewInstance(DexType type) { | 
|  | addType(type); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerStaticFieldRead(DexField field) { | 
|  | addField(field, true); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerStaticFieldWrite(DexField field) { | 
|  | addField(field, true); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean registerTypeReference(DexType type) { | 
|  | addType(type); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private void addType(DexType type) { | 
|  | if (isTargetType(type) && types.add(type)) { | 
|  | DexClass clazz = appInfo.definitionFor(type); | 
|  | if (clazz == null | 
|  | || clazz.accessFlags.isVisibilityDependingOnPackage() | 
|  | || !allowObfuscation) { | 
|  | noObfuscationTypes.add(type); | 
|  | } | 
|  | methods.put(type, Sets.newIdentityHashSet()); | 
|  | fields.put(type, Sets.newIdentityHashSet()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean isTargetType(DexType type) { | 
|  | return descriptors.contains(type.toDescriptorString()); | 
|  | } | 
|  |  | 
|  | private void addField(DexField field, boolean isStatic) { | 
|  | addType(field.type); | 
|  | DexEncodedField baseField = | 
|  | isStatic | 
|  | ? appInfo.lookupStaticTarget(field.holder, field) | 
|  | : appInfo.lookupInstanceTarget(field.holder, field); | 
|  | if (baseField != null && baseField.field.holder != field.holder) { | 
|  | field = baseField.field; | 
|  | } | 
|  | addType(field.holder); | 
|  | Set<DexField> typeFields = fields.get(field.holder); | 
|  | if (typeFields != null) { | 
|  | assert baseField != null; | 
|  | if (!allowObfuscation || baseField.accessFlags.isVisibilityDependingOnPackage()) { | 
|  | noObfuscationTypes.add(field.holder); | 
|  | } | 
|  | typeFields.add(field); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void addMethod(DexMethod method) { | 
|  | addType(method.holder); | 
|  | for (DexType parameterType : method.proto.parameters.values) { | 
|  | addType(parameterType); | 
|  | } | 
|  | addType(method.proto.returnType); | 
|  | Set<DexMethod> typeMethods = methods.get(method.holder); | 
|  | if (typeMethods != null) { | 
|  | DexEncodedMethod encodedMethod = appInfo.definitionFor(method); | 
|  | assert encodedMethod != null; | 
|  | if (!allowObfuscation || encodedMethod.accessFlags.isVisibilityDependingOnPackage()) { | 
|  | noObfuscationTypes.add(method.holder); | 
|  | } | 
|  | typeMethods.add(method); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void registerField(DexEncodedField field) { | 
|  | registerTypeReference(field.field.type); | 
|  | } | 
|  |  | 
|  | private void registerMethod(DexEncodedMethod method) { | 
|  | DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder); | 
|  | if (superTarget != null) { | 
|  | addMethod(superTarget.method); | 
|  | } | 
|  | for (DexType type : method.method.proto.parameters.values) { | 
|  | registerTypeReference(type); | 
|  | } | 
|  | for (DexAnnotation annotation : method.annotations.annotations) { | 
|  | if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) { | 
|  | DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value; | 
|  | for (DexValue dexValType : dexValues.getValues()) { | 
|  | registerTypeReference(((DexValueType) dexValType).value); | 
|  | } | 
|  | } | 
|  | } | 
|  | registerTypeReference(method.method.proto.returnType); | 
|  | method.registerCodeReferences(this); | 
|  | } | 
|  |  | 
|  | private void registerSuperType(DexProgramClass clazz, DexType superType) { | 
|  | registerTypeReference(superType); | 
|  | // If clazz overrides any methods in superType, we should keep those as well. | 
|  | clazz.forEachMethod( | 
|  | method -> { | 
|  | ResolutionResult resolutionResult = appInfo.resolveMethod(superType, method.method); | 
|  | for (DexEncodedMethod dexEncodedMethod : resolutionResult.asListOfTargets()) { | 
|  | addMethod(dexEncodedMethod.method); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerCallSite(DexCallSite callSite) { | 
|  | super.registerCallSite(callSite); | 
|  |  | 
|  | // For Lambda's, in order to find the correct use, we need to register the method for the | 
|  | // functional interface. | 
|  | List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo); | 
|  | if (directInterfaces != null) { | 
|  | for (DexType directInterface : directInterfaces) { | 
|  | DexClass clazz = appInfo.definitionFor(directInterface); | 
|  | if (clazz != null) { | 
|  | for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) { | 
|  | if (encodedMethod.method.name.equals(callSite.methodName)) { | 
|  | registerMethod(encodedMethod); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public static void main(String... args) throws Exception { | 
|  | if (args.length != 3 && args.length != 4) { | 
|  | System.out.println(USAGE.replace("\n", System.lineSeparator())); | 
|  | return; | 
|  | } | 
|  | int argumentIndex = 0; | 
|  | boolean printKeep = false; | 
|  | boolean allowObfuscation = false; | 
|  | if (args[0].equals("--keeprules") || args[0].equals("--keeprules-allowobfuscation")) { | 
|  | printKeep = true; | 
|  | argumentIndex++; | 
|  | allowObfuscation = args[0].equals("--keeprules-allowobfuscation"); | 
|  | // Make sure there is only one argument that mentions --keeprules | 
|  | for (int i = 1; i < args.length; i++) { | 
|  | if (args[i].startsWith("-keeprules")) { | 
|  | System.out.println("Use either --keeprules or --keeprules-allowobfuscation, not both."); | 
|  | System.out.println(USAGE.replace("\n", System.lineSeparator())); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | AndroidApp.Builder builder = AndroidApp.builder(); | 
|  | Path rtJar = Paths.get(args[argumentIndex++]); | 
|  | builder.addLibraryFile(rtJar); | 
|  | Path r8Jar = Paths.get(args[argumentIndex++]); | 
|  | builder.addLibraryFile(r8Jar); | 
|  | Path sampleJar = Paths.get(args[argumentIndex]); | 
|  | builder.addProgramFile(sampleJar); | 
|  | Set<String> descriptors = new HashSet<>(getDescriptors(r8Jar)); | 
|  | descriptors.removeAll(getDescriptors(sampleJar)); | 
|  | Printer printer = printKeep ? new KeepPrinter() : new DefaultPrinter(); | 
|  | PrintUses printUses = new PrintUses(descriptors, builder.build(), printer, allowObfuscation); | 
|  | printUses.analyze(); | 
|  | printUses.print(); | 
|  | if (printUses.errors > 0) { | 
|  | System.err.println(printUses.errors + " errors"); | 
|  | System.exit(1); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static Set<String> getDescriptors(Path path) throws IOException { | 
|  | return new ArchiveClassFileProvider(path).getClassDescriptors(); | 
|  | } | 
|  |  | 
|  | private PrintUses( | 
|  | Set<String> descriptors, AndroidApp inputApp, Printer printer, boolean allowObfuscation) | 
|  | throws Exception { | 
|  | this.descriptors = descriptors; | 
|  | this.printer = printer; | 
|  | this.allowObfuscation = allowObfuscation; | 
|  | InternalOptions options = new InternalOptions(); | 
|  | application = | 
|  | new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect(); | 
|  | appInfo = new AppInfoWithSubtyping(application); | 
|  | } | 
|  |  | 
|  | private void analyze() { | 
|  | UseCollector useCollector = new UseCollector(appInfo.dexItemFactory()); | 
|  | for (DexProgramClass dexProgramClass : application.classes()) { | 
|  | useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType); | 
|  | for (DexType implementsType : dexProgramClass.interfaces.values) { | 
|  | useCollector.registerSuperType(dexProgramClass, implementsType); | 
|  | } | 
|  | dexProgramClass.forEachMethod(useCollector::registerMethod); | 
|  | dexProgramClass.forEachField(useCollector::registerField); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void print() { | 
|  | errors = printer.print(application, types, noObfuscationTypes, methods, fields); | 
|  | } | 
|  |  | 
|  | private abstract static class Printer { | 
|  |  | 
|  | void append(String string) { | 
|  | System.out.print(string); | 
|  | } | 
|  |  | 
|  | void appendLine(String string) { | 
|  | System.out.println(string); | 
|  | } | 
|  |  | 
|  | void printArguments(DexMethod method) { | 
|  | append("("); | 
|  | for (int i = 0; i < method.getArity(); i++) { | 
|  | if (i != 0) { | 
|  | append(","); | 
|  | } | 
|  | append(method.proto.parameters.values[i].toSourceString()); | 
|  | } | 
|  | append(")"); | 
|  | } | 
|  |  | 
|  | abstract void printConstructorName(DexEncodedMethod encodedMethod); | 
|  |  | 
|  | void printError(String message) { | 
|  | appendLine("# Error: " + message); | 
|  | } | 
|  |  | 
|  | abstract void printField(DexClass dexClass, DexField field); | 
|  |  | 
|  | abstract void printMethod(DexEncodedMethod encodedMethod, String typeName); | 
|  |  | 
|  | void printNameAndReturn(DexEncodedMethod encodedMethod) { | 
|  | if (encodedMethod.accessFlags.isConstructor()) { | 
|  | printConstructorName(encodedMethod); | 
|  | } else { | 
|  | DexMethod method = encodedMethod.method; | 
|  | append(method.proto.returnType.toSourceString()); | 
|  | append(" "); | 
|  | append(method.name.toSourceString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | abstract void printTypeHeader(DexClass dexClass, boolean allowObfuscation); | 
|  |  | 
|  | abstract void printTypeFooter(); | 
|  |  | 
|  | int print( | 
|  | DexApplication application, | 
|  | Set<DexType> types, | 
|  | Set<DexType> noObfuscationTypes, | 
|  | Map<DexType, Set<DexMethod>> methods, | 
|  | Map<DexType, Set<DexField>> fields) { | 
|  | int errors = 0; | 
|  | List<DexType> sortedTypes = new ArrayList<>(types); | 
|  | sortedTypes.sort(Comparator.comparing(DexType::toSourceString)); | 
|  | for (DexType type : sortedTypes) { | 
|  | DexClass dexClass = application.definitionFor(type); | 
|  | if (dexClass == null) { | 
|  | printError("Could not find definition for type " + type.toSourceString()); | 
|  | errors++; | 
|  | continue; | 
|  | } | 
|  | printTypeHeader(dexClass, !noObfuscationTypes.contains(type)); | 
|  | List<DexEncodedMethod> methodDefinitions = new ArrayList<>(methods.size()); | 
|  | for (DexMethod method : methods.get(type)) { | 
|  | DexEncodedMethod encodedMethod = dexClass.lookupMethod(method); | 
|  | if (encodedMethod == null) { | 
|  | printError("Could not find definition for method " + method.toSourceString()); | 
|  | errors++; | 
|  | continue; | 
|  | } | 
|  | methodDefinitions.add(encodedMethod); | 
|  | } | 
|  | methodDefinitions.sort(Comparator.comparing(x -> x.method.name.toSourceString())); | 
|  | for (DexEncodedMethod encodedMethod : methodDefinitions) { | 
|  | printMethod(encodedMethod, dexClass.type.toSourceString()); | 
|  | } | 
|  | List<DexField> sortedFields = new ArrayList<>(fields.get(type)); | 
|  | sortedFields.sort(Comparator.comparing(DexField::toSourceString)); | 
|  | for (DexField field : sortedFields) { | 
|  | printField(dexClass, field); | 
|  | } | 
|  | printTypeFooter(); | 
|  | } | 
|  | return errors; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class DefaultPrinter extends Printer { | 
|  |  | 
|  | @Override | 
|  | public void printConstructorName(DexEncodedMethod encodedMethod) { | 
|  | if (encodedMethod.accessFlags.isStatic()) { | 
|  | append("<clinit>"); | 
|  | } else { | 
|  | String holderName = encodedMethod.method.holder.toSourceString(); | 
|  | String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1); | 
|  | append(constructorName); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | void printMethod(DexEncodedMethod encodedMethod, String typeName) { | 
|  | append(typeName + ": "); | 
|  | printNameAndReturn(encodedMethod); | 
|  | printArguments(encodedMethod.method); | 
|  | appendLine(""); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | void printTypeHeader(DexClass dexClass, boolean allowObfuscation) { | 
|  | appendLine(dexClass.type.toSourceString()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | void printTypeFooter() {} | 
|  |  | 
|  | @Override | 
|  | void printField(DexClass dexClass, DexField field) { | 
|  | appendLine( | 
|  | dexClass.type.toSourceString() | 
|  | + ": " | 
|  | + field.type.toSourceString() | 
|  | + " " | 
|  | + field.name.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class KeepPrinter extends Printer { | 
|  |  | 
|  | @Override | 
|  | public void printTypeHeader(DexClass dexClass, boolean allowObfuscation) { | 
|  | append(allowObfuscation ? "-keep,allowobfuscation" : "-keep"); | 
|  | if (dexClass.isInterface()) { | 
|  | append(" interface " + dexClass.type.toSourceString() + " {\n"); | 
|  | } else if (dexClass.accessFlags.isEnum()) { | 
|  | append(" enum " + dexClass.type.toSourceString() + " {\n"); | 
|  | } else { | 
|  | append(" class " + dexClass.type.toSourceString() + " {\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void printConstructorName(DexEncodedMethod encodedMethod) { | 
|  | append("<init>"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void printField(DexClass dexClass, DexField field) { | 
|  | append("  " + field.type.toSourceString() + " " + field.name.toString() + ";\n"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void printMethod(DexEncodedMethod encodedMethod, String typeName) { | 
|  | // Static initializers do not require keep rules - it is kept by keeping the class. | 
|  | if (encodedMethod.accessFlags.isConstructor() && encodedMethod.accessFlags.isStatic()) { | 
|  | return; | 
|  | } | 
|  | append("  "); | 
|  | if (encodedMethod.isPublicMethod()) { | 
|  | append("public "); | 
|  | } else if (encodedMethod.isPrivateMethod()) { | 
|  | append("private "); | 
|  | } | 
|  | if (encodedMethod.isStatic()) { | 
|  | append("static "); | 
|  | } | 
|  | printNameAndReturn(encodedMethod); | 
|  | printArguments(encodedMethod.method); | 
|  | appendLine(";"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void printTypeFooter() { | 
|  | appendLine("}"); | 
|  | } | 
|  | } | 
|  | } |