blob: 8b0f1bdd7bbd4567db49735a6d9c87494d69a7e3 [file] [log] [blame]
/*
* 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);
}
}