blob: 23e25aa525afce5e79464f4307f6b70e2ff12c72 [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.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() {
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();
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;
}
}