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.direct.StdAttributeFactory; 21 import com.android.dx.rop.cst.Constant; 22 import com.android.dx.rop.cst.ConstantPool; 23 import com.android.dx.rop.cst.CstType; 24 import com.android.dx.rop.type.Type; 25 import com.android.dx.rop.type.TypeList; 26 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.util.ArrayList; 33 import java.util.Enumeration; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Set; 37 import java.util.regex.Pattern; 38 import java.util.zip.ZipEntry; 39 import java.util.zip.ZipException; 40 import java.util.zip.ZipFile; 41 42 /** 43 * This is a command line tool used by mainDexClasses script to find direct class references to 44 * other classes. First argument of the command line is an archive, each class file contained in 45 * this archive is used to identify a class whose references are to be searched, those class files 46 * are not opened by this tool only their names matter. Other arguments must be zip files or 47 * directories, they constitute in a classpath in with the classes named by the first argument 48 * will be searched. Each searched class must be found. On each of this classes are searched for 49 * their dependencies to other classes. Finally the tools prints on standard output a list of class 50 * files names suitable as content of the file argument --main-dex-list of dx. 51 */ 52 public class ClassReferenceListBuilder { 53 54 private static final String CLASS_EXTENSION = ".class"; 55 56 private static final int STATUS_ERROR = 1; 57 58 private static final String EOL = System.getProperty("line.separator"); 59 60 private static String USAGE_MESSAGE = 61 "Usage:" + EOL + EOL + 62 "Short version: Don't use this." + EOL + EOL + 63 "Slightly longer version: This tool is used by mainDexClasses script to find direct" 64 + EOL + 65 "references of some classes." + EOL; 66 67 private Path path; 68 private Set<String> toKeep = new HashSet<String>(); 69 70 /** 71 * 72 * @param inputPath list of path to input jars or folders. Path elements must be separated by 73 * the system path separator: ':' on Unix, ';' on Windows. 74 */ ClassReferenceListBuilder(String inputPath)75 public ClassReferenceListBuilder(String inputPath) throws IOException { 76 this(new Path(inputPath)); 77 } 78 ClassReferenceListBuilder(Path path)79 private ClassReferenceListBuilder(Path path) { 80 this.path = path; 81 } 82 main(String[] args)83 public static void main(String[] args) { 84 85 if (args.length != 2) { 86 printUsage(); 87 System.exit(STATUS_ERROR); 88 } 89 90 ZipFile jarOfRoots; 91 try { 92 jarOfRoots = new ZipFile(args[0]); 93 } catch (IOException e) { 94 System.err.println("\"" + args[0] + "\" can not be read as a zip archive. (" 95 + e.getMessage() + ")"); 96 System.exit(STATUS_ERROR); 97 return; 98 } 99 100 Path path = null; 101 try { 102 path = new Path(args[1]); 103 104 ClassReferenceListBuilder builder = new ClassReferenceListBuilder(path); 105 builder.addRoots(jarOfRoots); 106 107 printList(builder.toKeep); 108 } catch (IOException e) { 109 System.err.println("A fatal error occured: " + e.getMessage()); 110 System.exit(STATUS_ERROR); 111 return; 112 } finally { 113 try { 114 jarOfRoots.close(); 115 } catch (IOException e) { 116 // ignore 117 } 118 if (path != null) { 119 for (ClassPathElement element : path.elements) { 120 try { 121 element.close(); 122 } catch (IOException e) { 123 // keep going, lets do our best. 124 } 125 } 126 } 127 } 128 } 129 130 /** 131 * @param jarOfRoots Archive containing the class files resulting of the tracing, typically 132 * this is the result of running ProGuard. 133 */ addRoots(ZipFile jarOfRoots)134 public void addRoots(ZipFile jarOfRoots) throws IOException { 135 136 // keep roots 137 for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries(); 138 entries.hasMoreElements();) { 139 ZipEntry entry = entries.nextElement(); 140 String name = entry.getName(); 141 if (name.endsWith(CLASS_EXTENSION)) { 142 toKeep.add(name.substring(0, name.length() - CLASS_EXTENSION.length())); 143 } 144 } 145 146 // keep direct references of roots (+ direct references hierarchy) 147 for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries(); 148 entries.hasMoreElements();) { 149 ZipEntry entry = entries.nextElement(); 150 String name = entry.getName(); 151 if (name.endsWith(CLASS_EXTENSION)) { 152 DirectClassFile classFile; 153 try { 154 classFile = path.getClass(name); 155 } catch (FileNotFoundException e) { 156 throw new IOException("Class " + name + 157 " is missing form original class path " + path, e); 158 } 159 160 addDependencies(classFile.getConstantPool()); 161 } 162 } 163 } 164 165 /** 166 * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list. 167 */ getMainDexList()168 public Set<String> getMainDexList() { 169 Set<String> resultSet = new HashSet<String>(toKeep.size()); 170 for (String classDescriptor : toKeep) { 171 resultSet.add(classDescriptor + CLASS_EXTENSION); 172 } 173 174 return resultSet; 175 } 176 printUsage()177 private static void printUsage() { 178 System.err.print(USAGE_MESSAGE); 179 } 180 getClassPathElement(File file)181 private static ClassPathElement getClassPathElement(File file) 182 throws ZipException, IOException { 183 if (file.isDirectory()) { 184 return new FolderPathElement(file); 185 } else if (file.isFile()) { 186 return new ArchivePathElement(new ZipFile(file)); 187 } else if (file.exists()) { 188 throw new IOException(file.getAbsolutePath() + 189 " is not a directory neither a zip file"); 190 } else { 191 throw new FileNotFoundException(file.getAbsolutePath()); 192 } 193 } 194 printList(Set<String> toKeep)195 private static void printList(Set<String> toKeep) { 196 for (String classDescriptor : toKeep) { 197 System.out.print(classDescriptor); 198 System.out.println(CLASS_EXTENSION); 199 } 200 } 201 addDependencies(ConstantPool pool)202 private void addDependencies(ConstantPool pool) { 203 for (Constant constant : pool.getEntries()) { 204 if (constant instanceof CstType) { 205 Type type = ((CstType) constant).getClassType(); 206 String descriptor = type.getDescriptor(); 207 if (descriptor.endsWith(";")) { 208 int lastBrace = descriptor.lastIndexOf('['); 209 if (lastBrace < 0) { 210 addClassWithHierachy(descriptor.substring(1, descriptor.length()-1)); 211 } else { 212 assert descriptor.length() > lastBrace + 3 213 && descriptor.charAt(lastBrace + 1) == 'L'; 214 addClassWithHierachy(descriptor.substring(lastBrace + 2, 215 descriptor.length() - 1)); 216 } 217 } 218 } 219 } 220 } 221 addClassWithHierachy(String classBinaryName)222 private void addClassWithHierachy(String classBinaryName) { 223 if (toKeep.contains(classBinaryName)) { 224 return; 225 } 226 227 String fileName = classBinaryName + CLASS_EXTENSION; 228 try { 229 DirectClassFile classFile = path.getClass(fileName); 230 toKeep.add(classBinaryName); 231 CstType superClass = classFile.getSuperclass(); 232 if (superClass != null) { 233 addClassWithHierachy(superClass.getClassType().getClassName()); 234 } 235 236 TypeList interfaceList = classFile.getInterfaces(); 237 int interfaceNumber = interfaceList.size(); 238 for (int i = 0; i < interfaceNumber; i++) { 239 addClassWithHierachy(interfaceList.getType(i).getClassName()); 240 } 241 } catch (FileNotFoundException e) { 242 // Ignore: The referenced type is not in the path it must be part of the libraries. 243 } 244 } 245 246 private static class Path { 247 private List<ClassPathElement> elements = new ArrayList<ClassPathElement>(); 248 private String definition; 249 private ByteArrayOutputStream baos = new ByteArrayOutputStream(40 * 1024); 250 private byte[] readBuffer = new byte[20 * 1024]; 251 Path(String definition)252 private Path(String definition) throws IOException { 253 this.definition = definition; 254 for (String filePath : definition.split(Pattern.quote(File.pathSeparator))) { 255 try { 256 addElement(getClassPathElement(new File(filePath))); 257 } catch (IOException e) { 258 throw new IOException("\"" + filePath + "\" can not be used as a classpath" 259 + " element. (" 260 + e.getMessage() + ")", e); 261 } 262 } 263 } 264 readStream(InputStream in, ByteArrayOutputStream baos, byte[] readBuffer)265 private static byte[] readStream(InputStream in, ByteArrayOutputStream baos, byte[] readBuffer) 266 throws IOException { 267 try { 268 for (;;) { 269 int amt = in.read(readBuffer); 270 if (amt < 0) { 271 break; 272 } 273 274 baos.write(readBuffer, 0, amt); 275 } 276 } finally { 277 in.close(); 278 } 279 return baos.toByteArray(); 280 } 281 282 @Override toString()283 public String toString() { 284 return definition; 285 } 286 addElement(ClassPathElement element)287 private void addElement(ClassPathElement element) { 288 assert element != null; 289 elements.add(element); 290 } 291 getClass(String path)292 private DirectClassFile getClass(String path) throws FileNotFoundException { 293 DirectClassFile classFile = null; 294 for (ClassPathElement element : elements) { 295 try { 296 InputStream in = element.open(path); 297 try { 298 byte[] bytes = readStream(in, baos, readBuffer); 299 baos.reset(); 300 classFile = new DirectClassFile(bytes, path, false); 301 classFile.setAttributeFactory(StdAttributeFactory.THE_ONE); 302 break; 303 } finally { 304 in.close(); 305 } 306 } catch (IOException e) { 307 // search next element 308 } 309 } 310 if (classFile == null) { 311 throw new FileNotFoundException(path); 312 } 313 return classFile; 314 } 315 } 316 317 318 } 319