Home | History | Annotate | Download | only in apkcheck
      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      */
     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      */
     82     public String getName() {
     83         return mName;
     84     }
     85 
     86     /**
     87      * Returns the name of the superclass.
     88      */
     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      */
    107     public String getStatic() {
    108         return mIsStatic;
    109     }
    110 
    111     /**
    112      * Returns whether or not this class is an enumerated type.
    113      */
    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      */
    122     public boolean isAnnotation() {
    123         assert mFlattened;
    124         return mIsAnnotation;
    125     }
    126 
    127     /**
    128      * Adds a field to the list.
    129      */
    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      */
    139     public FieldInfo getField(String nameAndType) {
    140         return mFieldList.get(nameAndType);
    141     }
    142 
    143     /**
    144      * Returns an iterator over all known fields.
    145      */
    146     public Iterator<FieldInfo> getFieldIterator() {
    147         return mFieldList.values().iterator();
    148     }
    149 
    150     /**
    151      * Adds a method to the list.
    152      */
    153     public void addMethod(MethodInfo methInfo) {
    154         mMethodList.put(methInfo.getNameAndDescriptor(), methInfo);
    155     }
    156 
    157     /**
    158      * Returns an iterator over all known methods.
    159      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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