1 /*
2  * Copyright (C) 2010 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.apkcheck;
18 
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.Set;
24 
25 /**
26  * Container representing a class or interface with fields and methods.
27  */
28 public class ClassInfo {
29     private String mName;
30     // methods are hashed on name:descriptor
31     private HashMap<String,MethodInfo> mMethodList;
32     // fields are hashed on name:type
33     private HashMap<String,FieldInfo> mFieldList;
34 
35     private String mSuperclassName;
36 
37     // is this a static inner class?
38     private String mIsStatic;
39 
40     // holds the name of the superclass and all declared interfaces
41     private ArrayList<String> mSuperNames;
42 
43     // is this an enumerated type?
44     private boolean mIsEnum;
45     // is this an annotation type?
46     private boolean mIsAnnotation;
47 
48     private boolean mFlattening = false;
49     private boolean mFlattened = false;
50 
51     /**
52      * Constructs a new ClassInfo with the provided class name.
53      *
54      * @param className Binary class name without the package name,
55      *      e.g. "AlertDialog$Builder".
56      * @param superclassName Fully-qualified binary or non-binary superclass
57      *      name (e.g. "java.lang.Enum").
58      * @param isStatic Class static attribute, may be "true", "false", or null.
59      */
ClassInfo(String className, String superclassName, String isStatic)60     public ClassInfo(String className, String superclassName, String isStatic) {
61         mName = className;
62         mMethodList = new HashMap<String,MethodInfo>();
63         mFieldList = new HashMap<String,FieldInfo>();
64         mSuperNames = new ArrayList<String>();
65         mIsStatic = isStatic;
66 
67         /*
68          * Record the superclass name, and add it to the interface list
69          * since we'll need to do the same "flattening" work on it.
70          *
71          * Interfaces and java.lang.Object have a null value.
72          */
73         if (superclassName != null) {
74             mSuperclassName = superclassName;
75             mSuperNames.add(superclassName);
76         }
77     }
78 
79     /**
80      * Returns the name of the class.
81      */
getName()82     public String getName() {
83         return mName;
84     }
85 
86     /**
87      * Returns the name of the superclass.
88      */
getSuperclassName()89     public String getSuperclassName() {
90         return mSuperclassName;
91     }
92 
93     /**
94      * Returns the "static" attribute.
95      *
96      * This is actually tri-state:
97      *   "true" means it is static
98      *   "false" means it's not static
99      *   null means it's unknown
100      *
101      * The "unknown" state is associated with the APK input, while the
102      * known states are from the public API definition.
103      *
104      * This relates to the handling of the "secret" first parameter to
105      * constructors of non-static inner classes.
106      */
getStatic()107     public String getStatic() {
108         return mIsStatic;
109     }
110 
111     /**
112      * Returns whether or not this class is an enumerated type.
113      */
isEnum()114     public boolean isEnum() {
115         assert mFlattened;
116         return mIsEnum;
117     }
118 
119     /**
120      * Returns whether or not this class is an annotation type.
121      */
isAnnotation()122     public boolean isAnnotation() {
123         assert mFlattened;
124         return mIsAnnotation;
125     }
126 
127     /**
128      * Adds a field to the list.
129      */
addField(FieldInfo fieldInfo)130     public void addField(FieldInfo fieldInfo) {
131         mFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
132     }
133 
134     /**
135      * Retrives a field from the list.
136      *
137      * @param nameAndType fieldName:type
138      */
getField(String nameAndType)139     public FieldInfo getField(String nameAndType) {
140         return mFieldList.get(nameAndType);
141     }
142 
143     /**
144      * Returns an iterator over all known fields.
145      */
getFieldIterator()146     public Iterator<FieldInfo> getFieldIterator() {
147         return mFieldList.values().iterator();
148     }
149 
150     /**
151      * Adds a method to the list.
152      */
addMethod(MethodInfo methInfo)153     public void addMethod(MethodInfo methInfo) {
154         mMethodList.put(methInfo.getNameAndDescriptor(), methInfo);
155     }
156 
157     /**
158      * Returns an iterator over all known methods.
159      */
getMethodIterator()160     public Iterator<MethodInfo> getMethodIterator() {
161         return mMethodList.values().iterator();
162     }
163 
164     /**
165      * Retrieves a method from the list.
166      *
167      * @param nameAndDescr methodName:descriptor
168      */
getMethod(String nameAndDescr)169     public MethodInfo getMethod(String nameAndDescr) {
170         return mMethodList.get(nameAndDescr);
171     }
172 
173     /**
174      * Retrieves a method from the list, matching on the part of the key
175      * before the return type.
176      *
177      * The API file doesn't include an entry for a method that overrides
178      * a method in the superclass.  Ordinarily this is a good thing, but
179      * if the override uses a covariant return type then the reference
180      * to it in the APK won't match.
181      *
182      * @param nameAndDescr methodName:descriptor
183      */
getMethodIgnoringReturn(String nameAndDescr)184     public MethodInfo getMethodIgnoringReturn(String nameAndDescr) {
185         String shortKey = nameAndDescr.substring(0, nameAndDescr.indexOf(')')+1);
186 
187         Iterator<MethodInfo> iter = getMethodIterator();
188         while (iter.hasNext()) {
189             MethodInfo methInfo = iter.next();
190             String nad = methInfo.getNameAndDescriptor();
191             if (nad.startsWith(shortKey))
192                 return methInfo;
193         }
194 
195         return null;
196     }
197 
198     /**
199      * Returns true if the method and field lists are empty.
200      */
hasNoFieldMethod()201     public boolean hasNoFieldMethod() {
202         return mMethodList.size() == 0 && mFieldList.size() == 0;
203     }
204 
205     /**
206      * Adds an interface to the list of classes implemented by this class.
207      */
addInterface(String interfaceName)208     public void addInterface(String interfaceName) {
209         mSuperNames.add(interfaceName);
210     }
211 
212     /**
213      * Flattens a class.  This involves copying all methods and fields
214      * declared by the superclass and interfaces (and, recursively, their
215      * superclasses and interfaces) into the local structure.
216      *
217      * The public API file must be fully parsed before calling here.
218      *
219      * This also detects if we're an Enum or Annotation.
220      */
flattenClass(ApiList apiList)221     public void flattenClass(ApiList apiList) {
222         if (mFlattened)
223             return;
224 
225         /*
226          * Recursive class definitions aren't allowed in Java code, but
227          * there could be one in the API definition file.
228          */
229         if (mFlattening) {
230             throw new RuntimeException("Recursive invoke; current class is "
231                 + mName);
232         }
233         mFlattening = true;
234 
235         /*
236          * Normalize the ambiguous types.  This requires regenerating the
237          * field and method lists, because the signature is used as the
238          * hash table key.
239          */
240         normalizeTypes(apiList);
241 
242         /*
243          * Figure out if this class is an enumerated type.
244          */
245         mIsEnum = "java.lang.Enum".equals(mSuperclassName);
246 
247         /*
248          * Figure out if this class is an annotation type.  We expect it
249          * to extend Object, implement java.lang.annotation.Annotation,
250          * and declare no fields or methods.  (If the API XML file is
251          * fixed, it will declare methods; but at that point having special
252          * handling for annotations will be unnecessary.)
253          */
254         if ("java.lang.Object".equals(mSuperclassName) &&
255             mSuperNames.contains("java.lang.annotation.Annotation") &&
256             hasNoFieldMethod())
257         {
258             mIsAnnotation = true;
259         }
260 
261         /*
262          * Flatten our superclass and interfaces.
263          */
264         for (int i = 0; i < mSuperNames.size(); i++) {
265             /*
266              * The contents of mSuperNames are in an ambiguous form.
267              * Normalize it to binary form before working with it.
268              */
269             String interfaceName = TypeUtils.ambiguousToBinaryName(mSuperNames.get(i),
270                     apiList);
271             ClassInfo classInfo = lookupClass(interfaceName, apiList);
272             if (classInfo == null) {
273                 ApkCheck.apkWarning("Class " + interfaceName +
274                     " not found (super of " + mName + ")");
275                 continue;
276             }
277 
278             /* flatten it */
279             classInfo.flattenClass(apiList);
280 
281             /* copy everything from it in here */
282             mergeFrom(classInfo);
283         }
284 
285         mFlattened = true;
286     }
287 
288     /**
289      * Normalizes the type names used in field and method descriptors.
290      *
291      * We call the field/method normalization function, which updates how
292      * it thinks of itself (and may be called multiple times from different
293      * classes).  We then have to re-add it to the hash map because the
294      * key may have changed.  (We're using an iterator, so we create a
295      * new hashmap and replace the old.)
296      */
normalizeTypes(ApiList apiList)297     private void normalizeTypes(ApiList apiList) {
298         Iterator<String> keyIter;
299 
300         HashMap<String,FieldInfo> tmpFieldList = new HashMap<String,FieldInfo>();
301         keyIter = mFieldList.keySet().iterator();
302         while (keyIter.hasNext()) {
303             String key = keyIter.next();
304             FieldInfo fieldInfo = mFieldList.get(key);
305             fieldInfo.normalizeType(apiList);
306             tmpFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
307         }
308         mFieldList = tmpFieldList;
309 
310         HashMap<String,MethodInfo> tmpMethodList = new HashMap<String,MethodInfo>();
311         keyIter = mMethodList.keySet().iterator();
312         while (keyIter.hasNext()) {
313             String key = keyIter.next();
314             MethodInfo methodInfo = mMethodList.get(key);
315             methodInfo.normalizeTypes(apiList);
316             tmpMethodList.put(methodInfo.getNameAndDescriptor(), methodInfo);
317         }
318         mMethodList = tmpMethodList;
319     }
320 
321     /**
322      * Merges the fields and methods from "otherClass" into this class.
323      *
324      * Redundant entries will be merged.  We don't specify who the winner
325      * will be.
326      */
mergeFrom(ClassInfo otherClass)327     private void mergeFrom(ClassInfo otherClass) {
328         /*System.out.println("merging into " + getName() + ": fields=" +
329             mFieldList.size() + "/" + otherClass.mFieldList.size() +
330             ", methods=" +
331             mMethodList.size() + "/" + otherClass.mMethodList.size());*/
332 
333         mFieldList.putAll(otherClass.mFieldList);
334         mMethodList.putAll(otherClass.mMethodList);
335 
336         /*System.out.println("  now fields=" + mFieldList.size() +
337             ", methods=" + mMethodList.size());*/
338     }
339 
340 
341     /**
342      * Finds the named class in the ApiList.
343      *
344      * @param className Fully-qualified dot notation (e.g. "java.lang.String")
345      * @param apiList The hierarchy to search in.
346      * @return The class or null if not found.
347      */
lookupClass(String fullname, ApiList apiList)348     private static ClassInfo lookupClass(String fullname, ApiList apiList) {
349         String packageName = TypeUtils.packageNameOnly(fullname);
350         String className = TypeUtils.classNameOnly(fullname);
351 
352         PackageInfo pkg = apiList.getPackage(packageName);
353         if (pkg == null)
354             return null;
355         return pkg.getClass(className);
356     }
357 }
358 
359