blob: a9ca13fef1ff313936fa879b1a9eba909037ab7f [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.InstructionField;
import org.jf.dexlib.code.Opcode;
import org.jf.dexlib.util.AnnotatedOutput;
import org.jf.dexlib.util.Input;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Collections;
public class CodeItem extends OffsettedItem<CodeItem> {
private final ArrayList<InstructionField> instructionList;
private final ArrayList<TryItem> tryItems = new ArrayList<TryItem>();
private final ArrayList<EncodedCatchHandler> catchHandlerList = new ArrayList<EncodedCatchHandler>();
private final ShortIntegerField registersCountField;
private final ShortIntegerField inArgumentCountField;
private final ShortIntegerField outArgumentCountField;
private final ListSizeField triesCountField;
private final OffsettedItemReference<DebugInfoItem> debugInfoReferenceField;
private final IntegerField instructionsSizeField;
private final InstructionListField instructionListField;
private final PaddingField paddingField;
private final FieldListField<TryItem> triesListField;
private final EncodedCatchHandlerList catchHandlersListField;
public CodeItem(final DexFile dexFile, int offset) {
super(offset);
instructionList = new ArrayList<InstructionField>();
fields = new Field[] {
registersCountField = new ShortIntegerField("registers_size"),
inArgumentCountField = new ShortIntegerField("ins_size"),
outArgumentCountField = new ShortIntegerField("outs_size"),
triesCountField = new ListSizeField(tryItems, new ShortIntegerField("tries_size")),
debugInfoReferenceField = new OffsettedItemReference<DebugInfoItem>(dexFile.DebugInfoItemsSection,
new IntegerField(null), "debug_off"),
instructionsSizeField = new IntegerField("insns_size"),
instructionListField = new InstructionListField(dexFile),
paddingField = new PaddingField(),
triesListField = new FieldListField<TryItem>(tryItems, "try_item") {
protected TryItem make() {
return new TryItem(catchHandlersListField);
}
},
catchHandlersListField = new EncodedCatchHandlerList(dexFile)
};
}
public CodeItem(final DexFile dexFile,
int registersCount,
int inArguments,
List<InstructionField> instructions,
DebugInfoItem debugInfo,
List<TryItem> tries,
List<EncodedCatchHandler> handlers) {
this(dexFile, 0);
instructionList.addAll(instructions);
instructionsSizeField.cacheValue(instructionListField.getInstructionWordCount());
if (tries != null) {
tryItems.addAll(tries);
if (handlers == null) {
throw new RuntimeException("The handlers parameter cannot be null if tries parameter is not null");
}
catchHandlerList.addAll(handlers);
} else if (handlers != null) {
throw new RuntimeException("The handlers parameter must be null if the tries parameter is null");
}
registersCountField.cacheValue(registersCount);
inArgumentCountField.cacheValue(inArguments);
outArgumentCountField.cacheValue(instructionListField.getOutArguments());
debugInfoReferenceField.setReference(debugInfo);
}
protected int getAlignment() {
return 4;
}
public ItemType getItemType() {
return ItemType.TYPE_CODE_ITEM;
}
public int getRegisterCount() {
return registersCountField.getCachedValue();
}
public List<InstructionField> getInstructions() {
return Collections.unmodifiableList(instructionList);
}
public List<TryItem> getTries() {
return Collections.unmodifiableList(tryItems);
}
public DebugInfoItem getDebugInfo() {
return debugInfoReferenceField.getReference();
}
public void copyTo(DexFile dexFile, CodeItem copy)
{
for (int i = 0; i < fields.length-2; i++) {
fields[i].copyTo(dexFile, copy.fields[i]);
}
//we need to do this in reverse order, so when the tries are copied,
//the catchHandler copies will already exist
catchHandlersListField.copyTo(dexFile, copy.catchHandlersListField);
triesListField.copyTo(dexFile, copy.triesListField);
}
public String getConciseIdentity() {
//TODO: should mention the method name here
return "code_item @0x" + Integer.toHexString(getOffset());
}
public static class TryItem extends CompositeField<TryItem> {
private final IntegerField startAddr;
private final ShortIntegerField insnCount;
private final EncodedCatchHandlerReference encodedCatchHandlerReference;
public TryItem(EncodedCatchHandlerList encodedCatchHandlerList) {
super("try_item");
fields = new Field[] {
startAddr = new IntegerField("start_addr"),
insnCount = new ShortIntegerField("insn_count"),
encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandlerList)
};
}
public TryItem(int startAddr, int insnCount, EncodedCatchHandler encodedCatchHandler) {
super("try_item");
fields = new Field[] {
this.startAddr = new IntegerField(startAddr, "start_addr"),
this.insnCount = new ShortIntegerField(insnCount, "insn_count"),
this.encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandler)
};
}
public int getStartAddress() {
return startAddr.getCachedValue();
}
public int getEndAddress() {
return startAddr.getCachedValue() + insnCount.getCachedValue();
}
public EncodedCatchHandler getHandler() {
return encodedCatchHandlerReference.getReference();
}
}
public static class EncodedCatchHandlerReference extends ShortIntegerField {
private final EncodedCatchHandlerList encodedCatchHandlerList;
private EncodedCatchHandler encodedCatchHandler;
public EncodedCatchHandlerReference(EncodedCatchHandlerList encodedCatchHandlerList) {
super("encoded_catch_handler");
this.encodedCatchHandlerList = encodedCatchHandlerList;
}
public EncodedCatchHandlerReference(EncodedCatchHandler encodedCatchHandler) {
super("encoded_catch_handler");
this.encodedCatchHandlerList = null;
this.encodedCatchHandler = encodedCatchHandler;
}
public EncodedCatchHandlerList getEncodedCatchHandlerList() {
return encodedCatchHandlerList;
}
private void setReference(EncodedCatchHandler encodedCatchHandler) {
this.encodedCatchHandler = encodedCatchHandler;
}
public EncodedCatchHandler getReference() {
return encodedCatchHandler;
}
public void copyTo(DexFile dexFile, CachedIntegerValueField _copy) {
EncodedCatchHandlerReference copy = (EncodedCatchHandlerReference)_copy;
EncodedCatchHandler copiedItem = copy.getEncodedCatchHandlerList().intern(encodedCatchHandler);
copy.setReference(copiedItem);
}
public void writeTo(AnnotatedOutput out) {
cacheValue(encodedCatchHandler.getOffsetInList());
super.writeTo(out);
}
public void readFrom(Input in) {
super.readFrom(in);
encodedCatchHandler = encodedCatchHandlerList.getByOffset(getCachedValue());
}
public int place(int offset) {
cacheValue(encodedCatchHandler.getOffsetInList());
return super.place(offset);
}
}
public class EncodedCatchHandlerList extends CompositeField<EncodedCatchHandlerList> {
private boolean fieldPresent = false;
//this field is only valid when reading a dex file in
protected HashMap<Integer, EncodedCatchHandler> itemsByOffset =
new HashMap<Integer, EncodedCatchHandler>();
protected HashMap<EncodedCatchHandler, EncodedCatchHandler> uniqueItems = null;
private final DexFile dexFile;
public EncodedCatchHandler getByOffset(int offset) {
EncodedCatchHandler encodedCatchHandler = itemsByOffset.get(offset);
if (encodedCatchHandler == null) {
encodedCatchHandler = new EncodedCatchHandler(dexFile, offset);
itemsByOffset.put(offset, encodedCatchHandler);
}
return encodedCatchHandler;
}
public EncodedCatchHandler intern(EncodedCatchHandler item) {
if (uniqueItems == null) {
buildInternedItemMap();
}
EncodedCatchHandler encodedCatchHandler = uniqueItems.get(item);
if (encodedCatchHandler == null) {
encodedCatchHandler = new EncodedCatchHandler(dexFile, -1);
catchHandlerList.add(encodedCatchHandler);
item.copyTo(dexFile, encodedCatchHandler);
uniqueItems.put(encodedCatchHandler, encodedCatchHandler);
}
return encodedCatchHandler;
}
private void buildInternedItemMap() {
uniqueItems = new HashMap<EncodedCatchHandler, EncodedCatchHandler>();
for (EncodedCatchHandler item: catchHandlerList) {
uniqueItems.put(item, item);
}
}
public EncodedCatchHandlerList(final DexFile dexFile) {
super("encoded_catch_handler_list");
this.dexFile = dexFile;
fields = new Field[] {
sizeField = new ListSizeField(catchHandlerList, new Leb128Field("size")),
listField = new FieldListField<EncodedCatchHandler>(catchHandlerList, "encoded_catch_handler") {
protected EncodedCatchHandler make() {
return new EncodedCatchHandler(dexFile, 0);
}
public void readFrom(Input in) {
int currentOffset = sizeField.place(0);
for (int i = 0; i < list.size(); i++) {
EncodedCatchHandler field = list.get(i);
if (field == null) {
field = itemsByOffset.get(currentOffset);
if (field == null) {
field = new EncodedCatchHandler(dexFile, currentOffset);
}
list.set(i, field);
}
int savedOffset = in.getCursor();
field.readFrom(in);
currentOffset += in.getCursor() - savedOffset;
}
}
}
};
}
private final ListSizeField sizeField;
private final FieldListField<EncodedCatchHandler> listField;
public void readFrom(Input in) {
if (tryItems.size() > 0) {
fieldPresent = true;
super.readFrom(in);
}
}
public void writeTo(AnnotatedOutput out) {
if (fieldPresent) {
super.writeTo(out);
}
}
public int place(int offset) {
for (EncodedCatchHandler encodedCatchHandler: listField.list) {
encodedCatchHandler.setBaseOffset(offset);
}
if (tryItems.size() > 0) {
fieldPresent = true;
return super.place(offset);
} else {
return offset;
}
}
public void copyTo(DexFile dexFile, EncodedCatchHandlerList copy) {
super.copyTo(dexFile, copy);
copy.fieldPresent = fieldPresent;
copy.itemsByOffset.clear();
int offset = 0;
for (EncodedCatchHandler encodedCatchHandler: copy.listField.list) {
copy.itemsByOffset.put(encodedCatchHandler.offset, encodedCatchHandler);
}
}
}
public static class EncodedCatchHandler extends CompositeField<EncodedCatchHandler> {
private ArrayList<EncodedTypeAddrPair> list;
boolean hasCatchAll = false;
private int baseOffset = 0;
private final ListSizeField size;
private final FieldListField<EncodedTypeAddrPair> handlers;
private final Leb128Field catchAllAddress;
private int offset;
public EncodedCatchHandler(final DexFile dexFile, int offset) {
super("encoded_catch_handler");
this.offset = offset;
list = new ArrayList<EncodedTypeAddrPair>();
fields = new Field[] {
size = new ListSizeField(list, new SignedLeb128Field("size") {
public void readFrom(Input in) {
super.readFrom(in);
hasCatchAll = (getCachedValue() <= 0);
}
public void cacheValue(int value) {
super.cacheValue(value * (hasCatchAll?-1:1));
}})
,
handlers = new FieldListField<EncodedTypeAddrPair>(list, "encoded_type_addr_pair") {
protected EncodedTypeAddrPair make() {
return new EncodedTypeAddrPair(dexFile);
}
},
catchAllAddress = new Leb128Field("catch_all_addr") {
public void readFrom(Input in) {
if (hasCatchAll) {
super.readFrom(in);
}
}
public void writeTo(AnnotatedOutput out) {
if (hasCatchAll) {
super.writeTo(out);
}
}
public int place(int offset) {
if (hasCatchAll) {
return super.place(offset);
}
return offset;
}
}
};
}
public EncodedCatchHandler(final DexFile dexFile, List<EncodedTypeAddrPair> handlers, int catchAllHandler) {
this(dexFile, 0);
list.addAll(handlers);
if (catchAllHandler >= 0) {
hasCatchAll = true;
catchAllAddress.cacheValue(catchAllHandler);
}
}
public int getOffsetInList() {
return offset-baseOffset;
}
public void setBaseOffset(int baseOffset) {
this.baseOffset = baseOffset;
}
public void copyTo(DexFile dexFile, EncodedCatchHandler copy) {
super.copyTo(dexFile, copy);
copy.hasCatchAll = hasCatchAll;
copy.offset = offset;
}
public int place(int offset) {
this.offset = offset;
return super.place(offset);
}
public int getCatchAllAddress() {
if (hasCatchAll) {
return catchAllAddress.getCachedValue();
} else {
return -1;
}
}
public List<EncodedTypeAddrPair> getHandlers() {
return Collections.unmodifiableList(list);
}
}
public static class EncodedTypeAddrPair extends CompositeField<EncodedTypeAddrPair> {
public final IndexedItemReference<TypeIdItem> typeReferenceField;
public final Leb128Field handlerAddressField;
public EncodedTypeAddrPair(DexFile dexFile) {
super("encoded_type_addr_pair");
fields = new Field[] {
typeReferenceField = new IndexedItemReference<TypeIdItem>(dexFile.TypeIdsSection,
new Leb128Field(null), "type_idx"),
handlerAddressField = new Leb128Field("addr")
};
}
public EncodedTypeAddrPair(DexFile dexFile, TypeIdItem type, int handlerOffset) {
this(dexFile);
typeReferenceField.setReference(type);
handlerAddressField.cacheValue(handlerOffset);
}
public TypeIdItem getTypeReferenceField() {
return typeReferenceField.getReference();
}
public int getHandlerAddress() {
return handlerAddressField.getCachedValue();
}
}
private class InstructionListField implements Field<InstructionListField> {
private final DexFile dexFile;
public InstructionListField(DexFile dexFile) {
this.dexFile = dexFile;
}
public void writeTo(AnnotatedOutput out) {
int startPosition = out.getCursor();
for (InstructionField instruction: instructionList) {
instruction.writeTo(out);
}
if ((out.getCursor() - startPosition) != (instructionsSizeField.getCachedValue() * 2)) {
throw new RuntimeException("Did not write the expected amount of bytes");
}
}
public void readFrom(Input in) {
int numBytes = instructionsSizeField.getCachedValue() * 2;
int startPosition = in.getCursor();
do {
InstructionField instruction = new InstructionField(dexFile);
instruction.readFrom(in);
instructionList.add(instruction);
} while (in.getCursor() - startPosition < numBytes);
if (in.getCursor() - startPosition != numBytes) {
throw new RuntimeException("Read past the end of the code section");
}
}
public int place(int offset) {
return offset + (instructionsSizeField.getCachedValue() * 2);
}
public void copyTo(DexFile dexFile, InstructionListField copy) {
ArrayList<InstructionField> copyInstructionList = copy.getInstructionList();
copyInstructionList.clear();
for (InstructionField instruction: instructionList) {
InstructionField instructionCopy = new InstructionField(dexFile);
instruction.copyTo(dexFile, instructionCopy);
copyInstructionList.add(instructionCopy);
}
}
private ArrayList<InstructionField> getInstructionList() {
return instructionList;
}
//return the word size of the instruction list
public int getInstructionWordCount() {
int bytes = 0;
for (InstructionField instruction: instructionList) {
bytes += instruction.getSize(bytes);
}
return bytes/2;
}
//return the highest parameter word count of any method invokation
public int getOutArguments() {
int maxParamWordCount = 0;
for (InstructionField instruction: instructionList) {
IndexedItem item = instruction.getInstruction().getReferencedItem();
if (item instanceof MethodIdItem) {
MethodIdItem methodIdItem = (MethodIdItem)item;
Opcode opcode = instruction.getInstruction().getOpcode();
boolean isStatic = false;
if (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) {
isStatic = true;
}
int paramWordCount = methodIdItem.getParameterRegisterCount(isStatic);
if (maxParamWordCount < paramWordCount) {
maxParamWordCount = paramWordCount;
}
}
}
return maxParamWordCount;
}
}
private class PaddingField implements Field {
public PaddingField() {
}
private boolean needsAlign() {
return (triesCountField.getCachedValue() > 0) && (instructionsSizeField.getCachedValue() % 2 == 1);
}
public void writeTo(AnnotatedOutput out) {
if (needsAlign()) {
out.writeShort(0);
}
}
public void readFrom(Input in) {
if (needsAlign()) {
in.skipBytes(2);
}
}
public int place(int offset) {
if (needsAlign()) {
return offset + 2;
} else {
return offset;
}
}
public int hashCode() {
return 0;
}
public boolean equals(Object o) {
return getClass() == o.getClass();
}
public void copyTo(DexFile dexFile, Field field) {
}
}
}