summaryrefslogtreecommitdiff
path: root/dx/src/com/android/dx/dex/code/StdCatchBuilder.java
diff options
context:
space:
mode:
Diffstat (limited to 'dx/src/com/android/dx/dex/code/StdCatchBuilder.java')
-rw-r--r--dx/src/com/android/dx/dex/code/StdCatchBuilder.java316
1 files changed, 316 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/dex/code/StdCatchBuilder.java b/dx/src/com/android/dx/dex/code/StdCatchBuilder.java
new file mode 100644
index 0000000..1e7612e
--- /dev/null
+++ b/dx/src/com/android/dx/dex/code/StdCatchBuilder.java
@@ -0,0 +1,316 @@
+/*
+ * 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.code;
+
+import com.android.dx.rop.code.BasicBlock;
+import com.android.dx.rop.code.BasicBlockList;
+import com.android.dx.rop.code.RopMethod;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Type;
+import com.android.dx.rop.type.TypeList;
+import com.android.dx.util.IntList;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Constructor of {@link CatchTable} instances from {@link RopMethod}
+ * and associated data.
+ */
+public final class StdCatchBuilder implements CatchBuilder {
+ /** the maximum range of a single catch handler, in code units */
+ private static final int MAX_CATCH_RANGE = 65535;
+
+ /** {@code non-null;} method to build the list for */
+ private final RopMethod method;
+
+ /** {@code non-null;} block output order */
+ private final int[] order;
+
+ /** {@code non-null;} address objects for each block */
+ private final BlockAddresses addresses;
+
+ /**
+ * Constructs an instance. It merely holds onto its parameters for
+ * a subsequent call to {@link #build}.
+ *
+ * @param method {@code non-null;} method to build the list for
+ * @param order {@code non-null;} block output order
+ * @param addresses {@code non-null;} address objects for each block
+ */
+ public StdCatchBuilder(RopMethod method, int[] order,
+ BlockAddresses addresses) {
+ if (method == null) {
+ throw new NullPointerException("method == null");
+ }
+
+ if (order == null) {
+ throw new NullPointerException("order == null");
+ }
+
+ if (addresses == null) {
+ throw new NullPointerException("addresses == null");
+ }
+
+ this.method = method;
+ this.order = order;
+ this.addresses = addresses;
+ }
+
+ /** {@inheritDoc} */
+ public CatchTable build() {
+ return build(method, order, addresses);
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasAnyCatches() {
+ BasicBlockList blocks = method.getBlocks();
+ int size = blocks.size();
+
+ for (int i = 0; i < size; i++) {
+ BasicBlock block = blocks.get(i);
+ TypeList catches = block.getLastInsn().getCatches();
+ if (catches.size() != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public HashSet<Type> getCatchTypes() {
+ HashSet<Type> result = new HashSet<Type>(20);
+ BasicBlockList blocks = method.getBlocks();
+ int size = blocks.size();
+
+ for (int i = 0; i < size; i++) {
+ BasicBlock block = blocks.get(i);
+ TypeList catches = block.getLastInsn().getCatches();
+ int catchSize = catches.size();
+
+ for (int j = 0; j < catchSize; j++) {
+ result.add(catches.getType(j));
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Builds and returns the catch table for a given method.
+ *
+ * @param method {@code non-null;} method to build the list for
+ * @param order {@code non-null;} block output order
+ * @param addresses {@code non-null;} address objects for each block
+ * @return {@code non-null;} the constructed table
+ */
+ public static CatchTable build(RopMethod method, int[] order,
+ BlockAddresses addresses) {
+ int len = order.length;
+ BasicBlockList blocks = method.getBlocks();
+ ArrayList<CatchTable.Entry> resultList =
+ new ArrayList<CatchTable.Entry>(len);
+ CatchHandlerList currentHandlers = CatchHandlerList.EMPTY;
+ BasicBlock currentStartBlock = null;
+ BasicBlock currentEndBlock = null;
+
+ for (int i = 0; i < len; i++) {
+ BasicBlock block = blocks.labelToBlock(order[i]);
+
+ if (!block.canThrow()) {
+ /*
+ * There is no need to concern ourselves with the
+ * placement of blocks that can't throw with respect
+ * to the blocks that *can* throw.
+ */
+ continue;
+ }
+
+ CatchHandlerList handlers = handlersFor(block, addresses);
+
+ if (currentHandlers.size() == 0) {
+ // This is the start of a new catch range.
+ currentStartBlock = block;
+ currentEndBlock = block;
+ currentHandlers = handlers;
+ continue;
+ }
+
+ if (currentHandlers.equals(handlers)
+ && rangeIsValid(currentStartBlock, block, addresses)) {
+ /*
+ * The block we are looking at now has the same handlers
+ * as the block that started the currently open catch
+ * range, and adding it to the currently open range won't
+ * cause it to be too long.
+ */
+ currentEndBlock = block;
+ continue;
+ }
+
+ /*
+ * The block we are looking at now has incompatible handlers,
+ * so we need to finish off the last entry and start a new
+ * one. Note: We only emit an entry if it has associated handlers.
+ */
+ if (currentHandlers.size() != 0) {
+ CatchTable.Entry entry =
+ makeEntry(currentStartBlock, currentEndBlock,
+ currentHandlers, addresses);
+ resultList.add(entry);
+ }
+
+ currentStartBlock = block;
+ currentEndBlock = block;
+ currentHandlers = handlers;
+ }
+
+ if (currentHandlers.size() != 0) {
+ // Emit an entry for the range that was left hanging.
+ CatchTable.Entry entry =
+ makeEntry(currentStartBlock, currentEndBlock,
+ currentHandlers, addresses);
+ resultList.add(entry);
+ }
+
+ // Construct the final result.
+
+ int resultSz = resultList.size();
+
+ if (resultSz == 0) {
+ return CatchTable.EMPTY;
+ }
+
+ CatchTable result = new CatchTable(resultSz);
+
+ for (int i = 0; i < resultSz; i++) {
+ result.set(i, resultList.get(i));
+ }
+
+ result.setImmutable();
+ return result;
+ }
+
+ /**
+ * Makes the {@link CatchHandlerList} for the given basic block.
+ *
+ * @param block {@code non-null;} block to get entries for
+ * @param addresses {@code non-null;} address objects for each block
+ * @return {@code non-null;} array of entries
+ */
+ private static CatchHandlerList handlersFor(BasicBlock block,
+ BlockAddresses addresses) {
+ IntList successors = block.getSuccessors();
+ int succSize = successors.size();
+ int primary = block.getPrimarySuccessor();
+ TypeList catches = block.getLastInsn().getCatches();
+ int catchSize = catches.size();
+
+ if (catchSize == 0) {
+ return CatchHandlerList.EMPTY;
+ }
+
+ if (((primary == -1) && (succSize != catchSize))
+ || ((primary != -1) &&
+ ((succSize != (catchSize + 1))
+ || (primary != successors.get(catchSize))))) {
+ /*
+ * Blocks that throw are supposed to list their primary
+ * successor -- if any -- last in the successors list, but
+ * that constraint appears to be violated here.
+ */
+ throw new RuntimeException(
+ "shouldn't happen: weird successors list");
+ }
+
+ /*
+ * Reduce the effective catchSize if we spot a catch-all that
+ * isn't at the end.
+ */
+ for (int i = 0; i < catchSize; i++) {
+ Type type = catches.getType(i);
+ if (type.equals(Type.OBJECT)) {
+ catchSize = i + 1;
+ break;
+ }
+ }
+
+ CatchHandlerList result = new CatchHandlerList(catchSize);
+
+ for (int i = 0; i < catchSize; i++) {
+ CstType oneType = new CstType(catches.getType(i));
+ CodeAddress oneHandler = addresses.getStart(successors.get(i));
+ result.set(i, oneType, oneHandler.getAddress());
+ }
+
+ result.setImmutable();
+ return result;
+ }
+
+ /**
+ * Makes a {@link CatchTable#Entry} for the given block range and
+ * handlers.
+ *
+ * @param start {@code non-null;} the start block for the range (inclusive)
+ * @param end {@code non-null;} the start block for the range (also inclusive)
+ * @param handlers {@code non-null;} the handlers for the range
+ * @param addresses {@code non-null;} address objects for each block
+ */
+ private static CatchTable.Entry makeEntry(BasicBlock start,
+ BasicBlock end, CatchHandlerList handlers,
+ BlockAddresses addresses) {
+ /*
+ * We start at the *last* instruction of the start block, since
+ * that's the instruction that can throw...
+ */
+ CodeAddress startAddress = addresses.getLast(start);
+
+ // ...And we end *after* the last instruction of the end block.
+ CodeAddress endAddress = addresses.getEnd(end);
+
+ return new CatchTable.Entry(startAddress.getAddress(),
+ endAddress.getAddress(), handlers);
+ }
+
+ /**
+ * Gets whether the address range for the given two blocks is valid
+ * for a catch handler. This is true as long as the covered range is
+ * under 65536 code units.
+ *
+ * @param start {@code non-null;} the start block for the range (inclusive)
+ * @param end {@code non-null;} the start block for the range (also inclusive)
+ * @param addresses {@code non-null;} address objects for each block
+ * @return {@code true} if the range is valid as a catch range
+ */
+ private static boolean rangeIsValid(BasicBlock start, BasicBlock end,
+ BlockAddresses addresses) {
+ if (start == null) {
+ throw new NullPointerException("start == null");
+ }
+
+ if (end == null) {
+ throw new NullPointerException("end == null");
+ }
+
+ // See above about selection of instructions.
+ int startAddress = addresses.getLast(start).getAddress();
+ int endAddress = addresses.getEnd(end).getAddress();
+
+ return (endAddress - startAddress) <= MAX_CATCH_RANGE;
+ }
+}