1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.multidex; 18 19 import com.android.dx.cf.direct.DirectClassFile; 20 import com.android.dx.cf.iface.FieldList; 21 import com.android.dx.cf.iface.MethodList; 22 import com.android.dx.rop.cst.Constant; 23 import com.android.dx.rop.cst.CstBaseMethodRef; 24 import com.android.dx.rop.cst.CstFieldRef; 25 import com.android.dx.rop.cst.CstType; 26 import com.android.dx.rop.type.Prototype; 27 import com.android.dx.rop.type.StdTypeList; 28 import com.android.dx.rop.type.TypeList; 29 30 import java.io.FileNotFoundException; 31 import java.io.IOException; 32 import java.util.Enumeration; 33 import java.util.HashSet; 34 import java.util.Set; 35 import java.util.zip.ZipEntry; 36 import java.util.zip.ZipFile; 37 38 /** 39 * Tool to find direct class references to other classes. 40 */ 41 public class ClassReferenceListBuilder { 42 private static final String CLASS_EXTENSION = ".class"; 43 44 private final Path path; 45 private final Set<String> classNames = new HashSet<String>(); 46 47 public ClassReferenceListBuilder(Path path) { 48 this.path = path; 49 } 50 51 /** 52 * Kept for compatibility with the gradle integration, this method just forwards to 53 * {@link MainDexListBuilder#main(String[])}. 54 * @deprecated use {@link MainDexListBuilder#main(String[])} instead. 55 */ 56 @Deprecated 57 public static void main(String[] args) { 58 MainDexListBuilder.main(args); 59 } 60 61 /** 62 * @param jarOfRoots Archive containing the class files resulting of the tracing, typically 63 * this is the result of running ProGuard. 64 */ 65 public void addRoots(ZipFile jarOfRoots) throws IOException { 66 67 // keep roots 68 for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries(); 69 entries.hasMoreElements();) { 70 ZipEntry entry = entries.nextElement(); 71 String name = entry.getName(); 72 if (name.endsWith(CLASS_EXTENSION)) { 73 classNames.add(name.substring(0, name.length() - CLASS_EXTENSION.length())); 74 } 75 } 76 77 // keep direct references of roots (+ direct references hierarchy) 78 for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries(); 79 entries.hasMoreElements();) { 80 ZipEntry entry = entries.nextElement(); 81 String name = entry.getName(); 82 if (name.endsWith(CLASS_EXTENSION)) { 83 DirectClassFile classFile; 84 try { 85 classFile = path.getClass(name); 86 } catch (FileNotFoundException e) { 87 throw new IOException("Class " + name + 88 " is missing form original class path " + path, e); 89 } 90 addDependencies(classFile); 91 } 92 } 93 } 94 95 Set<String> getClassNames() { 96 return classNames; 97 } 98 99 private void addDependencies(DirectClassFile classFile) { 100 for (Constant constant : classFile.getConstantPool().getEntries()) { 101 if (constant instanceof CstType) { 102 checkDescriptor(((CstType) constant).getClassType().getDescriptor()); 103 } else if (constant instanceof CstFieldRef) { 104 checkDescriptor(((CstFieldRef) constant).getType().getDescriptor()); 105 } else if (constant instanceof CstBaseMethodRef) { 106 checkPrototype(((CstBaseMethodRef) constant).getPrototype()); 107 } 108 } 109 110 FieldList fields = classFile.getFields(); 111 int nbField = fields.size(); 112 for (int i = 0; i < nbField; i++) { 113 checkDescriptor(fields.get(i).getDescriptor().getString()); 114 } 115 116 MethodList methods = classFile.getMethods(); 117 int nbMethods = methods.size(); 118 for (int i = 0; i < nbMethods; i++) { 119 checkPrototype(Prototype.intern(methods.get(i).getDescriptor().getString())); 120 } 121 } 122 123 private void checkPrototype(Prototype proto) { 124 checkDescriptor(proto.getReturnType().getDescriptor()); 125 StdTypeList args = proto.getParameterTypes(); 126 for (int i = 0; i < args.size(); i++) { 127 checkDescriptor(args.get(i).getDescriptor()); 128 } 129 } 130 131 private void checkDescriptor(String typeDescriptor) { 132 if (typeDescriptor.endsWith(";")) { 133 int lastBrace = typeDescriptor.lastIndexOf('['); 134 if (lastBrace < 0) { 135 addClassWithHierachy(typeDescriptor.substring(1, typeDescriptor.length()-1)); 136 } else { 137 assert typeDescriptor.length() > lastBrace + 3 138 && typeDescriptor.charAt(lastBrace + 1) == 'L'; 139 addClassWithHierachy(typeDescriptor.substring(lastBrace + 2, 140 typeDescriptor.length() - 1)); 141 } 142 } 143 } 144 145 private void addClassWithHierachy(String classBinaryName) { 146 if (classNames.contains(classBinaryName)) { 147 return; 148 } 149 150 try { 151 DirectClassFile classFile = path.getClass(classBinaryName + CLASS_EXTENSION); 152 classNames.add(classBinaryName); 153 CstType superClass = classFile.getSuperclass(); 154 if (superClass != null) { 155 addClassWithHierachy(superClass.getClassType().getClassName()); 156 } 157 158 TypeList interfaceList = classFile.getInterfaces(); 159 int interfaceNumber = interfaceList.size(); 160 for (int i = 0; i < interfaceNumber; i++) { 161 addClassWithHierachy(interfaceList.getType(i).getClassName()); 162 } 163 } catch (FileNotFoundException e) { 164 // Ignore: The referenced type is not in the path it must be part of the libraries. 165 } 166 } 167 168 } 169