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