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