Home | History | Annotate | Download | only in cst
      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.cst;
     18 
     19 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Class;
     20 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Double;
     21 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Fieldref;
     22 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Float;
     23 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Integer;
     24 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InterfaceMethodref;
     25 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Long;
     26 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Methodref;
     27 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_NameAndType;
     28 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_String;
     29 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Utf8;
     30 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodHandle;
     31 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodType;
     32 import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InvokeDynamic;
     33 import com.android.dx.cf.iface.ParseException;
     34 import com.android.dx.cf.iface.ParseObserver;
     35 import com.android.dx.rop.cst.Constant;
     36 import com.android.dx.rop.cst.CstDouble;
     37 import com.android.dx.rop.cst.CstFieldRef;
     38 import com.android.dx.rop.cst.CstFloat;
     39 import com.android.dx.rop.cst.CstInteger;
     40 import com.android.dx.rop.cst.CstInterfaceMethodRef;
     41 import com.android.dx.rop.cst.CstLong;
     42 import com.android.dx.rop.cst.CstMethodRef;
     43 import com.android.dx.rop.cst.CstNat;
     44 import com.android.dx.rop.cst.CstString;
     45 import com.android.dx.rop.cst.CstType;
     46 import com.android.dx.rop.cst.StdConstantPool;
     47 import com.android.dx.rop.type.Type;
     48 import com.android.dx.util.ByteArray;
     49 import com.android.dx.util.Hex;
     50 import java.util.BitSet;
     51 
     52 /**
     53  * Parser for a constant pool embedded in a class file.
     54  */
     55 public final class ConstantPoolParser {
     56     /** {@code non-null;} the bytes of the constant pool */
     57     private final ByteArray bytes;
     58 
     59     /** {@code non-null;} actual parsed constant pool contents */
     60     private final StdConstantPool pool;
     61 
     62     /** {@code non-null;} byte offsets to each cst */
     63     private final int[] offsets;
     64 
     65     /**
     66      * -1 || >= 10; the end offset of this constant pool in the
     67      * {@code byte[]} which it came from or {@code -1} if not
     68      * yet parsed
     69      */
     70     private int endOffset;
     71 
     72     /** {@code null-ok;} parse observer, if any */
     73     private ParseObserver observer;
     74 
     75     /**
     76      * Constructs an instance.
     77      *
     78      * @param bytes {@code non-null;} the bytes of the file
     79      */
     80     public ConstantPoolParser(ByteArray bytes) {
     81         int size = bytes.getUnsignedShort(8); // constant_pool_count
     82 
     83         this.bytes = bytes;
     84         this.pool = new StdConstantPool(size);
     85         this.offsets = new int[size];
     86         this.endOffset = -1;
     87     }
     88 
     89     /**
     90      * Sets the parse observer for this instance.
     91      *
     92      * @param observer {@code null-ok;} the observer
     93      */
     94     public void setObserver(ParseObserver observer) {
     95         this.observer = observer;
     96     }
     97 
     98     /**
     99      * Gets the end offset of this constant pool in the {@code byte[]}
    100      * which it came from.
    101      *
    102      * @return {@code >= 10;} the end offset
    103      */
    104     public int getEndOffset() {
    105         parseIfNecessary();
    106         return endOffset;
    107     }
    108 
    109     /**
    110      * Gets the actual constant pool.
    111      *
    112      * @return {@code non-null;} the constant pool
    113      */
    114     public StdConstantPool getPool() {
    115         parseIfNecessary();
    116         return pool;
    117     }
    118 
    119     /**
    120      * Runs {@link #parse} if it has not yet been run successfully.
    121      */
    122     private void parseIfNecessary() {
    123         if (endOffset < 0) {
    124             parse();
    125         }
    126     }
    127 
    128     /**
    129      * Does the actual parsing.
    130      */
    131     private void parse() {
    132         determineOffsets();
    133 
    134         if (observer != null) {
    135             observer.parsed(bytes, 8, 2,
    136                             "constant_pool_count: " + Hex.u2(offsets.length));
    137             observer.parsed(bytes, 10, 0, "\nconstant_pool:");
    138             observer.changeIndent(1);
    139         }
    140 
    141         /*
    142          * Track the constant value's original string type. True if constants[i] was
    143          * a CONSTANT_Utf8, false for any other type including CONSTANT_string.
    144          */
    145         BitSet wasUtf8 = new BitSet(offsets.length);
    146 
    147         for (int i = 1; i < offsets.length; i++) {
    148             int offset = offsets[i];
    149             if ((offset != 0) && (pool.getOrNull(i) == null)) {
    150                 parse0(i, wasUtf8);
    151             }
    152         }
    153 
    154         if (observer != null) {
    155             for (int i = 1; i < offsets.length; i++) {
    156                 Constant cst = pool.getOrNull(i);
    157                 if (cst == null) {
    158                     continue;
    159                 }
    160                 int offset = offsets[i];
    161                 int nextOffset = endOffset;
    162                 for (int j = i + 1; j < offsets.length; j++) {
    163                     int off = offsets[j];
    164                     if (off != 0) {
    165                         nextOffset = off;
    166                         break;
    167                     }
    168                 }
    169                 String human = wasUtf8.get(i)
    170                         ? Hex.u2(i) + ": utf8{\"" + cst.toHuman() + "\"}"
    171                         : Hex.u2(i) + ": " + cst.toString();
    172                 observer.parsed(bytes, offset, nextOffset - offset, human);
    173             }
    174 
    175             observer.changeIndent(-1);
    176             observer.parsed(bytes, endOffset, 0, "end constant_pool");
    177         }
    178     }
    179 
    180     /**
    181      * Populates {@link #offsets} and also completely parse utf8 constants.
    182      */
    183     private void determineOffsets() {
    184         int at = 10; // offset from the start of the file to the first cst
    185         int lastCategory;
    186 
    187         for (int i = 1; i < offsets.length; i += lastCategory) {
    188             offsets[i] = at;
    189             int tag = bytes.getUnsignedByte(at);
    190             try {
    191                 switch (tag) {
    192                     case CONSTANT_Integer:
    193                     case CONSTANT_Float:
    194                     case CONSTANT_Fieldref:
    195                     case CONSTANT_Methodref:
    196                     case CONSTANT_InterfaceMethodref:
    197                     case CONSTANT_NameAndType: {
    198                         lastCategory = 1;
    199                         at += 5;
    200                         break;
    201                     }
    202                     case CONSTANT_Long:
    203                     case CONSTANT_Double: {
    204                         lastCategory = 2;
    205                         at += 9;
    206                         break;
    207                     }
    208                     case CONSTANT_Class:
    209                     case CONSTANT_String: {
    210                         lastCategory = 1;
    211                         at += 3;
    212                         break;
    213                     }
    214                     case CONSTANT_Utf8: {
    215                         lastCategory = 1;
    216                         at += bytes.getUnsignedShort(at + 1) + 3;
    217                         break;
    218                     }
    219                     case CONSTANT_MethodHandle: {
    220                         throw new ParseException("MethodHandle not supported");
    221                     }
    222                     case CONSTANT_MethodType: {
    223                         throw new ParseException("MethodType not supported");
    224                     }
    225                     case CONSTANT_InvokeDynamic: {
    226                         throw new ParseException("InvokeDynamic not supported");
    227                     }
    228                     default: {
    229                         throw new ParseException("unknown tag byte: " + Hex.u1(tag));
    230                     }
    231                 }
    232             } catch (ParseException ex) {
    233                 ex.addContext("...while preparsing cst " + Hex.u2(i) + " at offset " + Hex.u4(at));
    234                 throw ex;
    235             }
    236         }
    237 
    238         endOffset = at;
    239     }
    240 
    241     /**
    242      * Parses the constant for the given index if it hasn't already been
    243      * parsed, also storing it in the constant pool. This will also
    244      * have the side effect of parsing any entries the indicated one
    245      * depends on.
    246      *
    247      * @param idx which constant
    248      * @return {@code non-null;} the parsed constant
    249      */
    250     private Constant parse0(int idx, BitSet wasUtf8) {
    251         Constant cst = pool.getOrNull(idx);
    252         if (cst != null) {
    253             return cst;
    254         }
    255 
    256         int at = offsets[idx];
    257 
    258         try {
    259             int tag = bytes.getUnsignedByte(at);
    260             switch (tag) {
    261                 case CONSTANT_Utf8: {
    262                     cst = parseUtf8(at);
    263                     wasUtf8.set(idx);
    264                     break;
    265                 }
    266                 case CONSTANT_Integer: {
    267                     int value = bytes.getInt(at + 1);
    268                     cst = CstInteger.make(value);
    269                     break;
    270                 }
    271                 case CONSTANT_Float: {
    272                     int bits = bytes.getInt(at + 1);
    273                     cst = CstFloat.make(bits);
    274                     break;
    275                 }
    276                 case CONSTANT_Long: {
    277                     long value = bytes.getLong(at + 1);
    278                     cst = CstLong.make(value);
    279                     break;
    280                 }
    281                 case CONSTANT_Double: {
    282                     long bits = bytes.getLong(at + 1);
    283                     cst = CstDouble.make(bits);
    284                     break;
    285                 }
    286                 case CONSTANT_Class: {
    287                     int nameIndex = bytes.getUnsignedShort(at + 1);
    288                     CstString name = (CstString) parse0(nameIndex, wasUtf8);
    289                     cst = new CstType(Type.internClassName(name.getString()));
    290                     break;
    291                 }
    292                 case CONSTANT_String: {
    293                     int stringIndex = bytes.getUnsignedShort(at + 1);
    294                     cst = parse0(stringIndex, wasUtf8);
    295                     break;
    296                 }
    297                 case CONSTANT_Fieldref: {
    298                     int classIndex = bytes.getUnsignedShort(at + 1);
    299                     CstType type = (CstType) parse0(classIndex, wasUtf8);
    300                     int natIndex = bytes.getUnsignedShort(at + 3);
    301                     CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
    302                     cst = new CstFieldRef(type, nat);
    303                     break;
    304                 }
    305                 case CONSTANT_Methodref: {
    306                     int classIndex = bytes.getUnsignedShort(at + 1);
    307                     CstType type = (CstType) parse0(classIndex, wasUtf8);
    308                     int natIndex = bytes.getUnsignedShort(at + 3);
    309                     CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
    310                     cst = new CstMethodRef(type, nat);
    311                     break;
    312                 }
    313                 case CONSTANT_InterfaceMethodref: {
    314                     int classIndex = bytes.getUnsignedShort(at + 1);
    315                     CstType type = (CstType) parse0(classIndex, wasUtf8);
    316                     int natIndex = bytes.getUnsignedShort(at + 3);
    317                     CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
    318                     cst = new CstInterfaceMethodRef(type, nat);
    319                     break;
    320                 }
    321                 case CONSTANT_NameAndType: {
    322                     int nameIndex = bytes.getUnsignedShort(at + 1);
    323                     CstString name = (CstString) parse0(nameIndex, wasUtf8);
    324                     int descriptorIndex = bytes.getUnsignedShort(at + 3);
    325                     CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8);
    326                     cst = new CstNat(name, descriptor);
    327                     break;
    328                 }
    329                 case CONSTANT_MethodHandle: {
    330                     throw new ParseException("MethodHandle not supported");
    331                 }
    332                 case CONSTANT_MethodType: {
    333                     throw new ParseException("MethodType not supported");
    334                 }
    335                 case CONSTANT_InvokeDynamic: {
    336                     throw new ParseException("InvokeDynamic not supported");
    337                 }
    338                 default: {
    339                     throw new ParseException("unknown tag byte: " + Hex.u1(tag));
    340                 }
    341             }
    342         } catch (ParseException ex) {
    343             ex.addContext("...while parsing cst " + Hex.u2(idx) +
    344                           " at offset " + Hex.u4(at));
    345             throw ex;
    346         } catch (RuntimeException ex) {
    347             ParseException pe = new ParseException(ex);
    348             pe.addContext("...while parsing cst " + Hex.u2(idx) +
    349                           " at offset " + Hex.u4(at));
    350             throw pe;
    351         }
    352 
    353         pool.set(idx, cst);
    354         return cst;
    355     }
    356 
    357     /**
    358      * Parses a utf8 constant.
    359      *
    360      * @param at offset to the start of the constant (where the tag byte is)
    361      * @return {@code non-null;} the parsed value
    362      */
    363     private CstString parseUtf8(int at) {
    364         int length = bytes.getUnsignedShort(at + 1);
    365 
    366         at += 3; // Skip to the data.
    367 
    368         ByteArray ubytes = bytes.slice(at, at + length);
    369 
    370         try {
    371             return new CstString(ubytes);
    372         } catch (IllegalArgumentException ex) {
    373             // Translate the exception
    374             throw new ParseException(ex);
    375         }
    376     }
    377 }
    378