diff options
author | Jesse Wilson <jessewilson@google.com> | 2011-09-15 15:49:25 -0400 |
---|---|---|
committer | Brian Carlstrom <bdc@google.com> | 2013-04-29 18:21:55 -0700 |
commit | 7e85b634fbafa4a3b927c80665dea6fac337d886 (patch) | |
tree | 51197d2d020fb9a69962542ae59e5810ed0b5f89 /dx | |
parent | 33a977ec6a12deb22bcc87f8c294439b3aba16ab (diff) | |
download | android_dalvik-7e85b634fbafa4a3b927c80665dea6fac337d886.tar.gz android_dalvik-7e85b634fbafa4a3b927c80665dea6fac337d886.tar.bz2 android_dalvik-7e85b634fbafa4a3b927c80665dea6fac337d886.zip |
Teach dex how to parse encoded values from .dex files.
Previously primitive values were treated as opaque byte arrays.
Now the encoded values can be interpreted as their proper types:
integers, indices, floats, chars, etc. This gets complicated
pretty fast due to the dense packing dx performs when encoding
the values.
The encoding code is moved from ValueEncoder. The decoding code
is ported from C++ to Java from Annotation.cpp.
There's a bunch of new tests to make sure the decoding is correct;
porting from C++ to Java was tricky because of the '>>' shifts on
unsigned values. The test input data is generated by dex!
Change-Id: I83b2fc3e16115d667fa94b3dab782d1a9687f3ad
(cherry picked from commit 27847605b9255358f0577ffec28886c450263898)
Diffstat (limited to 'dx')
-rw-r--r-- | dx/junit-tests/com/android/dx/io/EncodedValueReaderTest.java | 127 | ||||
-rw-r--r-- | dx/src/com/android/dx/command/grep/Grep.java | 17 | ||||
-rw-r--r-- | dx/src/com/android/dx/dex/file/ValueEncoder.java | 122 | ||||
-rw-r--r-- | dx/src/com/android/dx/io/DexBuffer.java | 4 | ||||
-rw-r--r-- | dx/src/com/android/dx/io/EncodedValueReader.java | 253 | ||||
-rw-r--r-- | dx/src/com/android/dx/merge/IndexMap.java | 163 | ||||
-rw-r--r-- | dx/src/com/android/dx/util/EncodedValueUtils.java | 184 |
7 files changed, 619 insertions, 251 deletions
diff --git a/dx/junit-tests/com/android/dx/io/EncodedValueReaderTest.java b/dx/junit-tests/com/android/dx/io/EncodedValueReaderTest.java new file mode 100644 index 000000000..63689ec03 --- /dev/null +++ b/dx/junit-tests/com/android/dx/io/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.dx.io; + +import com.android.dx.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)); + } +} diff --git a/dx/src/com/android/dx/command/grep/Grep.java b/dx/src/com/android/dx/command/grep/Grep.java index 741de9750..14c46d7cc 100644 --- a/dx/src/com/android/dx/command/grep/Grep.java +++ b/dx/src/com/android/dx/command/grep/Grep.java @@ -52,12 +52,17 @@ public final class Grep { }); } - private EncodedValueReader newEncodedValueReader(DexBuffer.Section section) { - return new EncodedValueReader(section) { - @Override protected void visitString(int type, int index) { - encounterString(index); + private void readArray(EncodedValueReader reader) { + for (int i = 0, size = reader.readArray(); i < size; i++) { + switch (reader.peek()) { + case EncodedValueReader.ENCODED_STRING: + encounterString(reader.readString()); + break; + case EncodedValueReader.ENCODED_ARRAY: + readArray(reader); + break; } - }; + } } private void encounterString(int index) { @@ -94,7 +99,7 @@ public final class Grep { // find the strings in encoded constants int staticValuesOffset = classDef.getStaticValuesOffset(); if (staticValuesOffset != 0) { - newEncodedValueReader(dex.open(staticValuesOffset)).readArray(); + readArray(new EncodedValueReader(dex.open(staticValuesOffset))); } // find the strings in method bodies diff --git a/dx/src/com/android/dx/dex/file/ValueEncoder.java b/dx/src/com/android/dx/dex/file/ValueEncoder.java index 9c433a3de..aafb42945 100644 --- a/dx/src/com/android/dx/dex/file/ValueEncoder.java +++ b/dx/src/com/android/dx/dex/file/ValueEncoder.java @@ -37,6 +37,7 @@ import com.android.dx.rop.cst.CstShort; import com.android.dx.rop.cst.CstString; import com.android.dx.rop.cst.CstType; import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.EncodedValueUtils; import com.android.dx.util.Hex; import java.util.Collection; @@ -133,49 +134,49 @@ public final class ValueEncoder { case VALUE_INT: case VALUE_LONG: { long value = ((CstLiteralBits) cst).getLongBits(); - writeSignedIntegralValue(type, value); + EncodedValueUtils.writeSignedIntegralValue(out, type, value); break; } case VALUE_CHAR: { long value = ((CstLiteralBits) cst).getLongBits(); - writeUnsignedIntegralValue(type, value); + EncodedValueUtils.writeUnsignedIntegralValue(out, type, value); break; } case VALUE_FLOAT: { // Shift value left 32 so that right-zero-extension works. long value = ((CstFloat) cst).getLongBits() << 32; - writeRightZeroExtendedValue(type, value); + EncodedValueUtils.writeRightZeroExtendedValue(out, type, value); break; } case VALUE_DOUBLE: { long value = ((CstDouble) cst).getLongBits(); - writeRightZeroExtendedValue(type, value); + EncodedValueUtils.writeRightZeroExtendedValue(out, type, value); break; } case VALUE_STRING: { int index = file.getStringIds().indexOf((CstString) cst); - writeUnsignedIntegralValue(type, (long) index); + EncodedValueUtils.writeUnsignedIntegralValue(out, type, (long) index); break; } case VALUE_TYPE: { int index = file.getTypeIds().indexOf((CstType) cst); - writeUnsignedIntegralValue(type, (long) index); + EncodedValueUtils.writeUnsignedIntegralValue(out, type, (long) index); break; } case VALUE_FIELD: { int index = file.getFieldIds().indexOf((CstFieldRef) cst); - writeUnsignedIntegralValue(type, (long) index); + EncodedValueUtils.writeUnsignedIntegralValue(out, type, (long) index); break; } case VALUE_METHOD: { int index = file.getMethodIds().indexOf((CstMethodRef) cst); - writeUnsignedIntegralValue(type, (long) index); + EncodedValueUtils.writeUnsignedIntegralValue(out, type, (long) index); break; } case VALUE_ENUM: { CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef(); int index = file.getFieldIds().indexOf(fieldRef); - writeUnsignedIntegralValue(type, (long) index); + EncodedValueUtils.writeUnsignedIntegralValue(out, type, (long) index); break; } case VALUE_ARRAY: { @@ -379,109 +380,6 @@ public final class ValueEncoder { } /** - * Helper for {@link #writeConstant}, which writes out the value - * for any signed integral type. - * - * @param type the type constant - * @param value {@code long} bits of the value - */ - private void writeSignedIntegralValue(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--; - } - } - - /** - * Helper for {@link #writeConstant}, which writes out the value - * for any unsigned integral type. - * - * @param type the type constant - * @param value {@code long} bits of the value - */ - private void writeUnsignedIntegralValue(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--; - } - } - - /** - * Helper for {@link #writeConstant}, which writes out a - * right-zero-extended value. - * - * @param type the type constant - * @param value {@code long} bits of the value - */ - private void writeRightZeroExtendedValue(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--; - } - } - - - /** * Helper for {@code addContents()} methods, which adds * contents for a particular {@link Annotation}, calling itself * recursively should it encounter a nested annotation. diff --git a/dx/src/com/android/dx/io/DexBuffer.java b/dx/src/com/android/dx/io/DexBuffer.java index 9fbc78cac..c338ef43e 100644 --- a/dx/src/com/android/dx/io/DexBuffer.java +++ b/dx/src/com/android/dx/io/DexBuffer.java @@ -565,14 +565,14 @@ public final class DexBuffer { public EncodedValue readEncodedValue() { int start = position; - new EncodedValueReader(this).readValue(); + new EncodedValueReader(this).skipValue(); int end = position; return new EncodedValue(Arrays.copyOfRange(data, start, end)); } public EncodedValue readEncodedArray() { int start = position; - new EncodedValueReader(this).readArray(); + new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue(); int end = position; return new EncodedValue(Arrays.copyOfRange(data, start, end)); } diff --git a/dx/src/com/android/dx/io/EncodedValueReader.java b/dx/src/com/android/dx/io/EncodedValueReader.java index ec84898ba..c53e5b892 100644 --- a/dx/src/com/android/dx/io/EncodedValueReader.java +++ b/dx/src/com/android/dx/io/EncodedValueReader.java @@ -17,13 +17,14 @@ package com.android.dx.io; import com.android.dx.util.ByteInput; +import com.android.dx.util.DexException; +import com.android.dx.util.EncodedValueUtils; import com.android.dx.util.Leb128Utils; /** - * SAX-style reader for encoded values. - * TODO: convert this to a pull-style reader + * Pull parser for encoded values. */ -public class EncodedValueReader { +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; @@ -41,7 +42,13 @@ public class EncodedValueReader { 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; @@ -51,96 +58,228 @@ public class EncodedValueReader { this(in.asByteInput()); } - public final void readArray() { - int size = Leb128Utils.readUnsignedLeb128(in); - visitArray(size); + /** + * Creates a new encoded value reader whose only value is the specified + * known type. This is useful for class_def_item, which references an + * encoded array that doesn't contain a type+arg prefix. + */ + public EncodedValueReader(ByteInput in, int knownType) { + this.in = in; + this.type = knownType; + } - for (int i = 0; i < size; i++) { - readValue(); + /** + * 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; } - public final void readAnnotation() { - int typeIndex = Leb128Utils.readUnsignedLeb128(in); - int size = Leb128Utils.readUnsignedLeb128(in); - visitAnnotation(typeIndex, size); + /** + * 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 Leb128Utils.readUnsignedLeb128(in); + } - for (int i = 0; i < size; i++) { - visitAnnotationName(Leb128Utils.readUnsignedLeb128(in)); - readValue(); - } + /** + * 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 = Leb128Utils.readUnsignedLeb128(in); + return Leb128Utils.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 Leb128Utils.readUnsignedLeb128(in); } - public final void readValue() { - int argAndType = in.readByte() & 0xff; - int type = argAndType & 0x1f; - int arg = (argAndType & 0xe0) >> 5; - int size = arg + 1; + public byte readByte() { + checkType(ENCODED_BYTE); + type = MUST_READ; + return (byte) EncodedValueUtils.readSignedInt(in, arg); + } + + public short readShort() { + checkType(ENCODED_SHORT); + type = MUST_READ; + return (short) EncodedValueUtils.readSignedInt(in, arg); + } - switch (type) { + public char readChar() { + checkType(ENCODED_CHAR); + type = MUST_READ; + return (char) EncodedValueUtils.readUnsignedInt(in, arg, false); + } + + public int readInt() { + checkType(ENCODED_INT); + type = MUST_READ; + return EncodedValueUtils.readSignedInt(in, arg); + } + + public long readLong() { + checkType(ENCODED_LONG); + type = MUST_READ; + return EncodedValueUtils.readSignedLong(in, arg); + } + + public float readFloat() { + checkType(ENCODED_FLOAT); + type = MUST_READ; + return Float.intBitsToFloat(EncodedValueUtils.readUnsignedInt(in, arg, true)); + } + + public double readDouble() { + checkType(ENCODED_DOUBLE); + type = MUST_READ; + return Double.longBitsToDouble(EncodedValueUtils.readUnsignedLong(in, arg, true)); + } + + public int readString() { + checkType(ENCODED_STRING); + type = MUST_READ; + return EncodedValueUtils.readUnsignedInt(in, arg, false); + } + + public int readType() { + checkType(ENCODED_TYPE); + type = MUST_READ; + return EncodedValueUtils.readUnsignedInt(in, arg, false); + } + + public int readField() { + checkType(ENCODED_FIELD); + type = MUST_READ; + return EncodedValueUtils.readUnsignedInt(in, arg, false); + } + + public int readEnum() { + checkType(ENCODED_ENUM); + type = MUST_READ; + return EncodedValueUtils.readUnsignedInt(in, arg, false); + } + + public int readMethod() { + checkType(ENCODED_METHOD); + type = MUST_READ; + return EncodedValueUtils.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: - visitPrimitive(argAndType, type, arg, size); + readDouble(); break; case ENCODED_STRING: - visitString(type, readIndex(in, size)); + readString(); break; case ENCODED_TYPE: - visitType(type, readIndex(in, size)); + readType(); break; case ENCODED_FIELD: + readField(); + break; case ENCODED_ENUM: - visitField(type, readIndex(in, size)); + readEnum(); break; case ENCODED_METHOD: - visitMethod(type, readIndex(in, size)); + readMethod(); break; case ENCODED_ARRAY: - visitArrayValue(argAndType); - readArray(); + for (int i = 0, size = readArray(); i < size; i++) { + skipValue(); + } break; case ENCODED_ANNOTATION: - visitAnnotationValue(argAndType); - readAnnotation(); + for (int i = 0, size = readAnnotation(); i < size; i++) { + readAnnotationName(); + skipValue(); + } break; case ENCODED_NULL: - visitEncodedNull(argAndType); + readNull(); break; case ENCODED_BOOLEAN: - visitEncodedBoolean(argAndType); + readBoolean(); break; + default: + throw new DexException("Unexpected type: " + Integer.toHexString(type)); } } - protected void visitArray(int size) {} - protected void visitAnnotation(int typeIndex, int size) {} - protected void visitAnnotationName(int nameIndex) {} - protected void visitPrimitive(int argAndType, int type, int arg, int size) { - for (int i = 0; i < size; i++) { - in.readByte(); - } - } - protected void visitString(int type, int index) {} - protected void visitType(int type, int index) {} - protected void visitField(int type, int index) {} - protected void visitMethod(int type, int index) {} - protected void visitArrayValue(int argAndType) {} - protected void visitAnnotationValue(int argAndType) {} - protected void visitEncodedBoolean(int argAndType) {} - protected void visitEncodedNull(int argAndType) {} - - private int readIndex(ByteInput in, int byteCount) { - int result = 0; - int shift = 0; - for (int i = 0; i < byteCount; i++) { - result += (in.readByte() & 0xff) << shift; - shift += 8; + private void checkType(int expected) { + if (peek() != expected) { + throw new IllegalStateException("Expected array but was " + + Integer.toHexString(peek())); } - return result; } } diff --git a/dx/src/com/android/dx/merge/IndexMap.java b/dx/src/com/android/dx/merge/IndexMap.java index 89d2dd671..0a870cfae 100644 --- a/dx/src/com/android/dx/merge/IndexMap.java +++ b/dx/src/com/android/dx/merge/IndexMap.java @@ -22,14 +22,15 @@ import com.android.dx.io.ClassDef; import com.android.dx.io.DexBuffer; import com.android.dx.io.EncodedValue; import com.android.dx.io.EncodedValueReader; +import static com.android.dx.io.EncodedValueReader.*; import com.android.dx.io.FieldId; import com.android.dx.io.MethodId; import com.android.dx.io.ProtoId; import com.android.dx.util.ByteArrayAnnotatedOutput; -import com.android.dx.util.ByteInput; import com.android.dx.util.ByteOutput; +import com.android.dx.util.DexException; +import com.android.dx.util.EncodedValueUtils; import com.android.dx.util.Leb128Utils; -import com.android.dx.util.Unsigned; import java.util.HashMap; /** @@ -208,13 +209,14 @@ public final class IndexMap { public EncodedValue adjustEncodedValue(EncodedValue encodedValue) { ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); - new EncodedValueTransformer(encodedValue, out).readValue(); + new EncodedValueTransformer(out).transform(new EncodedValueReader(encodedValue)); return new EncodedValue(out.toByteArray()); } public EncodedValue adjustEncodedArray(EncodedValue encodedArray) { ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); - new EncodedValueTransformer(encodedArray, out).readArray(); + new EncodedValueTransformer(out).transformArray( + new EncodedValueReader(encodedArray.asByteInput(), ENCODED_ARRAY)); return new EncodedValue(out.toByteArray()); } @@ -232,88 +234,101 @@ public final class IndexMap { /** * Adjust an encoded value or array. */ - private final class EncodedValueTransformer extends EncodedValueReader { + private final class EncodedValueTransformer { private final ByteOutput out; - public EncodedValueTransformer(EncodedValue encodedValue, ByteOutput out) { - super(encodedValue); + public EncodedValueTransformer(ByteOutput out) { this.out = out; } - protected void visitArray(int size) { - Leb128Utils.writeUnsignedLeb128(out, size); - } - - protected void visitAnnotation(int typeIndex, int size) { - Leb128Utils.writeUnsignedLeb128(out, adjustType(typeIndex)); - Leb128Utils.writeUnsignedLeb128(out, size); - } - - protected void visitAnnotationName(int index) { - Leb128Utils.writeUnsignedLeb128(out, adjustString(index)); - } - - protected void visitPrimitive(int argAndType, int type, int arg, int size) { - out.writeByte(argAndType); - copyBytes(in, out, size); - } - - protected void visitString(int type, int index) { - writeTypeAndSizeAndIndex(type, adjustString(index)); - } - - protected void visitType(int type, int index) { - writeTypeAndSizeAndIndex(type, adjustType(index)); - } - - protected void visitField(int type, int index) { - writeTypeAndSizeAndIndex(type, adjustField(index)); - } - - protected void visitMethod(int type, int index) { - writeTypeAndSizeAndIndex(type, adjustMethod(index)); - } - - protected void visitArrayValue(int argAndType) { - out.writeByte(argAndType); - } - - protected void visitAnnotationValue(int argAndType) { - out.writeByte(argAndType); - } - - protected void visitEncodedBoolean(int argAndType) { - out.writeByte(argAndType); - } - - protected void visitEncodedNull(int argAndType) { - out.writeByte(argAndType); - } - - private void writeTypeAndSizeAndIndex(int type, int index) { - int byteCount; - if (Unsigned.compare(index, 0xff) <= 0) { - byteCount = 1; - } else if (Unsigned.compare(index, 0xffff) <= 0) { - byteCount = 2; - } else if (Unsigned.compare(index, 0xffffff) <= 0) { - byteCount = 3; - } else { - byteCount = 4; + public void transform(EncodedValueReader reader) { + // TODO: extract this into a helper class, EncodedValueWriter + switch (reader.peek()) { + case ENCODED_BYTE: + EncodedValueUtils.writeSignedIntegralValue(out, ENCODED_BYTE, reader.readByte()); + break; + case ENCODED_SHORT: + EncodedValueUtils.writeSignedIntegralValue(out, ENCODED_SHORT, reader.readShort()); + break; + case ENCODED_INT: + EncodedValueUtils.writeSignedIntegralValue(out, ENCODED_INT, reader.readInt()); + break; + case ENCODED_LONG: + EncodedValueUtils.writeSignedIntegralValue(out, ENCODED_LONG, reader.readLong()); + break; + case ENCODED_CHAR: + EncodedValueUtils.writeUnsignedIntegralValue(out, ENCODED_CHAR, reader.readChar()); + break; + case ENCODED_FLOAT: + // Shift value left 32 so that right-zero-extension works. + long longBits = ((long) Float.floatToIntBits(reader.readFloat())) << 32; + EncodedValueUtils.writeRightZeroExtendedValue(out, ENCODED_FLOAT, longBits); + break; + case ENCODED_DOUBLE: + EncodedValueUtils.writeRightZeroExtendedValue( + out, ENCODED_DOUBLE, Double.doubleToLongBits(reader.readDouble())); + break; + case ENCODED_STRING: + EncodedValueUtils.writeUnsignedIntegralValue( + out, ENCODED_STRING, adjustString(reader.readString())); + break; + case ENCODED_TYPE: + EncodedValueUtils.writeUnsignedIntegralValue( + out, ENCODED_TYPE, adjustType(reader.readType())); + break; + case ENCODED_FIELD: + EncodedValueUtils.writeUnsignedIntegralValue( + out, ENCODED_FIELD, adjustField(reader.readField())); + break; + case ENCODED_ENUM: + EncodedValueUtils.writeUnsignedIntegralValue( + out, ENCODED_ENUM, adjustField(reader.readEnum())); + break; + case ENCODED_METHOD: + EncodedValueUtils.writeUnsignedIntegralValue( + out, ENCODED_METHOD, adjustMethod(reader.readMethod())); + break; + case ENCODED_ARRAY: + writeTypeAndArg(ENCODED_ARRAY, 0); + transformArray(reader); + break; + case ENCODED_ANNOTATION: + writeTypeAndArg(ENCODED_ANNOTATION, 0); + transformAnnotation(reader); + break; + case ENCODED_NULL: + reader.readNull(); + writeTypeAndArg(ENCODED_NULL, 0); + break; + case ENCODED_BOOLEAN: + boolean value = reader.readBoolean(); + writeTypeAndArg(ENCODED_BOOLEAN, value ? 1 : 0); + break; + default: + throw new DexException("Unexpected type: " + Integer.toHexString(reader.peek())); } - int argAndType = ((byteCount - 1) << 5) | type; - out.writeByte(argAndType); + } - for (int i = 0; i < byteCount; i++) { - out.writeByte(index & 0xff); - index >>>= 8; + private void transformAnnotation(EncodedValueReader reader) { + int fieldCount = reader.readAnnotation(); + Leb128Utils.writeUnsignedLeb128(out, adjustType(reader.getAnnotationType())); + Leb128Utils.writeUnsignedLeb128(out, fieldCount); + for (int i = 0; i < fieldCount; i++) { + Leb128Utils.writeUnsignedLeb128(out, adjustString(reader.readAnnotationName())); + transform(reader); } } - private void copyBytes(ByteInput in, ByteOutput out, int size) { + private void transformArray(EncodedValueReader reader) { + int size = reader.readArray(); + Leb128Utils.writeUnsignedLeb128(out, size); for (int i = 0; i < size; i++) { - out.writeByte(in.readByte()); + transform(reader); } } + + private void writeTypeAndArg(int type, int arg) { + out.writeByte((arg << 5) | type); + } } } diff --git a/dx/src/com/android/dx/util/EncodedValueUtils.java b/dx/src/com/android/dx/util/EncodedValueUtils.java new file mode 100644 index 000000000..bb92389b7 --- /dev/null +++ b/dx/src/com/android/dx/util/EncodedValueUtils.java @@ -0,0 +1,184 @@ +/* + * 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.dx.util; + +/** + * Helpers for {@code encoded_values} and parts thereof. + */ +public final class EncodedValueUtils { + private EncodedValueUtils() { + } + + /** + * 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; + } +} |