Home | History | Annotate | Download | only in cf
      1 /*
      2  * Copyright (C) 2007 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.dx.dex.cf;
     18 
     19 import com.android.dx.cf.attrib.AttAnnotationDefault;
     20 import com.android.dx.cf.attrib.AttEnclosingMethod;
     21 import com.android.dx.cf.attrib.AttExceptions;
     22 import com.android.dx.cf.attrib.AttInnerClasses;
     23 import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations;
     24 import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations;
     25 import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
     26 import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations;
     27 import com.android.dx.cf.attrib.AttSignature;
     28 import com.android.dx.cf.attrib.AttSourceDebugExtension;
     29 import com.android.dx.cf.attrib.InnerClassList;
     30 import com.android.dx.cf.direct.DirectClassFile;
     31 import com.android.dx.cf.iface.AttributeList;
     32 import com.android.dx.cf.iface.Method;
     33 import com.android.dx.cf.iface.MethodList;
     34 import com.android.dx.dex.file.AnnotationUtils;
     35 import com.android.dx.rop.annotation.Annotation;
     36 import com.android.dx.rop.annotation.AnnotationVisibility;
     37 import com.android.dx.rop.annotation.Annotations;
     38 import com.android.dx.rop.annotation.AnnotationsList;
     39 import com.android.dx.rop.annotation.NameValuePair;
     40 import com.android.dx.rop.code.AccessFlags;
     41 import com.android.dx.rop.cst.CstMethodRef;
     42 import com.android.dx.rop.cst.CstNat;
     43 import com.android.dx.rop.cst.CstType;
     44 import com.android.dx.rop.type.StdTypeList;
     45 import com.android.dx.rop.type.Type;
     46 import com.android.dx.rop.type.TypeList;
     47 import com.android.dx.util.Warning;
     48 import java.util.ArrayList;
     49 
     50 /**
     51  * Utility methods that translate various classfile attributes
     52  * into forms suitable for use in creating {@code dex} files.
     53  */
     54 /*package*/ class AttributeTranslator {
     55     /**
     56      * This class is uninstantiable.
     57      */
     58     private AttributeTranslator() {
     59         // This space intentionally left blank.
     60     }
     61 
     62     /**
     63      * Gets the list of thrown exceptions for a given method.
     64      *
     65      * @param method {@code non-null;} the method in question
     66      * @return {@code non-null;} the list of thrown exceptions
     67      */
     68     public static TypeList getExceptions(Method method) {
     69         AttributeList attribs = method.getAttributes();
     70         AttExceptions exceptions = (AttExceptions)
     71             attribs.findFirst(AttExceptions.ATTRIBUTE_NAME);
     72 
     73         if (exceptions == null) {
     74             return StdTypeList.EMPTY;
     75         }
     76 
     77         return exceptions.getExceptions();
     78     }
     79 
     80     /**
     81      * Gets the annotations out of a given {@link AttributeList}. This
     82      * combines both visible and invisible annotations into a single
     83      * result set and also adds in a system annotation for the
     84      * {@code Signature} attribute if present.
     85      *
     86      * @param attribs {@code non-null;} the attributes list to search in
     87      * @return {@code non-null;} the set of annotations, which may be empty
     88      */
     89     public static Annotations getAnnotations(AttributeList attribs) {
     90         Annotations result = getAnnotations0(attribs);
     91         Annotation signature = getSignature(attribs);
     92         Annotation sourceDebugExtension = getSourceDebugExtension(attribs);
     93 
     94         if (signature != null) {
     95             result = Annotations.combine(result, signature);
     96         }
     97 
     98         if (sourceDebugExtension != null) {
     99             result = Annotations.combine(result, sourceDebugExtension);
    100         }
    101 
    102         return result;
    103     }
    104 
    105     /**
    106      * Gets the annotations out of a given class, similar to {@link
    107      * #getAnnotations}, also including annotations for translations
    108      * of class-level attributes {@code EnclosingMethod} and
    109      * {@code InnerClasses}, if present. Additionally, if the
    110      * class is an annotation class, then this also includes a
    111      * representation of all the {@code AnnotationDefault}
    112      * values.
    113      *
    114      * @param cf {@code non-null;} the class in question
    115      * @param args {@code non-null;} the high-level options
    116      * @return {@code non-null;} the set of annotations, which may be empty
    117      */
    118     public static Annotations getClassAnnotations(DirectClassFile cf,
    119             CfOptions args) {
    120         CstType thisClass = cf.getThisClass();
    121         AttributeList attribs = cf.getAttributes();
    122         Annotations result = getAnnotations(attribs);
    123         Annotation enclosingMethod = translateEnclosingMethod(attribs);
    124 
    125         try {
    126             Annotations innerClassAnnotations =
    127                 translateInnerClasses(thisClass, attribs,
    128                         enclosingMethod == null);
    129             if (innerClassAnnotations != null) {
    130                 result = Annotations.combine(result, innerClassAnnotations);
    131             }
    132         } catch (Warning warn) {
    133             args.warn.println("warning: " + warn.getMessage());
    134         }
    135 
    136         if (enclosingMethod != null) {
    137             result = Annotations.combine(result, enclosingMethod);
    138         }
    139 
    140         if (AccessFlags.isAnnotation(cf.getAccessFlags())) {
    141             Annotation annotationDefault =
    142                 translateAnnotationDefaults(cf);
    143             if (annotationDefault != null) {
    144                 result = Annotations.combine(result, annotationDefault);
    145             }
    146         }
    147 
    148         return result;
    149     }
    150 
    151     /**
    152      * Gets the annotations out of a given method, similar to {@link
    153      * #getAnnotations}, also including an annotation for the translation
    154      * of the method-specific attribute {@code Exceptions}.
    155      *
    156      * @param method {@code non-null;} the method in question
    157      * @return {@code non-null;} the set of annotations, which may be empty
    158      */
    159     public static Annotations getMethodAnnotations(Method method) {
    160         Annotations result = getAnnotations(method.getAttributes());
    161         TypeList exceptions = getExceptions(method);
    162 
    163         if (exceptions.size() != 0) {
    164             Annotation throwsAnnotation =
    165                 AnnotationUtils.makeThrows(exceptions);
    166             result = Annotations.combine(result, throwsAnnotation);
    167         }
    168 
    169         return result;
    170     }
    171 
    172     /**
    173      * Helper method for {@link #getAnnotations} which just gets the
    174      * existing annotations, per se.
    175      *
    176      * @param attribs {@code non-null;} the attributes list to search in
    177      * @return {@code non-null;} the set of annotations, which may be empty
    178      */
    179     private static Annotations getAnnotations0(AttributeList attribs) {
    180         AttRuntimeVisibleAnnotations visible =
    181             (AttRuntimeVisibleAnnotations)
    182             attribs.findFirst(AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
    183         AttRuntimeInvisibleAnnotations invisible =
    184             (AttRuntimeInvisibleAnnotations)
    185             attribs.findFirst(AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
    186 
    187         if (visible == null) {
    188             if (invisible == null) {
    189                 return Annotations.EMPTY;
    190             }
    191             return invisible.getAnnotations();
    192         }
    193 
    194         if (invisible == null) {
    195             return visible.getAnnotations();
    196         }
    197 
    198         // Both are non-null, so combine them.
    199 
    200         return Annotations.combine(visible.getAnnotations(),
    201                 invisible.getAnnotations());
    202     }
    203 
    204     /**
    205      * Gets the {@code Signature} attribute out of a given
    206      * {@link AttributeList}, if any, translating it to an annotation.
    207      *
    208      * @param attribs {@code non-null;} the attributes list to search in
    209      * @return {@code null-ok;} the converted {@code Signature} annotation,
    210      * if there was an attribute to translate
    211      */
    212     private static Annotation getSignature(AttributeList attribs) {
    213         AttSignature signature = (AttSignature)
    214             attribs.findFirst(AttSignature.ATTRIBUTE_NAME);
    215 
    216         if (signature == null) {
    217             return null;
    218         }
    219 
    220         return AnnotationUtils.makeSignature(signature.getSignature());
    221     }
    222 
    223 
    224     private static Annotation getSourceDebugExtension(AttributeList attribs) {
    225         AttSourceDebugExtension extension = (AttSourceDebugExtension)
    226             attribs.findFirst(AttSourceDebugExtension.ATTRIBUTE_NAME);
    227 
    228         if (extension == null) {
    229             return null;
    230         }
    231 
    232         return AnnotationUtils.makeSourceDebugExtension(extension.getSmapString());
    233     }
    234 
    235     /**
    236      * Gets the {@code EnclosingMethod} attribute out of a given
    237      * {@link AttributeList}, if any, translating it to an annotation.
    238      * If the class really has an enclosing method, this returns an
    239      * {@code EnclosingMethod} annotation; if not, this returns
    240      * an {@code EnclosingClass} annotation.
    241      *
    242      * @param attribs {@code non-null;} the attributes list to search in
    243      * @return {@code null-ok;} the converted {@code EnclosingMethod} or
    244      * {@code EnclosingClass} annotation, if there was an
    245      * attribute to translate
    246      */
    247     private static Annotation translateEnclosingMethod(AttributeList attribs) {
    248         AttEnclosingMethod enclosingMethod = (AttEnclosingMethod)
    249             attribs.findFirst(AttEnclosingMethod.ATTRIBUTE_NAME);
    250 
    251         if (enclosingMethod == null) {
    252             return null;
    253         }
    254 
    255         CstType enclosingClass = enclosingMethod.getEnclosingClass();
    256         CstNat nat = enclosingMethod.getMethod();
    257 
    258         if (nat == null) {
    259             /*
    260              * Dalvik doesn't use EnclosingMethod annotations unless
    261              * there really is an enclosing method. Anonymous classes
    262              * are unambiguously identified by having an InnerClass
    263              * annotation with an empty name along with an appropriate
    264              * EnclosingClass.
    265              */
    266             return AnnotationUtils.makeEnclosingClass(enclosingClass);
    267         }
    268 
    269         return AnnotationUtils.makeEnclosingMethod(
    270                 new CstMethodRef(enclosingClass, nat));
    271     }
    272 
    273     /**
    274      * Gets the {@code InnerClasses} attribute out of a given
    275      * {@link AttributeList}, if any, translating it to one or more of an
    276      * {@code InnerClass}, {@code EnclosingClass}, or
    277      * {@code MemberClasses} annotation.
    278      *
    279      * @param thisClass {@code non-null;} type representing the class being
    280      * processed
    281      * @param attribs {@code non-null;} the attributes list to search in
    282      * @param needEnclosingClass whether to include an
    283      * {@code EnclosingClass} annotation
    284      * @return {@code null-ok;} the converted list of annotations, if there
    285      * was an attribute to translate
    286      */
    287     private static Annotations translateInnerClasses(CstType thisClass,
    288             AttributeList attribs, boolean needEnclosingClass) {
    289         AttInnerClasses innerClasses = (AttInnerClasses)
    290             attribs.findFirst(AttInnerClasses.ATTRIBUTE_NAME);
    291 
    292         if (innerClasses == null) {
    293             return null;
    294         }
    295 
    296         /*
    297          * Search the list for the element representing the current class
    298          * as well as for any named member classes.
    299          */
    300 
    301         InnerClassList list = innerClasses.getInnerClasses();
    302         int size = list.size();
    303         InnerClassList.Item foundThisClass = null;
    304         ArrayList<Type> membersList = new ArrayList<Type>();
    305 
    306         for (int i = 0; i < size; i++) {
    307             InnerClassList.Item item = list.get(i);
    308             CstType innerClass = item.getInnerClass();
    309             if (innerClass.equals(thisClass)) {
    310                 foundThisClass = item;
    311             } else if (thisClass.equals(item.getOuterClass())) {
    312                 membersList.add(innerClass.getClassType());
    313             }
    314         }
    315 
    316         int membersSize = membersList.size();
    317 
    318         if ((foundThisClass == null) && (membersSize == 0)) {
    319             return null;
    320         }
    321 
    322         Annotations result = new Annotations();
    323 
    324         if (foundThisClass != null) {
    325             result.add(AnnotationUtils.makeInnerClass(
    326                                foundThisClass.getInnerName(),
    327                                foundThisClass.getAccessFlags()));
    328             if (needEnclosingClass) {
    329                 CstType outer = foundThisClass.getOuterClass();
    330                 if (outer == null) {
    331                     throw new Warning(
    332                             "Ignoring InnerClasses attribute for an " +
    333                             "anonymous inner class\n" +
    334                             "(" + thisClass.toHuman() +
    335                             ") that doesn't come with an\n" +
    336                             "associated EnclosingMethod attribute. " +
    337                             "This class was probably produced by a\n" +
    338                             "compiler that did not target the modern " +
    339                             ".class file format. The recommended\n" +
    340                             "solution is to recompile the class from " +
    341                             "source, using an up-to-date compiler\n" +
    342                             "and without specifying any \"-target\" type " +
    343                             "options. The consequence of ignoring\n" +
    344                             "this warning is that reflective operations " +
    345                             "on this class will incorrectly\n" +
    346                             "indicate that it is *not* an inner class.");
    347                 }
    348                 result.add(AnnotationUtils.makeEnclosingClass(
    349                                    foundThisClass.getOuterClass()));
    350             }
    351         }
    352 
    353         if (membersSize != 0) {
    354             StdTypeList typeList = new StdTypeList(membersSize);
    355             for (int i = 0; i < membersSize; i++) {
    356                 typeList.set(i, membersList.get(i));
    357             }
    358             typeList.setImmutable();
    359             result.add(AnnotationUtils.makeMemberClasses(typeList));
    360         }
    361 
    362         result.setImmutable();
    363         return result;
    364     }
    365 
    366     /**
    367      * Gets the parameter annotations out of a given method. This
    368      * combines both visible and invisible annotations into a single
    369      * result set.
    370      *
    371      * @param method {@code non-null;} the method in question
    372      * @return {@code non-null;} the list of annotation sets, which may be
    373      * empty
    374      */
    375     public static AnnotationsList getParameterAnnotations(Method method) {
    376         AttributeList attribs = method.getAttributes();
    377         AttRuntimeVisibleParameterAnnotations visible =
    378             (AttRuntimeVisibleParameterAnnotations)
    379             attribs.findFirst(
    380                     AttRuntimeVisibleParameterAnnotations.ATTRIBUTE_NAME);
    381         AttRuntimeInvisibleParameterAnnotations invisible =
    382             (AttRuntimeInvisibleParameterAnnotations)
    383             attribs.findFirst(
    384                     AttRuntimeInvisibleParameterAnnotations.ATTRIBUTE_NAME);
    385 
    386         if (visible == null) {
    387             if (invisible == null) {
    388                 return AnnotationsList.EMPTY;
    389             }
    390             return invisible.getParameterAnnotations();
    391         }
    392 
    393         if (invisible == null) {
    394             return visible.getParameterAnnotations();
    395         }
    396 
    397         // Both are non-null, so combine them.
    398 
    399         return AnnotationsList.combine(visible.getParameterAnnotations(),
    400                 invisible.getParameterAnnotations());
    401     }
    402 
    403     /**
    404      * Gets the {@code AnnotationDefault} attributes out of a
    405      * given class, if any, reforming them as an
    406      * {@code AnnotationDefault} annotation.
    407      *
    408      * @param cf {@code non-null;} the class in question
    409      * @return {@code null-ok;} an appropriately-constructed
    410      * {@code AnnotationDefault} annotation, if there were any
    411      * annotation defaults in the class, or {@code null} if not
    412      */
    413     private static Annotation translateAnnotationDefaults(DirectClassFile cf) {
    414         CstType thisClass = cf.getThisClass();
    415         MethodList methods = cf.getMethods();
    416         int sz = methods.size();
    417         Annotation result =
    418             new Annotation(thisClass, AnnotationVisibility.EMBEDDED);
    419         boolean any = false;
    420 
    421         for (int i = 0; i < sz; i++) {
    422             Method one = methods.get(i);
    423             AttributeList attribs = one.getAttributes();
    424             AttAnnotationDefault oneDefault = (AttAnnotationDefault)
    425                 attribs.findFirst(AttAnnotationDefault.ATTRIBUTE_NAME);
    426 
    427             if (oneDefault != null) {
    428                 NameValuePair pair = new NameValuePair(
    429                         one.getNat().getName(),
    430                         oneDefault.getValue());
    431                 result.add(pair);
    432                 any = true;
    433             }
    434         }
    435 
    436         if (! any) {
    437             return null;
    438         }
    439 
    440         result.setImmutable();
    441         return AnnotationUtils.makeAnnotationDefault(result);
    442     }
    443 }
    444