summaryrefslogtreecommitdiffstats
path: root/dx/src/com/android/dex/Dex.java
diff options
context:
space:
mode:
Diffstat (limited to 'dx/src/com/android/dex/Dex.java')
-rw-r--r--dx/src/com/android/dex/Dex.java691
1 files changed, 691 insertions, 0 deletions
diff --git a/dx/src/com/android/dex/Dex.java b/dx/src/com/android/dex/Dex.java
new file mode 100644
index 000000000..2395e50fa
--- /dev/null
+++ b/dx/src/com/android/dex/Dex.java
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2011 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.dex;
+
+import com.android.dex.Code.CatchHandler;
+import com.android.dex.Code.Try;
+import com.android.dex.util.ByteInput;
+import com.android.dex.util.ByteOutput;
+import com.android.dex.util.FileUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UTFDataFormatException;
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * The bytes of a dex file in memory for reading and writing. All int offsets
+ * are unsigned.
+ */
+public final class Dex {
+ private byte[] data;
+ private final TableOfContents tableOfContents = new TableOfContents();
+ private int length = 0;
+
+ private final List<String> strings = new AbstractList<String>() {
+ @Override public String get(int index) {
+ checkBounds(index, tableOfContents.stringIds.size);
+ return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM))
+ .readString();
+ }
+ @Override public int size() {
+ return tableOfContents.stringIds.size;
+ }
+ };
+
+ private final List<Integer> typeIds = new AbstractList<Integer>() {
+ @Override public Integer get(int index) {
+ checkBounds(index, tableOfContents.typeIds.size);
+ return open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();
+ }
+ @Override public int size() {
+ return tableOfContents.typeIds.size;
+ }
+ };
+
+ private final List<String> typeNames = new AbstractList<String>() {
+ @Override public String get(int index) {
+ checkBounds(index, tableOfContents.typeIds.size);
+ return strings.get(typeIds.get(index));
+ }
+ @Override public int size() {
+ return tableOfContents.typeIds.size;
+ }
+ };
+
+ private final List<ProtoId> protoIds = new AbstractList<ProtoId>() {
+ @Override public ProtoId get(int index) {
+ checkBounds(index, tableOfContents.protoIds.size);
+ return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index))
+ .readProtoId();
+ }
+ @Override public int size() {
+ return tableOfContents.protoIds.size;
+ }
+ };
+
+ private final List<FieldId> fieldIds = new AbstractList<FieldId>() {
+ @Override public FieldId get(int index) {
+ checkBounds(index, tableOfContents.fieldIds.size);
+ return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index))
+ .readFieldId();
+ }
+ @Override public int size() {
+ return tableOfContents.fieldIds.size;
+ }
+ };
+
+ private final List<MethodId> methodIds = new AbstractList<MethodId>() {
+ @Override public MethodId get(int index) {
+ checkBounds(index, tableOfContents.methodIds.size);
+ return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index))
+ .readMethodId();
+ }
+ @Override public int size() {
+ return tableOfContents.methodIds.size;
+ }
+ };
+
+ /**
+ * Creates a new dex buffer defining no classes.
+ */
+ public Dex() {
+ this.data = new byte[0];
+ }
+
+ /**
+ * Creates a new dex buffer that reads from {@code data}. It is an error to
+ * modify {@code data} after using it to create a dex buffer.
+ */
+ public Dex(byte[] data) throws IOException {
+ this.data = data;
+ this.length = data.length;
+ this.tableOfContents.readFrom(this);
+ }
+
+ /**
+ * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}.
+ */
+ public Dex(InputStream in) throws IOException {
+ loadFrom(in);
+ }
+
+ /**
+ * Creates a new dex buffer from the dex file {@code file}.
+ */
+ public Dex(File file) throws IOException {
+ if (FileUtils.hasArchiveSuffix(file.getName())) {
+ ZipFile zipFile = new ZipFile(file);
+ ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME);
+ if (entry != null) {
+ loadFrom(zipFile.getInputStream(entry));
+ zipFile.close();
+ } else {
+ throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file);
+ }
+ } else if (file.getName().endsWith(".dex")) {
+ loadFrom(new FileInputStream(file));
+ } else {
+ throw new DexException("unknown output extension: " + file);
+ }
+ }
+
+ private void loadFrom(InputStream in) throws IOException {
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+ byte[] buffer = new byte[8192];
+
+ int count;
+ while ((count = in.read(buffer)) != -1) {
+ bytesOut.write(buffer, 0, count);
+ }
+ in.close();
+
+ this.data = bytesOut.toByteArray();
+ this.length = data.length;
+ this.tableOfContents.readFrom(this);
+ }
+
+ private static void checkBounds(int index, int length) {
+ if (index < 0 || index >= length) {
+ throw new IndexOutOfBoundsException("index:" + index + ", length=" + length);
+ }
+ }
+
+ public void writeTo(OutputStream out) throws IOException {
+ out.write(data);
+ }
+
+ public void writeTo(File dexOut) throws IOException {
+ OutputStream out = new FileOutputStream(dexOut);
+ writeTo(out);
+ out.close();
+ }
+
+ public TableOfContents getTableOfContents() {
+ return tableOfContents;
+ }
+
+ public Section open(int position) {
+ if (position < 0 || position > length) {
+ throw new IllegalArgumentException("position=" + position + " length=" + length);
+ }
+ return new Section(position);
+ }
+
+ public Section appendSection(int maxByteCount, String name) {
+ int limit = fourByteAlign(length + maxByteCount);
+ Section result = new Section(name, length, limit);
+ length = limit;
+ return result;
+ }
+
+ public void noMoreSections() {
+ data = new byte[length];
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public static int fourByteAlign(int position) {
+ return (position + 3) & ~3;
+ }
+
+ public byte[] getBytes() {
+ return data;
+ }
+
+ public List<String> strings() {
+ return strings;
+ }
+
+ public List<Integer> typeIds() {
+ return typeIds;
+ }
+
+ public List<String> typeNames() {
+ return typeNames;
+ }
+
+ public List<ProtoId> protoIds() {
+ return protoIds;
+ }
+
+ public List<FieldId> fieldIds() {
+ return fieldIds;
+ }
+
+ public List<MethodId> methodIds() {
+ return methodIds;
+ }
+
+ public Iterable<ClassDef> classDefs() {
+ return new Iterable<ClassDef>() {
+ public Iterator<ClassDef> iterator() {
+ if (!tableOfContents.classDefs.exists()) {
+ return Collections.<ClassDef>emptySet().iterator();
+ }
+ return new Iterator<ClassDef>() {
+ private Dex.Section in = open(tableOfContents.classDefs.off);
+ private int count = 0;
+
+ public boolean hasNext() {
+ return count < tableOfContents.classDefs.size;
+ }
+ public ClassDef next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ count++;
+ return in.readClassDef();
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ public TypeList readTypeList(int offset) {
+ if (offset == 0) {
+ return TypeList.EMPTY;
+ }
+ return open(offset).readTypeList();
+ }
+
+ public ClassData readClassData(ClassDef classDef) {
+ int offset = classDef.getClassDataOffset();
+ if (offset == 0) {
+ throw new IllegalArgumentException("offset == 0");
+ }
+ return open(offset).readClassData();
+ }
+
+ public Code readCode(ClassData.Method method) {
+ int offset = method.getCodeOffset();
+ if (offset == 0) {
+ throw new IllegalArgumentException("offset == 0");
+ }
+ return open(offset).readCode();
+ }
+
+ public final class Section implements ByteInput, ByteOutput {
+ private final String name;
+ private int position;
+ private final int limit;
+ private final int initialPosition;
+
+ private Section(String name, int position, int limit) {
+ this.name = name;
+ this.position = this.initialPosition = position;
+ this.limit = limit;
+ }
+
+ private Section(int position) {
+ this("section", position, data.length);
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public int readInt() {
+ int result = (data[position] & 0xff)
+ | (data[position + 1] & 0xff) << 8
+ | (data[position + 2] & 0xff) << 16
+ | (data[position + 3] & 0xff) << 24;
+ position += 4;
+ return result;
+ }
+
+ public short readShort() {
+ int result = (data[position] & 0xff)
+ | (data[position + 1] & 0xff) << 8;
+ position += 2;
+ return (short) result;
+ }
+
+ public int readUnsignedShort() {
+ return readShort() & 0xffff;
+ }
+
+ public byte readByte() {
+ return (byte) (data[position++] & 0xff);
+ }
+
+ public byte[] readByteArray(int length) {
+ byte[] result = Arrays.copyOfRange(data, position, position + length);
+ position += length;
+ return result;
+ }
+
+ public short[] readShortArray(int length) {
+ short[] result = new short[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = readShort();
+ }
+ return result;
+ }
+
+ public int readUleb128() {
+ return Leb128.readUnsignedLeb128(this);
+ }
+
+ public int readUleb128p1() {
+ return Leb128.readUnsignedLeb128(this) - 1;
+ }
+
+ public int readSleb128() {
+ return Leb128.readSignedLeb128(this);
+ }
+
+ public TypeList readTypeList() {
+ int size = readInt();
+ short[] types = new short[size];
+ for (int i = 0; i < size; i++) {
+ types[i] = readShort();
+ }
+ alignToFourBytes();
+ return new TypeList(Dex.this, types);
+ }
+
+ public String readString() {
+ int offset = readInt();
+ int savedPosition = position;
+ position = offset;
+ try {
+ int expectedLength = readUleb128();
+ String result = Mutf8.decode(this, new char[expectedLength]);
+ if (result.length() != expectedLength) {
+ throw new DexException("Declared length " + expectedLength
+ + " doesn't match decoded length of " + result.length());
+ }
+ return result;
+ } catch (UTFDataFormatException e) {
+ throw new DexException(e);
+ } finally {
+ position = savedPosition;
+ }
+ }
+
+ public FieldId readFieldId() {
+ int declaringClassIndex = readUnsignedShort();
+ int typeIndex = readUnsignedShort();
+ int nameIndex = readInt();
+ return new FieldId(Dex.this, declaringClassIndex, typeIndex, nameIndex);
+ }
+
+ public MethodId readMethodId() {
+ int declaringClassIndex = readUnsignedShort();
+ int protoIndex = readUnsignedShort();
+ int nameIndex = readInt();
+ return new MethodId(Dex.this, declaringClassIndex, protoIndex, nameIndex);
+ }
+
+ public ProtoId readProtoId() {
+ int shortyIndex = readInt();
+ int returnTypeIndex = readInt();
+ int parametersOffset = readInt();
+ return new ProtoId(Dex.this, shortyIndex, returnTypeIndex, parametersOffset);
+ }
+
+ public ClassDef readClassDef() {
+ int offset = getPosition();
+ int type = readInt();
+ int accessFlags = readInt();
+ int supertype = readInt();
+ int interfacesOffset = readInt();
+ int sourceFileIndex = readInt();
+ int annotationsOffset = readInt();
+ int classDataOffset = readInt();
+ int staticValuesOffset = readInt();
+ return new ClassDef(Dex.this, offset, type, accessFlags, supertype,
+ interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset,
+ staticValuesOffset);
+ }
+
+ private Code readCode() {
+ int registersSize = readUnsignedShort();
+ int insSize = readUnsignedShort();
+ int outsSize = readUnsignedShort();
+ int triesSize = readUnsignedShort();
+ int debugInfoOffset = readInt();
+ int instructionsSize = readInt();
+ short[] instructions = readShortArray(instructionsSize);
+ Try[] tries;
+ CatchHandler[] catchHandlers;
+ if (triesSize > 0) {
+ if (instructions.length % 2 == 1) {
+ readShort(); // padding
+ }
+
+ /*
+ * We can't read the tries until we've read the catch handlers.
+ * Unfortunately they're in the opposite order in the dex file
+ * so we need to read them out-of-order.
+ */
+ Section triesSection = open(position);
+ skip(triesSize * SizeOf.TRY_ITEM);
+ catchHandlers = readCatchHandlers();
+ tries = triesSection.readTries(triesSize, catchHandlers);
+ } else {
+ tries = new Try[0];
+ catchHandlers = new CatchHandler[0];
+ }
+ return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions,
+ tries, catchHandlers);
+ }
+
+ private CatchHandler[] readCatchHandlers() {
+ int baseOffset = position;
+ int catchHandlersSize = readUleb128();
+ CatchHandler[] result = new CatchHandler[catchHandlersSize];
+ for (int i = 0; i < catchHandlersSize; i++) {
+ int offset = position - baseOffset;
+ result[i] = readCatchHandler(offset);
+ }
+ return result;
+ }
+
+ private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) {
+ Try[] result = new Try[triesSize];
+ for (int i = 0; i < triesSize; i++) {
+ int startAddress = readInt();
+ int instructionCount = readUnsignedShort();
+ int handlerOffset = readUnsignedShort();
+ int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset);
+ result[i] = new Try(startAddress, instructionCount, catchHandlerIndex);
+ }
+ return result;
+ }
+
+ private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) {
+ for (int i = 0; i < catchHandlers.length; i++) {
+ CatchHandler catchHandler = catchHandlers[i];
+ if (catchHandler.getOffset() == offset) {
+ return i;
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private CatchHandler readCatchHandler(int offset) {
+ int size = readSleb128();
+ int handlersCount = Math.abs(size);
+ int[] typeIndexes = new int[handlersCount];
+ int[] addresses = new int[handlersCount];
+ for (int i = 0; i < handlersCount; i++) {
+ typeIndexes[i] = readUleb128();
+ addresses[i] = readUleb128();
+ }
+ int catchAllAddress = size <= 0 ? readUleb128() : -1;
+ return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset);
+ }
+
+ private ClassData readClassData() {
+ int staticFieldsSize = readUleb128();
+ int instanceFieldsSize = readUleb128();
+ int directMethodsSize = readUleb128();
+ int virtualMethodsSize = readUleb128();
+ ClassData.Field[] staticFields = readFields(staticFieldsSize);
+ ClassData.Field[] instanceFields = readFields(instanceFieldsSize);
+ ClassData.Method[] directMethods = readMethods(directMethodsSize);
+ ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize);
+ return new ClassData(staticFields, instanceFields, directMethods, virtualMethods);
+ }
+
+ private ClassData.Field[] readFields(int count) {
+ ClassData.Field[] result = new ClassData.Field[count];
+ int fieldIndex = 0;
+ for (int i = 0; i < count; i++) {
+ fieldIndex += readUleb128(); // field index diff
+ int accessFlags = readUleb128();
+ result[i] = new ClassData.Field(fieldIndex, accessFlags);
+ }
+ return result;
+ }
+
+ private ClassData.Method[] readMethods(int count) {
+ ClassData.Method[] result = new ClassData.Method[count];
+ int methodIndex = 0;
+ for (int i = 0; i < count; i++) {
+ methodIndex += readUleb128(); // method index diff
+ int accessFlags = readUleb128();
+ int codeOff = readUleb128();
+ result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff);
+ }
+ return result;
+ }
+
+ public Annotation readAnnotation() {
+ byte visibility = readByte();
+ int start = position;
+ new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue();
+ int end = position;
+ return new Annotation(Dex.this, visibility,
+ new EncodedValue(Arrays.copyOfRange(data, start, end)));
+ }
+
+ public EncodedValue readEncodedArray() {
+ int start = position;
+ new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue();
+ int end = position;
+ return new EncodedValue(Arrays.copyOfRange(data, start, end));
+ }
+
+ private void ensureCapacity(int size) {
+ if (position + size > limit) {
+ throw new DexException("Section limit " + limit + " exceeded by " + name);
+ }
+ }
+
+ public void skip(int count) {
+ if (count < 0) {
+ throw new IllegalArgumentException();
+ }
+ ensureCapacity(count);
+ position += count;
+ }
+
+ /**
+ * Writes 0x00 until the position is aligned to a multiple of 4.
+ */
+ public void alignToFourBytes() {
+ int unalignedCount = position;
+ position = Dex.fourByteAlign(position);
+ for (int i = unalignedCount; i < position; i++) {
+ data[i] = 0;
+ }
+ }
+
+ public void assertFourByteAligned() {
+ if ((position & 3) != 0) {
+ throw new IllegalStateException("Not four byte aligned!");
+ }
+ }
+
+ public void write(byte[] bytes) {
+ ensureCapacity(bytes.length);
+ System.arraycopy(bytes, 0, data, position, bytes.length);
+ position += bytes.length;
+ }
+
+ public void writeByte(int b) {
+ ensureCapacity(1);
+ data[position++] = (byte) b;
+ }
+
+ public void writeShort(short i) {
+ ensureCapacity(2);
+ data[position ] = (byte) i;
+ data[position + 1] = (byte) (i >>> 8);
+ position += 2;
+ }
+
+ public void writeUnsignedShort(int i) {
+ short s = (short) i;
+ if (i != (s & 0xffff)) {
+ throw new IllegalArgumentException("Expected an unsigned short: " + i);
+ }
+ writeShort(s);
+ }
+
+ public void write(short[] shorts) {
+ for (short s : shorts) {
+ writeShort(s);
+ }
+ }
+
+ public void writeInt(int i) {
+ ensureCapacity(4);
+ data[position ] = (byte) i;
+ data[position + 1] = (byte) (i >>> 8);
+ data[position + 2] = (byte) (i >>> 16);
+ data[position + 3] = (byte) (i >>> 24);
+ position += 4;
+ }
+
+ public void writeUleb128(int i) {
+ try {
+ Leb128.writeUnsignedLeb128(this, i);
+ ensureCapacity(0);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new DexException("Section limit " + limit + " exceeded by " + name);
+ }
+ }
+
+ public void writeUleb128p1(int i) {
+ writeUleb128(i + 1);
+ }
+
+ public void writeSleb128(int i) {
+ try {
+ Leb128.writeSignedLeb128(this, i);
+ ensureCapacity(0);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new DexException("Section limit " + limit + " exceeded by " + name);
+ }
+ }
+
+ public void writeStringData(String value) {
+ try {
+ int length = value.length();
+ writeUleb128(length);
+ write(Mutf8.encode(value));
+ writeByte(0);
+ } catch (UTFDataFormatException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public void writeTypeList(TypeList typeList) {
+ short[] types = typeList.getTypes();
+ writeInt(types.length);
+ for (short type : types) {
+ writeShort(type);
+ }
+ alignToFourBytes();
+ }
+
+ /**
+ * Returns the number of bytes remaining in this section.
+ */
+ public int remaining() {
+ return limit - position;
+ }
+
+ /**
+ * Returns the number of bytes used by this section.
+ */
+ public int used () {
+ return position - initialPosition;
+ }
+ }
+}