• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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