diff options
Diffstat (limited to 'dx/src/com/android/dx/dex/code/StdCatchBuilder.java')
-rw-r--r-- | dx/src/com/android/dx/dex/code/StdCatchBuilder.java | 316 |
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; + } +} |