summaryrefslogtreecommitdiffstats
path: root/dex/src
diff options
context:
space:
mode:
authorJesse Wilson <jessewilson@google.com>2011-09-18 12:55:16 -0400
committerBrian Carlstrom <bdc@google.com>2013-04-30 14:02:10 -0700
commit2bea5ee615b0f4add658d5660bd81c5145a0d05e (patch)
treee6c2f7f6b833143b4cf2e99e63f0a76e021ddab8 /dex/src
parent87b4b0f88fe0b5fa5c271593e483fb5bd306a6a4 (diff)
downloadlibcore-2bea5ee615b0f4add658d5660bd81c5145a0d05e.zip
libcore-2bea5ee615b0f4add658d5660bd81c5145a0d05e.tar.gz
libcore-2bea5ee615b0f4add658d5660bd81c5145a0d05e.tar.bz2
Move dex utilities from dalvik/dx to libcore/dex part 2
Change-Id: Ib5bb7bec80d77464f632f1cdfef708d868301c42 (cherry picked from commit 9ae95d8f3381204108abc722e8e5a03d6ecbf311)
Diffstat (limited to 'dex/src')
-rw-r--r--dex/src/main/java/com/android/dex/Annotation.java63
-rw-r--r--dex/src/main/java/com/android/dex/ClassData.java104
-rw-r--r--dex/src/main/java/com/android/dex/ClassDef.java102
-rw-r--r--dex/src/main/java/com/android/dex/Code.java114
-rw-r--r--dex/src/main/java/com/android/dex/Dex.java631
-rw-r--r--dex/src/main/java/com/android/dex/DexException.java33
-rw-r--r--dex/src/main/java/com/android/dex/DexFormat.java98
-rw-r--r--dex/src/main/java/com/android/dex/EncodedValue.java57
-rw-r--r--dex/src/main/java/com/android/dex/EncodedValueCodec.java187
-rw-r--r--dex/src/main/java/com/android/dex/EncodedValueReader.java287
-rw-r--r--dex/src/main/java/com/android/dex/FieldId.java68
-rw-r--r--dex/src/main/java/com/android/dex/Leb128.java161
-rw-r--r--dex/src/main/java/com/android/dex/MethodId.java70
-rw-r--r--dex/src/main/java/com/android/dex/Mutf8.java115
-rw-r--r--dex/src/main/java/com/android/dex/ProtoId.java68
-rw-r--r--dex/src/main/java/com/android/dex/SizeOf.java103
-rw-r--r--dex/src/main/java/com/android/dex/TableOfContents.java236
-rw-r--r--dex/src/main/java/com/android/dex/TypeList.java55
-rw-r--r--dex/src/main/java/com/android/dex/util/ByteArrayByteInput.java31
-rw-r--r--dex/src/main/java/com/android/dex/util/ByteInput.java30
-rw-r--r--dex/src/main/java/com/android/dex/util/ByteOutput.java30
-rw-r--r--dex/src/main/java/com/android/dex/util/ExceptionWithContext.java148
-rw-r--r--dex/src/main/java/com/android/dex/util/FileUtils.java97
-rw-r--r--dex/src/main/java/com/android/dex/util/Unsigned.java42
-rw-r--r--dex/src/test/java/com/android/dex/EncodedValueReaderTest.java127
25 files changed, 3057 insertions, 0 deletions
diff --git a/dex/src/main/java/com/android/dex/Annotation.java b/dex/src/main/java/com/android/dex/Annotation.java
new file mode 100644
index 0000000..e5ef978
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/Annotation.java
@@ -0,0 +1,63 @@
+/*
+ * 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 static com.android.dex.EncodedValueReader.ENCODED_ANNOTATION;
+
+/**
+ * An annotation.
+ */
+public final class Annotation implements Comparable<Annotation> {
+ private final Dex dex;
+ private final byte visibility;
+ private final EncodedValue encodedAnnotation;
+
+ public Annotation(Dex dex, byte visibility, EncodedValue encodedAnnotation) {
+ this.dex = dex;
+ this.visibility = visibility;
+ this.encodedAnnotation = encodedAnnotation;
+ }
+
+ public byte getVisibility() {
+ return visibility;
+ }
+
+ public EncodedValueReader getReader() {
+ return new EncodedValueReader(encodedAnnotation, ENCODED_ANNOTATION);
+ }
+
+ public int getTypeIndex() {
+ EncodedValueReader reader = getReader();
+ reader.readAnnotation();
+ return reader.getAnnotationType();
+ }
+
+ public void writeTo(Dex.Section out) {
+ out.writeByte(visibility);
+ encodedAnnotation.writeTo(out);
+ }
+
+ @Override public int compareTo(Annotation other) {
+ return encodedAnnotation.compareTo(other.encodedAnnotation);
+ }
+
+ @Override public String toString() {
+ return dex == null
+ ? visibility + " " + getTypeIndex()
+ : visibility + " " + dex.typeNames().get(getTypeIndex());
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/ClassData.java b/dex/src/main/java/com/android/dex/ClassData.java
new file mode 100644
index 0000000..840756c
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/ClassData.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+public final class ClassData {
+ private final Field[] staticFields;
+ private final Field[] instanceFields;
+ private final Method[] directMethods;
+ private final Method[] virtualMethods;
+
+ public ClassData(Field[] staticFields, Field[] instanceFields,
+ Method[] directMethods, Method[] virtualMethods) {
+ this.staticFields = staticFields;
+ this.instanceFields = instanceFields;
+ this.directMethods = directMethods;
+ this.virtualMethods = virtualMethods;
+ }
+
+ public Field[] getStaticFields() {
+ return staticFields;
+ }
+
+ public Field[] getInstanceFields() {
+ return instanceFields;
+ }
+
+ public Method[] getDirectMethods() {
+ return directMethods;
+ }
+
+ public Method[] getVirtualMethods() {
+ return virtualMethods;
+ }
+
+ public Field[] allFields() {
+ Field[] result = new Field[staticFields.length + instanceFields.length];
+ System.arraycopy(staticFields, 0, result, 0, staticFields.length);
+ System.arraycopy(instanceFields, 0, result, staticFields.length, instanceFields.length);
+ return result;
+ }
+
+ public Method[] allMethods() {
+ Method[] result = new Method[directMethods.length + virtualMethods.length];
+ System.arraycopy(directMethods, 0, result, 0, directMethods.length);
+ System.arraycopy(virtualMethods, 0, result, directMethods.length, virtualMethods.length);
+ return result;
+ }
+
+ public static class Field {
+ private final int fieldIndex;
+ private final int accessFlags;
+
+ public Field(int fieldIndex, int accessFlags) {
+ this.fieldIndex = fieldIndex;
+ this.accessFlags = accessFlags;
+ }
+
+ public int getFieldIndex() {
+ return fieldIndex;
+ }
+
+ public int getAccessFlags() {
+ return accessFlags;
+ }
+ }
+
+ public static class Method {
+ private final int methodIndex;
+ private final int accessFlags;
+ private final int codeOffset;
+
+ public Method(int methodIndex, int accessFlags, int codeOffset) {
+ this.methodIndex = methodIndex;
+ this.accessFlags = accessFlags;
+ this.codeOffset = codeOffset;
+ }
+
+ public int getMethodIndex() {
+ return methodIndex;
+ }
+
+ public int getAccessFlags() {
+ return accessFlags;
+ }
+
+ public int getCodeOffset() {
+ return codeOffset;
+ }
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/ClassDef.java b/dex/src/main/java/com/android/dex/ClassDef.java
new file mode 100644
index 0000000..b3225ec
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/ClassDef.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+/**
+ * A type definition.
+ */
+public final class ClassDef {
+ public static final int NO_INDEX = -1;
+ private final Dex buffer;
+ private final int offset;
+ private final int typeIndex;
+ private final int accessFlags;
+ private final int supertypeIndex;
+ private final int interfacesOffset;
+ private final int sourceFileIndex;
+ private final int annotationsOffset;
+ private final int classDataOffset;
+ private final int staticValuesOffset;
+
+ public ClassDef(Dex buffer, int offset, int typeIndex, int accessFlags,
+ int supertypeIndex, int interfacesOffset, int sourceFileIndex,
+ int annotationsOffset, int classDataOffset, int staticValuesOffset) {
+ this.buffer = buffer;
+ this.offset = offset;
+ this.typeIndex = typeIndex;
+ this.accessFlags = accessFlags;
+ this.supertypeIndex = supertypeIndex;
+ this.interfacesOffset = interfacesOffset;
+ this.sourceFileIndex = sourceFileIndex;
+ this.annotationsOffset = annotationsOffset;
+ this.classDataOffset = classDataOffset;
+ this.staticValuesOffset = staticValuesOffset;
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public int getTypeIndex() {
+ return typeIndex;
+ }
+
+ public int getSupertypeIndex() {
+ return supertypeIndex;
+ }
+
+ public int getInterfacesOffset() {
+ return interfacesOffset;
+ }
+
+ public short[] getInterfaces() {
+ return buffer.readTypeList(interfacesOffset).getTypes();
+ }
+
+ public int getAccessFlags() {
+ return accessFlags;
+ }
+
+ public int getSourceFileIndex() {
+ return sourceFileIndex;
+ }
+
+ public int getAnnotationsOffset() {
+ return annotationsOffset;
+ }
+
+ public int getClassDataOffset() {
+ return classDataOffset;
+ }
+
+ public int getStaticValuesOffset() {
+ return staticValuesOffset;
+ }
+
+ @Override public String toString() {
+ if (buffer == null) {
+ return typeIndex + " " + supertypeIndex;
+ }
+
+ StringBuilder result = new StringBuilder();
+ result.append(buffer.typeNames().get(typeIndex));
+ if (supertypeIndex != NO_INDEX) {
+ result.append(" extends ").append(buffer.typeNames().get(supertypeIndex));
+ }
+ return result.toString();
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/Code.java b/dex/src/main/java/com/android/dex/Code.java
new file mode 100644
index 0000000..8a9b885
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/Code.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+public final class Code {
+ private final int registersSize;
+ private final int insSize;
+ private final int outsSize;
+ private final int debugInfoOffset;
+ private final short[] instructions;
+ private final Try[] tries;
+ private final CatchHandler[] catchHandlers;
+
+ public Code(int registersSize, int insSize, int outsSize, int debugInfoOffset,
+ short[] instructions, Try[] tries, CatchHandler[] catchHandlers) {
+ this.registersSize = registersSize;
+ this.insSize = insSize;
+ this.outsSize = outsSize;
+ this.debugInfoOffset = debugInfoOffset;
+ this.instructions = instructions;
+ this.tries = tries;
+ this.catchHandlers = catchHandlers;
+ }
+
+ public int getRegistersSize() {
+ return registersSize;
+ }
+
+ public int getInsSize() {
+ return insSize;
+ }
+
+ public int getOutsSize() {
+ return outsSize;
+ }
+
+ public int getDebugInfoOffset() {
+ return debugInfoOffset;
+ }
+
+ public short[] getInstructions() {
+ return instructions;
+ }
+
+ public Try[] getTries() {
+ return tries;
+ }
+
+ public CatchHandler[] getCatchHandlers() {
+ return catchHandlers;
+ }
+
+ public static class Try {
+ final int startAddress;
+ final int instructionCount;
+ final int handlerOffset;
+
+ Try(int startAddress, int instructionCount, int handlerOffset) {
+ this.startAddress = startAddress;
+ this.instructionCount = instructionCount;
+ this.handlerOffset = handlerOffset;
+ }
+
+ public int getStartAddress() {
+ return startAddress;
+ }
+
+ public int getInstructionCount() {
+ return instructionCount;
+ }
+
+ public int getHandlerOffset() {
+ return handlerOffset;
+ }
+ }
+
+ public static class CatchHandler {
+ final int[] typeIndexes;
+ final int[] addresses;
+ final int catchAllAddress;
+
+ public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress) {
+ this.typeIndexes = typeIndexes;
+ this.addresses = addresses;
+ this.catchAllAddress = catchAllAddress;
+ }
+
+ public int[] getTypeIndexes() {
+ return typeIndexes;
+ }
+
+ public int[] getAddresses() {
+ return addresses;
+ }
+
+ public int getCatchAllAddress() {
+ return catchAllAddress;
+ }
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/Dex.java b/dex/src/main/java/com/android/dex/Dex.java
new file mode 100644
index 0000000..f179b99
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/Dex.java
@@ -0,0 +1,631 @@
+/*
+ * 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.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;
+ }
+
+ private 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 Section(String name, int position, int limit) {
+ this.name = name;
+ this.position = 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 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);
+ Code.Try[] tries = new Code.Try[triesSize];
+ Code.CatchHandler[] catchHandlers = new Code.CatchHandler[0];
+ if (triesSize > 0) {
+ if (instructions.length % 2 == 1) {
+ readShort(); // padding
+ }
+
+ for (int i = 0; i < triesSize; i++) {
+ int startAddress = readInt();
+ int instructionCount = readUnsignedShort();
+ int handlerOffset = readUnsignedShort();
+ tries[i] = new Code.Try(startAddress, instructionCount, handlerOffset);
+ }
+
+ int catchHandlersSize = readUleb128();
+ catchHandlers = new Code.CatchHandler[catchHandlersSize];
+ for (int i = 0; i < catchHandlersSize; i++) {
+ catchHandlers[i] = readCatchHandler();
+ }
+ }
+ return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions,
+ tries, catchHandlers);
+ }
+
+ private Code.CatchHandler readCatchHandler() {
+ 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 Code.CatchHandler(typeIndexes, addresses, catchAllAddress);
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * 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 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;
+ }
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/DexException.java b/dex/src/main/java/com/android/dex/DexException.java
new file mode 100644
index 0000000..a30a46f
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/DexException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util.ExceptionWithContext;
+
+/**
+ * Thrown when there's a format problem reading, writing, or generally
+ * processing a dex file.
+ */
+public final class DexException extends ExceptionWithContext {
+ public DexException(String message) {
+ super(message);
+ }
+
+ public DexException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/DexFormat.java b/dex/src/main/java/com/android/dex/DexFormat.java
new file mode 100644
index 0000000..85941fd
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/DexFormat.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+/**
+ * Constants that show up in and are otherwise related to {@code .dex}
+ * files, and helper methods for same.
+ */
+public final class DexFormat {
+ private DexFormat() {}
+
+ /**
+ * API level to target in order to produce the most modern file
+ * format
+ */
+ public static final int API_CURRENT = 14;
+
+ /** API level to target in order to suppress extended opcode usage */
+ public static final int API_NO_EXTENDED_OPCODES = 13;
+
+ /**
+ * file name of the primary {@code .dex} file inside an
+ * application or library {@code .jar} file
+ */
+ public static final String DEX_IN_JAR_NAME = "classes.dex";
+
+ /** common prefix for all dex file "magic numbers" */
+ public static final String MAGIC_PREFIX = "dex\n";
+
+ /** common suffix for all dex file "magic numbers" */
+ public static final String MAGIC_SUFFIX = "\0";
+
+ /** dex file version number for the current format variant */
+ public static final String VERSION_CURRENT = "036";
+
+ /** dex file version number for API level 13 and earlier */
+ public static final String VERSION_FOR_API_13 = "035";
+
+ /**
+ * value used to indicate endianness of file contents
+ */
+ public static final int ENDIAN_TAG = 0x12345678;
+
+ /**
+ * Returns the API level corresponding to the given magic number,
+ * or {@code -1} if the given array is not a well-formed dex file
+ * magic number.
+ */
+ public static int magicToApi(byte[] magic) {
+ if (magic.length != 8) {
+ return -1;
+ }
+
+ if ((magic[0] != 'd') || (magic[1] != 'e') || (magic[2] != 'x') || (magic[3] != '\n') ||
+ (magic[7] != '\0')) {
+ return -1;
+ }
+
+ String version = "" + ((char) magic[4]) + ((char) magic[5]) +((char) magic[6]);
+
+ if (version.equals(VERSION_CURRENT)) {
+ return API_CURRENT;
+ } else if (version.equals(VERSION_FOR_API_13)) {
+ return 13;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the magic number corresponding to the given target API level.
+ */
+ public static String apiToMagic(int targetApiLevel) {
+ String version;
+
+ if (targetApiLevel >= API_CURRENT) {
+ version = VERSION_CURRENT;
+ } else {
+ version = VERSION_FOR_API_13;
+ }
+
+ return MAGIC_PREFIX + version + MAGIC_SUFFIX;
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/EncodedValue.java b/dex/src/main/java/com/android/dex/EncodedValue.java
new file mode 100644
index 0000000..8d0c3ad
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/EncodedValue.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util.ByteArrayByteInput;
+import com.android.dex.util.ByteInput;
+
+/**
+ * An encoded value or array.
+ */
+public final class EncodedValue implements Comparable<EncodedValue> {
+ private final byte[] data;
+
+ public EncodedValue(byte[] data) {
+ this.data = data;
+ }
+
+ public ByteInput asByteInput() {
+ return new ByteArrayByteInput(data);
+ }
+
+ public byte[] getBytes() {
+ return data;
+ }
+
+ public void writeTo(Dex.Section out) {
+ out.write(data);
+ }
+
+ @Override public int compareTo(EncodedValue other) {
+ int size = Math.min(data.length, other.data.length);
+ for (int i = 0; i < size; i++) {
+ if (data[i] != other.data[i]) {
+ return (data[i] & 0xff) - (other.data[i] & 0xff);
+ }
+ }
+ return data.length - other.data.length;
+ }
+
+ @Override public String toString() {
+ return Integer.toHexString(data[0] & 0xff) + "...(" + data.length + ")";
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/EncodedValueCodec.java b/dex/src/main/java/com/android/dex/EncodedValueCodec.java
new file mode 100644
index 0000000..7fc1724
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/EncodedValueCodec.java
@@ -0,0 +1,187 @@
+/*
+ * 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.util.ByteInput;
+import com.android.dex.util.ByteOutput;
+
+/**
+ * Read and write {@code encoded_value} primitives.
+ */
+public final class EncodedValueCodec {
+ private EncodedValueCodec() {
+ }
+
+ /**
+ * Writes a signed integral to {@code out}.
+ */
+ public static void writeSignedIntegralValue(ByteOutput out, int type, long value) {
+ /*
+ * Figure out how many bits are needed to represent the value,
+ * including a sign bit: The bit count is subtracted from 65
+ * and not 64 to account for the sign bit. The xor operation
+ * has the effect of leaving non-negative values alone and
+ * unary complementing negative values (so that a leading zero
+ * count always returns a useful number for our present
+ * purpose).
+ */
+ int requiredBits = 65 - Long.numberOfLeadingZeros(value ^ (value >> 63));
+
+ // Round up the requiredBits to a number of bytes.
+ int requiredBytes = (requiredBits + 0x07) >> 3;
+
+ /*
+ * Write the header byte, which includes the type and
+ * requiredBytes - 1.
+ */
+ out.writeByte(type | ((requiredBytes - 1) << 5));
+
+ // Write the value, per se.
+ while (requiredBytes > 0) {
+ out.writeByte((byte) value);
+ value >>= 8;
+ requiredBytes--;
+ }
+ }
+
+ /**
+ * Writes an unsigned integral to {@code out}.
+ */
+ public static void writeUnsignedIntegralValue(ByteOutput out, int type, long value) {
+ // Figure out how many bits are needed to represent the value.
+ int requiredBits = 64 - Long.numberOfLeadingZeros(value);
+ if (requiredBits == 0) {
+ requiredBits = 1;
+ }
+
+ // Round up the requiredBits to a number of bytes.
+ int requiredBytes = (requiredBits + 0x07) >> 3;
+
+ /*
+ * Write the header byte, which includes the type and
+ * requiredBytes - 1.
+ */
+ out.writeByte(type | ((requiredBytes - 1) << 5));
+
+ // Write the value, per se.
+ while (requiredBytes > 0) {
+ out.writeByte((byte) value);
+ value >>= 8;
+ requiredBytes--;
+ }
+ }
+
+ /**
+ * Writes a right-zero-extended value to {@code out}.
+ */
+ public static void writeRightZeroExtendedValue(ByteOutput out, int type, long value) {
+ // Figure out how many bits are needed to represent the value.
+ int requiredBits = 64 - Long.numberOfTrailingZeros(value);
+ if (requiredBits == 0) {
+ requiredBits = 1;
+ }
+
+ // Round up the requiredBits to a number of bytes.
+ int requiredBytes = (requiredBits + 0x07) >> 3;
+
+ // Scootch the first bits to be written down to the low-order bits.
+ value >>= 64 - (requiredBytes * 8);
+
+ /*
+ * Write the header byte, which includes the type and
+ * requiredBytes - 1.
+ */
+ out.writeByte(type | ((requiredBytes - 1) << 5));
+
+ // Write the value, per se.
+ while (requiredBytes > 0) {
+ out.writeByte((byte) value);
+ value >>= 8;
+ requiredBytes--;
+ }
+ }
+
+ /**
+ * Read a signed integer.
+ *
+ * @param zwidth byte count minus one
+ */
+ public static int readSignedInt(ByteInput in, int zwidth) {
+ int result = 0;
+ for (int i = zwidth; i >= 0; i--) {
+ result = (result >>> 8) | ((in.readByte() & 0xff) << 24);
+ }
+ result >>= (3 - zwidth) * 8;
+ return result;
+ }
+
+ /**
+ * Read an unsigned integer.
+ *
+ * @param zwidth byte count minus one
+ * @param fillOnRight true to zero fill on the right; false on the left
+ */
+ public static int readUnsignedInt(ByteInput in, int zwidth, boolean fillOnRight) {
+ int result = 0;
+ if (!fillOnRight) {
+ for (int i = zwidth; i >= 0; i--) {
+ result = (result >>> 8) | ((in.readByte() & 0xff) << 24);
+ }
+ result >>>= (3 - zwidth) * 8;
+ } else {
+ for (int i = zwidth; i >= 0; i--) {
+ result = (result >>> 8) | ((in.readByte() & 0xff) << 24);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Read a signed long.
+ *
+ * @param zwidth byte count minus one
+ */
+ public static long readSignedLong(ByteInput in, int zwidth) {
+ long result = 0;
+ for (int i = zwidth; i >= 0; i--) {
+ result = (result >>> 8) | ((in.readByte() & 0xffL) << 56);
+ }
+ result >>= (7 - zwidth) * 8;
+ return result;
+ }
+
+ /**
+ * Read an unsigned long.
+ *
+ * @param zwidth byte count minus one
+ * @param fillOnRight true to zero fill on the right; false on the left
+ */
+ public static long readUnsignedLong(ByteInput in, int zwidth, boolean fillOnRight) {
+ long result = 0;
+ if (!fillOnRight) {
+ for (int i = zwidth; i >= 0; i--) {
+ result = (result >>> 8) | ((in.readByte() & 0xffL) << 56);
+ }
+ result >>>= (7 - zwidth) * 8;
+ } else {
+ for (int i = zwidth; i >= 0; i--) {
+ result = (result >>> 8) | ((in.readByte() & 0xffL) << 56);
+ }
+ }
+ return result;
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/EncodedValueReader.java b/dex/src/main/java/com/android/dex/EncodedValueReader.java
new file mode 100644
index 0000000..a263464
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/EncodedValueReader.java
@@ -0,0 +1,287 @@
+/*
+ * 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.util.ByteInput;
+
+/**
+ * Pull parser for encoded values.
+ */
+public final class EncodedValueReader {
+ public static final int ENCODED_BYTE = 0x00;
+ public static final int ENCODED_SHORT = 0x02;
+ public static final int ENCODED_CHAR = 0x03;
+ public static final int ENCODED_INT = 0x04;
+ public static final int ENCODED_LONG = 0x06;
+ public static final int ENCODED_FLOAT = 0x10;
+ public static final int ENCODED_DOUBLE = 0x11;
+ public static final int ENCODED_STRING = 0x17;
+ public static final int ENCODED_TYPE = 0x18;
+ public static final int ENCODED_FIELD = 0x19;
+ public static final int ENCODED_ENUM = 0x1b;
+ public static final int ENCODED_METHOD = 0x1a;
+ public static final int ENCODED_ARRAY = 0x1c;
+ public static final int ENCODED_ANNOTATION = 0x1d;
+ public static final int ENCODED_NULL = 0x1e;
+ public static final int ENCODED_BOOLEAN = 0x1f;
+
+ /** placeholder type if the type is not yet known */
+ private static final int MUST_READ = -1;
+
+ protected final ByteInput in;
+ private int type = MUST_READ;
+ private int annotationType;
+ private int arg;
+
+ public EncodedValueReader(ByteInput in) {
+ this.in = in;
+ }
+
+ public EncodedValueReader(EncodedValue in) {
+ this(in.asByteInput());
+ }
+
+ /**
+ * Creates a new encoded value reader whose only value is the specified
+ * known type. This is useful for encoded values without a type prefix,
+ * such as class_def_item's encoded_array or annotation_item's
+ * encoded_annotation.
+ */
+ public EncodedValueReader(ByteInput in, int knownType) {
+ this.in = in;
+ this.type = knownType;
+ }
+
+ public EncodedValueReader(EncodedValue in, int knownType) {
+ this(in.asByteInput(), knownType);
+ }
+
+ /**
+ * Returns the type of the next value to read.
+ */
+ public int peek() {
+ if (type == MUST_READ) {
+ int argAndType = in.readByte() & 0xff;
+ type = argAndType & 0x1f;
+ arg = (argAndType & 0xe0) >> 5;
+ }
+ return type;
+ }
+
+ /**
+ * Begins reading the elements of an array, returning the array's size. The
+ * caller must follow up by calling a read method for each element in the
+ * array. For example, this reads a byte array: <pre> {@code
+ * int arraySize = readArray();
+ * for (int i = 0, i < arraySize; i++) {
+ * readByte();
+ * }
+ * }</pre>
+ */
+ public int readArray() {
+ checkType(ENCODED_ARRAY);
+ type = MUST_READ;
+ return Leb128.readUnsignedLeb128(in);
+ }
+
+ /**
+ * Begins reading the fields of an annotation, returning the number of
+ * fields. The caller must follow up by making alternating calls to {@link
+ * #readAnnotationName()} and another read method. For example, this reads
+ * an annotation whose fields are all bytes: <pre> {@code
+ * int fieldCount = readAnnotation();
+ * int annotationType = getAnnotationType();
+ * for (int i = 0; i < fieldCount; i++) {
+ * readAnnotationName();
+ * readByte();
+ * }
+ * }</pre>
+ */
+ public int readAnnotation() {
+ checkType(ENCODED_ANNOTATION);
+ type = MUST_READ;
+ annotationType = Leb128.readUnsignedLeb128(in);
+ return Leb128.readUnsignedLeb128(in);
+ }
+
+ /**
+ * Returns the type of the annotation just returned by {@link
+ * #readAnnotation()}. This method's value is undefined unless the most
+ * recent call was to {@link #readAnnotation()}.
+ */
+ public int getAnnotationType() {
+ return annotationType;
+ }
+
+ public int readAnnotationName() {
+ return Leb128.readUnsignedLeb128(in);
+ }
+
+ public byte readByte() {
+ checkType(ENCODED_BYTE);
+ type = MUST_READ;
+ return (byte) EncodedValueCodec.readSignedInt(in, arg);
+ }
+
+ public short readShort() {
+ checkType(ENCODED_SHORT);
+ type = MUST_READ;
+ return (short) EncodedValueCodec.readSignedInt(in, arg);
+ }
+
+ public char readChar() {
+ checkType(ENCODED_CHAR);
+ type = MUST_READ;
+ return (char) EncodedValueCodec.readUnsignedInt(in, arg, false);
+ }
+
+ public int readInt() {
+ checkType(ENCODED_INT);
+ type = MUST_READ;
+ return EncodedValueCodec.readSignedInt(in, arg);
+ }
+
+ public long readLong() {
+ checkType(ENCODED_LONG);
+ type = MUST_READ;
+ return EncodedValueCodec.readSignedLong(in, arg);
+ }
+
+ public float readFloat() {
+ checkType(ENCODED_FLOAT);
+ type = MUST_READ;
+ return Float.intBitsToFloat(EncodedValueCodec.readUnsignedInt(in, arg, true));
+ }
+
+ public double readDouble() {
+ checkType(ENCODED_DOUBLE);
+ type = MUST_READ;
+ return Double.longBitsToDouble(EncodedValueCodec.readUnsignedLong(in, arg, true));
+ }
+
+ public int readString() {
+ checkType(ENCODED_STRING);
+ type = MUST_READ;
+ return EncodedValueCodec.readUnsignedInt(in, arg, false);
+ }
+
+ public int readType() {
+ checkType(ENCODED_TYPE);
+ type = MUST_READ;
+ return EncodedValueCodec.readUnsignedInt(in, arg, false);
+ }
+
+ public int readField() {
+ checkType(ENCODED_FIELD);
+ type = MUST_READ;
+ return EncodedValueCodec.readUnsignedInt(in, arg, false);
+ }
+
+ public int readEnum() {
+ checkType(ENCODED_ENUM);
+ type = MUST_READ;
+ return EncodedValueCodec.readUnsignedInt(in, arg, false);
+ }
+
+ public int readMethod() {
+ checkType(ENCODED_METHOD);
+ type = MUST_READ;
+ return EncodedValueCodec.readUnsignedInt(in, arg, false);
+ }
+
+ public void readNull() {
+ checkType(ENCODED_NULL);
+ type = MUST_READ;
+ }
+
+ public boolean readBoolean() {
+ checkType(ENCODED_BOOLEAN);
+ type = MUST_READ;
+ return arg != 0;
+ }
+
+ /**
+ * Skips a single value, including its nested values if it is an array or
+ * annotation.
+ */
+ public void skipValue() {
+ switch (peek()) {
+ case ENCODED_BYTE:
+ readByte();
+ break;
+ case ENCODED_SHORT:
+ readShort();
+ break;
+ case ENCODED_CHAR:
+ readChar();
+ break;
+ case ENCODED_INT:
+ readInt();
+ break;
+ case ENCODED_LONG:
+ readLong();
+ break;
+ case ENCODED_FLOAT:
+ readFloat();
+ break;
+ case ENCODED_DOUBLE:
+ readDouble();
+ break;
+ case ENCODED_STRING:
+ readString();
+ break;
+ case ENCODED_TYPE:
+ readType();
+ break;
+ case ENCODED_FIELD:
+ readField();
+ break;
+ case ENCODED_ENUM:
+ readEnum();
+ break;
+ case ENCODED_METHOD:
+ readMethod();
+ break;
+ case ENCODED_ARRAY:
+ for (int i = 0, size = readArray(); i < size; i++) {
+ skipValue();
+ }
+ break;
+ case ENCODED_ANNOTATION:
+ for (int i = 0, size = readAnnotation(); i < size; i++) {
+ readAnnotationName();
+ skipValue();
+ }
+ break;
+ case ENCODED_NULL:
+ readNull();
+ break;
+ case ENCODED_BOOLEAN:
+ readBoolean();
+ break;
+ default:
+ throw new DexException("Unexpected type: " + Integer.toHexString(type));
+ }
+ }
+
+ private void checkType(int expected) {
+ if (peek() != expected) {
+ throw new IllegalStateException("Expected array but was "
+ + Integer.toHexString(peek()));
+ }
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/FieldId.java b/dex/src/main/java/com/android/dex/FieldId.java
new file mode 100644
index 0000000..2f41708
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/FieldId.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util.Unsigned;
+
+public final class FieldId implements Comparable<FieldId> {
+ private final Dex dex;
+ private final int declaringClassIndex;
+ private final int typeIndex;
+ private final int nameIndex;
+
+ public FieldId(Dex dex, int declaringClassIndex, int typeIndex, int nameIndex) {
+ this.dex = dex;
+ this.declaringClassIndex = declaringClassIndex;
+ this.typeIndex = typeIndex;
+ this.nameIndex = nameIndex;
+ }
+
+ public int getDeclaringClassIndex() {
+ return declaringClassIndex;
+ }
+
+ public int getTypeIndex() {
+ return typeIndex;
+ }
+
+ public int getNameIndex() {
+ return nameIndex;
+ }
+
+ public int compareTo(FieldId other) {
+ if (declaringClassIndex != other.declaringClassIndex) {
+ return Unsigned.compare(declaringClassIndex, other.declaringClassIndex);
+ }
+ if (nameIndex != other.nameIndex) {
+ return Unsigned.compare(nameIndex, other.nameIndex);
+ }
+ return Unsigned.compare(typeIndex, other.typeIndex); // should always be 0
+ }
+
+ public void writeTo(Dex.Section out) {
+ out.writeUnsignedShort(declaringClassIndex);
+ out.writeUnsignedShort(typeIndex);
+ out.writeInt(nameIndex);
+ }
+
+ @Override public String toString() {
+ if (dex == null) {
+ return declaringClassIndex + " " + typeIndex + " " + nameIndex;
+ }
+ return dex.typeNames().get(typeIndex) + "." + dex.strings().get(nameIndex);
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/Leb128.java b/dex/src/main/java/com/android/dex/Leb128.java
new file mode 100644
index 0000000..1a82e38
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/Leb128.java
@@ -0,0 +1,161 @@
+/*
+ * 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.dex;
+
+import com.android.dex.util.ByteInput;
+import com.android.dex.util.ByteOutput;
+
+/**
+ * Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3
+ * section 7.6.
+ */
+public final class Leb128 {
+ private Leb128() {
+ }
+
+ /**
+ * Gets the number of bytes in the unsigned LEB128 encoding of the
+ * given value.
+ *
+ * @param value the value in question
+ * @return its write size, in bytes
+ */
+ public static int unsignedLeb128Size(int value) {
+ // TODO: This could be much cleverer.
+
+ int remaining = value >> 7;
+ int count = 0;
+
+ while (remaining != 0) {
+ remaining >>= 7;
+ count++;
+ }
+
+ return count + 1;
+ }
+
+ /**
+ * Gets the number of bytes in the signed LEB128 encoding of the
+ * given value.
+ *
+ * @param value the value in question
+ * @return its write size, in bytes
+ */
+ public static int signedLeb128Size(int value) {
+ // TODO: This could be much cleverer.
+
+ int remaining = value >> 7;
+ int count = 0;
+ boolean hasMore = true;
+ int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
+
+ while (hasMore) {
+ hasMore = (remaining != end)
+ || ((remaining & 1) != ((value >> 6) & 1));
+
+ value = remaining;
+ remaining >>= 7;
+ count++;
+ }
+
+ return count;
+ }
+
+ /**
+ * Reads an signed integer from {@code in}.
+ */
+ public static int readSignedLeb128(ByteInput in) {
+ int result = 0;
+ int cur;
+ int count = 0;
+ int signBits = -1;
+
+ do {
+ cur = in.readByte() & 0xff;
+ result |= (cur & 0x7f) << (count * 7);
+ signBits <<= 7;
+ count++;
+ } while (((cur & 0x80) == 0x80) && count < 5);
+
+ if ((cur & 0x80) == 0x80) {
+ throw new DexException("invalid LEB128 sequence");
+ }
+
+ // Sign extend if appropriate
+ if (((signBits >> 1) & result) != 0 ) {
+ result |= signBits;
+ }
+
+ return result;
+ }
+
+ /**
+ * Reads an unsigned integer from {@code in}.
+ */
+ public static int readUnsignedLeb128(ByteInput in) {
+ int result = 0;
+ int cur;
+ int count = 0;
+
+ do {
+ cur = in.readByte() & 0xff;
+ result |= (cur & 0x7f) << (count * 7);
+ count++;
+ } while (((cur & 0x80) == 0x80) && count < 5);
+
+ if ((cur & 0x80) == 0x80) {
+ throw new DexException("invalid LEB128 sequence");
+ }
+
+ return result;
+ }
+
+ /**
+ * Writes {@code value} as an unsigned integer to {@code out}, starting at
+ * {@code offset}. Returns the number of bytes written.
+ */
+ public static void writeUnsignedLeb128(ByteOutput out, int value) {
+ int remaining = value >>> 7;
+
+ while (remaining != 0) {
+ out.writeByte((byte) ((value & 0x7f) | 0x80));
+ value = remaining;
+ remaining >>>= 7;
+ }
+
+ out.writeByte((byte) (value & 0x7f));
+ }
+
+ /**
+ * Writes {@code value} as a signed integer to {@code out}, starting at
+ * {@code offset}. Returns the number of bytes written.
+ */
+ public static void writeSignedLeb128(ByteOutput out, int value) {
+ int remaining = value >> 7;
+ boolean hasMore = true;
+ int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
+
+ while (hasMore) {
+ hasMore = (remaining != end)
+ || ((remaining & 1) != ((value >> 6) & 1));
+
+ out.writeByte((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0)));
+ value = remaining;
+ remaining >>= 7;
+ }
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/MethodId.java b/dex/src/main/java/com/android/dex/MethodId.java
new file mode 100644
index 0000000..e518740
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/MethodId.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util.Unsigned;
+
+public final class MethodId implements Comparable<MethodId> {
+ private final Dex dex;
+ private final int declaringClassIndex;
+ private final int protoIndex;
+ private final int nameIndex;
+
+ public MethodId(Dex dex, int declaringClassIndex, int protoIndex, int nameIndex) {
+ this.dex = dex;
+ this.declaringClassIndex = declaringClassIndex;
+ this.protoIndex = protoIndex;
+ this.nameIndex = nameIndex;
+ }
+
+ public int getDeclaringClassIndex() {
+ return declaringClassIndex;
+ }
+
+ public int getProtoIndex() {
+ return protoIndex;
+ }
+
+ public int getNameIndex() {
+ return nameIndex;
+ }
+
+ public int compareTo(MethodId other) {
+ if (declaringClassIndex != other.declaringClassIndex) {
+ return Unsigned.compare(declaringClassIndex, other.declaringClassIndex);
+ }
+ if (nameIndex != other.nameIndex) {
+ return Unsigned.compare(nameIndex, other.nameIndex);
+ }
+ return Unsigned.compare(protoIndex, other.protoIndex);
+ }
+
+ public void writeTo(Dex.Section out) {
+ out.writeUnsignedShort(declaringClassIndex);
+ out.writeUnsignedShort(protoIndex);
+ out.writeInt(nameIndex);
+ }
+
+ @Override public String toString() {
+ if (dex == null) {
+ return declaringClassIndex + " " + protoIndex + " " + nameIndex;
+ }
+ return dex.typeNames().get(declaringClassIndex)
+ + "." + dex.strings().get(nameIndex)
+ + dex.readTypeList(dex.protoIds().get(protoIndex).getParametersOffset());
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/Mutf8.java b/dex/src/main/java/com/android/dex/Mutf8.java
new file mode 100644
index 0000000..c64da33
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/Mutf8.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.ByteInput;
+import java.io.UTFDataFormatException;
+
+/**
+ * Modified UTF-8 as described in the dex file format spec.
+ *
+ * <p>Derived from libcore's MUTF-8 encoder at java.nio.charset.ModifiedUtf8.
+ */
+public final class Mutf8 {
+ private Mutf8() {}
+
+ /**
+ * Decodes bytes from {@code in} into {@code out} until a delimiter 0x00 is
+ * encountered. Returns a new string containing the decoded characters.
+ */
+ public static String decode(ByteInput in, char[] out) throws UTFDataFormatException {
+ int s = 0;
+ while (true) {
+ char a = (char) (in.readByte() & 0xff);
+ if (a == 0) {
+ return new String(out, 0, s);
+ }
+ out[s] = a;
+ if (a < '\u0080') {
+ s++;
+ } else if ((a & 0xe0) == 0xc0) {
+ int b = in.readByte() & 0xff;
+ if ((b & 0xC0) != 0x80) {
+ throw new UTFDataFormatException("bad second byte");
+ }
+ out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
+ } else if ((a & 0xf0) == 0xe0) {
+ int b = in.readByte() & 0xff;
+ int c = in.readByte() & 0xff;
+ if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
+ throw new UTFDataFormatException("bad second or third byte");
+ }
+ out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
+ } else {
+ throw new UTFDataFormatException("bad byte");
+ }
+ }
+ }
+
+ /**
+ * Returns the number of bytes the modified UTF8 representation of 's' would take.
+ */
+ private static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
+ long result = 0;
+ final int length = s.length();
+ for (int i = 0; i < length; ++i) {
+ char ch = s.charAt(i);
+ if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
+ ++result;
+ } else if (ch <= 2047) {
+ result += 2;
+ } else {
+ result += 3;
+ }
+ if (shortLength && result > 65535) {
+ throw new UTFDataFormatException("String more than 65535 UTF bytes long");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Encodes the modified UTF-8 bytes corresponding to {@code s} into {@code
+ * dst}, starting at {@code offset}.
+ */
+ public static void encode(byte[] dst, int offset, String s) {
+ final int length = s.length();
+ for (int i = 0; i < length; i++) {
+ char ch = s.charAt(i);
+ if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
+ dst[offset++] = (byte) ch;
+ } else if (ch <= 2047) {
+ dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
+ dst[offset++] = (byte) (0x80 | (0x3f & ch));
+ } else {
+ dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
+ dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
+ dst[offset++] = (byte) (0x80 | (0x3f & ch));
+ }
+ }
+ }
+
+ /**
+ * Returns an array containing the <i>modified UTF-8</i> form of {@code s}.
+ */
+ public static byte[] encode(String s) throws UTFDataFormatException {
+ int utfCount = (int) countBytes(s, true);
+ byte[] result = new byte[utfCount];
+ encode(result, 0, s);
+ return result;
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/ProtoId.java b/dex/src/main/java/com/android/dex/ProtoId.java
new file mode 100644
index 0000000..9d9f484
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/ProtoId.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util.Unsigned;
+
+public final class ProtoId implements Comparable<ProtoId> {
+ private final Dex dex;
+ private final int shortyIndex;
+ private final int returnTypeIndex;
+ private final int parametersOffset;
+
+ public ProtoId(Dex dex, int shortyIndex, int returnTypeIndex, int parametersOffset) {
+ this.dex = dex;
+ this.shortyIndex = shortyIndex;
+ this.returnTypeIndex = returnTypeIndex;
+ this.parametersOffset = parametersOffset;
+ }
+
+ public int compareTo(ProtoId other) {
+ if (returnTypeIndex != other.returnTypeIndex) {
+ return Unsigned.compare(returnTypeIndex, other.returnTypeIndex);
+ }
+ return Unsigned.compare(parametersOffset, other.parametersOffset);
+ }
+
+ public int getShortyIndex() {
+ return shortyIndex;
+ }
+
+ public int getReturnTypeIndex() {
+ return returnTypeIndex;
+ }
+
+ public int getParametersOffset() {
+ return parametersOffset;
+ }
+
+ public void writeTo(Dex.Section out) {
+ out.writeInt(shortyIndex);
+ out.writeInt(returnTypeIndex);
+ out.writeInt(parametersOffset);
+ }
+
+ @Override public String toString() {
+ if (dex == null) {
+ return shortyIndex + " " + returnTypeIndex + " " + parametersOffset;
+ }
+
+ return dex.strings().get(shortyIndex)
+ + ": " + dex.typeNames().get(returnTypeIndex)
+ + " " + dex.readTypeList(parametersOffset);
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/SizeOf.java b/dex/src/main/java/com/android/dex/SizeOf.java
new file mode 100644
index 0000000..03b40bd
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/SizeOf.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+public final class SizeOf {
+ private SizeOf() {}
+
+ public static final int UBYTE = 1;
+ public static final int USHORT = 2;
+ public static final int UINT = 4;
+
+ public static final int SIGNATURE = UBYTE * 20;
+
+ /**
+ * magic ubyte[8]
+ * checksum uint
+ * signature ubyte[20]
+ * file_size uint
+ * header_size uint
+ * endian_tag uint
+ * link_size uint
+ * link_off uint
+ * map_off uint
+ * string_ids_size uint
+ * string_ids_off uint
+ * type_ids_size uint
+ * type_ids_off uint
+ * proto_ids_size uint
+ * proto_ids_off uint
+ * field_ids_size uint
+ * field_ids_off uint
+ * method_ids_size uint
+ * method_ids_off uint
+ * class_defs_size uint
+ * class_defs_off uint
+ * data_size uint
+ * data_off uint
+ */
+ public static final int HEADER_ITEM = (8 * UBYTE) + UINT + SIGNATURE + (20 * UINT); // 0x70
+
+ /**
+ * string_data_off uint
+ */
+ public static final int STRING_ID_ITEM = UINT;
+
+ /**
+ * descriptor_idx uint
+ */
+ public static final int TYPE_ID_ITEM = UINT;
+
+ /**
+ * type_idx ushort
+ */
+ public static final int TYPE_ITEM = USHORT;
+
+ /**
+ * shorty_idx uint
+ * return_type_idx uint
+ * return_type_idx uint
+ */
+ public static final int PROTO_ID_ITEM = UINT + UINT + UINT;
+
+ /**
+ * class_idx ushort
+ * type_idx/proto_idx ushort
+ * name_idx uint
+ */
+ public static final int MEMBER_ID_ITEM = USHORT + USHORT + UINT;
+
+ /**
+ * class_idx uint
+ * access_flags uint
+ * superclass_idx uint
+ * interfaces_off uint
+ * source_file_idx uint
+ * annotations_off uint
+ * class_data_off uint
+ * static_values_off uint
+ */
+ public static final int CLASS_DEF_ITEM = 8 * UINT;
+
+ /**
+ * type ushort
+ * unused ushort
+ * size uint
+ * offset uint
+ */
+ public static final int MAP_ITEM = USHORT + USHORT + UINT + UINT;
+}
diff --git a/dex/src/main/java/com/android/dex/TableOfContents.java b/dex/src/main/java/com/android/dex/TableOfContents.java
new file mode 100644
index 0000000..54411ca
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/TableOfContents.java
@@ -0,0 +1,236 @@
+/*
+ * 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 java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+/**
+ * The file header and map.
+ */
+public final class TableOfContents {
+
+ /*
+ * TODO: factor out ID constants.
+ */
+
+ public final Section header = new Section(0x0000);
+ public final Section stringIds = new Section(0x0001);
+ public final Section typeIds = new Section(0x0002);
+ public final Section protoIds = new Section(0x0003);
+ public final Section fieldIds = new Section(0x0004);
+ public final Section methodIds = new Section(0x0005);
+ public final Section classDefs = new Section(0x0006);
+ public final Section mapList = new Section(0x1000);
+ public final Section typeLists = new Section(0x1001);
+ public final Section annotationSetRefLists = new Section(0x1002);
+ public final Section annotationSets = new Section(0x1003);
+ public final Section classDatas = new Section(0x2000);
+ public final Section codes = new Section(0x2001);
+ public final Section stringDatas = new Section(0x2002);
+ public final Section debugInfos = new Section(0x2003);
+ public final Section annotations = new Section(0x2004);
+ public final Section encodedArrays = new Section(0x2005);
+ public final Section annotationsDirectories = new Section(0x2006);
+ public final Section[] sections = {
+ header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList,
+ typeLists, annotationSetRefLists, annotationSets, classDatas, codes, stringDatas,
+ debugInfos, annotations, encodedArrays, annotationsDirectories
+ };
+
+ public int checksum;
+ public byte[] signature;
+ public int fileSize;
+ public int linkSize;
+ public int linkOff;
+ public int dataSize;
+ public int dataOff;
+
+ public TableOfContents() {
+ signature = new byte[20];
+ }
+
+ public void readFrom(Dex dex) throws IOException {
+ readHeader(dex.open(0));
+ readMap(dex.open(mapList.off));
+ computeSizesFromOffsets();
+ }
+
+ private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException {
+ byte[] magic = headerIn.readByteArray(8);
+ int apiTarget = DexFormat.magicToApi(magic);
+
+ if (apiTarget < 0) {
+ throw new DexException("Unexpected magic: " + Arrays.toString(magic));
+ }
+
+ checksum = headerIn.readInt();
+ signature = headerIn.readByteArray(20);
+ fileSize = headerIn.readInt();
+ int headerSize = headerIn.readInt();
+ if (headerSize != SizeOf.HEADER_ITEM) {
+ throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize));
+ }
+ int endianTag = headerIn.readInt();
+ if (endianTag != DexFormat.ENDIAN_TAG) {
+ throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag));
+ }
+ linkSize = headerIn.readInt();
+ linkOff = headerIn.readInt();
+ mapList.off = headerIn.readInt();
+ if (mapList.off == 0) {
+ throw new DexException("Cannot merge dex files that do not contain a map");
+ }
+ stringIds.size = headerIn.readInt();
+ stringIds.off = headerIn.readInt();
+ typeIds.size = headerIn.readInt();
+ typeIds.off = headerIn.readInt();
+ protoIds.size = headerIn.readInt();
+ protoIds.off = headerIn.readInt();
+ fieldIds.size = headerIn.readInt();
+ fieldIds.off = headerIn.readInt();
+ methodIds.size = headerIn.readInt();
+ methodIds.off = headerIn.readInt();
+ classDefs.size = headerIn.readInt();
+ classDefs.off = headerIn.readInt();
+ dataSize = headerIn.readInt();
+ dataOff = headerIn.readInt();
+ }
+
+ private void readMap(Dex.Section in) throws IOException {
+ int mapSize = in.readInt();
+ Section previous = null;
+ for (int i = 0; i < mapSize; i++) {
+ short type = in.readShort();
+ in.readShort(); // unused
+ Section section = getSection(type);
+ int size = in.readInt();
+ int offset = in.readInt();
+
+ if ((section.size != 0 && section.size != size)
+ || (section.off != -1 && section.off != offset)) {
+ throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type));
+ }
+
+ section.size = size;
+ section.off = offset;
+
+ if (previous != null && previous.off > section.off) {
+ throw new DexException("Map is unsorted at " + previous + ", " + section);
+ }
+
+ previous = section;
+ }
+ Arrays.sort(sections);
+ }
+
+ public void computeSizesFromOffsets() {
+ int end = dataOff + dataSize;
+ for (int i = sections.length - 1; i >= 0; i--) {
+ Section section = sections[i];
+ if (section.off == -1) {
+ continue;
+ }
+ if (section.off > end) {
+ throw new DexException("Map is unsorted at " + section);
+ }
+ section.byteCount = end - section.off;
+ end = section.off;
+ }
+ }
+
+ private Section getSection(short type) {
+ for (Section section : sections) {
+ if (section.type == type) {
+ return section;
+ }
+ }
+ throw new IllegalArgumentException("No such map item: " + type);
+ }
+
+ public void writeHeader(Dex.Section out) throws IOException {
+ out.write(DexFormat.apiToMagic(DexFormat.API_CURRENT).getBytes("UTF-8"));
+ out.writeInt(checksum);
+ out.write(signature);
+ out.writeInt(fileSize);
+ out.writeInt(SizeOf.HEADER_ITEM);
+ out.writeInt(DexFormat.ENDIAN_TAG);
+ out.writeInt(linkSize);
+ out.writeInt(linkOff);
+ out.writeInt(mapList.off);
+ out.writeInt(stringIds.size);
+ out.writeInt(stringIds.off);
+ out.writeInt(typeIds.size);
+ out.writeInt(typeIds.off);
+ out.writeInt(protoIds.size);
+ out.writeInt(protoIds.off);
+ out.writeInt(fieldIds.size);
+ out.writeInt(fieldIds.off);
+ out.writeInt(methodIds.size);
+ out.writeInt(methodIds.off);
+ out.writeInt(classDefs.size);
+ out.writeInt(classDefs.off);
+ out.writeInt(dataSize);
+ out.writeInt(dataOff);
+ }
+
+ public void writeMap(Dex.Section out) throws IOException {
+ int count = 0;
+ for (Section section : sections) {
+ if (section.exists()) {
+ count++;
+ }
+ }
+
+ out.writeInt(count);
+ for (Section section : sections) {
+ if (section.exists()) {
+ out.writeShort(section.type);
+ out.writeShort((short) 0);
+ out.writeInt(section.size);
+ out.writeInt(section.off);
+ }
+ }
+ }
+
+ public static class Section implements Comparable<Section> {
+ public final short type;
+ public int size = 0;
+ public int off = -1;
+ public int byteCount = 0;
+
+ public Section(int type) {
+ this.type = (short) type;
+ }
+
+ public boolean exists() {
+ return size > 0;
+ }
+
+ public int compareTo(Section section) {
+ if (off != section.off) {
+ return off < section.off ? -1 : 1;
+ }
+ return 0;
+ }
+
+ @Override public String toString() {
+ return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size);
+ }
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/TypeList.java b/dex/src/main/java/com/android/dex/TypeList.java
new file mode 100644
index 0000000..aa1bf58
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/TypeList.java
@@ -0,0 +1,55 @@
+/*
+ * 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.util.Unsigned;
+
+public final class TypeList implements Comparable<TypeList> {
+
+ public static final TypeList EMPTY = new TypeList(null, new short[0]);
+
+ private final Dex dex;
+ private final short[] types;
+
+ public TypeList(Dex dex, short[] types) {
+ this.dex = dex;
+ this.types = types;
+ }
+
+ public short[] getTypes() {
+ return types;
+ }
+
+ public int compareTo(TypeList other) {
+ for (int i = 0; i < types.length && i < other.types.length; i++) {
+ if (types[i] != other.types[i]) {
+ return Unsigned.compare(types[i], other.types[i]);
+ }
+ }
+ return Unsigned.compare(types.length, other.types.length);
+ }
+
+ @Override public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("(");
+ for (int i = 0, typesLength = types.length; i < typesLength; i++) {
+ result.append(dex != null ? dex.typeNames().get(types[i]) : types[i]);
+ }
+ result.append(")");
+ return result.toString();
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/util/ByteArrayByteInput.java b/dex/src/main/java/com/android/dex/util/ByteArrayByteInput.java
new file mode 100644
index 0000000..889a936
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/util/ByteArrayByteInput.java
@@ -0,0 +1,31 @@
+/*
+ * 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.util;
+
+public final class ByteArrayByteInput implements ByteInput {
+
+ private final byte[] bytes;
+ private int position;
+
+ public ByteArrayByteInput(byte... bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override public byte readByte() {
+ return bytes[position++];
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/util/ByteInput.java b/dex/src/main/java/com/android/dex/util/ByteInput.java
new file mode 100644
index 0000000..f1a7196
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/util/ByteInput.java
@@ -0,0 +1,30 @@
+/*
+ * 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.util;
+
+/**
+ * A byte source.
+ */
+public interface ByteInput {
+
+ /**
+ * Returns a byte.
+ *
+ * @throws IndexOutOfBoundsException if all bytes have been read.
+ */
+ byte readByte();
+}
diff --git a/dex/src/main/java/com/android/dex/util/ByteOutput.java b/dex/src/main/java/com/android/dex/util/ByteOutput.java
new file mode 100644
index 0000000..eb77040
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/util/ByteOutput.java
@@ -0,0 +1,30 @@
+/*
+ * 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.util;
+
+/**
+ * A byte sink.
+ */
+public interface ByteOutput {
+
+ /**
+ * Writes a byte.
+ *
+ * @throws IndexOutOfBoundsException if all bytes have been written.
+ */
+ void writeByte(int i);
+}
diff --git a/dex/src/main/java/com/android/dex/util/ExceptionWithContext.java b/dex/src/main/java/com/android/dex/util/ExceptionWithContext.java
new file mode 100644
index 0000000..5dfd954
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/util/ExceptionWithContext.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2007 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.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * Exception which carries around structured context.
+ */
+public class ExceptionWithContext extends RuntimeException {
+ /** {@code non-null;} human-oriented context of the exception */
+ private StringBuffer context;
+
+ /**
+ * Augments the given exception with the given context, and return the
+ * result. The result is either the given exception if it was an
+ * {@link ExceptionWithContext}, or a newly-constructed exception if it
+ * was not.
+ *
+ * @param ex {@code non-null;} the exception to augment
+ * @param str {@code non-null;} context to add
+ * @return {@code non-null;} an appropriate instance
+ */
+ public static ExceptionWithContext withContext(Throwable ex, String str) {
+ ExceptionWithContext ewc;
+
+ if (ex instanceof ExceptionWithContext) {
+ ewc = (ExceptionWithContext) ex;
+ } else {
+ ewc = new ExceptionWithContext(ex);
+ }
+
+ ewc.addContext(str);
+ return ewc;
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param message human-oriented message
+ */
+ public ExceptionWithContext(String message) {
+ this(message, null);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param cause {@code null-ok;} exception that caused this one
+ */
+ public ExceptionWithContext(Throwable cause) {
+ this(null, cause);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param message human-oriented message
+ * @param cause {@code null-ok;} exception that caused this one
+ */
+ public ExceptionWithContext(String message, Throwable cause) {
+ super((message != null) ? message :
+ (cause != null) ? cause.getMessage() : null,
+ cause);
+
+ if (cause instanceof ExceptionWithContext) {
+ String ctx = ((ExceptionWithContext) cause).context.toString();
+ context = new StringBuffer(ctx.length() + 200);
+ context.append(ctx);
+ } else {
+ context = new StringBuffer(200);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void printStackTrace(PrintStream out) {
+ super.printStackTrace(out);
+ out.println(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void printStackTrace(PrintWriter out) {
+ super.printStackTrace(out);
+ out.println(context);
+ }
+
+ /**
+ * Adds a line of context to this instance.
+ *
+ * @param str {@code non-null;} new context
+ */
+ public void addContext(String str) {
+ if (str == null) {
+ throw new NullPointerException("str == null");
+ }
+
+ context.append(str);
+ if (!str.endsWith("\n")) {
+ context.append('\n');
+ }
+ }
+
+ /**
+ * Gets the context.
+ *
+ * @return {@code non-null;} the context
+ */
+ public String getContext() {
+ return context.toString();
+ }
+
+ /**
+ * Prints the message and context.
+ *
+ * @param out {@code non-null;} where to print to
+ */
+ public void printContext(PrintStream out) {
+ out.println(getMessage());
+ out.print(context);
+ }
+
+ /**
+ * Prints the message and context.
+ *
+ * @param out {@code non-null;} where to print to
+ */
+ public void printContext(PrintWriter out) {
+ out.println(getMessage());
+ out.print(context);
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/util/FileUtils.java b/dex/src/main/java/com/android/dex/util/FileUtils.java
new file mode 100644
index 0000000..4cea95c
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/util/FileUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007 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.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * File I/O utilities.
+ */
+public final class FileUtils {
+ private FileUtils() {
+ }
+
+ /**
+ * Reads the named file, translating {@link IOException} to a
+ * {@link RuntimeException} of some sort.
+ *
+ * @param fileName {@code non-null;} name of the file to read
+ * @return {@code non-null;} contents of the file
+ */
+ public static byte[] readFile(String fileName) {
+ File file = new File(fileName);
+ return readFile(file);
+ }
+
+ /**
+ * Reads the given file, translating {@link IOException} to a
+ * {@link RuntimeException} of some sort.
+ *
+ * @param file {@code non-null;} the file to read
+ * @return {@code non-null;} contents of the file
+ */
+ public static byte[] readFile(File file) {
+ if (!file.exists()) {
+ throw new RuntimeException(file + ": file not found");
+ }
+
+ if (!file.isFile()) {
+ throw new RuntimeException(file + ": not a file");
+ }
+
+ if (!file.canRead()) {
+ throw new RuntimeException(file + ": file not readable");
+ }
+
+ long longLength = file.length();
+ int length = (int) longLength;
+ if (length != longLength) {
+ throw new RuntimeException(file + ": file too long");
+ }
+
+ byte[] result = new byte[length];
+
+ try {
+ FileInputStream in = new FileInputStream(file);
+ int at = 0;
+ while (length > 0) {
+ int amt = in.read(result, at, length);
+ if (amt == -1) {
+ throw new RuntimeException(file + ": unexpected EOF");
+ }
+ at += amt;
+ length -= amt;
+ }
+ in.close();
+ } catch (IOException ex) {
+ throw new RuntimeException(file + ": trouble reading", ex);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns true if {@code fileName} names a .zip, .jar, or .apk.
+ */
+ public static boolean hasArchiveSuffix(String fileName) {
+ return fileName.endsWith(".zip")
+ || fileName.endsWith(".jar")
+ || fileName.endsWith(".apk");
+ }
+}
diff --git a/dex/src/main/java/com/android/dex/util/Unsigned.java b/dex/src/main/java/com/android/dex/util/Unsigned.java
new file mode 100644
index 0000000..cb50d0a
--- /dev/null
+++ b/dex/src/main/java/com/android/dex/util/Unsigned.java
@@ -0,0 +1,42 @@
+/*
+ * 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.util;
+
+/**
+ * Unsigned arithmetic over Java's signed types.
+ */
+public final class Unsigned {
+ private Unsigned() {}
+
+ public static int compare(short ushortA, short ushortB) {
+ if (ushortA == ushortB) {
+ return 0;
+ }
+ int a = ushortA & 0xFFFF;
+ int b = ushortB & 0xFFFF;
+ return a < b ? -1 : 1;
+ }
+
+ public static int compare(int uintA, int uintB) {
+ if (uintA == uintB) {
+ return 0;
+ }
+ long a = uintA & 0xFFFFFFFFL;
+ long b = uintB & 0xFFFFFFFFL;
+ return a < b ? -1 : 1;
+ }
+}
diff --git a/dex/src/test/java/com/android/dex/EncodedValueReaderTest.java b/dex/src/test/java/com/android/dex/EncodedValueReaderTest.java
new file mode 100644
index 0000000..a4ca376
--- /dev/null
+++ b/dex/src/test/java/com/android/dex/EncodedValueReaderTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.util.ByteArrayByteInput;
+import junit.framework.TestCase;
+
+public final class EncodedValueReaderTest extends TestCase {
+
+ public void testReadByte() {
+ assertEquals((byte) 0x80, readerOf(0, 0x80).readByte());
+ assertEquals((byte) 0xff, readerOf(0, 0xff).readByte());
+ assertEquals((byte) 0x00, readerOf(0, 0x00).readByte());
+ assertEquals((byte) 0x01, readerOf(0, 0x01).readByte());
+ assertEquals((byte) 0x7f, readerOf(0, 0x7f).readByte());
+ }
+
+ public void testReadShort() {
+ assertEquals((short) 0x8000, readerOf(34, 0x00, 0x80).readShort());
+ assertEquals((short) 0, readerOf( 2, 0x00).readShort());
+ assertEquals((short) 0xab, readerOf(34, 0xab, 0x00).readShort());
+ assertEquals((short) 0xabcd, readerOf(34, 0xcd, 0xab).readShort());
+ assertEquals((short) 0x7FFF, readerOf(34, 0xff, 0x7f).readShort());
+ }
+
+ public void testReadInt() {
+ assertEquals(0x80000000, readerOf(100, 0x00, 0x00, 0x00, 0x80).readInt());
+ assertEquals( 0x00, readerOf( 4, 0x00).readInt());
+ assertEquals( 0xab, readerOf( 36, 0xab, 0x00).readInt());
+ assertEquals( 0xabcd, readerOf( 68, 0xcd, 0xab, 0x00).readInt());
+ assertEquals( 0xabcdef, readerOf(100, 0xef, 0xcd, 0xab, 0x00).readInt());
+ assertEquals(0xabcdef01, readerOf(100, 0x01, 0xef, 0xcd, 0xab).readInt());
+ assertEquals(0x7fffffff, readerOf(100, 0xff, 0xff, 0xff, 127).readInt());
+ }
+
+ public void testReadLong() {
+ assertEquals(0x8000000000000000L, readerOf( -26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80).readLong());
+ assertEquals( 0x00L, readerOf( 6, 0x00).readLong());
+ assertEquals( 0xabL, readerOf( 38, 0xab, 0x00).readLong());
+ assertEquals( 0xabcdL, readerOf( 70, 0xcd, 0xab, 0x00).readLong());
+ assertEquals( 0xabcdefL, readerOf( 102, 0xef, 0xcd, 0xab, 0x00).readLong());
+ assertEquals( 0xabcdef01L, readerOf(-122, 0x01, 0xef, 0xcd, 0xab, 0x00).readLong());
+ assertEquals( 0xabcdef0123L, readerOf( -90, 0x23, 0x01, 0xef, 0xcd, 0xab, 0x00).readLong());
+ assertEquals( 0xabcdef012345L, readerOf( -58, 0x45, 0x23, 0x01, 0xef, 0xcd, 0xab, 0x00).readLong());
+ assertEquals( 0xabcdef01234567L, readerOf( -26, 0x67, 0x45, 0x23, 0x01, 0xef, 0xcd, 0xab, 0x00).readLong());
+ assertEquals(0xabcdef0123456789L, readerOf( -26, 0x89, 0x67, 0x45, 0x23, 0x01, 0xef, 0xcd, 0xab).readLong());
+ assertEquals(0x7fffffffffffffffL, readerOf( -26, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f).readLong());
+ }
+
+ public void testReadFloat() {
+ assertEquals(Float.NEGATIVE_INFINITY, readerOf(48, -128, -1).readFloat());
+ assertEquals(Float.POSITIVE_INFINITY, readerOf(48, -128, 127).readFloat());
+ assertEquals(Float.NaN, readerOf(48, -64, 127).readFloat());
+ assertEquals(-0.0f, readerOf(16, -128).readFloat());
+ assertEquals(0.0f, readerOf(16, 0).readFloat());
+ assertEquals(0.5f, readerOf(16, 63).readFloat());
+ assertEquals(1f, readerOf(48, -128, 63).readFloat());
+ assertEquals(1.0E06f, readerOf(80, 36, 116, 73).readFloat());
+ assertEquals(1.0E12f, readerOf(112, -91, -44, 104, 83).readFloat());
+ }
+
+ public void testReadDouble() {
+ assertEquals(Double.NEGATIVE_INFINITY, readerOf(49, -16, -1).readDouble());
+ assertEquals(Double.POSITIVE_INFINITY, readerOf(49, -16, 127).readDouble());
+ assertEquals(Double.NaN, readerOf(49, -8, 127).readDouble());
+ assertEquals(-0.0, readerOf(17, -128).readDouble());
+ assertEquals(0.0, readerOf(17, 0).readDouble());
+ assertEquals(0.5, readerOf(49, -32, 63).readDouble());
+ assertEquals(1.0, readerOf(49, -16, 63).readDouble());
+ assertEquals(1.0E06, readerOf(113, -128, -124, 46, 65).readDouble());
+ assertEquals(1.0E12, readerOf(-111, -94, -108, 26, 109, 66).readDouble());
+ assertEquals(1.0E24, readerOf(-15, -76, -99, -39, 121, 67, 120, -22, 68).readDouble());
+ }
+
+ public void testReadChar() {
+ assertEquals('\u0000', readerOf( 3, 0x00).readChar());
+ assertEquals('\u00ab', readerOf( 3, 0xab).readChar());
+ assertEquals('\uabcd', readerOf(35, 0xcd, 0xab).readChar());
+ assertEquals('\uffff', readerOf(35, 0xff, 0xff).readChar());
+ }
+
+ public void testReadBoolean() {
+ assertEquals(true, readerOf(63).readBoolean());
+ assertEquals(false, readerOf(31).readBoolean());
+ }
+
+ public void testReadNull() {
+ readerOf(30).readNull();
+ }
+
+ public void testReadReference() {
+ assertEquals( 0xab, readerOf(0x17, 0xab).readString());
+ assertEquals( 0xabcd, readerOf(0x37, 0xcd, 0xab).readString());
+ assertEquals( 0xabcdef, readerOf(0x57, 0xef, 0xcd, 0xab).readString());
+ assertEquals(0xabcdef01, readerOf(0x77, 0x01, 0xef, 0xcd, 0xab).readString());
+ }
+
+ public void testReadWrongType() {
+ try {
+ readerOf(0x17, 0xab).readField();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ private EncodedValueReader readerOf(int... bytes) {
+ byte[] data = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ data[i] = (byte) bytes[i];
+ }
+ return new EncodedValueReader(new ByteArrayByteInput(data));
+ }
+}