blob: 68ebd388f08cd8ee4b69d8caa0d39c18709ec1a6 [file] [log] [blame]
/*
* [The "BSD licence"]
* Copyright (c) 2009 Ben Gruver
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jf.dexlib;
import org.jf.dexlib.Code.*;
import org.jf.dexlib.Code.Format.Instruction20t;
import org.jf.dexlib.Code.Format.Instruction30t;
import org.jf.dexlib.Code.Format.Instruction21c;
import org.jf.dexlib.Code.Format.Instruction31c;
import org.jf.dexlib.Util.*;
import org.jf.dexlib.Debug.DebugInstructionIterator;
import java.util.List;
import java.util.ArrayList;
public class CodeItem extends Item<CodeItem> {
private int registerCount;
private int inWords;
private int outWords;
private DebugInfoItem debugInfo;
private Instruction[] instructions;
private TryItem[] tries;
private EncodedCatchHandler[] encodedCatchHandlers;
private ClassDataItem.EncodedMethod parent;
/**
* Creates a new uninitialized <code>CodeItem</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
*/
public CodeItem(DexFile dexFile) {
super(dexFile);
}
/**
* Creates a new <code>CodeItem</code> with the given values.
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param registerCount the number of registers that the method containing this code uses
* @param inWords the number of 2-byte words that the parameters to the method containing this code take
* @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
* @param debugInfo the debug information for this code/method
* @param instructions the instructions for this code item
* @param tries an array of the tries defined for this code/method
* @param encodedCatchHandlers an array of the exception handlers defined for this code/method
*/
private CodeItem(DexFile dexFile,
int registerCount,
int inWords,
int outWords,
DebugInfoItem debugInfo,
Instruction[] instructions,
TryItem[] tries,
EncodedCatchHandler[] encodedCatchHandlers) {
super(dexFile);
this.registerCount = registerCount;
this.inWords = inWords;
this.outWords = outWords;
this.debugInfo = debugInfo;
if (debugInfo != null) {
debugInfo.setParent(this);
}
this.instructions = instructions;
this.tries = tries;
this.encodedCatchHandlers = encodedCatchHandlers;
}
/**
* Returns a new <code>CodeItem</code> with the given values.
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param registerCount the number of registers that the method containing this code uses
* @param inWords the number of 2-byte words that the parameters to the method containing this code take
* @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
* @param debugInfo the debug information for this code/method
* @param instructions the instructions for this code item
* @param tries a list of the tries defined for this code/method or null if none
* @param encodedCatchHandlers a list of the exception handlers defined for this code/method or null if none
* @return a new <code>CodeItem</code> with the given values.
*/
public static CodeItem getInternedCodeItem(DexFile dexFile,
int registerCount,
int inWords,
int outWords,
DebugInfoItem debugInfo,
List<Instruction> instructions,
List<TryItem> tries,
List<EncodedCatchHandler> encodedCatchHandlers) {
TryItem[] triesArray = null;
EncodedCatchHandler[] encodedCatchHandlersArray = null;
Instruction[] instructionsArray = null;
if (tries != null && tries.size() > 0) {
triesArray = new TryItem[tries.size()];
tries.toArray(triesArray);
}
if (encodedCatchHandlers != null && encodedCatchHandlers.size() > 0) {
encodedCatchHandlersArray = new EncodedCatchHandler[encodedCatchHandlers.size()];
encodedCatchHandlers.toArray(encodedCatchHandlersArray);
}
if (instructions != null && instructions.size() > 0) {
instructionsArray = new Instruction[instructions.size()];
instructions.toArray(instructionsArray);
}
CodeItem codeItem = new CodeItem(dexFile, registerCount, inWords, outWords, debugInfo, instructionsArray,
triesArray, encodedCatchHandlersArray);
return dexFile.CodeItemsSection.intern(codeItem);
}
/** {@inheritDoc} */
protected void readItem(Input in, ReadContext readContext) {
this.registerCount = in.readShort();
this.inWords = in.readShort();
this.outWords = in.readShort();
int triesCount = in.readShort();
this.debugInfo = (DebugInfoItem)readContext.getOffsettedItemByOffset(ItemType.TYPE_DEBUG_INFO_ITEM,
in.readInt());
if (this.debugInfo != null) {
this.debugInfo.setParent(this);
}
int instructionCount = in.readInt();
final ArrayList<Instruction> instructionList = new ArrayList<Instruction>();
byte[] encodedInstructions = in.readBytes(instructionCount * 2);
InstructionIterator.IterateInstructions(dexFile, encodedInstructions,
new InstructionIterator.ProcessInstructionDelegate() {
public void ProcessInstruction(int index, Instruction instruction) {
instructionList.add(instruction);
}
});
this.instructions = new Instruction[instructionList.size()];
instructionList.toArray(instructions);
if (triesCount > 0) {
in.alignTo(4);
//we need to read in the catch handlers first, so save the offset to the try items for future reference
int triesOffset = in.getCursor();
in.setCursor(triesOffset + 8 * triesCount);
//read in the encoded catch handlers
int encodedHandlerStart = in.getCursor();
int handlerCount = in.readUnsignedLeb128();
SparseArray<EncodedCatchHandler> handlerMap = new SparseArray<EncodedCatchHandler>(handlerCount);
encodedCatchHandlers = new EncodedCatchHandler[handlerCount];
for (int i=0; i<handlerCount; i++) {
int position = in.getCursor() - encodedHandlerStart;
encodedCatchHandlers[i] = new EncodedCatchHandler(dexFile, in);
handlerMap.append(position, encodedCatchHandlers[i]);
}
int codeItemEnd = in.getCursor();
//now go back and read the tries
in.setCursor(triesOffset);
tries = new TryItem[triesCount];
for (int i=0; i<triesCount; i++) {
tries[i] = new TryItem(in, handlerMap);
}
//and now back to the end of the code item
in.setCursor(codeItemEnd);
}
}
/** {@inheritDoc} */
protected int placeItem(int offset) {
offset += 16 + getInstructionsLength();
if (tries != null && tries.length > 0) {
if (offset % 4 != 0) {
offset+=2;
}
offset += tries.length * 8;
int encodedCatchHandlerBaseOffset = offset;
offset += Leb128Utils.unsignedLeb128Size(encodedCatchHandlers.length);
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
offset = encodedCatchHandler.place(offset, encodedCatchHandlerBaseOffset);
}
}
return offset;
}
/** {@inheritDoc} */
protected void writeItem(final AnnotatedOutput out) {
int instructionsLength = getInstructionsLength()/2;
if (out.annotates()) {
out.annotate(0, parent.method.getMethodString());
out.annotate(2, "registers_size: 0x" + Integer.toHexString(registerCount) + " (" + registerCount + ")");
out.annotate(2, "ins_size: 0x" + Integer.toHexString(inWords) + " (" + inWords + ")");
out.annotate(2, "outs_size: 0x" + Integer.toHexString(outWords) + " (" + outWords + ")");
int triesLength = tries==null?0:tries.length;
out.annotate(2, "tries_size: 0x" + Integer.toHexString(triesLength) + " (" + triesLength + ")");
if (debugInfo == null) {
out.annotate(4, "debug_info_off:");
} else {
out.annotate(4, "debug_info_off: 0x" + debugInfo.getOffset());
}
out.annotate(4, "insns_size: 0x" + Integer.toHexString(instructionsLength) + " (" +
(instructionsLength) + ")");
}
out.writeShort(registerCount);
out.writeShort(inWords);
out.writeShort(outWords);
if (tries == null) {
out.writeShort(0);
} else {
out.writeShort(tries.length);
}
if (debugInfo == null) {
out.writeInt(0);
} else {
out.writeInt(debugInfo.getOffset());
}
int currentCodeOffset = 0;
for (Instruction instruction: instructions) {
currentCodeOffset += instruction.getSize(currentCodeOffset);
}
out.writeInt(instructionsLength);
currentCodeOffset = 0;
for (Instruction instruction: instructions) {
currentCodeOffset = instruction.write(out, currentCodeOffset);
}
if (tries != null && tries.length > 0) {
if (out.annotates()) {
if ((currentCodeOffset % 4) != 0) {
out.annotate("padding");
out.writeShort(0);
}
int index = 0;
for (TryItem tryItem: tries) {
out.annotate(0, "[0x" + Integer.toHexString(index++) + "] try_item");
out.indent();
tryItem.writeTo(out);
out.deindent();
}
out.annotate("handler_count: 0x" + Integer.toHexString(encodedCatchHandlers.length) + "(" +
encodedCatchHandlers.length + ")");
out.writeUnsignedLeb128(encodedCatchHandlers.length);
index = 0;
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
out.annotate(0, "[" + Integer.toHexString(index++) + "] encoded_catch_handler");
out.indent();
encodedCatchHandler.writeTo(out);
out.deindent();
}
} else {
if ((currentCodeOffset % 4) != 0) {
out.writeShort(0);
}
for (TryItem tryItem: tries) {
tryItem.writeTo(out);
}
out.writeUnsignedLeb128(encodedCatchHandlers.length);
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
encodedCatchHandler.writeTo(out);
}
}
}
}
/** {@inheritDoc} */
public ItemType getItemType() {
return ItemType.TYPE_CODE_ITEM;
}
/** {@inheritDoc} */
public String getConciseIdentity() {
//TODO: should mention the method name here
return "code_item @0x" + Integer.toHexString(getOffset());
}
/** {@inheritDoc} */
public int compareTo(CodeItem other) {
if (parent == null) {
if (other.parent == null) {
return 0;
}
return -1;
}
if (other.parent == null) {
return 1;
}
return parent.method.compareTo(other.parent.method);
}
/**
* @return the register count
*/
public int getRegisterCount() {
return registerCount;
}
/**
* @return an array of the instructions in this code item
*/
public Instruction[] getInstructions() {
return instructions;
}
/**
* @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code>
*/
public TryItem[] getTries() {
return tries;
}
/**
* @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code>
*/
public EncodedCatchHandler[] getHandlers() {
return encodedCatchHandlers;
}
/**
* @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code>
*/
public DebugInfoItem getDebugInfo() {
return debugInfo;
}
/**
* Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with
* @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated
* with
*/
protected void setParent(ClassDataItem.EncodedMethod encodedMethod) {
this.parent = encodedMethod;
}
/**
* @return the MethodIdItem of the method that this CodeItem belongs to
*/
public ClassDataItem.EncodedMethod getParent() {
return parent;
}
/**
* Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions
* @param newInstructions the new instructions to use for this code item
*/
public void updateCode(Instruction[] newInstructions) {
this.instructions = newInstructions;
}
private int getInstructionsLength() {
int offset = 0;
for (Instruction instruction: instructions) {
offset += instruction.getSize(offset);
}
return offset;
}
/**
* Go through the instructions and perform any of the following fixes that are applicable
* - Replace const-string instruction with const-string/jumbo, when the string index is too big
* - Replace goto and goto/16 with a larger version of goto, when the target is too far away
* TODO: we should be able to replace if-* instructions with targets that are too far away with a negated if followed by a goto/32 to the original target
*
* The above fixes are applied iteratively, until no more fixes have been performed
*/
public void fixInstructions(boolean fixStringConst, boolean fixGoto) {
boolean didSomething = false;
do
{
didSomething = false;
int currentCodeOffset = 0;
for (int i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
if (fixGoto && instruction.opcode == Opcode.GOTO) {
int offset = ((OffsetInstruction)instruction).getOffset();
if (((byte)offset) != offset) {
//the offset doesn't fit within a byte, we need to upgrade to a goto/16 or goto/32
if ((short)offset == offset) {
//the offset fits in a short, so upgrade to a goto/16 h
replaceInstructionAtOffset(currentCodeOffset, new Instruction20t(Opcode.GOTO_16, offset));
}
else {
//The offset won't fit into a short, we have to upgrade to a goto/32
replaceInstructionAtOffset(currentCodeOffset, new Instruction30t(Opcode.GOTO_32, offset));
}
didSomething = true;
break;
}
} else if (fixGoto && instruction.opcode == Opcode.GOTO_16) {
int offset = ((OffsetInstruction)instruction).getOffset();
if (((short)offset) != offset) {
//the offset doesn't fit within a short, we need to upgrade to a goto/32
replaceInstructionAtOffset(currentCodeOffset, new Instruction30t(Opcode.GOTO_32, offset));
didSomething = true;
break;
}
} else if (fixStringConst && instruction.opcode == Opcode.CONST_STRING) {
Instruction21c constStringInstruction = (Instruction21c)instruction;
if (constStringInstruction.getReferencedItem().getIndex() > 0xFFFF) {
replaceInstructionAtOffset(currentCodeOffset, new Instruction31c(Opcode.CONST_STRING_JUMBO,
(short)constStringInstruction.getRegisterA(),
constStringInstruction.getReferencedItem()));
didSomething = true;
break;
}
}
currentCodeOffset += instruction.getSize(currentCodeOffset);
}
}while(didSomething);
}
private void replaceInstructionAtOffset(int offset, Instruction replacementInstruction) {
Instruction originalInstruction = null;
int[] originalInstructionOffsets = new int[instructions.length+1];
SparseIntArray originalSwitchOffsetByOriginalSwitchDataOffset = new SparseIntArray();
int currentCodeOffset = 0;
int instructionIndex = 0;
int i;
for (i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
if (currentCodeOffset == offset) {
originalInstruction = instruction;
instructionIndex = i;
}
if (instruction.opcode == Opcode.PACKED_SWITCH || instruction.opcode == Opcode.SPARSE_SWITCH) {
OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
int switchDataOffset = currentCodeOffset + offsetInstruction.getOffset() * 2;
if (originalSwitchOffsetByOriginalSwitchDataOffset.indexOfKey(switchDataOffset) < 0) {
originalSwitchOffsetByOriginalSwitchDataOffset.put(switchDataOffset, currentCodeOffset);
}
}
originalInstructionOffsets[i] = currentCodeOffset;
currentCodeOffset += instruction.getSize(currentCodeOffset);
}
//add the offset just past the end of the last instruction, to help when fixing up try blocks that end
//at the end of the method
originalInstructionOffsets[i] = currentCodeOffset;
if (originalInstruction == null) {
throw new RuntimeException("There is no instruction at offset " + offset);
}
instructions[instructionIndex] = replacementInstruction;
//if we're replacing the instruction with one of the same size, we don't have to worry about fixing
//up any offsets
if (originalInstruction.getSize(offset) == replacementInstruction.getSize(offset)) {
return;
}
//TODO: replace these with a callable delegate
final SparseIntArray originalOffsetsByNewOffset = new SparseIntArray();
final SparseIntArray newOffsetsByOriginalOffset = new SparseIntArray();
currentCodeOffset = 0;
for (i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
int originalOffset = originalInstructionOffsets[i];
originalOffsetsByNewOffset.append(currentCodeOffset, originalOffset);
newOffsetsByOriginalOffset.append(originalOffset, currentCodeOffset);
currentCodeOffset += instruction.getSize(currentCodeOffset);
}
//add the offset just past the end of the last instruction, to help when fixing up try blocks that end
//at the end of the method
originalOffsetsByNewOffset.append(currentCodeOffset, originalInstructionOffsets[i]);
newOffsetsByOriginalOffset.append(originalInstructionOffsets[i], currentCodeOffset);
//update any "offset" instructions, or switch data instructions
currentCodeOffset = 0;
for (i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
if (instruction instanceof OffsetInstruction) {
OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
assert originalOffsetsByNewOffset.indexOfKey(currentCodeOffset) >= 0;
int originalOffset = originalOffsetsByNewOffset.get(currentCodeOffset);
int originalInstructionTarget = originalOffset + offsetInstruction.getOffset() * 2;
assert newOffsetsByOriginalOffset.indexOfKey(originalInstructionTarget) >= 0;
int newInstructionTarget = newOffsetsByOriginalOffset.get(originalInstructionTarget);
int newOffset = (newInstructionTarget - currentCodeOffset) / 2;
if (newOffset != offsetInstruction.getOffset()) {
offsetInstruction.updateOffset(newOffset);
}
} else if (instruction instanceof MultiOffsetInstruction) {
MultiOffsetInstruction multiOffsetInstruction = (MultiOffsetInstruction)instruction;
assert originalOffsetsByNewOffset.indexOfKey(currentCodeOffset) >= 0;
int originalDataOffset = originalOffsetsByNewOffset.get(currentCodeOffset);
int originalSwitchOffset = originalSwitchOffsetByOriginalSwitchDataOffset.get(originalDataOffset);
if (originalSwitchOffset == 0) {
//TODO: is it safe to skip an unreferenced switch data instruction? Or should it throw an exception?
continue;
}
assert newOffsetsByOriginalOffset.indexOfKey(originalSwitchOffset) >= 0;
int newSwitchOffset = newOffsetsByOriginalOffset.get(originalSwitchOffset);
int[] targets = multiOffsetInstruction.getTargets();
for (int t=0; t<targets.length; t++) {
int originalTargetOffset = originalSwitchOffset + targets[t]*2;
assert newOffsetsByOriginalOffset.indexOfKey(originalTargetOffset) >= 0;
int newTargetOffset = newOffsetsByOriginalOffset.get(originalTargetOffset);
int newOffset = (newTargetOffset - newSwitchOffset)/2;
if (newOffset != targets[t]) {
multiOffsetInstruction.updateTarget(t, newOffset);
}
}
}
currentCodeOffset += instruction.getSize(currentCodeOffset);
}
if (debugInfo != null) {
final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo();
ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo);
DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo,
newOffsetsByOriginalOffset, originalOffsetsByNewOffset);
DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer);
assert debugInstructionFixer.result != null;
if (debugInstructionFixer.result != null) {
debugInfo.setEncodedDebugInfo(debugInstructionFixer.result);
}
}
if (encodedCatchHandlers != null) {
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
if (encodedCatchHandler.catchAllHandlerAddress != -1) {
assert newOffsetsByOriginalOffset.indexOfKey(encodedCatchHandler.catchAllHandlerAddress*2) >= 0;
encodedCatchHandler.catchAllHandlerAddress =
newOffsetsByOriginalOffset.get(encodedCatchHandler.catchAllHandlerAddress*2)/2;
}
for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) {
assert newOffsetsByOriginalOffset.indexOfKey(handler.handlerAddress*2) >= 0;
handler.handlerAddress = newOffsetsByOriginalOffset.get(handler.handlerAddress*2)/2;
}
}
}
if (this.tries != null) {
for (TryItem tryItem: tries) {
int startAddress = tryItem.startAddress;
int endAddress = tryItem.startAddress + tryItem.instructionCount;
assert newOffsetsByOriginalOffset.indexOfKey(startAddress * 2) >= 0;
tryItem.startAddress = newOffsetsByOriginalOffset.get(startAddress * 2)/2;
assert newOffsetsByOriginalOffset.indexOfKey(endAddress * 2) >= 0;
tryItem.instructionCount = newOffsetsByOriginalOffset.get(endAddress * 2)/2 - tryItem.startAddress;
}
}
}
private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate {
private int address = 0;
private SparseIntArray newOffsetsByOriginalOffset;
private SparseIntArray originalOffsetsByNewOffset;
private final byte[] originalEncodedDebugInfo;
public byte[] result = null;
public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newOffsetsByOriginalOffset,
SparseIntArray originalOffsetsByNewOffset) {
this.newOffsetsByOriginalOffset = newOffsetsByOriginalOffset;
this.originalOffsetsByNewOffset = originalOffsetsByNewOffset;
this.originalEncodedDebugInfo = originalEncodedDebugInfo;
}
@Override
public void ProcessAdvancePC(int startOffset, int length, int addressDelta) {
address += addressDelta;
if (result != null) {
return;
}
int newOffset = newOffsetsByOriginalOffset.get(address*2, -1);
//The address might not point to an actual instruction in some cases, for example, if an AdvancePC
//instruction was inserted just before a "special" instruction, to fix up the offsets for a previous
//instruction replacement.
//In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will
//bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted
if (newOffset == -1) {
return;
}
assert newOffset != -1;
newOffset = newOffset / 2;
if (newOffset != address) {
int newAddressDelta = newOffset - (address - addressDelta);
assert newAddressDelta > 0;
int addressDiffSize = Leb128Utils.unsignedLeb128Size(newAddressDelta);
result = new byte[originalEncodedDebugInfo.length + addressDiffSize - (length - 1)];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset);
result[startOffset] = 0x01; //DBG_ADVANCE_PC debug opcode
Leb128Utils.writeUnsignedLeb128(newAddressDelta, result, startOffset+1);
System.arraycopy(originalEncodedDebugInfo, startOffset+length, result,
startOffset + addressDiffSize + 1,
originalEncodedDebugInfo.length - (startOffset + addressDiffSize + 1));
}
}
@Override
public void ProcessSpecialOpcode(int startOffset, int debugOpcode, int lineDelta,
int addressDelta) {
address += addressDelta;
if (result != null) {
return;
}
int newOffset = newOffsetsByOriginalOffset.get(address*2, -1);
assert newOffset != -1;
newOffset = newOffset / 2;
if (newOffset != address) {
int newAddressDelta = newOffset - (address - addressDelta);
assert newAddressDelta > 0;
//if the new address delta won't fit in the special opcode, we need to insert
//an additional DBG_ADVANCE_PC opcode
if (lineDelta < 2 && newAddressDelta > 16 || lineDelta > 1 && newAddressDelta > 15) {
int additionalAddressDelta = newOffset - address;
int additionalAddressDeltaSize = Leb128Utils.signedLeb128Size(additionalAddressDelta);
result = new byte[originalEncodedDebugInfo.length + additionalAddressDeltaSize + 1];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset);
result[startOffset] = 0x01; //DBG_ADVANCE_PC
Leb128Utils.writeUnsignedLeb128(additionalAddressDelta, result, startOffset+1);
System.arraycopy(originalEncodedDebugInfo, startOffset, result,
startOffset+additionalAddressDeltaSize+1,
result.length - (startOffset+additionalAddressDeltaSize+1));
} else {
result = new byte[originalEncodedDebugInfo.length];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, result.length);
result[startOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta,
newAddressDelta);
}
}
}
}
public static class TryItem {
/**
* The address (in 2-byte words) within the code where the try block starts
*/
private int startAddress;
/**
* The number of 2-byte words that the try block covers
*/
private int instructionCount;
/**
* The associated exception handler
*/
public final EncodedCatchHandler encodedCatchHandler;
/**
* Construct a new <code>TryItem</code> with the given values
* @param startAddress the address (in 2-byte words) within the code where the try block starts
* @param instructionCount the number of 2-byte words that the try block covers
* @param encodedCatchHandler the associated exception handler
*/
public TryItem(int startAddress, int instructionCount, EncodedCatchHandler encodedCatchHandler) {
this.startAddress = startAddress;
this.instructionCount = instructionCount;
this.encodedCatchHandler = encodedCatchHandler;
}
/**
* This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code>
* @param in the Input object to read the <code>TryItem</code> from
* @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The
* key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list
* structure.
*/
private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) {
startAddress = in.readInt();
instructionCount = in.readShort();
encodedCatchHandler = encodedCatchHandlers.get(in.readShort());
if (encodedCatchHandler == null) {
throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem");
}
}
/**
* Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object
* @param out the <code>AnnotatedOutput</code> object to write to
*/
private void writeTo(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate(4, "start_addr: 0x" + Integer.toHexString(startAddress));
out.annotate(2, "insn_count: 0x" + Integer.toHexString(instructionCount) + " (" + instructionCount +
")");
out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList()));
}
out.writeInt(startAddress);
out.writeShort(instructionCount);
out.writeShort(encodedCatchHandler.getOffsetInList());
}
/**
* @return The address (in 2-byte words) within the code where the try block starts
*/
public int getStartAddress() {
return startAddress;
}
/**
* @return The number of 2-byte words that the try block covers
*/
public int getInstructionCount() {
return instructionCount;
}
}
public static class EncodedCatchHandler {
/**
* An array of the individual exception handlers
*/
public final EncodedTypeAddrPair[] handlers;
/**
* The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all
* handler
*/
private int catchAllHandlerAddress;
//TODO: would it be possible to get away without having these? and generate/create these values while writing?
private int baseOffset;
private int offset;
/**
* Constructs a new <code>EncodedCatchHandler</code> with the given values
* @param handlers an array of the individual exception handlers
* @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1
* if there is no catch all handler
*/
public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) {
this.handlers = handlers;
this.catchAllHandlerAddress = catchAllHandlerAddress;
}
/**
* This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a
* <code>DexFile</code>
* @param dexFile the <code>DexFile</code> that is being read in
* @param in the Input object to read the <code>EncodedCatchHandler</code> from
*/
private EncodedCatchHandler(DexFile dexFile, Input in) {
int handlerCount = in.readSignedLeb128();
if (handlerCount < 0) {
handlers = new EncodedTypeAddrPair[-1 * handlerCount];
} else {
handlers = new EncodedTypeAddrPair[handlerCount];
}
for (int i=0; i<handlers.length; i++) {
handlers[i] = new EncodedTypeAddrPair(dexFile, in);
}
if (handlerCount <= 0) {
catchAllHandlerAddress = in.readUnsignedLeb128();
} else {
catchAllHandlerAddress = -1;
}
}
/**
* Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code>
* @return
*/
public int getCatchAllHandlerAddress() {
return catchAllHandlerAddress;
}
/**
* @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the
* encoded_catch_handler_list structure
*/
private int getOffsetInList() {
return offset-baseOffset;
}
/**
* Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset
* immediately following this <code>EncodedCatchHandler</code>
* @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code>
* @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the
* <code>DexFile</code>
* @return the offset immediately following this <code>EncodedCatchHandler</code>
*/
private int place(int offset, int baseOffset) {
this.offset = offset;
this.baseOffset = baseOffset;
int size = handlers.length;
if (catchAllHandlerAddress > -1) {
size *= -1;
offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress);
}
offset += Leb128Utils.signedLeb128Size(size);
for (EncodedTypeAddrPair handler: handlers) {
offset += handler.getSize();
}
return offset;
}
/**
* Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object
* @param out the <code>AnnotatedOutput</code> object to write to
*/
private void writeTo(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")");
int size = handlers.length;
if (catchAllHandlerAddress > -1) {
size = size * -1;
}
out.writeSignedLeb128(size);
int index = 0;
for (EncodedTypeAddrPair handler: handlers) {
out.annotate(0, "[" + index++ + "] encoded_type_addr_pair");
out.indent();
handler.writeTo(out);
out.deindent();
}
if (catchAllHandlerAddress > -1) {
out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress));
out.writeUnsignedLeb128(catchAllHandlerAddress);
}
} else {
int size = handlers.length;
if (catchAllHandlerAddress > -1) {
size = size * -1;
}
out.writeSignedLeb128(size);
for (EncodedTypeAddrPair handler: handlers) {
handler.writeTo(out);
}
if (catchAllHandlerAddress > -1) {
out.writeUnsignedLeb128(catchAllHandlerAddress);
}
}
}
@Override
public int hashCode() {
int hash = 0;
for (EncodedTypeAddrPair handler: handlers) {
hash = hash * 31 + handler.hashCode();
}
hash = hash * 31 + catchAllHandlerAddress;
return hash;
}
@Override
public boolean equals(Object o) {
if (this==o) {
return true;
}
if (o==null || !this.getClass().equals(o.getClass())) {
return false;
}
EncodedCatchHandler other = (EncodedCatchHandler)o;
if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) {
return false;
}
for (int i=0; i<handlers.length; i++) {
if (!handlers[i].equals(other.handlers[i])) {
return false;
}
}
return true;
}
}
public static class EncodedTypeAddrPair {
/**
* The type of the <code>Exception</code> that this handler handles
*/
public final TypeIdItem exceptionType;
/**
* The address (in 2-byte words) in the code of the handler
*/
private int handlerAddress;
/**
* Constructs a new <code>EncodedTypeAddrPair</code> with the given values
* @param exceptionType the type of the <code>Exception</code> that this handler handles
* @param handlerAddress the address (in 2-byte words) in the code of the handler
*/
public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) {
this.exceptionType = exceptionType;
this.handlerAddress = handlerAddress;
}
/**
* This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a
* <code>DexFile</code>
* @param dexFile the <code>DexFile</code> that is being read in
* @param in the Input object to read the <code>EncodedCatchHandler</code> from
*/
private EncodedTypeAddrPair(DexFile dexFile, Input in) {
exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128());
handlerAddress = in.readUnsignedLeb128();
}
/**
* @return the size of this <code>EncodedTypeAddrPair</code>
*/
private int getSize() {
return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) +
Leb128Utils.unsignedLeb128Size(handlerAddress);
}
/**
* Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object
* @param out the <code>AnnotatedOutput</code> object to write to
*/
private void writeTo(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate("exception_type: " + exceptionType.getTypeDescriptor());
out.writeUnsignedLeb128(exceptionType.getIndex());
out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress));
out.writeUnsignedLeb128(handlerAddress);
} else {
out.writeUnsignedLeb128(exceptionType.getIndex());
out.writeUnsignedLeb128(handlerAddress);
}
}
public int getHandlerAddress() {
return handlerAddress;
}
@Override
public int hashCode() {
return exceptionType.hashCode() * 31 + handlerAddress;
}
@Override
public boolean equals(Object o) {
if (this==o) {
return true;
}
if (o==null || !this.getClass().equals(o.getClass())) {
return false;
}
EncodedTypeAddrPair other = (EncodedTypeAddrPair)o;
return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress;
}
}
}