Home | History | Annotate | Download | only in direct
      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.cf.direct;
     18 
     19 import com.android.dx.cf.iface.ParseException;
     20 import com.android.dx.cf.iface.ParseObserver;
     21 import com.android.dx.rop.annotation.Annotation;
     22 import com.android.dx.rop.annotation.AnnotationVisibility;
     23 import com.android.dx.rop.annotation.Annotations;
     24 import com.android.dx.rop.annotation.AnnotationsList;
     25 import com.android.dx.rop.annotation.NameValuePair;
     26 import com.android.dx.rop.cst.Constant;
     27 import com.android.dx.rop.cst.ConstantPool;
     28 import com.android.dx.rop.cst.CstAnnotation;
     29 import com.android.dx.rop.cst.CstArray;
     30 import com.android.dx.rop.cst.CstBoolean;
     31 import com.android.dx.rop.cst.CstByte;
     32 import com.android.dx.rop.cst.CstChar;
     33 import com.android.dx.rop.cst.CstDouble;
     34 import com.android.dx.rop.cst.CstEnumRef;
     35 import com.android.dx.rop.cst.CstFloat;
     36 import com.android.dx.rop.cst.CstInteger;
     37 import com.android.dx.rop.cst.CstLong;
     38 import com.android.dx.rop.cst.CstNat;
     39 import com.android.dx.rop.cst.CstShort;
     40 import com.android.dx.rop.cst.CstString;
     41 import com.android.dx.rop.cst.CstType;
     42 import com.android.dx.rop.type.Type;
     43 import com.android.dx.util.ByteArray;
     44 import com.android.dx.util.Hex;
     45 import java.io.IOException;
     46 
     47 /**
     48  * Parser for annotations.
     49  */
     50 public final class AnnotationParser {
     51     /** {@code non-null;} class file being parsed */
     52     private final DirectClassFile cf;
     53 
     54     /** {@code non-null;} constant pool to use */
     55     private final ConstantPool pool;
     56 
     57     /** {@code non-null;} bytes of the attribute data */
     58     private final ByteArray bytes;
     59 
     60     /** {@code null-ok;} parse observer, if any */
     61     private final ParseObserver observer;
     62 
     63     /** {@code non-null;} input stream to parse from */
     64     private final ByteArray.MyDataInputStream input;
     65 
     66     /**
     67      * {@code non-null;} cursor for use when informing the observer of what
     68      * was parsed
     69      */
     70     private int parseCursor;
     71 
     72     /**
     73      * Constructs an instance.
     74      *
     75      * @param cf {@code non-null;} class file to parse from
     76      * @param offset {@code >= 0;} offset into the class file data to parse at
     77      * @param length {@code >= 0;} number of bytes left in the attribute data
     78      * @param observer {@code null-ok;} parse observer to notify, if any
     79      */
     80     public AnnotationParser(DirectClassFile cf, int offset, int length,
     81             ParseObserver observer) {
     82         if (cf == null) {
     83             throw new NullPointerException("cf == null");
     84         }
     85 
     86         this.cf = cf;
     87         this.pool = cf.getConstantPool();
     88         this.observer = observer;
     89         this.bytes = cf.getBytes().slice(offset, offset + length);
     90         this.input = bytes.makeDataInputStream();
     91         this.parseCursor = 0;
     92     }
     93 
     94     /**
     95      * Parses an annotation value ({@code element_value}) attribute.
     96      *
     97      * @return {@code non-null;} the parsed constant value
     98      */
     99     public Constant parseValueAttribute() {
    100         Constant result;
    101 
    102         try {
    103             result = parseValue();
    104 
    105             if (input.available() != 0) {
    106                 throw new ParseException("extra data in attribute");
    107             }
    108         } catch (IOException ex) {
    109             // ByteArray.MyDataInputStream should never throw.
    110             throw new RuntimeException("shouldn't happen", ex);
    111         }
    112 
    113         return result;
    114     }
    115 
    116     /**
    117      * Parses a parameter annotation attribute.
    118      *
    119      * @param visibility {@code non-null;} visibility of the parsed annotations
    120      * @return {@code non-null;} the parsed list of lists of annotations
    121      */
    122     public AnnotationsList parseParameterAttribute(
    123             AnnotationVisibility visibility) {
    124         AnnotationsList result;
    125 
    126         try {
    127             result = parseAnnotationsList(visibility);
    128 
    129             if (input.available() != 0) {
    130                 throw new ParseException("extra data in attribute");
    131             }
    132         } catch (IOException ex) {
    133             // ByteArray.MyDataInputStream should never throw.
    134             throw new RuntimeException("shouldn't happen", ex);
    135         }
    136 
    137         return result;
    138     }
    139 
    140     /**
    141      * Parses an annotation attribute, per se.
    142      *
    143      * @param visibility {@code non-null;} visibility of the parsed annotations
    144      * @return {@code non-null;} the list of annotations read from the attribute
    145      * data
    146      */
    147     public Annotations parseAnnotationAttribute(
    148             AnnotationVisibility visibility) {
    149         Annotations result;
    150 
    151         try {
    152             result = parseAnnotations(visibility);
    153 
    154             if (input.available() != 0) {
    155                 throw new ParseException("extra data in attribute");
    156             }
    157         } catch (IOException ex) {
    158             // ByteArray.MyDataInputStream should never throw.
    159             throw new RuntimeException("shouldn't happen", ex);
    160         }
    161 
    162         return result;
    163     }
    164 
    165     /**
    166      * Parses a list of annotation lists.
    167      *
    168      * @param visibility {@code non-null;} visibility of the parsed annotations
    169      * @return {@code non-null;} the list of annotation lists read from the attribute
    170      * data
    171      */
    172     private AnnotationsList parseAnnotationsList(
    173             AnnotationVisibility visibility) throws IOException {
    174         int count = input.readUnsignedByte();
    175 
    176         if (observer != null) {
    177             parsed(1, "num_parameters: " + Hex.u1(count));
    178         }
    179 
    180         AnnotationsList outerList = new AnnotationsList(count);
    181 
    182         for (int i = 0; i < count; i++) {
    183             if (observer != null) {
    184                 parsed(0, "parameter_annotations[" + i + "]:");
    185                 changeIndent(1);
    186             }
    187 
    188             Annotations annotations = parseAnnotations(visibility);
    189             outerList.set(i, annotations);
    190 
    191             if (observer != null) {
    192                 observer.changeIndent(-1);
    193             }
    194         }
    195 
    196         outerList.setImmutable();
    197         return outerList;
    198     }
    199 
    200     /**
    201      * Parses an annotation list.
    202      *
    203      * @param visibility {@code non-null;} visibility of the parsed annotations
    204      * @return {@code non-null;} the list of annotations read from the attribute
    205      * data
    206      */
    207     private Annotations parseAnnotations(AnnotationVisibility visibility)
    208             throws IOException {
    209         int count = input.readUnsignedShort();
    210 
    211         if (observer != null) {
    212             parsed(2, "num_annotations: " + Hex.u2(count));
    213         }
    214 
    215         Annotations annotations = new Annotations();
    216 
    217         for (int i = 0; i < count; i++) {
    218             if (observer != null) {
    219                 parsed(0, "annotations[" + i + "]:");
    220                 changeIndent(1);
    221             }
    222 
    223             Annotation annotation = parseAnnotation(visibility);
    224             annotations.add(annotation);
    225 
    226             if (observer != null) {
    227                 observer.changeIndent(-1);
    228             }
    229         }
    230 
    231         annotations.setImmutable();
    232         return annotations;
    233     }
    234 
    235     /**
    236      * Parses a single annotation.
    237      *
    238      * @param visibility {@code non-null;} visibility of the parsed annotation
    239      * @return {@code non-null;} the parsed annotation
    240      */
    241     private Annotation parseAnnotation(AnnotationVisibility visibility)
    242             throws IOException {
    243         requireLength(4);
    244 
    245         int typeIndex = input.readUnsignedShort();
    246         int numElements = input.readUnsignedShort();
    247         CstString typeString = (CstString) pool.get(typeIndex);
    248         CstType type = new CstType(Type.intern(typeString.getString()));
    249 
    250         if (observer != null) {
    251             parsed(2, "type: " + type.toHuman());
    252             parsed(2, "num_elements: " + numElements);
    253         }
    254 
    255         Annotation annotation = new Annotation(type, visibility);
    256 
    257         for (int i = 0; i < numElements; i++) {
    258             if (observer != null) {
    259                 parsed(0, "elements[" + i + "]:");
    260                 changeIndent(1);
    261             }
    262 
    263             NameValuePair element = parseElement();
    264             annotation.add(element);
    265 
    266             if (observer != null) {
    267                 changeIndent(-1);
    268             }
    269         }
    270 
    271         annotation.setImmutable();
    272         return annotation;
    273     }
    274 
    275     /**
    276      * Parses a {@link NameValuePair}.
    277      *
    278      * @return {@code non-null;} the parsed element
    279      */
    280     private NameValuePair parseElement() throws IOException {
    281         requireLength(5);
    282 
    283         int elementNameIndex = input.readUnsignedShort();
    284         CstString elementName = (CstString) pool.get(elementNameIndex);
    285 
    286         if (observer != null) {
    287             parsed(2, "element_name: " + elementName.toHuman());
    288             parsed(0, "value: ");
    289             changeIndent(1);
    290         }
    291 
    292         Constant value = parseValue();
    293 
    294         if (observer != null) {
    295             changeIndent(-1);
    296         }
    297 
    298         return new NameValuePair(elementName, value);
    299     }
    300 
    301     /**
    302      * Parses an annotation value.
    303      *
    304      * @return {@code non-null;} the parsed value
    305      */
    306     private Constant parseValue() throws IOException {
    307         int tag = input.readUnsignedByte();
    308 
    309         if (observer != null) {
    310             CstString humanTag = new CstString(Character.toString((char) tag));
    311             parsed(1, "tag: " + humanTag.toQuoted());
    312         }
    313 
    314         switch (tag) {
    315             case 'B': {
    316                 CstInteger value = (CstInteger) parseConstant();
    317                 return CstByte.make(value.getValue());
    318             }
    319             case 'C': {
    320                 CstInteger value = (CstInteger) parseConstant();
    321                 int intValue = value.getValue();
    322                 return CstChar.make(value.getValue());
    323             }
    324             case 'D': {
    325                 CstDouble value = (CstDouble) parseConstant();
    326                 return value;
    327             }
    328             case 'F': {
    329                 CstFloat value = (CstFloat) parseConstant();
    330                 return value;
    331             }
    332             case 'I': {
    333                 CstInteger value = (CstInteger) parseConstant();
    334                 return value;
    335             }
    336             case 'J': {
    337                 CstLong value = (CstLong) parseConstant();
    338                 return value;
    339             }
    340             case 'S': {
    341                 CstInteger value = (CstInteger) parseConstant();
    342                 return CstShort.make(value.getValue());
    343             }
    344             case 'Z': {
    345                 CstInteger value = (CstInteger) parseConstant();
    346                 return CstBoolean.make(value.getValue());
    347             }
    348             case 'c': {
    349                 int classInfoIndex = input.readUnsignedShort();
    350                 CstString value = (CstString) pool.get(classInfoIndex);
    351                 Type type = Type.internReturnType(value.getString());
    352 
    353                 if (observer != null) {
    354                     parsed(2, "class_info: " + type.toHuman());
    355                 }
    356 
    357                 return new CstType(type);
    358             }
    359             case 's': {
    360                 return parseConstant();
    361             }
    362             case 'e': {
    363                 requireLength(4);
    364 
    365                 int typeNameIndex = input.readUnsignedShort();
    366                 int constNameIndex = input.readUnsignedShort();
    367                 CstString typeName = (CstString) pool.get(typeNameIndex);
    368                 CstString constName = (CstString) pool.get(constNameIndex);
    369 
    370                 if (observer != null) {
    371                     parsed(2, "type_name: " + typeName.toHuman());
    372                     parsed(2, "const_name: " + constName.toHuman());
    373                 }
    374 
    375                 return new CstEnumRef(new CstNat(constName, typeName));
    376             }
    377             case '@': {
    378                 Annotation annotation =
    379                     parseAnnotation(AnnotationVisibility.EMBEDDED);
    380                 return new CstAnnotation(annotation);
    381             }
    382             case '[': {
    383                 requireLength(2);
    384 
    385                 int numValues = input.readUnsignedShort();
    386                 CstArray.List list = new CstArray.List(numValues);
    387 
    388                 if (observer != null) {
    389                     parsed(2, "num_values: " + numValues);
    390                     changeIndent(1);
    391                 }
    392 
    393                 for (int i = 0; i < numValues; i++) {
    394                     if (observer != null) {
    395                         changeIndent(-1);
    396                         parsed(0, "element_value[" + i + "]:");
    397                         changeIndent(1);
    398                     }
    399                     list.set(i, parseValue());
    400                 }
    401 
    402                 if (observer != null) {
    403                     changeIndent(-1);
    404                 }
    405 
    406                 list.setImmutable();
    407                 return new CstArray(list);
    408             }
    409             default: {
    410                 throw new ParseException("unknown annotation tag: " +
    411                         Hex.u1(tag));
    412             }
    413         }
    414     }
    415 
    416     /**
    417      * Helper for {@link #parseValue}, which parses a constant reference
    418      * and returns the referred-to constant value.
    419      *
    420      * @return {@code non-null;} the parsed value
    421      */
    422     private Constant parseConstant() throws IOException {
    423         int constValueIndex = input.readUnsignedShort();
    424         Constant value = (Constant) pool.get(constValueIndex);
    425 
    426         if (observer != null) {
    427             String human = (value instanceof CstString)
    428                 ? ((CstString) value).toQuoted()
    429                 : value.toHuman();
    430             parsed(2, "constant_value: " + human);
    431         }
    432 
    433         return value;
    434     }
    435 
    436     /**
    437      * Helper which will throw an exception if the given number of bytes
    438      * is not available to be read.
    439      *
    440      * @param requiredLength the number of required bytes
    441      */
    442     private void requireLength(int requiredLength) throws IOException {
    443         if (input.available() < requiredLength) {
    444             throw new ParseException("truncated annotation attribute");
    445         }
    446     }
    447 
    448     /**
    449      * Helper which indicates that some bytes were just parsed. This should
    450      * only be used (for efficiency sake) if the parse is known to be
    451      * observed.
    452      *
    453      * @param length {@code >= 0;} number of bytes parsed
    454      * @param message {@code non-null;} associated message
    455      */
    456     private void parsed(int length, String message) {
    457         observer.parsed(bytes, parseCursor, length, message);
    458         parseCursor += length;
    459     }
    460 
    461     /**
    462      * Convenience wrapper that simply calls through to
    463      * {@code observer.changeIndent()}.
    464      *
    465      * @param indent the amount to change the indent by
    466      */
    467     private void changeIndent(int indent) {
    468         observer.changeIndent(indent);
    469     }
    470 }
    471