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 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.util.Enumeration;
32 import java.util.HashSet;
33 import java.util.Set;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipFile;
36 
37 /**
38  * Tool to find direct class references to other classes.
39  */
40 public class ClassReferenceListBuilder {
41     private static final String CLASS_EXTENSION = ".class";
42 
43     private final Path path;
44     private final Set<String> classNames = new HashSet<String>();
45 
ClassReferenceListBuilder(Path path)46     public ClassReferenceListBuilder(Path path) {
47         this.path = path;
48     }
49 
50     /**
51      * Kept for compatibility with the gradle integration, this method just forwards to
52      * {@link MainDexListBuilder#main(String[])}.
53      * @deprecated use {@link MainDexListBuilder#main(String[])} instead.
54      */
55     @Deprecated
main(String[] args)56     public static void main(String[] args) {
57         MainDexListBuilder.main(args);
58     }
59 
60     /**
61      * @param jarOfRoots Archive containing the class files resulting of the tracing, typically
62      * this is the result of running ProGuard.
63      */
addRoots(ZipFile jarOfRoots)64     public void addRoots(ZipFile jarOfRoots) throws IOException {
65 
66         // keep roots
67         for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
68                 entries.hasMoreElements();) {
69             ZipEntry entry = entries.nextElement();
70             String name = entry.getName();
71             if (name.endsWith(CLASS_EXTENSION)) {
72                 classNames.add(name.substring(0, name.length() - CLASS_EXTENSION.length()));
73             }
74         }
75 
76         // keep direct references of roots (+ direct references hierarchy)
77         for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
78                 entries.hasMoreElements();) {
79             ZipEntry entry = entries.nextElement();
80             String name = entry.getName();
81             if (name.endsWith(CLASS_EXTENSION)) {
82                 DirectClassFile classFile;
83                 try {
84                     classFile = path.getClass(name);
85                 } catch (FileNotFoundException e) {
86                     throw new IOException("Class " + name +
87                             " is missing form original class path " + path, e);
88                 }
89                 addDependencies(classFile);
90             }
91         }
92     }
93 
getClassNames()94     Set<String> getClassNames() {
95         return classNames;
96     }
97 
addDependencies(DirectClassFile classFile)98     private void addDependencies(DirectClassFile classFile) {
99         for (Constant constant : classFile.getConstantPool().getEntries()) {
100             if (constant instanceof CstType) {
101                 checkDescriptor(((CstType) constant).getClassType().getDescriptor());
102             } else if (constant instanceof CstFieldRef) {
103                 checkDescriptor(((CstFieldRef) constant).getType().getDescriptor());
104             } else if (constant instanceof CstBaseMethodRef) {
105                 checkPrototype(((CstBaseMethodRef) constant).getPrototype());
106             }
107         }
108 
109         FieldList fields = classFile.getFields();
110         int nbField = fields.size();
111         for (int i = 0; i < nbField; i++) {
112           checkDescriptor(fields.get(i).getDescriptor().getString());
113         }
114 
115         MethodList methods = classFile.getMethods();
116         int nbMethods = methods.size();
117         for (int i = 0; i < nbMethods; i++) {
118           checkPrototype(Prototype.intern(methods.get(i).getDescriptor().getString()));
119         }
120     }
121 
checkPrototype(Prototype proto)122     private void checkPrototype(Prototype proto) {
123       checkDescriptor(proto.getReturnType().getDescriptor());
124       StdTypeList args = proto.getParameterTypes();
125       for (int i = 0; i < args.size(); i++) {
126           checkDescriptor(args.get(i).getDescriptor());
127       }
128     }
129 
checkDescriptor(String typeDescriptor)130     private void checkDescriptor(String typeDescriptor) {
131         if (typeDescriptor.endsWith(";")) {
132             int lastBrace = typeDescriptor.lastIndexOf('[');
133             if (lastBrace < 0) {
134                 addClassWithHierachy(typeDescriptor.substring(1, typeDescriptor.length()-1));
135             } else {
136                 assert typeDescriptor.length() > lastBrace + 3
137                 && typeDescriptor.charAt(lastBrace + 1) == 'L';
138                 addClassWithHierachy(typeDescriptor.substring(lastBrace + 2,
139                         typeDescriptor.length() - 1));
140             }
141         }
142     }
143 
addClassWithHierachy(String classBinaryName)144     private void addClassWithHierachy(String classBinaryName) {
145         if (classNames.contains(classBinaryName)) {
146             return;
147         }
148 
149         try {
150             DirectClassFile classFile = path.getClass(classBinaryName + CLASS_EXTENSION);
151             classNames.add(classBinaryName);
152             CstType superClass = classFile.getSuperclass();
153             if (superClass != null) {
154                 addClassWithHierachy(superClass.getClassType().getClassName());
155             }
156 
157             TypeList interfaceList = classFile.getInterfaces();
158             int interfaceNumber = interfaceList.size();
159             for (int i = 0; i < interfaceNumber; i++) {
160                 addClassWithHierachy(interfaceList.getType(i).getClassName());
161             }
162         } catch (FileNotFoundException e) {
163             // Ignore: The referenced type is not in the path it must be part of the libraries.
164         }
165     }
166 
167 }
168