/* * 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; } }