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