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.dexgen.dex.file; 18 19import com.android.dexgen.dex.code.CatchHandlerList; 20import com.android.dexgen.dex.code.CatchTable; 21import com.android.dexgen.dex.code.DalvCode; 22import com.android.dexgen.rop.cst.CstType; 23import com.android.dexgen.rop.type.Type; 24import com.android.dexgen.util.AnnotatedOutput; 25import com.android.dexgen.util.ByteArrayAnnotatedOutput; 26import com.android.dexgen.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[]} and a 36 * {@code catch_handler_item[]}. 37 */ 38public 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