CatchStructs.java revision f6c387128427e121477c1b32ad35cdcaa5101ba3
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 17package com.android.dx.dex.file; 18 19import com.android.dx.dex.code.CatchHandlerList; 20import com.android.dx.dex.code.CatchTable; 21import com.android.dx.dex.code.DalvCode; 22import com.android.dx.rop.cst.CstType; 23import com.android.dx.rop.type.Type; 24import com.android.dx.util.AnnotatedOutput; 25import com.android.dx.util.ByteArrayAnnotatedOutput; 26import com.android.dx.util.Hex; 27 28import java.io.PrintWriter; 29import java.util.Map; 30import 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[]</code> and a 36 * <code>catch_handler_item[]</code>. 37 */ 38public final class CatchStructs { 39 /** 40 * the size of a <code>try_item</code>: a <code>uint</code> 41 * and two <code>ushort</code>s 42 */ 43 private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2); 44 45 /** non-null; code that contains the catches */ 46 private final DalvCode code; 47 48 /** 49 * null-ok; the underlying table; set in 50 * {@link #finishProcessingIfNecessary} 51 */ 52 private CatchTable table; 53 54 /** 55 * 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 * 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 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 >= 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 non-null; where to dump 108 * @param prefix 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 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 >= 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 non-null; file this instance is part of 193 * @param out non-null; where to write to 194 */ 195 public void writeTo(DexFile file, AnnotatedOutput out) { 196 finishProcessingIfNecessary(); 197 198 TypeIdsSection typeIds = file.getTypeIds(); 199 int tableSize = table.size(); 200 int handlersSize = handlerOffsets.size(); 201 202 if (out.annotates()) { 203 annotateEntries(" ", null, out); 204 } 205 206 for (int i = 0; i < tableSize; i++) { 207 CatchTable.Entry one = table.get(i); 208 int start = one.getStart(); 209 int end = one.getEnd(); 210 int insnCount = end - start; 211 212 if (insnCount >= 65536) { 213 throw new UnsupportedOperationException( 214 "bogus exception range: " + Hex.u4(start) + ".." + 215 Hex.u4(end)); 216 } 217 218 out.writeInt(start); 219 out.writeShort(insnCount); 220 out.writeShort(handlerOffsets.get(one.getHandlers())); 221 } 222 223 out.write(encodedHandlers); 224 } 225 226 /** 227 * Helper method to annotate or simply print the exception handlers. 228 * Only one of <code>printTo</code> or <code>annotateTo</code> should 229 * be non-null. 230 * 231 * @param prefix non-null; prefix for each line 232 * @param printTo null-ok; where to print to 233 * @param annotateTo null-ok; where to consume bytes and annotate to 234 */ 235 private void annotateEntries(String prefix, PrintWriter printTo, 236 AnnotatedOutput annotateTo) { 237 finishProcessingIfNecessary(); 238 239 boolean consume = (annotateTo != null); 240 int amt1 = consume ? 6 : 0; 241 int amt2 = consume ? 2 : 0; 242 int size = table.size(); 243 String subPrefix = prefix + " "; 244 245 if (consume) { 246 annotateTo.annotate(0, prefix + "tries:"); 247 } else { 248 printTo.println(prefix + "tries:"); 249 } 250 251 for (int i = 0; i < size; i++) { 252 CatchTable.Entry entry = table.get(i); 253 CatchHandlerList handlers = entry.getHandlers(); 254 String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart()) 255 + ".." + Hex.u2or4(entry.getEnd()); 256 String s2 = handlers.toHuman(subPrefix, ""); 257 258 if (consume) { 259 annotateTo.annotate(amt1, s1); 260 annotateTo.annotate(amt2, s2); 261 } else { 262 printTo.println(s1); 263 printTo.println(s2); 264 } 265 } 266 267 if (! consume) { 268 // Only emit the handler lists if we are consuming bytes. 269 return; 270 } 271 272 annotateTo.annotate(0, prefix + "handlers:"); 273 annotateTo.annotate(encodedHandlerHeaderSize, 274 subPrefix + "size: " + Hex.u2(handlerOffsets.size())); 275 276 int lastOffset = 0; 277 CatchHandlerList lastList = null; 278 279 for (Map.Entry<CatchHandlerList, Integer> mapping : 280 handlerOffsets.entrySet()) { 281 CatchHandlerList list = mapping.getKey(); 282 int offset = mapping.getValue(); 283 284 if (lastList != null) { 285 annotateAndConsumeHandlers(lastList, lastOffset, 286 offset - lastOffset, subPrefix, printTo, annotateTo); 287 } 288 289 lastList = list; 290 lastOffset = offset; 291 } 292 293 annotateAndConsumeHandlers(lastList, lastOffset, 294 encodedHandlers.length - lastOffset, 295 subPrefix, printTo, annotateTo); 296 } 297 298 /** 299 * Helper for {@link #annotateEntries} to annotate a catch handler list 300 * while consuming it. 301 * 302 * @param handlers non-null; handlers to annotate 303 * @param offset >= 0; the offset of this handler 304 * @param size >= 1; the number of bytes the handlers consume 305 * @param prefix non-null; prefix for each line 306 * @param printTo null-ok; where to print to 307 * @param annotateTo non-null; where to annotate to 308 */ 309 private static void annotateAndConsumeHandlers(CatchHandlerList handlers, 310 int offset, int size, String prefix, PrintWriter printTo, 311 AnnotatedOutput annotateTo) { 312 String s = handlers.toHuman(prefix, Hex.u2(offset) + ": "); 313 314 if (printTo != null) { 315 printTo.println(s); 316 } 317 318 annotateTo.annotate(size, s); 319 } 320} 321