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