Home | History | Annotate | Download | only in file
      1 /*
      2  * Copyright (C) 2008 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.dexgen.dex.file;
     18 
     19 import com.android.dexgen.dex.code.CatchHandlerList;
     20 import com.android.dexgen.dex.code.CatchTable;
     21 import com.android.dexgen.dex.code.DalvCode;
     22 import com.android.dexgen.rop.cst.CstType;
     23 import com.android.dexgen.rop.type.Type;
     24 import com.android.dexgen.util.AnnotatedOutput;
     25 import com.android.dexgen.util.ByteArrayAnnotatedOutput;
     26 import com.android.dexgen.util.Hex;
     27 
     28 import java.io.PrintWriter;
     29 import java.util.Map;
     30 import java.util.TreeMap;
     31 
     32 /**
     33  * List of exception handlers (tuples of covered range, catch type,
     34  * handler address) for a particular piece of code. Instances of this
     35  * class correspond to a {@code try_item[]} and a
     36  * {@code catch_handler_item[]}.
     37  */
     38 public final class CatchStructs {
     39     /**
     40      * the size of a {@code try_item}: a {@code uint}
     41      * and two {@code ushort}s
     42      */
     43     private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2);
     44 
     45     /** {@code non-null;} code that contains the catches */
     46     private final DalvCode code;
     47 
     48     /**
     49      * {@code null-ok;} the underlying table; set in
     50      * {@link #finishProcessingIfNecessary}
     51      */
     52     private CatchTable table;
     53 
     54     /**
     55      * {@code null-ok;} the encoded handler list, if calculated; set in
     56      * {@link #encode}
     57      */
     58     private byte[] encodedHandlers;
     59 
     60     /**
     61      * length of the handlers header (encoded size), if known; used for
     62      * annotation
     63      */
     64     private int encodedHandlerHeaderSize;
     65 
     66     /**
     67      * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in
     68      * {@link #encode}
     69      */
     70     private TreeMap<CatchHandlerList, Integer> handlerOffsets;
     71 
     72     /**
     73      * Constructs an instance.
     74      *
     75      * @param code {@code non-null;} code that contains the catches
     76      */
     77     public CatchStructs(DalvCode code) {
     78         this.code = code;
     79         this.table = null;
     80         this.encodedHandlers = null;
     81         this.encodedHandlerHeaderSize = 0;
     82         this.handlerOffsets = null;
     83     }
     84 
     85     /**
     86      * Finish processing the catches, if necessary.
     87      */
     88     private void finishProcessingIfNecessary() {
     89         if (table == null) {
     90             table = code.getCatches();
     91         }
     92     }
     93 
     94     /**
     95      * Gets the size of the tries list, in entries.
     96      *
     97      * @return {@code >= 0;} the tries list size
     98      */
     99     public int triesSize() {
    100         finishProcessingIfNecessary();
    101         return table.size();
    102     }
    103 
    104     /**
    105      * Does a human-friendly dump of this instance.
    106      *
    107      * @param out {@code non-null;} where to dump
    108      * @param prefix {@code non-null;} prefix to attach to each line of output
    109      */
    110     public void debugPrint(PrintWriter out, String prefix) {
    111         annotateEntries(prefix, out, null);
    112     }
    113 
    114     /**
    115      * Encodes the handler lists.
    116      *
    117      * @param file {@code non-null;} file this instance is part of
    118      */
    119     public void encode(DexFile file) {
    120         finishProcessingIfNecessary();
    121 
    122         TypeIdsSection typeIds = file.getTypeIds();
    123         int size = table.size();
    124 
    125         handlerOffsets = new TreeMap<CatchHandlerList, Integer>();
    126 
    127         /*
    128          * First add a map entry for each unique list. The tree structure
    129          * will ensure they are sorted when we reiterate later.
    130          */
    131         for (int i = 0; i < size; i++) {
    132             handlerOffsets.put(table.get(i).getHandlers(), null);
    133         }
    134 
    135         if (handlerOffsets.size() > 65535) {
    136             throw new UnsupportedOperationException(
    137                     "too many catch handlers");
    138         }
    139 
    140         ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
    141 
    142         // Write out the handlers "header" consisting of its size in entries.
    143         encodedHandlerHeaderSize =
    144             out.writeUnsignedLeb128(handlerOffsets.size());
    145 
    146         // Now write the lists out in order, noting the offset of each.
    147         for (Map.Entry<CatchHandlerList, Integer> mapping :
    148                  handlerOffsets.entrySet()) {
    149             CatchHandlerList list = mapping.getKey();
    150             int listSize = list.size();
    151             boolean catchesAll = list.catchesAll();
    152 
    153             // Set the offset before we do any writing.
    154             mapping.setValue(out.getCursor());
    155 
    156             if (catchesAll) {
    157                 // A size <= 0 means that the list ends with a catch-all.
    158                 out.writeSignedLeb128(-(listSize - 1));
    159                 listSize--;
    160             } else {
    161                 out.writeSignedLeb128(listSize);
    162             }
    163 
    164             for (int i = 0; i < listSize; i++) {
    165                 CatchHandlerList.Entry entry = list.get(i);
    166                 out.writeUnsignedLeb128(
    167                         typeIds.indexOf(entry.getExceptionType()));
    168                 out.writeUnsignedLeb128(entry.getHandler());
    169             }
    170 
    171             if (catchesAll) {
    172                 out.writeUnsignedLeb128(list.get(listSize).getHandler());
    173             }
    174         }
    175 
    176         encodedHandlers = out.toByteArray();
    177     }
    178 
    179     /**
    180      * Gets the write size of this instance, in bytes.
    181      *
    182      * @return {@code >= 0;} the write size
    183      */
    184     public int writeSize() {
    185         return (triesSize() * TRY_ITEM_WRITE_SIZE) +
    186                 + encodedHandlers.length;
    187     }
    188 
    189     /**
    190      * Writes this instance to the given stream.
    191      *
    192      * @param file {@code non-null;} file this instance is part of
    193      * @param out {@code non-null;} where to write to
    194      */
    195     public void writeTo(DexFile file, AnnotatedOutput out) {
    196         finishProcessingIfNecessary();
    197 
    198         if (out.annotates()) {
    199             annotateEntries("  ", null, out);
    200         }
    201 
    202         int tableSize = table.size();
    203         for (int i = 0; i < tableSize; i++) {
    204             CatchTable.Entry one = table.get(i);
    205             int start = one.getStart();
    206             int end = one.getEnd();
    207             int insnCount = end - start;
    208 
    209             if (insnCount >= 65536) {
    210                 throw new UnsupportedOperationException(
    211                         "bogus exception range: " + Hex.u4(start) + ".." +
    212                         Hex.u4(end));
    213             }
    214 
    215             out.writeInt(start);
    216             out.writeShort(insnCount);
    217             out.writeShort(handlerOffsets.get(one.getHandlers()));
    218         }
    219 
    220         out.write(encodedHandlers);
    221     }
    222 
    223     /**
    224      * Helper method to annotate or simply print the exception handlers.
    225      * Only one of {@code printTo} or {@code annotateTo} should
    226      * be non-null.
    227      *
    228      * @param prefix {@code non-null;} prefix for each line
    229      * @param printTo {@code null-ok;} where to print to
    230      * @param annotateTo {@code null-ok;} where to consume bytes and annotate to
    231      */
    232     private void annotateEntries(String prefix, PrintWriter printTo,
    233             AnnotatedOutput annotateTo) {
    234         finishProcessingIfNecessary();
    235 
    236         boolean consume = (annotateTo != null);
    237         int amt1 = consume ? 6 : 0;
    238         int amt2 = consume ? 2 : 0;
    239         int size = table.size();
    240         String subPrefix = prefix + "  ";
    241 
    242         if (consume) {
    243             annotateTo.annotate(0, prefix + "tries:");
    244         } else {
    245             printTo.println(prefix + "tries:");
    246         }
    247 
    248         for (int i = 0; i < size; i++) {
    249             CatchTable.Entry entry = table.get(i);
    250             CatchHandlerList handlers = entry.getHandlers();
    251             String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart())
    252                 + ".." + Hex.u2or4(entry.getEnd());
    253             String s2 = handlers.toHuman(subPrefix, "");
    254 
    255             if (consume) {
    256                 annotateTo.annotate(amt1, s1);
    257                 annotateTo.annotate(amt2, s2);
    258             } else {
    259                 printTo.println(s1);
    260                 printTo.println(s2);
    261             }
    262         }
    263 
    264         if (! consume) {
    265             // Only emit the handler lists if we are consuming bytes.
    266             return;
    267         }
    268 
    269         annotateTo.annotate(0, prefix + "handlers:");
    270         annotateTo.annotate(encodedHandlerHeaderSize,
    271                 subPrefix + "size: " + Hex.u2(handlerOffsets.size()));
    272 
    273         int lastOffset = 0;
    274         CatchHandlerList lastList = null;
    275 
    276         for (Map.Entry<CatchHandlerList, Integer> mapping :
    277                  handlerOffsets.entrySet()) {
    278             CatchHandlerList list = mapping.getKey();
    279             int offset = mapping.getValue();
    280 
    281             if (lastList != null) {
    282                 annotateAndConsumeHandlers(lastList, lastOffset,
    283                         offset - lastOffset, subPrefix, printTo, annotateTo);
    284             }
    285 
    286             lastList = list;
    287             lastOffset = offset;
    288         }
    289 
    290         annotateAndConsumeHandlers(lastList, lastOffset,
    291                 encodedHandlers.length - lastOffset,
    292                 subPrefix, printTo, annotateTo);
    293     }
    294 
    295     /**
    296      * Helper for {@link #annotateEntries} to annotate a catch handler list
    297      * while consuming it.
    298      *
    299      * @param handlers {@code non-null;} handlers to annotate
    300      * @param offset {@code >= 0;} the offset of this handler
    301      * @param size {@code >= 1;} the number of bytes the handlers consume
    302      * @param prefix {@code non-null;} prefix for each line
    303      * @param printTo {@code null-ok;} where to print to
    304      * @param annotateTo {@code non-null;} where to annotate to
    305      */
    306     private static void annotateAndConsumeHandlers(CatchHandlerList handlers,
    307             int offset, int size, String prefix, PrintWriter printTo,
    308             AnnotatedOutput annotateTo) {
    309         String s = handlers.toHuman(prefix, Hex.u2(offset) + ": ");
    310 
    311         if (printTo != null) {
    312             printTo.println(s);
    313         }
    314 
    315         annotateTo.annotate(size, s);
    316     }
    317 }
    318