diff options
author | Jesse Wilson <jessewilson@google.com> | 2011-09-18 12:55:16 -0400 |
---|---|---|
committer | Brian Carlstrom <bdc@google.com> | 2013-04-30 14:02:10 -0700 |
commit | 2bea5ee615b0f4add658d5660bd81c5145a0d05e (patch) | |
tree | e6c2f7f6b833143b4cf2e99e63f0a76e021ddab8 | |
parent | 87b4b0f88fe0b5fa5c271593e483fb5bd306a6a4 (diff) | |
download | libcore-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)
26 files changed, 3058 insertions, 1 deletions
diff --git a/JavaLibrary.mk b/JavaLibrary.mk index ff2a445..16e26e9 100644 --- a/JavaLibrary.mk +++ b/JavaLibrary.mk @@ -50,7 +50,7 @@ $(shell cd $(LOCAL_PATH) && ls -d */src/$(1)/{java,resources} 2> /dev/null) endef # The Java files and their associated resources. -core_src_files := $(call all-main-java-files-under,dalvik dom json luni support xml) +core_src_files := $(call all-main-java-files-under,dalvik dex dom json luni support xml) core_resource_dirs := $(call all-core-resource-dirs,main) test_resource_dirs := $(call all-core-resource-dirs,test) 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)); + } +} |