| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dx.dex.file; |
| |
| import com.android.dx.dex.code.CatchHandlerList; |
| import com.android.dx.dex.code.CatchTable; |
| import com.android.dx.dex.code.DalvCode; |
| import com.android.dx.util.AnnotatedOutput; |
| import com.android.dx.util.ByteArrayAnnotatedOutput; |
| import com.android.dx.util.Hex; |
| |
| import java.io.PrintWriter; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** |
| * List of exception handlers (tuples of covered range, catch type, |
| * handler address) for a particular piece of code. Instances of this |
| * class correspond to a {@code try_item[]} and a |
| * {@code catch_handler_item[]}. |
| */ |
| public final class CatchStructs { |
| /** |
| * the size of a {@code try_item}: a {@code uint} |
| * and two {@code ushort}s |
| */ |
| private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2); |
| |
| /** {@code non-null;} code that contains the catches */ |
| private final DalvCode code; |
| |
| /** |
| * {@code null-ok;} the underlying table; set in |
| * {@link #finishProcessingIfNecessary} |
| */ |
| private CatchTable table; |
| |
| /** |
| * {@code null-ok;} the encoded handler list, if calculated; set in |
| * {@link #encode} |
| */ |
| private byte[] encodedHandlers; |
| |
| /** |
| * length of the handlers header (encoded size), if known; used for |
| * annotation |
| */ |
| private int encodedHandlerHeaderSize; |
| |
| /** |
| * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in |
| * {@link #encode} |
| */ |
| private TreeMap<CatchHandlerList, Integer> handlerOffsets; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param code {@code non-null;} code that contains the catches |
| */ |
| public CatchStructs(DalvCode code) { |
| this.code = code; |
| this.table = null; |
| this.encodedHandlers = null; |
| this.encodedHandlerHeaderSize = 0; |
| this.handlerOffsets = null; |
| } |
| |
| /** |
| * Finish processing the catches, if necessary. |
| */ |
| private void finishProcessingIfNecessary() { |
| if (table == null) { |
| table = code.getCatches(); |
| } |
| } |
| |
| /** |
| * Gets the size of the tries list, in entries. |
| * |
| * @return {@code >= 0;} the tries list size |
| */ |
| public int triesSize() { |
| finishProcessingIfNecessary(); |
| return table.size(); |
| } |
| |
| /** |
| * Does a human-friendly dump of this instance. |
| * |
| * @param out {@code non-null;} where to dump |
| * @param prefix {@code non-null;} prefix to attach to each line of output |
| */ |
| public void debugPrint(PrintWriter out, String prefix) { |
| annotateEntries(prefix, out, null); |
| } |
| |
| /** |
| * Encodes the handler lists. |
| * |
| * @param file {@code non-null;} file this instance is part of |
| */ |
| public void encode(DexFile file) { |
| finishProcessingIfNecessary(); |
| |
| TypeIdsSection typeIds = file.getTypeIds(); |
| int size = table.size(); |
| |
| handlerOffsets = new TreeMap<CatchHandlerList, Integer>(); |
| |
| /* |
| * First add a map entry for each unique list. The tree structure |
| * will ensure they are sorted when we reiterate later. |
| */ |
| for (int i = 0; i < size; i++) { |
| handlerOffsets.put(table.get(i).getHandlers(), null); |
| } |
| |
| if (handlerOffsets.size() > 65535) { |
| throw new UnsupportedOperationException( |
| "too many catch handlers"); |
| } |
| |
| ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); |
| |
| // Write out the handlers "header" consisting of its size in entries. |
| encodedHandlerHeaderSize = |
| out.writeUleb128(handlerOffsets.size()); |
| |
| // Now write the lists out in order, noting the offset of each. |
| for (Map.Entry<CatchHandlerList, Integer> mapping : |
| handlerOffsets.entrySet()) { |
| CatchHandlerList list = mapping.getKey(); |
| int listSize = list.size(); |
| boolean catchesAll = list.catchesAll(); |
| |
| // Set the offset before we do any writing. |
| mapping.setValue(out.getCursor()); |
| |
| if (catchesAll) { |
| // A size <= 0 means that the list ends with a catch-all. |
| out.writeSleb128(-(listSize - 1)); |
| listSize--; |
| } else { |
| out.writeSleb128(listSize); |
| } |
| |
| for (int i = 0; i < listSize; i++) { |
| CatchHandlerList.Entry entry = list.get(i); |
| out.writeUleb128( |
| typeIds.indexOf(entry.getExceptionType())); |
| out.writeUleb128(entry.getHandler()); |
| } |
| |
| if (catchesAll) { |
| out.writeUleb128(list.get(listSize).getHandler()); |
| } |
| } |
| |
| encodedHandlers = out.toByteArray(); |
| } |
| |
| /** |
| * Gets the write size of this instance, in bytes. |
| * |
| * @return {@code >= 0;} the write size |
| */ |
| public int writeSize() { |
| return (triesSize() * TRY_ITEM_WRITE_SIZE) + |
| + encodedHandlers.length; |
| } |
| |
| /** |
| * Writes this instance to the given stream. |
| * |
| * @param file {@code non-null;} file this instance is part of |
| * @param out {@code non-null;} where to write to |
| */ |
| public void writeTo(DexFile file, AnnotatedOutput out) { |
| finishProcessingIfNecessary(); |
| |
| if (out.annotates()) { |
| annotateEntries(" ", null, out); |
| } |
| |
| int tableSize = table.size(); |
| for (int i = 0; i < tableSize; i++) { |
| CatchTable.Entry one = table.get(i); |
| int start = one.getStart(); |
| int end = one.getEnd(); |
| int insnCount = end - start; |
| |
| if (insnCount >= 65536) { |
| throw new UnsupportedOperationException( |
| "bogus exception range: " + Hex.u4(start) + ".." + |
| Hex.u4(end)); |
| } |
| |
| out.writeInt(start); |
| out.writeShort(insnCount); |
| out.writeShort(handlerOffsets.get(one.getHandlers())); |
| } |
| |
| out.write(encodedHandlers); |
| } |
| |
| /** |
| * Helper method to annotate or simply print the exception handlers. |
| * Only one of {@code printTo} or {@code annotateTo} should |
| * be non-null. |
| * |
| * @param prefix {@code non-null;} prefix for each line |
| * @param printTo {@code null-ok;} where to print to |
| * @param annotateTo {@code null-ok;} where to consume bytes and annotate to |
| */ |
| private void annotateEntries(String prefix, PrintWriter printTo, |
| AnnotatedOutput annotateTo) { |
| finishProcessingIfNecessary(); |
| |
| boolean consume = (annotateTo != null); |
| int amt1 = consume ? 6 : 0; |
| int amt2 = consume ? 2 : 0; |
| int size = table.size(); |
| String subPrefix = prefix + " "; |
| |
| if (consume) { |
| annotateTo.annotate(0, prefix + "tries:"); |
| } else { |
| printTo.println(prefix + "tries:"); |
| } |
| |
| for (int i = 0; i < size; i++) { |
| CatchTable.Entry entry = table.get(i); |
| CatchHandlerList handlers = entry.getHandlers(); |
| String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart()) |
| + ".." + Hex.u2or4(entry.getEnd()); |
| String s2 = handlers.toHuman(subPrefix, ""); |
| |
| if (consume) { |
| annotateTo.annotate(amt1, s1); |
| annotateTo.annotate(amt2, s2); |
| } else { |
| printTo.println(s1); |
| printTo.println(s2); |
| } |
| } |
| |
| if (! consume) { |
| // Only emit the handler lists if we are consuming bytes. |
| return; |
| } |
| |
| annotateTo.annotate(0, prefix + "handlers:"); |
| annotateTo.annotate(encodedHandlerHeaderSize, |
| subPrefix + "size: " + Hex.u2(handlerOffsets.size())); |
| |
| int lastOffset = 0; |
| CatchHandlerList lastList = null; |
| |
| for (Map.Entry<CatchHandlerList, Integer> mapping : |
| handlerOffsets.entrySet()) { |
| CatchHandlerList list = mapping.getKey(); |
| int offset = mapping.getValue(); |
| |
| if (lastList != null) { |
| annotateAndConsumeHandlers(lastList, lastOffset, |
| offset - lastOffset, subPrefix, printTo, annotateTo); |
| } |
| |
| lastList = list; |
| lastOffset = offset; |
| } |
| |
| annotateAndConsumeHandlers(lastList, lastOffset, |
| encodedHandlers.length - lastOffset, |
| subPrefix, printTo, annotateTo); |
| } |
| |
| /** |
| * Helper for {@link #annotateEntries} to annotate a catch handler list |
| * while consuming it. |
| * |
| * @param handlers {@code non-null;} handlers to annotate |
| * @param offset {@code >= 0;} the offset of this handler |
| * @param size {@code >= 1;} the number of bytes the handlers consume |
| * @param prefix {@code non-null;} prefix for each line |
| * @param printTo {@code null-ok;} where to print to |
| * @param annotateTo {@code non-null;} where to annotate to |
| */ |
| private static void annotateAndConsumeHandlers(CatchHandlerList handlers, |
| int offset, int size, String prefix, PrintWriter printTo, |
| AnnotatedOutput annotateTo) { |
| String s = handlers.toHuman(prefix, Hex.u2(offset) + ": "); |
| |
| if (printTo != null) { |
| printTo.println(s); |
| } |
| |
| annotateTo.annotate(size, s); |
| } |
| } |