+ * Note: Dex version 36 was loadable in some versions of Dalvik but was never fully supported or + * completed and is not considered a valid dex file format. + *
+ */ + public static final String VERSION_CURRENT = VERSION_FOR_API_26; + + /** + * 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"; + + /** + * value used to indicate endianness of file contents + */ + public static final int ENDIAN_TAG = 0x12345678; + + /** + * Maximum addressable field or method index. + * The largest addressable member is 0xffff, in the "instruction formats" spec as field@CCCC or + * meth@CCCC. + */ + public static final int MAX_MEMBER_IDX = 0xFFFF; + + /** + * Maximum addressable type index. + * The largest addressable type is 0xffff, in the "instruction formats" spec as type@CCCC. + */ + public static final int MAX_TYPE_IDX = 0xFFFF; + + /** + * 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_FOR_API_13)) { + return API_NO_EXTENDED_OPCODES; + } else if (version.equals(VERSION_FOR_API_24)) { + return API_DEFAULT_INTERFACE_METHODS; + } else if (version.equals(VERSION_FOR_API_26)) { + return API_INVOKE_POLYMORPHIC; + } else if (version.equals(VERSION_CURRENT)) { + return API_CURRENT; + } + + 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 if (targetApiLevel >= API_INVOKE_POLYMORPHIC) { + version = VERSION_FOR_API_26; + } else if (targetApiLevel >= API_DEFAULT_INTERFACE_METHODS) { + version = VERSION_FOR_API_24; + } else { + version = VERSION_FOR_API_13; + } + + return MAGIC_PREFIX + version + MAGIC_SUFFIX; + } + + public static boolean isSupportedDexMagic(byte[] magic) { + int api = magicToApi(magic); + return api > 0; + } +} diff --git a/dexlib/src/main/java/com/android/dex/DexIndexOverflowException.java b/dexlib/src/main/java/com/android/dex/DexIndexOverflowException.java new file mode 100644 index 000000000..32262072b --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/DexIndexOverflowException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 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; + +/** + * Thrown when there's an index overflow writing a dex file. + */ +public final class DexIndexOverflowException extends DexException { + public DexIndexOverflowException(String message) { + super(message); + } + + public DexIndexOverflowException(Throwable cause) { + super(cause); + } +} diff --git a/dexlib/src/main/java/com/android/dex/EncodedValue.java b/dexlib/src/main/java/com/android/dex/EncodedValue.java new file mode 100644 index 000000000..8d0c3adcf --- /dev/null +++ b/dexlib/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{@code + * int arraySize = readArray(); + * for (int i = 0, i < arraySize; i++) { + * readByte(); + * } + * }+ */ + 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:
{@code + * int fieldCount = readAnnotation(); + * int annotationType = getAnnotationType(); + * for (int i = 0; i < fieldCount; i++) { + * readAnnotationName(); + * readByte(); + * } + * }+ */ + 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( + String.format("Expected %x but was %x", expected, peek())); + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/FieldId.java b/dexlib/src/main/java/com/android/dex/FieldId.java new file mode 100644 index 000000000..2f41708c8 --- /dev/null +++ b/dexlib/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
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 modified UTF-8 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/dexlib/src/main/java/com/android/dex/ProtoId.java b/dexlib/src/main/java/com/android/dex/ProtoId.java
new file mode 100644
index 000000000..9d9f484f2
--- /dev/null
+++ b/dexlib/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 Implementation of containers and utilities for all the standard Java
+attribute types. PACKAGES USED:
+ Note: For the most part, the documentation for this class
+ * ignores the distinction between {@link Type} and {@link
+ * TypeBearer}. Note: If there is more than one result value, the
+ * others may be added by using {@link #addResult}. In order to simplify further processing, the opcodes passed
+ * to the visitor are canonicalized, altering the opcode to a more
+ * universal one and making formerly implicit arguments
+ * explicit. In particular: Note: In order to avoid giving it a barely-useful
+ * visitor all its own, {@code newarray} also uses this
+ * form, passing {@code value} as the array type code and
+ * {@code cst} as a {@link CstType} instance
+ * corresponding to the array type. Note: For the most part, the documentation for this class
+ * ignores the distinction between {@link Type} and {@link
+ * TypeBearer}. Note: At least one of {@code descriptor} or
+ * {@code signature} must be passed as non-null. Note: At least one of {@code descriptor} or
+ * {@code signature} must be passed as non-null. Note: For the most part, the documentation for this class
+ * ignores the distinction between {@link Type} and {@link
+ * TypeBearer}. Note: For the most part, the documentation for this class
+ * ignores the distinction between {@link com.android.dx.rop.type.Type} and {@link
+ * com.android.dx.rop.type.TypeBearer}. Note: Perhaps unintuitively, the stack manipulation
+ * ops (e.g., {@code dup} and {@code swap}) use this to
+ * indicate the result stack pattern with a straightforward hex
+ * encoding of the push order starting with least-significant
+ * nibbles getting pushed first). For example, an all-category-1
+ * {@code dup2_x1} sets this to {@code 0x12312}, and the
+ * other form of that op sets this to
+ * {@code 0x121}. Also Note: For {@code switch*} instructions, this is
+ * used to indicate the padding value (which is only useful for
+ * verification). Note: Some opcodes use both {@code int} and
+ * constant auxiliary arguments. Note: This is generally used in conjunction with
+ * {@link #auxIntArg} (which holds the padding). Note: For the most part, the documentation for this class
+ * ignores the distinction between {@link com.android.dx.rop.type.Type} and {@link
+ * com.android.dx.rop.type.TypeBearer}. Note: This class is not thread-safe. If multiple threads
+ * need to use a single instance, they must synchronize access explicitly
+ * between themselves. The interesting cases here have to do with object arrays,
+ * In the case of arrays of objects, we want to narrow the type
+ * to the actual array present on the stack, as long as what is
+ * present is an object type. Similarly, due to a quirk of the
+ * original bytecode representation, the instructions for dealing
+ * with In the case where there is a known-null on the stack where
+ * an array is expected, our behavior depends on the implied type
+ * of the instruction. When the implied type is a reference, we
+ * don't attempt to infer anything, as we don't know the dimension
+ * of the null constant and thus any explicit inferred type could
+ * be wrong. When the implied type is a primitive, we fall back to
+ * the implied type of the instruction. Due to the quirk described
+ * above, this means that source code that uses
+ * Implementation of classes having to do with Java simulation, such as
+is needed for verification or stack-to-register conversion. PACKAGES USED:
+ Implementation of PACKAGES USED:
+ Note: The fields referred to in this documentation are of the
+ * {@code ClassFile} structure defined in vmspec-2 sec4.1.
+ */
+public interface ClassFile extends HasAttribute {
+ /**
+ * Gets the field {@code magic}.
+ *
+ * @return the value in question
+ */
+ public int getMagic();
+
+ /**
+ * Gets the field {@code minor_version}.
+ *
+ * @return the value in question
+ */
+ public int getMinorVersion();
+
+ /**
+ * Gets the field {@code major_version}.
+ *
+ * @return the value in question
+ */
+ public int getMajorVersion();
+
+ /**
+ * Gets the field {@code access_flags}.
+ *
+ * @return the value in question
+ */
+ public int getAccessFlags();
+
+ /**
+ * Gets the field {@code this_class}, interpreted as a type constant.
+ *
+ * @return {@code non-null;} the value in question
+ */
+ public CstType getThisClass();
+
+ /**
+ * Gets the field {@code super_class}, interpreted as a type constant
+ * if non-zero.
+ *
+ * @return {@code null-ok;} the value in question
+ */
+ public CstType getSuperclass();
+
+ /**
+ * Gets the field {@code constant_pool} (along with
+ * {@code constant_pool_count}).
+ *
+ * @return {@code non-null;} the constant pool
+ */
+ public ConstantPool getConstantPool();
+
+ /**
+ * Gets the field {@code interfaces} (along with
+ * {@code interfaces_count}).
+ *
+ * @return {@code non-null;} the list of interfaces
+ */
+ public TypeList getInterfaces();
+
+ /**
+ * Gets the field {@code fields} (along with
+ * {@code fields_count}).
+ *
+ * @return {@code non-null;} the list of fields
+ */
+ public FieldList getFields();
+
+ /**
+ * Gets the field {@code methods} (along with
+ * {@code methods_count}).
+ *
+ * @return {@code non-null;} the list of fields
+ */
+ public MethodList getMethods();
+
+ /**
+ * Gets the field {@code attributes} (along with
+ * {@code attributes_count}).
+ *
+ * @return {@code non-null;} the list of attributes
+ */
+ public AttributeList getAttributes();
+
+ /**
+ * Gets the name out of the {@code SourceFile} attribute of this
+ * file, if any. This is a convenient shorthand for scrounging around
+ * the class's attributes.
+ *
+ * @return {@code non-null;} the constant pool
+ */
+ public CstString getSourceFile();
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Field.java b/dexlib/src/main/java/com/android/dx/cf/iface/Field.java
new file mode 100644
index 000000000..e3002bcd0
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/Field.java
@@ -0,0 +1,35 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.rop.cst.TypedConstant;
+
+/**
+ * Interface representing fields of class files.
+ */
+public interface Field
+ extends Member {
+ /**
+ * Get the constant value for this field, if any. This only returns
+ * non-{@code null} for a {@code static final} field which
+ * includes a {@code ConstantValue} attribute.
+ *
+ * @return {@code null-ok;} the constant value, or {@code null} if this
+ * field isn't a constant
+ */
+ public TypedConstant getConstantValue();
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/FieldList.java b/dexlib/src/main/java/com/android/dx/cf/iface/FieldList.java
new file mode 100644
index 000000000..9cd27a311
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/FieldList.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dx.cf.iface;
+
+/**
+ * Interface for lists of fields.
+ */
+public interface FieldList
+{
+ /**
+ * Get whether this instance is mutable. Note that the
+ * {@code FieldList} interface itself doesn't provide any means
+ * of mutation, but that doesn't mean that there isn't a non-interface
+ * way of mutating an instance.
+ *
+ * @return {@code true} iff this instance is somehow mutable
+ */
+ public boolean isMutable();
+
+ /**
+ * Get the number of fields in the list.
+ *
+ * @return the size
+ */
+ public int size();
+
+ /**
+ * Get the {@code n}th field.
+ *
+ * @param n {@code n >= 0, n < size();} which field
+ * @return {@code non-null;} the field in question
+ */
+ public Field get(int n);
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/HasAttribute.java b/dexlib/src/main/java/com/android/dx/cf/iface/HasAttribute.java
new file mode 100644
index 000000000..9f3e48db2
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/HasAttribute.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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.cf.iface;
+
+/**
+ * An element that can have {@link Attribute}
+ */
+public interface HasAttribute {
+
+ /**
+ * Get the element {@code attributes} (along with
+ * {@code attributes_count}).
+ *
+ * @return {@code non-null;} the attributes list
+ */
+ public AttributeList getAttributes();
+
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Member.java b/dexlib/src/main/java/com/android/dx/cf/iface/Member.java
new file mode 100644
index 000000000..1097d1906
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/Member.java
@@ -0,0 +1,74 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+
+/**
+ * Interface representing members of class files (that is, fields and methods).
+ */
+public interface Member extends HasAttribute {
+ /**
+ * Get the defining class.
+ *
+ * @return {@code non-null;} the defining class
+ */
+ public CstType getDefiningClass();
+
+ /**
+ * Get the field {@code access_flags}.
+ *
+ * @return the access flags
+ */
+ public int getAccessFlags();
+
+ /**
+ * Get the field {@code name_index} of the member. This is
+ * just a convenient shorthand for {@code getNat().getName()}.
+ *
+ * @return {@code non-null;} the name
+ */
+ public CstString getName();
+
+ /**
+ * Get the field {@code descriptor_index} of the member. This is
+ * just a convenient shorthand for {@code getNat().getDescriptor()}.
+ *
+ * @return {@code non-null;} the descriptor
+ */
+ public CstString getDescriptor();
+
+ /**
+ * Get the name and type associated with this member. This is a
+ * combination of the fields {@code name_index} and
+ * {@code descriptor_index} in the original classfile, interpreted
+ * via the constant pool.
+ *
+ * @return {@code non-null;} the name and type
+ */
+ public CstNat getNat();
+
+ /**
+ * Get the field {@code attributes} (along with
+ * {@code attributes_count}).
+ *
+ * @return {@code non-null;} the constant pool
+ */
+ public AttributeList getAttributes();
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Method.java b/dexlib/src/main/java/com/android/dx/cf/iface/Method.java
new file mode 100644
index 000000000..18b9af64f
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/Method.java
@@ -0,0 +1,34 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.rop.type.Prototype;
+
+/**
+ * Interface representing methods of class files.
+ */
+public interface Method
+ extends Member
+{
+ /**
+ * Get the effective method descriptor, which includes, if
+ * necessary, a first {@code this} parameter.
+ *
+ * @return {@code non-null;} the effective method descriptor
+ */
+ public Prototype getEffectiveDescriptor();
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/MethodList.java b/dexlib/src/main/java/com/android/dx/cf/iface/MethodList.java
new file mode 100644
index 000000000..dfa6528a6
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/MethodList.java
@@ -0,0 +1,47 @@
+/*
+ * 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.dx.cf.iface;
+
+/**
+ * Interface for lists of methods.
+ */
+public interface MethodList {
+ /**
+ * Get whether this instance is mutable. Note that the
+ * {@code MethodList} interface itself doesn't provide any means
+ * of mutation, but that doesn't mean that there isn't a non-interface
+ * way of mutating an instance.
+ *
+ * @return {@code true} iff this instance is somehow mutable
+ */
+ public boolean isMutable();
+
+ /**
+ * Get the number of methods in the list.
+ *
+ * @return the size
+ */
+ public int size();
+
+ /**
+ * Get the {@code n}th method.
+ *
+ * @param n {@code n >= 0, n < size();} which method
+ * @return {@code non-null;} the method in question
+ */
+ public Method get(int n);
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/ParseException.java b/dexlib/src/main/java/com/android/dx/cf/iface/ParseException.java
new file mode 100644
index 000000000..6ed6d3bbf
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/ParseException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dex.util.ExceptionWithContext;
+
+/**
+ * Exception from parsing.
+ */
+public class ParseException
+ extends ExceptionWithContext {
+ public ParseException(String message) {
+ super(message);
+ }
+
+ public ParseException(Throwable cause) {
+ super(cause);
+ }
+
+ public ParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/ParseObserver.java b/dexlib/src/main/java/com/android/dx/cf/iface/ParseObserver.java
new file mode 100644
index 000000000..98d5a75c2
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/ParseObserver.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.util.ByteArray;
+
+/**
+ * Observer of parsing in action. This is used to supply feedback from
+ * the various things that parse particularly to the dumping utilities.
+ */
+public interface ParseObserver {
+ /**
+ * Indicate that the level of indentation for a dump should increase
+ * or decrease (positive or negative argument, respectively).
+ *
+ * @param indentDelta the amount to change indentation
+ */
+ public void changeIndent(int indentDelta);
+
+ /**
+ * Indicate that a particular member is now being parsed.
+ *
+ * @param bytes {@code non-null;} the source that is being parsed
+ * @param offset offset into {@code bytes} for the start of the
+ * member
+ * @param name {@code non-null;} name of the member
+ * @param descriptor {@code non-null;} descriptor of the member
+ */
+ public void startParsingMember(ByteArray bytes, int offset, String name,
+ String descriptor);
+
+ /**
+ * Indicate that a particular member is no longer being parsed.
+ *
+ * @param bytes {@code non-null;} the source that was parsed
+ * @param offset offset into {@code bytes} for the end of the
+ * member
+ * @param name {@code non-null;} name of the member
+ * @param descriptor {@code non-null;} descriptor of the member
+ * @param member {@code non-null;} the actual member that was parsed
+ */
+ public void endParsingMember(ByteArray bytes, int offset, String name,
+ String descriptor, Member member);
+
+ /**
+ * Indicate that some parsing happened.
+ *
+ * @param bytes {@code non-null;} the source that was parsed
+ * @param offset offset into {@code bytes} for what was parsed
+ * @param len number of bytes parsed
+ * @param human {@code non-null;} human form for what was parsed
+ */
+ public void parsed(ByteArray bytes, int offset, int len, String human);
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdAttributeList.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdAttributeList.java
new file mode 100644
index 000000000..287b8c7f3
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdAttributeList.java
@@ -0,0 +1,104 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.util.FixedSizeList;
+
+/**
+ * Standard implementation of {@link AttributeList}, which directly stores
+ * an array of {@link Attribute} objects and can be made immutable.
+ */
+public final class StdAttributeList extends FixedSizeList
+ implements AttributeList {
+ /**
+ * Constructs an instance. All indices initially contain {@code null}.
+ *
+ * @param size the size of the list
+ */
+ public StdAttributeList(int size) {
+ super(size);
+ }
+
+ /** {@inheritDoc} */
+ public Attribute get(int n) {
+ return (Attribute) get0(n);
+ }
+
+ /** {@inheritDoc} */
+ public int byteLength() {
+ int sz = size();
+ int result = 2; // u2 attributes_count
+
+ for (int i = 0; i < sz; i++) {
+ result += get(i).byteLength();
+ }
+
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Attribute findFirst(String name) {
+ int sz = size();
+
+ for (int i = 0; i < sz; i++) {
+ Attribute att = get(i);
+ if (att.getName().equals(name)) {
+ return att;
+ }
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public Attribute findNext(Attribute attrib) {
+ int sz = size();
+ int at;
+
+ outer: {
+ for (at = 0; at < sz; at++) {
+ Attribute att = get(at);
+ if (att == attrib) {
+ break outer;
+ }
+ }
+
+ return null;
+ }
+
+ String name = attrib.getName();
+
+ for (at++; at < sz; at++) {
+ Attribute att = get(at);
+ if (att.getName().equals(name)) {
+ return att;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the attribute at the given index.
+ *
+ * @param n {@code >= 0, < size();} which attribute
+ * @param attribute {@code null-ok;} the attribute object
+ */
+ public void set(int n, Attribute attribute) {
+ set0(n, attribute);
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdField.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdField.java
new file mode 100644
index 000000000..ef9873d6f
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdField.java
@@ -0,0 +1,54 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.cf.attrib.AttConstantValue;
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.cst.TypedConstant;
+
+/**
+ * Standard implementation of {@link Field}, which directly stores
+ * all the associated data.
+ */
+public final class StdField extends StdMember implements Field {
+ /**
+ * Constructs an instance.
+ *
+ * @param definingClass {@code non-null;} the defining class
+ * @param accessFlags access flags
+ * @param nat {@code non-null;} member name and type (descriptor)
+ * @param attributes {@code non-null;} list of associated attributes
+ */
+ public StdField(CstType definingClass, int accessFlags, CstNat nat,
+ AttributeList attributes) {
+ super(definingClass, accessFlags, nat, attributes);
+ }
+
+ /** {@inheritDoc} */
+ public TypedConstant getConstantValue() {
+ AttributeList attribs = getAttributes();
+ AttConstantValue cval = (AttConstantValue)
+ attribs.findFirst(AttConstantValue.ATTRIBUTE_NAME);
+
+ if (cval == null) {
+ return null;
+ }
+
+ return cval.getConstantValue();
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdFieldList.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdFieldList.java
new file mode 100644
index 000000000..f27bd2261
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdFieldList.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.util.FixedSizeList;
+
+/**
+ * Standard implementation of {@link FieldList}, which directly stores
+ * an array of {@link Field} objects and can be made immutable.
+ */
+public final class StdFieldList extends FixedSizeList implements FieldList {
+ /**
+ * Constructs an instance. All indices initially contain {@code null}.
+ *
+ * @param size the size of the list
+ */
+ public StdFieldList(int size) {
+ super(size);
+ }
+
+ /** {@inheritDoc} */
+ public Field get(int n) {
+ return (Field) get0(n);
+ }
+
+ /**
+ * Sets the field at the given index.
+ *
+ * @param n {@code >= 0, < size();} which field
+ * @param field {@code null-ok;} the field object
+ */
+ public void set(int n, Field field) {
+ set0(n, field);
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdMember.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdMember.java
new file mode 100644
index 000000000..e67b216ec
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdMember.java
@@ -0,0 +1,110 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+
+/**
+ * Standard implementation of {@link Member}, which directly stores
+ * all the associated data.
+ */
+public abstract class StdMember implements Member {
+ /** {@code non-null;} the defining class */
+ private final CstType definingClass;
+
+ /** access flags */
+ private final int accessFlags;
+
+ /** {@code non-null;} member name and type */
+ private final CstNat nat;
+
+ /** {@code non-null;} list of associated attributes */
+ private final AttributeList attributes;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param definingClass {@code non-null;} the defining class
+ * @param accessFlags access flags
+ * @param nat {@code non-null;} member name and type (descriptor)
+ * @param attributes {@code non-null;} list of associated attributes
+ */
+ public StdMember(CstType definingClass, int accessFlags, CstNat nat,
+ AttributeList attributes) {
+ if (definingClass == null) {
+ throw new NullPointerException("definingClass == null");
+ }
+
+ if (nat == null) {
+ throw new NullPointerException("nat == null");
+ }
+
+ if (attributes == null) {
+ throw new NullPointerException("attributes == null");
+ }
+
+ this.definingClass = definingClass;
+ this.accessFlags = accessFlags;
+ this.nat = nat;
+ this.attributes = attributes;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer(100);
+
+ sb.append(getClass().getName());
+ sb.append('{');
+ sb.append(nat.toHuman());
+ sb.append('}');
+
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ public final CstType getDefiningClass() {
+ return definingClass;
+ }
+
+ /** {@inheritDoc} */
+ public final int getAccessFlags() {
+ return accessFlags;
+ }
+
+ /** {@inheritDoc} */
+ public final CstNat getNat() {
+ return nat;
+ }
+
+ /** {@inheritDoc} */
+ public final CstString getName() {
+ return nat.getName();
+ }
+
+ /** {@inheritDoc} */
+ public final CstString getDescriptor() {
+ return nat.getDescriptor();
+ }
+
+ /** {@inheritDoc} */
+ public final AttributeList getAttributes() {
+ return attributes;
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdMethod.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethod.java
new file mode 100644
index 000000000..c511d7d0a
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethod.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.rop.code.AccessFlags;
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Prototype;
+
+/**
+ * Standard implementation of {@link Method}, which directly stores
+ * all the associated data.
+ */
+public final class StdMethod extends StdMember implements Method {
+ /** {@code non-null;} the effective method descriptor */
+ private final Prototype effectiveDescriptor;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param definingClass {@code non-null;} the defining class
+ * @param accessFlags access flags
+ * @param nat {@code non-null;} member name and type (descriptor)
+ * @param attributes {@code non-null;} list of associated attributes
+ */
+ public StdMethod(CstType definingClass, int accessFlags, CstNat nat,
+ AttributeList attributes) {
+ super(definingClass, accessFlags, nat, attributes);
+
+ String descStr = getDescriptor().getString();
+ effectiveDescriptor =
+ Prototype.intern(descStr, definingClass.getClassType(),
+ AccessFlags.isStatic(accessFlags),
+ nat.isInstanceInit());
+ }
+
+ /** {@inheritDoc} */
+ public Prototype getEffectiveDescriptor() {
+ return effectiveDescriptor;
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdMethodList.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethodList.java
new file mode 100644
index 000000000..417cdeee1
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethodList.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.util.FixedSizeList;
+
+/**
+ * Standard implementation of {@link MethodList}, which directly stores
+ * an array of {@link Method} objects and can be made immutable.
+ */
+public final class StdMethodList extends FixedSizeList implements MethodList {
+ /**
+ * Constructs an instance. All indices initially contain {@code null}.
+ *
+ * @param size the size of the list
+ */
+ public StdMethodList(int size) {
+ super(size);
+ }
+
+ /** {@inheritDoc} */
+ public Method get(int n) {
+ return (Method) get0(n);
+ }
+
+ /**
+ * Sets the method at the given index.
+ *
+ * @param n {@code >= 0, < size();} which method
+ * @param method {@code null-ok;} the method object
+ */
+ public void set(int n, Method method) {
+ set0(n, method);
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/package.html b/dexlib/src/main/java/com/android/dx/cf/iface/package.html
new file mode 100644
index 000000000..c7345527d
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/package.html
@@ -0,0 +1,10 @@
+ Interfaces and base classes for dealing with class files. This package
+doesn't have any parsing but does have basic container implementations. PACKAGES USED:
+ Classes for translating Java classfiles into Dalvik classes. PACKAGES USED:
+ Note: In the unlikely event that an instruction takes
+ * absolutely no registers (e.g., a {@code nop} or a
+ * no-argument no-result static method call), then the given
+ * register list may be passed as {@link
+ * RegisterSpecList#EMPTY}. Note: In the unlikely event that an instruction takes
+ * absolutely no registers (e.g., a {@code nop} or a
+ * no-argument no-result * static method call), then the given
+ * register list may be passed as {@link
+ * RegisterSpecList#EMPTY}. Subclasses must override this method. Subclasses must override this method. Subclasses must override this method. The default implementation of this method always returns
+ * an empty BitSet. Subclasses must override this method if they
+ * have registers. The default implementation of this method always returns
+ * {@code false}. Subclasses must override this method if they
+ * include branch offsets. Subclasses must override this method. This isn't necessarily the cleanest way to achieve the
+ * goal of not representing known nulls in a locals list, but
+ * it gets the job done. Note: This method may only be called once per instance
+ * of this class. This method may also reserve a number of low-numbered
+ * registers, renumbering the instructions' original registers, in
+ * order to have register space available in which to move
+ * very-high registers when expanding instructions into
+ * multi-instruction sequences. This expansion is done when no
+ * simple instruction format can be found for a given instruction that
+ * is able to accomodate that instruction's registers. This method ignores issues of branch target size, since
+ * final addresses aren't known at the point that this method is
+ * called. Note:: This throws an exception if this item is not
+ * internable.
+ * Args: none
+ *
+ */
+ static final int DBG_END_SEQUENCE = 0x00;
+
+ /**
+ * Advances the program counter/address register without emitting
+ * a positions entry.
+ *
+ * Args:
+ *
+ *
+ * Args:
+ *
+ *
+ * Args:
+ *
+ *
+ * Args:
+ *
+ *
+ * Args:
+ *
+ *
+ * Args:
+ *
+ *
+ * The prologue_end register is cleared by any special
+ * ({@code >= OPCODE_BASE}) opcode.
+ */
+ static final int DBG_SET_PROLOGUE_END = 0x07;
+
+ /**
+ * Sets the "epilogue_begin" state machine register, indicating that the
+ * next position entry that is added should be considered the beginning of
+ * a method epilogue (an appropriate place to suspend execution before
+ * method exit).
+ *
+ * The epilogue_begin register is cleared by any special
+ * ({@code >= OPCODE_BASE}) opcode.
+ */
+ static final int DBG_SET_EPILOGUE_BEGIN = 0x08;
+
+ /**
+ * Sets the current file that that line numbers refer to. All subsequent
+ * line number entries make reference to this source file name, instead
+ * of the default name specified in code_item.
+ *
+ * Args:
+ * This is package-scope in order to allow
+ * the {@link HeaderSection} to set itself up properly. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is package-scope in order to allow the header section
+ * to query it. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is public in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance and help early counting of type ids. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is public in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance and help early counting of field ids. This is public in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance and help early counting of method ids. This is package-scope in order to allow
+ * the various {@link Item} instances to add items to the
+ * instance. This is package-scope in order to allow the header section
+ * to query it. This is package-scope in order to allow the header section
+ * to query it. Note: This compares the method constants only,
+ * ignoring any associated code, because it should never be the
+ * case that two different items with the same method constant
+ * ever appear in the same list (or same file, even). Note: This compares the method constants only,
+ * ignoring any associated code, because it should never be the
+ * case that two different items with the same method constant
+ * ever appear in the same list (or same file, even). Note: Subclasses must override this to do something
+ * appropriate. Note: Subclasses must implement this as appropriate for
+ * their contents.
+
+
diff --git a/dexlib/src/main/java/com/android/dx/cf/code/BaseMachine.java b/dexlib/src/main/java/com/android/dx/cf/code/BaseMachine.java
new file mode 100644
index 000000000..7bff2ff2f
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/code/BaseMachine.java
@@ -0,0 +1,579 @@
+/*
+ * 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.dx.cf.code;
+
+import com.android.dx.rop.code.LocalItem;
+import com.android.dx.rop.code.RegisterSpec;
+import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.type.Prototype;
+import com.android.dx.rop.type.StdTypeList;
+import com.android.dx.rop.type.Type;
+import com.android.dx.rop.type.TypeBearer;
+import java.util.ArrayList;
+
+/**
+ * Base implementation of {@link Machine}.
+ *
+ * com.android.dx.cf.iface
com.android.dx.rop.pool
com.android.dx.util
+ *
+ *
+ * @param offset {@code >= 0, < bytes.size();} offset to the start of the
+ * instruction
+ * @param visitor {@code null-ok;} visitor to call back to
+ * @return the length of the instruction, in bytes
+ */
+ public int parseInstruction(int offset, Visitor visitor) {
+ if (visitor == null) {
+ visitor = EMPTY_VISITOR;
+ }
+
+ try {
+ int opcode = bytes.getUnsignedByte(offset);
+ int info = ByteOps.opInfo(opcode);
+ int fmt = info & ByteOps.FMT_MASK;
+
+ switch (opcode) {
+ case ByteOps.NOP: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.VOID);
+ return 1;
+ }
+ case ByteOps.ACONST_NULL: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstKnownNull.THE_ONE, 0);
+ return 1;
+ }
+ case ByteOps.ICONST_M1: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_M1, -1);
+ return 1;
+ }
+ case ByteOps.ICONST_0: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_0, 0);
+ return 1;
+ }
+ case ByteOps.ICONST_1: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_1, 1);
+ return 1;
+ }
+ case ByteOps.ICONST_2: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_2, 2);
+ return 1;
+ }
+ case ByteOps.ICONST_3: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_3, 3);
+ return 1;
+ }
+ case ByteOps.ICONST_4: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_4, 4);
+ return 1;
+ }
+ case ByteOps.ICONST_5: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstInteger.VALUE_5, 5);
+ return 1;
+ }
+ case ByteOps.LCONST_0: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstLong.VALUE_0, 0);
+ return 1;
+ }
+ case ByteOps.LCONST_1: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstLong.VALUE_1, 0);
+ return 1;
+ }
+ case ByteOps.FCONST_0: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstFloat.VALUE_0, 0);
+ return 1;
+ }
+ case ByteOps.FCONST_1: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstFloat.VALUE_1, 0);
+ return 1;
+ }
+ case ByteOps.FCONST_2: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstFloat.VALUE_2, 0);
+ return 1;
+ }
+ case ByteOps.DCONST_0: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstDouble.VALUE_0, 0);
+ return 1;
+ }
+ case ByteOps.DCONST_1: {
+ visitor.visitConstant(ByteOps.LDC, offset, 1,
+ CstDouble.VALUE_1, 0);
+ return 1;
+ }
+ case ByteOps.BIPUSH: {
+ int value = bytes.getByte(offset + 1);
+ visitor.visitConstant(ByteOps.LDC, offset, 2,
+ CstInteger.make(value), value);
+ return 2;
+ }
+ case ByteOps.SIPUSH: {
+ int value = bytes.getShort(offset + 1);
+ visitor.visitConstant(ByteOps.LDC, offset, 3,
+ CstInteger.make(value), value);
+ return 3;
+ }
+ case ByteOps.LDC: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ Constant cst = pool.get(idx);
+ int value = (cst instanceof CstInteger) ?
+ ((CstInteger) cst).getValue() : 0;
+ visitor.visitConstant(ByteOps.LDC, offset, 2, cst, value);
+ return 2;
+ }
+ case ByteOps.LDC_W: {
+ int idx = bytes.getUnsignedShort(offset + 1);
+ Constant cst = pool.get(idx);
+ int value = (cst instanceof CstInteger) ?
+ ((CstInteger) cst).getValue() : 0;
+ visitor.visitConstant(ByteOps.LDC, offset, 3, cst, value);
+ return 3;
+ }
+ case ByteOps.LDC2_W: {
+ int idx = bytes.getUnsignedShort(offset + 1);
+ Constant cst = pool.get(idx);
+ visitor.visitConstant(ByteOps.LDC2_W, offset, 3, cst, 0);
+ return 3;
+ }
+ case ByteOps.ILOAD: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx,
+ Type.INT, 0);
+ return 2;
+ }
+ case ByteOps.LLOAD: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx,
+ Type.LONG, 0);
+ return 2;
+ }
+ case ByteOps.FLOAD: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx,
+ Type.FLOAT, 0);
+ return 2;
+ }
+ case ByteOps.DLOAD: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx,
+ Type.DOUBLE, 0);
+ return 2;
+ }
+ case ByteOps.ALOAD: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx,
+ Type.OBJECT, 0);
+ return 2;
+ }
+ case ByteOps.ILOAD_0:
+ case ByteOps.ILOAD_1:
+ case ByteOps.ILOAD_2:
+ case ByteOps.ILOAD_3: {
+ int idx = opcode - ByteOps.ILOAD_0;
+ visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx,
+ Type.INT, 0);
+ return 1;
+ }
+ case ByteOps.LLOAD_0:
+ case ByteOps.LLOAD_1:
+ case ByteOps.LLOAD_2:
+ case ByteOps.LLOAD_3: {
+ int idx = opcode - ByteOps.LLOAD_0;
+ visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx,
+ Type.LONG, 0);
+ return 1;
+ }
+ case ByteOps.FLOAD_0:
+ case ByteOps.FLOAD_1:
+ case ByteOps.FLOAD_2:
+ case ByteOps.FLOAD_3: {
+ int idx = opcode - ByteOps.FLOAD_0;
+ visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx,
+ Type.FLOAT, 0);
+ return 1;
+ }
+ case ByteOps.DLOAD_0:
+ case ByteOps.DLOAD_1:
+ case ByteOps.DLOAD_2:
+ case ByteOps.DLOAD_3: {
+ int idx = opcode - ByteOps.DLOAD_0;
+ visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx,
+ Type.DOUBLE, 0);
+ return 1;
+ }
+ case ByteOps.ALOAD_0:
+ case ByteOps.ALOAD_1:
+ case ByteOps.ALOAD_2:
+ case ByteOps.ALOAD_3: {
+ int idx = opcode - ByteOps.ALOAD_0;
+ visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx,
+ Type.OBJECT, 0);
+ return 1;
+ }
+ case ByteOps.IALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.INT);
+ return 1;
+ }
+ case ByteOps.LALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.LONG);
+ return 1;
+ }
+ case ByteOps.FALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1,
+ Type.FLOAT);
+ return 1;
+ }
+ case ByteOps.DALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1,
+ Type.DOUBLE);
+ return 1;
+ }
+ case ByteOps.AALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1,
+ Type.OBJECT);
+ return 1;
+ }
+ case ByteOps.BALOAD: {
+ /*
+ * Note: This is a load from either a byte[] or a
+ * boolean[].
+ */
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.BYTE);
+ return 1;
+ }
+ case ByteOps.CALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.CHAR);
+ return 1;
+ }
+ case ByteOps.SALOAD: {
+ visitor.visitNoArgs(ByteOps.IALOAD, offset, 1,
+ Type.SHORT);
+ return 1;
+ }
+ case ByteOps.ISTORE: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx,
+ Type.INT, 0);
+ return 2;
+ }
+ case ByteOps.LSTORE: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx,
+ Type.LONG, 0);
+ return 2;
+ }
+ case ByteOps.FSTORE: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx,
+ Type.FLOAT, 0);
+ return 2;
+ }
+ case ByteOps.DSTORE: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx,
+ Type.DOUBLE, 0);
+ return 2;
+ }
+ case ByteOps.ASTORE: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx,
+ Type.OBJECT, 0);
+ return 2;
+ }
+ case ByteOps.ISTORE_0:
+ case ByteOps.ISTORE_1:
+ case ByteOps.ISTORE_2:
+ case ByteOps.ISTORE_3: {
+ int idx = opcode - ByteOps.ISTORE_0;
+ visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx,
+ Type.INT, 0);
+ return 1;
+ }
+ case ByteOps.LSTORE_0:
+ case ByteOps.LSTORE_1:
+ case ByteOps.LSTORE_2:
+ case ByteOps.LSTORE_3: {
+ int idx = opcode - ByteOps.LSTORE_0;
+ visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx,
+ Type.LONG, 0);
+ return 1;
+ }
+ case ByteOps.FSTORE_0:
+ case ByteOps.FSTORE_1:
+ case ByteOps.FSTORE_2:
+ case ByteOps.FSTORE_3: {
+ int idx = opcode - ByteOps.FSTORE_0;
+ visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx,
+ Type.FLOAT, 0);
+ return 1;
+ }
+ case ByteOps.DSTORE_0:
+ case ByteOps.DSTORE_1:
+ case ByteOps.DSTORE_2:
+ case ByteOps.DSTORE_3: {
+ int idx = opcode - ByteOps.DSTORE_0;
+ visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx,
+ Type.DOUBLE, 0);
+ return 1;
+ }
+ case ByteOps.ASTORE_0:
+ case ByteOps.ASTORE_1:
+ case ByteOps.ASTORE_2:
+ case ByteOps.ASTORE_3: {
+ int idx = opcode - ByteOps.ASTORE_0;
+ visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx,
+ Type.OBJECT, 0);
+ return 1;
+ }
+ case ByteOps.IASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.INT);
+ return 1;
+ }
+ case ByteOps.LASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.LONG);
+ return 1;
+ }
+ case ByteOps.FASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.FLOAT);
+ return 1;
+ }
+ case ByteOps.DASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.DOUBLE);
+ return 1;
+ }
+ case ByteOps.AASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.OBJECT);
+ return 1;
+ }
+ case ByteOps.BASTORE: {
+ /*
+ * Note: This is a load from either a byte[] or a
+ * boolean[].
+ */
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.BYTE);
+ return 1;
+ }
+ case ByteOps.CASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.CHAR);
+ return 1;
+ }
+ case ByteOps.SASTORE: {
+ visitor.visitNoArgs(ByteOps.IASTORE, offset, 1,
+ Type.SHORT);
+ return 1;
+ }
+ case ByteOps.POP:
+ case ByteOps.POP2:
+ case ByteOps.DUP:
+ case ByteOps.DUP_X1:
+ case ByteOps.DUP_X2:
+ case ByteOps.DUP2:
+ case ByteOps.DUP2_X1:
+ case ByteOps.DUP2_X2:
+ case ByteOps.SWAP: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.VOID);
+ return 1;
+ }
+ case ByteOps.IADD:
+ case ByteOps.ISUB:
+ case ByteOps.IMUL:
+ case ByteOps.IDIV:
+ case ByteOps.IREM:
+ case ByteOps.INEG:
+ case ByteOps.ISHL:
+ case ByteOps.ISHR:
+ case ByteOps.IUSHR:
+ case ByteOps.IAND:
+ case ByteOps.IOR:
+ case ByteOps.IXOR: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.INT);
+ return 1;
+ }
+ case ByteOps.LADD:
+ case ByteOps.LSUB:
+ case ByteOps.LMUL:
+ case ByteOps.LDIV:
+ case ByteOps.LREM:
+ case ByteOps.LNEG:
+ case ByteOps.LSHL:
+ case ByteOps.LSHR:
+ case ByteOps.LUSHR:
+ case ByteOps.LAND:
+ case ByteOps.LOR:
+ case ByteOps.LXOR: {
+ /*
+ * It's "opcode - 1" because, conveniently enough, all
+ * these long ops are one past the int variants.
+ */
+ visitor.visitNoArgs(opcode - 1, offset, 1, Type.LONG);
+ return 1;
+ }
+ case ByteOps.FADD:
+ case ByteOps.FSUB:
+ case ByteOps.FMUL:
+ case ByteOps.FDIV:
+ case ByteOps.FREM:
+ case ByteOps.FNEG: {
+ /*
+ * It's "opcode - 2" because, conveniently enough, all
+ * these float ops are two past the int variants.
+ */
+ visitor.visitNoArgs(opcode - 2, offset, 1, Type.FLOAT);
+ return 1;
+ }
+ case ByteOps.DADD:
+ case ByteOps.DSUB:
+ case ByteOps.DMUL:
+ case ByteOps.DDIV:
+ case ByteOps.DREM:
+ case ByteOps.DNEG: {
+ /*
+ * It's "opcode - 3" because, conveniently enough, all
+ * these double ops are three past the int variants.
+ */
+ visitor.visitNoArgs(opcode - 3, offset, 1, Type.DOUBLE);
+ return 1;
+ }
+ case ByteOps.IINC: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ int value = bytes.getByte(offset + 2);
+ visitor.visitLocal(opcode, offset, 3, idx,
+ Type.INT, value);
+ return 3;
+ }
+ case ByteOps.I2L:
+ case ByteOps.F2L:
+ case ByteOps.D2L: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.LONG);
+ return 1;
+ }
+ case ByteOps.I2F:
+ case ByteOps.L2F:
+ case ByteOps.D2F: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.FLOAT);
+ return 1;
+ }
+ case ByteOps.I2D:
+ case ByteOps.L2D:
+ case ByteOps.F2D: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.DOUBLE);
+ return 1;
+ }
+ case ByteOps.L2I:
+ case ByteOps.F2I:
+ case ByteOps.D2I:
+ case ByteOps.I2B:
+ case ByteOps.I2C:
+ case ByteOps.I2S:
+ case ByteOps.LCMP:
+ case ByteOps.FCMPL:
+ case ByteOps.FCMPG:
+ case ByteOps.DCMPL:
+ case ByteOps.DCMPG:
+ case ByteOps.ARRAYLENGTH: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.INT);
+ return 1;
+ }
+ case ByteOps.IFEQ:
+ case ByteOps.IFNE:
+ case ByteOps.IFLT:
+ case ByteOps.IFGE:
+ case ByteOps.IFGT:
+ case ByteOps.IFLE:
+ case ByteOps.IF_ICMPEQ:
+ case ByteOps.IF_ICMPNE:
+ case ByteOps.IF_ICMPLT:
+ case ByteOps.IF_ICMPGE:
+ case ByteOps.IF_ICMPGT:
+ case ByteOps.IF_ICMPLE:
+ case ByteOps.IF_ACMPEQ:
+ case ByteOps.IF_ACMPNE:
+ case ByteOps.GOTO:
+ case ByteOps.JSR:
+ case ByteOps.IFNULL:
+ case ByteOps.IFNONNULL: {
+ int target = offset + bytes.getShort(offset + 1);
+ visitor.visitBranch(opcode, offset, 3, target);
+ return 3;
+ }
+ case ByteOps.RET: {
+ int idx = bytes.getUnsignedByte(offset + 1);
+ visitor.visitLocal(opcode, offset, 2, idx,
+ Type.RETURN_ADDRESS, 0);
+ return 2;
+ }
+ case ByteOps.TABLESWITCH: {
+ return parseTableswitch(offset, visitor);
+ }
+ case ByteOps.LOOKUPSWITCH: {
+ return parseLookupswitch(offset, visitor);
+ }
+ case ByteOps.IRETURN: {
+ visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.INT);
+ return 1;
+ }
+ case ByteOps.LRETURN: {
+ visitor.visitNoArgs(ByteOps.IRETURN, offset, 1,
+ Type.LONG);
+ return 1;
+ }
+ case ByteOps.FRETURN: {
+ visitor.visitNoArgs(ByteOps.IRETURN, offset, 1,
+ Type.FLOAT);
+ return 1;
+ }
+ case ByteOps.DRETURN: {
+ visitor.visitNoArgs(ByteOps.IRETURN, offset, 1,
+ Type.DOUBLE);
+ return 1;
+ }
+ case ByteOps.ARETURN: {
+ visitor.visitNoArgs(ByteOps.IRETURN, offset, 1,
+ Type.OBJECT);
+ return 1;
+ }
+ case ByteOps.RETURN:
+ case ByteOps.ATHROW:
+ case ByteOps.MONITORENTER:
+ case ByteOps.MONITOREXIT: {
+ visitor.visitNoArgs(opcode, offset, 1, Type.VOID);
+ return 1;
+ }
+ case ByteOps.GETSTATIC:
+ case ByteOps.PUTSTATIC:
+ case ByteOps.GETFIELD:
+ case ByteOps.PUTFIELD:
+ case ByteOps.INVOKEVIRTUAL:
+ case ByteOps.INVOKESPECIAL:
+ case ByteOps.INVOKESTATIC:
+ case ByteOps.NEW:
+ case ByteOps.ANEWARRAY:
+ case ByteOps.CHECKCAST:
+ case ByteOps.INSTANCEOF: {
+ int idx = bytes.getUnsignedShort(offset + 1);
+ Constant cst = pool.get(idx);
+ visitor.visitConstant(opcode, offset, 3, cst, 0);
+ return 3;
+ }
+ case ByteOps.INVOKEINTERFACE: {
+ int idx = bytes.getUnsignedShort(offset + 1);
+ int count = bytes.getUnsignedByte(offset + 3);
+ int expectZero = bytes.getUnsignedByte(offset + 4);
+ Constant cst = pool.get(idx);
+ visitor.visitConstant(opcode, offset, 5, cst,
+ count | (expectZero << 8));
+ return 5;
+ }
+ case ByteOps.INVOKEDYNAMIC: {
+ throw new ParseException("invokedynamic not supported");
+ }
+ case ByteOps.NEWARRAY: {
+ return parseNewarray(offset, visitor);
+ }
+ case ByteOps.WIDE: {
+ return parseWide(offset, visitor);
+ }
+ case ByteOps.MULTIANEWARRAY: {
+ int idx = bytes.getUnsignedShort(offset + 1);
+ int dimensions = bytes.getUnsignedByte(offset + 3);
+ Constant cst = pool.get(idx);
+ visitor.visitConstant(opcode, offset, 4, cst, dimensions);
+ return 4;
+ }
+ case ByteOps.GOTO_W:
+ case ByteOps.JSR_W: {
+ int target = offset + bytes.getInt(offset + 1);
+ int newop =
+ (opcode == ByteOps.GOTO_W) ? ByteOps.GOTO :
+ ByteOps.JSR;
+ visitor.visitBranch(newop, offset, 5, target);
+ return 5;
+ }
+ default: {
+ visitor.visitInvalid(opcode, offset, 1);
+ return 1;
+ }
+ }
+ } catch (SimException ex) {
+ ex.addContext("...at bytecode offset " + Hex.u4(offset));
+ throw ex;
+ } catch (RuntimeException ex) {
+ SimException se = new SimException(ex);
+ se.addContext("...at bytecode offset " + Hex.u4(offset));
+ throw se;
+ }
+ }
+
+ /**
+ * Helper to deal with {@code tableswitch}.
+ *
+ * @param offset the offset to the {@code tableswitch} opcode itself
+ * @param visitor {@code non-null;} visitor to use
+ * @return instruction length, in bytes
+ */
+ private int parseTableswitch(int offset, Visitor visitor) {
+ int at = (offset + 4) & ~3; // "at" skips the padding.
+
+ // Collect the padding.
+ int padding = 0;
+ for (int i = offset + 1; i < at; i++) {
+ padding = (padding << 8) | bytes.getUnsignedByte(i);
+ }
+
+ int defaultTarget = offset + bytes.getInt(at);
+ int low = bytes.getInt(at + 4);
+ int high = bytes.getInt(at + 8);
+ int count = high - low + 1;
+ at += 12;
+
+ if (low > high) {
+ throw new SimException("low / high inversion");
+ }
+
+ SwitchList cases = new SwitchList(count);
+ for (int i = 0; i < count; i++) {
+ int target = offset + bytes.getInt(at);
+ at += 4;
+ cases.add(low + i, target);
+ }
+ cases.setDefaultTarget(defaultTarget);
+ cases.removeSuperfluousDefaults();
+ cases.setImmutable();
+
+ int length = at - offset;
+ visitor.visitSwitch(ByteOps.LOOKUPSWITCH, offset, length, cases,
+ padding);
+
+ return length;
+ }
+
+ /**
+ * Helper to deal with {@code lookupswitch}.
+ *
+ * @param offset the offset to the {@code lookupswitch} opcode itself
+ * @param visitor {@code non-null;} visitor to use
+ * @return instruction length, in bytes
+ */
+ private int parseLookupswitch(int offset, Visitor visitor) {
+ int at = (offset + 4) & ~3; // "at" skips the padding.
+
+ // Collect the padding.
+ int padding = 0;
+ for (int i = offset + 1; i < at; i++) {
+ padding = (padding << 8) | bytes.getUnsignedByte(i);
+ }
+
+ int defaultTarget = offset + bytes.getInt(at);
+ int npairs = bytes.getInt(at + 4);
+ at += 8;
+
+ SwitchList cases = new SwitchList(npairs);
+ for (int i = 0; i < npairs; i++) {
+ int match = bytes.getInt(at);
+ int target = offset + bytes.getInt(at + 4);
+ at += 8;
+ cases.add(match, target);
+ }
+ cases.setDefaultTarget(defaultTarget);
+ cases.removeSuperfluousDefaults();
+ cases.setImmutable();
+
+ int length = at - offset;
+ visitor.visitSwitch(ByteOps.LOOKUPSWITCH, offset, length, cases,
+ padding);
+
+ return length;
+ }
+
+ /**
+ * Helper to deal with {@code newarray}.
+ *
+ * @param offset the offset to the {@code newarray} opcode itself
+ * @param visitor {@code non-null;} visitor to use
+ * @return instruction length, in bytes
+ */
+ private int parseNewarray(int offset, Visitor visitor) {
+ int value = bytes.getUnsignedByte(offset + 1);
+ CstType type;
+ switch (value) {
+ case ByteOps.NEWARRAY_BOOLEAN: {
+ type = CstType.BOOLEAN_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_CHAR: {
+ type = CstType.CHAR_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_DOUBLE: {
+ type = CstType.DOUBLE_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_FLOAT: {
+ type = CstType.FLOAT_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_BYTE: {
+ type = CstType.BYTE_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_SHORT: {
+ type = CstType.SHORT_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_INT: {
+ type = CstType.INT_ARRAY;
+ break;
+ }
+ case ByteOps.NEWARRAY_LONG: {
+ type = CstType.LONG_ARRAY;
+ break;
+ }
+ default: {
+ throw new SimException("bad newarray code " +
+ Hex.u1(value));
+ }
+ }
+
+ // Revisit the previous bytecode to find out the length of the array
+ int previousOffset = visitor.getPreviousOffset();
+ ConstantParserVisitor constantVisitor = new ConstantParserVisitor();
+ int arrayLength = 0;
+
+ /*
+ * For visitors that don't record the previous offset, -1 will be
+ * seen here
+ */
+ if (previousOffset >= 0) {
+ parseInstruction(previousOffset, constantVisitor);
+ if (constantVisitor.cst instanceof CstInteger &&
+ constantVisitor.length + previousOffset == offset) {
+ arrayLength = constantVisitor.value;
+
+ }
+ }
+
+ /*
+ * Try to match the array initialization idiom. For example, if the
+ * subsequent code is initializing an int array, we are expecting the
+ * following pattern repeatedly:
+ * dup
+ * push index
+ * push value
+ * *astore
+ *
+ * where the index value will be incrimented sequentially from 0 up.
+ */
+ int nInit = 0;
+ int curOffset = offset+2;
+ int lastOffset = curOffset;
+ ArrayList
+ *
+ *
+ * @return {@code >= 0;} an available label with the guaranty that all greater labels are
+ * also available.
+ */
+ private int getAvailableLabel() {
+ int candidate = getMinimumUnreservedLabel();
+
+ for (BasicBlock bb : result) {
+ int label = bb.getLabel();
+ if (label >= candidate) {
+ candidate = label + 1;
+ }
+ }
+
+ return candidate;
+ }
+
+ /**
+ * Gets whether the method being translated is synchronized.
+ *
+ * @return whether the method being translated is synchronized
+ */
+ private boolean isSynchronized() {
+ int accessFlags = method.getAccessFlags();
+ return (accessFlags & AccessFlags.ACC_SYNCHRONIZED) != 0;
+ }
+
+ /**
+ * Gets whether the method being translated is static.
+ *
+ * @return whether the method being translated is static
+ */
+ private boolean isStatic() {
+ int accessFlags = method.getAccessFlags();
+ return (accessFlags & AccessFlags.ACC_STATIC) != 0;
+ }
+
+ /**
+ * Gets the total number of registers used for "normal" purposes (i.e.,
+ * for the straightforward translation from the original Java).
+ *
+ * @return {@code >= 0;} the total number of registers used
+ */
+ private int getNormalRegCount() {
+ return maxLocals + method.getMaxStack();
+ }
+
+ /**
+ * Gets the register spec to use to hold the object to synchronize on,
+ * for a synchronized method.
+ *
+ * @return {@code non-null;} the register spec
+ */
+ private RegisterSpec getSynchReg() {
+ /*
+ * We use the register that is just past the deepest possible
+ * stack element, with a minimum of v1 since v0 is what's
+ * always used to hold the caught exception when unwinding. We
+ * don't need to do anything else special at this level, since
+ * later passes will merely notice the highest register used
+ * by explicit inspection.
+ */
+ int reg = getNormalRegCount();
+ return RegisterSpec.make((reg < 1) ? 1 : reg, Type.OBJECT);
+ }
+
+ /**
+ * Searches {@link #result} for a block with the given label. Returns its
+ * index if found, or returns {@code -1} if there is no such block.
+ *
+ * @param label the label to look for
+ * @return {@code >= -1;} the index for the block with the given label or
+ * {@code -1} if there is no such block
+ */
+ private int labelToResultIndex(int label) {
+ int sz = result.size();
+ for (int i = 0; i < sz; i++) {
+ BasicBlock one = result.get(i);
+ if (one.getLabel() == label) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Searches {@link #result} for a block with the given label. Returns it if
+ * found, or throws an exception if there is no such block.
+ *
+ * @param label the label to look for
+ * @return {@code non-null;} the block with the given label
+ */
+ private BasicBlock labelToBlock(int label) {
+ int idx = labelToResultIndex(label);
+
+ if (idx < 0) {
+ throw new IllegalArgumentException("no such label " +
+ Hex.u2(label));
+ }
+
+ return result.get(idx);
+ }
+
+ /**
+ * Adds a block to the output result.
+ *
+ * @param block {@code non-null;} the block to add
+ * @param subroutines {@code non-null;} subroutine label list
+ * as described in {@link Frame#getSubroutines}
+ */
+ private void addBlock(BasicBlock block, IntList subroutines) {
+ if (block == null) {
+ throw new NullPointerException("block == null");
+ }
+
+ result.add(block);
+ subroutines.throwIfMutable();
+ resultSubroutines.add(subroutines);
+ }
+
+ /**
+ * Adds or replace a block in the output result. If this is a
+ * replacement, then any extra blocks that got added with the
+ * original get removed as a result of calling this method.
+ *
+ * @param block {@code non-null;} the block to add or replace
+ * @param subroutines {@code non-null;} subroutine label list
+ * as described in {@link Frame#getSubroutines}
+ * @return {@code true} if the block was replaced or
+ * {@code false} if it was added for the first time
+ */
+ private boolean addOrReplaceBlock(BasicBlock block, IntList subroutines) {
+ if (block == null) {
+ throw new NullPointerException("block == null");
+ }
+
+ int idx = labelToResultIndex(block.getLabel());
+ boolean ret;
+
+ if (idx < 0) {
+ ret = false;
+ } else {
+ /*
+ * We are replacing a pre-existing block, so find any
+ * blocks that got added as part of the original and
+ * remove those too. Such blocks are (possibly indirect)
+ * successors of this block which are out of the range of
+ * normally-translated blocks.
+ */
+ removeBlockAndSpecialSuccessors(idx);
+ ret = true;
+ }
+
+ result.add(block);
+ subroutines.throwIfMutable();
+ resultSubroutines.add(subroutines);
+ return ret;
+ }
+
+ /**
+ * Adds or replaces a block in the output result. Do not delete
+ * any successors.
+ *
+ * @param block {@code non-null;} the block to add or replace
+ * @param subroutines {@code non-null;} subroutine label list
+ * as described in {@link Frame#getSubroutines}
+ * @return {@code true} if the block was replaced or
+ * {@code false} if it was added for the first time
+ */
+ private boolean addOrReplaceBlockNoDelete(BasicBlock block,
+ IntList subroutines) {
+ if (block == null) {
+ throw new NullPointerException("block == null");
+ }
+
+ int idx = labelToResultIndex(block.getLabel());
+ boolean ret;
+
+ if (idx < 0) {
+ ret = false;
+ } else {
+ result.remove(idx);
+ resultSubroutines.remove(idx);
+ ret = true;
+ }
+
+ result.add(block);
+ subroutines.throwIfMutable();
+ resultSubroutines.add(subroutines);
+ return ret;
+ }
+
+ /**
+ * Helper for {@link #addOrReplaceBlock} which recursively removes
+ * the given block and all blocks that are (direct and indirect)
+ * successors of it whose labels indicate that they are not in the
+ * normally-translated range.
+ *
+ * @param idx {@code non-null;} block to remove (etc.)
+ */
+ private void removeBlockAndSpecialSuccessors(int idx) {
+ int minLabel = getMinimumUnreservedLabel();
+ BasicBlock block = result.get(idx);
+ IntList successors = block.getSuccessors();
+ int sz = successors.size();
+
+ result.remove(idx);
+ resultSubroutines.remove(idx);
+
+ for (int i = 0; i < sz; i++) {
+ int label = successors.get(i);
+ if (label >= minLabel) {
+ idx = labelToResultIndex(label);
+ if (idx < 0) {
+ throw new RuntimeException("Invalid label "
+ + Hex.u2(label));
+ }
+ removeBlockAndSpecialSuccessors(idx);
+ }
+ }
+ }
+
+ /**
+ * Extracts the resulting {@link RopMethod} from the instance.
+ *
+ * @return {@code non-null;} the method object
+ */
+ private RopMethod getRopMethod() {
+
+ // Construct the final list of blocks.
+
+ int sz = result.size();
+ BasicBlockList bbl = new BasicBlockList(sz);
+ for (int i = 0; i < sz; i++) {
+ bbl.set(i, result.get(i));
+ }
+ bbl.setImmutable();
+
+ // Construct the method object to wrap it all up.
+
+ /*
+ * Note: The parameter assignment block is always the first
+ * that should be executed, hence the second argument to the
+ * constructor.
+ */
+ return new RopMethod(bbl, getSpecialLabel(PARAM_ASSIGNMENT));
+ }
+
+ /**
+ * Does the conversion.
+ */
+ private void doit() {
+ int[] workSet = Bits.makeBitSet(maxLabel);
+
+ Bits.set(workSet, 0);
+ addSetupBlocks();
+ setFirstFrame();
+
+ for (;;) {
+ int offset = Bits.findFirst(workSet, 0);
+ if (offset < 0) {
+ break;
+ }
+ Bits.clear(workSet, offset);
+ ByteBlock block = blocks.labelToBlock(offset);
+ Frame frame = startFrames[offset];
+ try {
+ processBlock(block, frame, workSet);
+ } catch (SimException ex) {
+ ex.addContext("...while working on block " + Hex.u2(offset));
+ throw ex;
+ }
+ }
+
+ addReturnBlock();
+ addSynchExceptionHandlerBlock();
+ addExceptionSetupBlocks();
+
+ if (hasSubroutines) {
+ // Subroutines are very rare, so skip this step if it's n/a
+ inlineSubroutines();
+ }
+ }
+
+ /**
+ * Sets up the first frame to contain all the incoming parameters in
+ * locals.
+ */
+ private void setFirstFrame() {
+ Prototype desc = method.getEffectiveDescriptor();
+ startFrames[0].initializeWithParameters(desc.getParameterTypes());
+ startFrames[0].setImmutable();
+ }
+
+ /**
+ * Processes the given block.
+ *
+ * @param block {@code non-null;} block to process
+ * @param frame {@code non-null;} start frame for the block
+ * @param workSet {@code non-null;} bits representing work to do,
+ * which this method may add to
+ */
+ private void processBlock(ByteBlock block, Frame frame, int[] workSet) {
+ // Prepare the list of caught exceptions for this block.
+ ByteCatchList catches = block.getCatches();
+ machine.startBlock(catches.toRopCatchList());
+
+ /*
+ * Using a copy of the given frame, simulate each instruction,
+ * calling into machine for each.
+ */
+ frame = frame.copy();
+ sim.simulate(block, frame);
+ frame.setImmutable();
+
+ int extraBlockCount = machine.getExtraBlockCount();
+ ArrayListbyte[]
s, boolean[]
s, and
+ * known-nulls.byte[]
and boolean[]
are
+ * undifferentiated, and we aim here to return whichever one was
+ * actually present on the stack.boolean[]
might get translated surprisingly -- but
+ * correctly -- into an instruction that specifies a
+ * byte[]
. It will be correct, because should the
+ * code actually execute, it will necessarily throw a
+ * NullPointerException
, and it won't matter what
+ * opcode variant is used to achieve that result.
+
+
diff --git a/dexlib/src/main/java/com/android/dx/cf/cst/ConstantPoolParser.java b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantPoolParser.java
new file mode 100644
index 000000000..147edf3bb
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantPoolParser.java
@@ -0,0 +1,428 @@
+/*
+ * 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.dx.cf.cst;
+
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Class;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Double;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Fieldref;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Float;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Integer;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InterfaceMethodref;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Long;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Methodref;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_NameAndType;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_String;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Utf8;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodHandle;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodType;
+import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InvokeDynamic;
+import com.android.dx.cf.iface.ParseException;
+import com.android.dx.cf.iface.ParseObserver;
+import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.CstDouble;
+import com.android.dx.rop.cst.CstFieldRef;
+import com.android.dx.rop.cst.CstFloat;
+import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstInterfaceMethodRef;
+import com.android.dx.rop.cst.CstInvokeDynamic;
+import com.android.dx.rop.cst.CstLong;
+import com.android.dx.rop.cst.CstMethodHandle;
+import com.android.dx.rop.cst.CstMethodRef;
+import com.android.dx.rop.cst.CstMethodType;
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.cst.StdConstantPool;
+import com.android.dx.rop.type.Type;
+import com.android.dx.util.ByteArray;
+import com.android.dx.util.Hex;
+import java.util.BitSet;
+
+/**
+ * Parser for a constant pool embedded in a class file.
+ */
+public final class ConstantPoolParser {
+ /** {@code non-null;} the bytes of the constant pool */
+ private final ByteArray bytes;
+
+ /** {@code non-null;} actual parsed constant pool contents */
+ private final StdConstantPool pool;
+
+ /** {@code non-null;} byte offsets to each cst */
+ private final int[] offsets;
+
+ /**
+ * -1 || >= 10; the end offset of this constant pool in the
+ * {@code byte[]} which it came from or {@code -1} if not
+ * yet parsed
+ */
+ private int endOffset;
+
+ /** {@code null-ok;} parse observer, if any */
+ private ParseObserver observer;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param bytes {@code non-null;} the bytes of the file
+ */
+ public ConstantPoolParser(ByteArray bytes) {
+ int size = bytes.getUnsignedShort(8); // constant_pool_count
+
+ this.bytes = bytes;
+ this.pool = new StdConstantPool(size);
+ this.offsets = new int[size];
+ this.endOffset = -1;
+ }
+
+ /**
+ * Sets the parse observer for this instance.
+ *
+ * @param observer {@code null-ok;} the observer
+ */
+ public void setObserver(ParseObserver observer) {
+ this.observer = observer;
+ }
+
+ /**
+ * Gets the end offset of this constant pool in the {@code byte[]}
+ * which it came from.
+ *
+ * @return {@code >= 10;} the end offset
+ */
+ public int getEndOffset() {
+ parseIfNecessary();
+ return endOffset;
+ }
+
+ /**
+ * Gets the actual constant pool.
+ *
+ * @return {@code non-null;} the constant pool
+ */
+ public StdConstantPool getPool() {
+ parseIfNecessary();
+ return pool;
+ }
+
+ /**
+ * Runs {@link #parse} if it has not yet been run successfully.
+ */
+ private void parseIfNecessary() {
+ if (endOffset < 0) {
+ parse();
+ }
+ }
+
+ /**
+ * Does the actual parsing.
+ */
+ private void parse() {
+ determineOffsets();
+
+ if (observer != null) {
+ observer.parsed(bytes, 8, 2,
+ "constant_pool_count: " + Hex.u2(offsets.length));
+ observer.parsed(bytes, 10, 0, "\nconstant_pool:");
+ observer.changeIndent(1);
+ }
+
+ /*
+ * Track the constant value's original string type. True if constants[i] was
+ * a CONSTANT_Utf8, false for any other type including CONSTANT_string.
+ */
+ BitSet wasUtf8 = new BitSet(offsets.length);
+
+ for (int i = 1; i < offsets.length; i++) {
+ int offset = offsets[i];
+ if ((offset != 0) && (pool.getOrNull(i) == null)) {
+ parse0(i, wasUtf8);
+ }
+ }
+
+ if (observer != null) {
+ for (int i = 1; i < offsets.length; i++) {
+ Constant cst = pool.getOrNull(i);
+ if (cst == null) {
+ continue;
+ }
+ int offset = offsets[i];
+ int nextOffset = endOffset;
+ for (int j = i + 1; j < offsets.length; j++) {
+ int off = offsets[j];
+ if (off != 0) {
+ nextOffset = off;
+ break;
+ }
+ }
+ String human = wasUtf8.get(i)
+ ? Hex.u2(i) + ": utf8{\"" + cst.toHuman() + "\"}"
+ : Hex.u2(i) + ": " + cst.toString();
+ observer.parsed(bytes, offset, nextOffset - offset, human);
+ }
+
+ observer.changeIndent(-1);
+ observer.parsed(bytes, endOffset, 0, "end constant_pool");
+ }
+ }
+
+ /**
+ * Populates {@link #offsets} and also completely parse utf8 constants.
+ */
+ private void determineOffsets() {
+ int at = 10; // offset from the start of the file to the first cst
+ int lastCategory;
+
+ for (int i = 1; i < offsets.length; i += lastCategory) {
+ offsets[i] = at;
+ int tag = bytes.getUnsignedByte(at);
+ try {
+ switch (tag) {
+ case CONSTANT_Integer:
+ case CONSTANT_Float:
+ case CONSTANT_Fieldref:
+ case CONSTANT_Methodref:
+ case CONSTANT_InterfaceMethodref:
+ case CONSTANT_NameAndType: {
+ lastCategory = 1;
+ at += 5;
+ break;
+ }
+ case CONSTANT_Long:
+ case CONSTANT_Double: {
+ lastCategory = 2;
+ at += 9;
+ break;
+ }
+ case CONSTANT_Class:
+ case CONSTANT_String: {
+ lastCategory = 1;
+ at += 3;
+ break;
+ }
+ case CONSTANT_Utf8: {
+ lastCategory = 1;
+ at += bytes.getUnsignedShort(at + 1) + 3;
+ break;
+ }
+ case CONSTANT_MethodHandle: {
+ lastCategory = 1;
+ at += 4;
+ break;
+ }
+ case CONSTANT_MethodType: {
+ lastCategory = 1;
+ at += 3;
+ break;
+ }
+ case CONSTANT_InvokeDynamic: {
+ lastCategory = 1;
+ at += 5;
+ break;
+ }
+ default: {
+ throw new ParseException("unknown tag byte: " + Hex.u1(tag));
+ }
+ }
+ } catch (ParseException ex) {
+ ex.addContext("...while preparsing cst " + Hex.u2(i) + " at offset " + Hex.u4(at));
+ throw ex;
+ }
+ }
+
+ endOffset = at;
+ }
+
+ /**
+ * Parses the constant for the given index if it hasn't already been
+ * parsed, also storing it in the constant pool. This will also
+ * have the side effect of parsing any entries the indicated one
+ * depends on.
+ *
+ * @param idx which constant
+ * @return {@code non-null;} the parsed constant
+ */
+ private Constant parse0(int idx, BitSet wasUtf8) {
+ Constant cst = pool.getOrNull(idx);
+ if (cst != null) {
+ return cst;
+ }
+
+ int at = offsets[idx];
+
+ try {
+ int tag = bytes.getUnsignedByte(at);
+ switch (tag) {
+ case CONSTANT_Utf8: {
+ cst = parseUtf8(at);
+ wasUtf8.set(idx);
+ break;
+ }
+ case CONSTANT_Integer: {
+ int value = bytes.getInt(at + 1);
+ cst = CstInteger.make(value);
+ break;
+ }
+ case CONSTANT_Float: {
+ int bits = bytes.getInt(at + 1);
+ cst = CstFloat.make(bits);
+ break;
+ }
+ case CONSTANT_Long: {
+ long value = bytes.getLong(at + 1);
+ cst = CstLong.make(value);
+ break;
+ }
+ case CONSTANT_Double: {
+ long bits = bytes.getLong(at + 1);
+ cst = CstDouble.make(bits);
+ break;
+ }
+ case CONSTANT_Class: {
+ int nameIndex = bytes.getUnsignedShort(at + 1);
+ CstString name = (CstString) parse0(nameIndex, wasUtf8);
+ cst = new CstType(Type.internClassName(name.getString()));
+ break;
+ }
+ case CONSTANT_String: {
+ int stringIndex = bytes.getUnsignedShort(at + 1);
+ cst = parse0(stringIndex, wasUtf8);
+ break;
+ }
+ case CONSTANT_Fieldref: {
+ int classIndex = bytes.getUnsignedShort(at + 1);
+ CstType type = (CstType) parse0(classIndex, wasUtf8);
+ int natIndex = bytes.getUnsignedShort(at + 3);
+ CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
+ cst = new CstFieldRef(type, nat);
+ break;
+ }
+ case CONSTANT_Methodref: {
+ int classIndex = bytes.getUnsignedShort(at + 1);
+ CstType type = (CstType) parse0(classIndex, wasUtf8);
+ int natIndex = bytes.getUnsignedShort(at + 3);
+ CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
+ cst = new CstMethodRef(type, nat);
+ break;
+ }
+ case CONSTANT_InterfaceMethodref: {
+ int classIndex = bytes.getUnsignedShort(at + 1);
+ CstType type = (CstType) parse0(classIndex, wasUtf8);
+ int natIndex = bytes.getUnsignedShort(at + 3);
+ CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
+ cst = new CstInterfaceMethodRef(type, nat);
+ break;
+ }
+ case CONSTANT_NameAndType: {
+ int nameIndex = bytes.getUnsignedShort(at + 1);
+ CstString name = (CstString) parse0(nameIndex, wasUtf8);
+ int descriptorIndex = bytes.getUnsignedShort(at + 3);
+ CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8);
+ cst = new CstNat(name, descriptor);
+ break;
+ }
+ case CONSTANT_MethodHandle: {
+ int kind = bytes.getUnsignedByte(at + 1);
+ int constantIndex = bytes.getUnsignedShort(at + 2);
+ Constant ref;
+ switch (kind) {
+ case CstMethodHandle.KIND_GETFIELD:
+ case CstMethodHandle.KIND_GETSTATIC:
+ case CstMethodHandle.KIND_PUTFIELD:
+ case CstMethodHandle.KIND_PUTSTATIC:
+ CstFieldRef field = (CstFieldRef) parse0(constantIndex, wasUtf8);
+ ref = field;
+ break;
+ case CstMethodHandle.KIND_INVOKEVIRTUAL:
+ case CstMethodHandle.KIND_NEWINVOKESPECIAL:
+ CstMethodRef method = (CstMethodRef) parse0(constantIndex, wasUtf8);
+ ref = method;
+ break;
+ case CstMethodHandle.KIND_INVOKESTATIC:
+ case CstMethodHandle.KIND_INVOKESPECIAL:
+ ref = parse0(constantIndex, wasUtf8);
+ if (!(ref instanceof CstMethodRef
+ || ref instanceof CstInterfaceMethodRef)) {
+ throw new ParseException(
+ "Unsupported ref constant type for MethodHandle "
+ + ref.getClass());
+ }
+ break;
+ case CstMethodHandle.KIND_INVOKEINTERFACE:
+ CstInterfaceMethodRef interfaceMethod =
+ (CstInterfaceMethodRef) parse0(constantIndex, wasUtf8);
+ ref = interfaceMethod;
+ break;
+ default:
+ throw new ParseException("Unsupported MethodHandle kind: " + kind);
+ }
+ cst = CstMethodHandle.make(kind, ref);
+ break;
+ }
+ case CONSTANT_MethodType: {
+ int descriptorIndex = bytes.getUnsignedShort(at + 1);
+ CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8);
+ cst = CstMethodType.make(descriptor);
+ break;
+ }
+ case CONSTANT_InvokeDynamic: {
+ int bootstrapMethodIndex = bytes.getUnsignedShort(at + 1);
+ int natIndex = bytes.getUnsignedShort(at + 3);
+ CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
+ cst = CstInvokeDynamic.make(bootstrapMethodIndex, nat);
+ break;
+ }
+ default: {
+ throw new ParseException("unknown tag byte: " + Hex.u1(tag));
+ }
+ }
+ } catch (ParseException ex) {
+ ex.addContext("...while parsing cst " + Hex.u2(idx) +
+ " at offset " + Hex.u4(at));
+ throw ex;
+ } catch (RuntimeException ex) {
+ ParseException pe = new ParseException(ex);
+ pe.addContext("...while parsing cst " + Hex.u2(idx) +
+ " at offset " + Hex.u4(at));
+ throw pe;
+ }
+
+ pool.set(idx, cst);
+ return cst;
+ }
+
+ /**
+ * Parses a utf8 constant.
+ *
+ * @param at offset to the start of the constant (where the tag byte is)
+ * @return {@code non-null;} the parsed value
+ */
+ private CstString parseUtf8(int at) {
+ int length = bytes.getUnsignedShort(at + 1);
+
+ at += 3; // Skip to the data.
+
+ ByteArray ubytes = bytes.slice(at, at + length);
+
+ try {
+ return new CstString(ubytes);
+ } catch (IllegalArgumentException ex) {
+ // Translate the exception
+ throw new ParseException(ex);
+ }
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/cst/ConstantTags.java b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantTags.java
new file mode 100644
index 000000000..56ef4d75f
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantTags.java
@@ -0,0 +1,64 @@
+/*
+ * 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.dx.cf.cst;
+
+/**
+ * Tags for constant pool constants.
+ */
+public interface ConstantTags {
+ /** tag for a {@code CONSTANT_Utf8_info} */
+ int CONSTANT_Utf8 = 1;
+
+ /** tag for a {@code CONSTANT_Integer_info} */
+ int CONSTANT_Integer = 3;
+
+ /** tag for a {@code CONSTANT_Float_info} */
+ int CONSTANT_Float = 4;
+
+ /** tag for a {@code CONSTANT_Long_info} */
+ int CONSTANT_Long = 5;
+
+ /** tag for a {@code CONSTANT_Double_info} */
+ int CONSTANT_Double = 6;
+
+ /** tag for a {@code CONSTANT_Class_info} */
+ int CONSTANT_Class = 7;
+
+ /** tag for a {@code CONSTANT_String_info} */
+ int CONSTANT_String = 8;
+
+ /** tag for a {@code CONSTANT_Fieldref_info} */
+ int CONSTANT_Fieldref = 9;
+
+ /** tag for a {@code CONSTANT_Methodref_info} */
+ int CONSTANT_Methodref = 10;
+
+ /** tag for a {@code CONSTANT_InterfaceMethodref_info} */
+ int CONSTANT_InterfaceMethodref = 11;
+
+ /** tag for a {@code CONSTANT_NameAndType_info} */
+ int CONSTANT_NameAndType = 12;
+
+ /** tag for a {@code CONSTANT_MethodHandle} */
+ int CONSTANT_MethodHandle = 15;
+
+ /** tag for a {@code CONSTANT_MethodType} */
+ int CONSTANT_MethodType = 16;
+
+ /** tag for a {@code CONSTANT_InvokeDynamic} */
+ int CONSTANT_InvokeDynamic = 18;
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/AnnotationParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/AnnotationParser.java
new file mode 100644
index 000000000..f76669445
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/direct/AnnotationParser.java
@@ -0,0 +1,470 @@
+/*
+ * 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.dx.cf.direct;
+
+import com.android.dx.cf.iface.ParseException;
+import com.android.dx.cf.iface.ParseObserver;
+import com.android.dx.rop.annotation.Annotation;
+import com.android.dx.rop.annotation.AnnotationVisibility;
+import com.android.dx.rop.annotation.Annotations;
+import com.android.dx.rop.annotation.AnnotationsList;
+import com.android.dx.rop.annotation.NameValuePair;
+import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstAnnotation;
+import com.android.dx.rop.cst.CstArray;
+import com.android.dx.rop.cst.CstBoolean;
+import com.android.dx.rop.cst.CstByte;
+import com.android.dx.rop.cst.CstChar;
+import com.android.dx.rop.cst.CstDouble;
+import com.android.dx.rop.cst.CstEnumRef;
+import com.android.dx.rop.cst.CstFloat;
+import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstLong;
+import com.android.dx.rop.cst.CstNat;
+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.rop.type.Type;
+import com.android.dx.util.ByteArray;
+import com.android.dx.util.Hex;
+import java.io.IOException;
+
+/**
+ * Parser for annotations.
+ */
+public final class AnnotationParser {
+ /** {@code non-null;} class file being parsed */
+ private final DirectClassFile cf;
+
+ /** {@code non-null;} constant pool to use */
+ private final ConstantPool pool;
+
+ /** {@code non-null;} bytes of the attribute data */
+ private final ByteArray bytes;
+
+ /** {@code null-ok;} parse observer, if any */
+ private final ParseObserver observer;
+
+ /** {@code non-null;} input stream to parse from */
+ private final ByteArray.MyDataInputStream input;
+
+ /**
+ * {@code non-null;} cursor for use when informing the observer of what
+ * was parsed
+ */
+ private int parseCursor;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param cf {@code non-null;} class file to parse from
+ * @param offset {@code >= 0;} offset into the class file data to parse at
+ * @param length {@code >= 0;} number of bytes left in the attribute data
+ * @param observer {@code null-ok;} parse observer to notify, if any
+ */
+ public AnnotationParser(DirectClassFile cf, int offset, int length,
+ ParseObserver observer) {
+ if (cf == null) {
+ throw new NullPointerException("cf == null");
+ }
+
+ this.cf = cf;
+ this.pool = cf.getConstantPool();
+ this.observer = observer;
+ this.bytes = cf.getBytes().slice(offset, offset + length);
+ this.input = bytes.makeDataInputStream();
+ this.parseCursor = 0;
+ }
+
+ /**
+ * Parses an annotation value ({@code element_value}) attribute.
+ *
+ * @return {@code non-null;} the parsed constant value
+ */
+ public Constant parseValueAttribute() {
+ Constant result;
+
+ try {
+ result = parseValue();
+
+ if (input.available() != 0) {
+ throw new ParseException("extra data in attribute");
+ }
+ } catch (IOException ex) {
+ // ByteArray.MyDataInputStream should never throw.
+ throw new RuntimeException("shouldn't happen", ex);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parses a parameter annotation attribute.
+ *
+ * @param visibility {@code non-null;} visibility of the parsed annotations
+ * @return {@code non-null;} the parsed list of lists of annotations
+ */
+ public AnnotationsList parseParameterAttribute(
+ AnnotationVisibility visibility) {
+ AnnotationsList result;
+
+ try {
+ result = parseAnnotationsList(visibility);
+
+ if (input.available() != 0) {
+ throw new ParseException("extra data in attribute");
+ }
+ } catch (IOException ex) {
+ // ByteArray.MyDataInputStream should never throw.
+ throw new RuntimeException("shouldn't happen", ex);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parses an annotation attribute, per se.
+ *
+ * @param visibility {@code non-null;} visibility of the parsed annotations
+ * @return {@code non-null;} the list of annotations read from the attribute
+ * data
+ */
+ public Annotations parseAnnotationAttribute(
+ AnnotationVisibility visibility) {
+ Annotations result;
+
+ try {
+ result = parseAnnotations(visibility);
+
+ if (input.available() != 0) {
+ throw new ParseException("extra data in attribute");
+ }
+ } catch (IOException ex) {
+ // ByteArray.MyDataInputStream should never throw.
+ throw new RuntimeException("shouldn't happen", ex);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parses a list of annotation lists.
+ *
+ * @param visibility {@code non-null;} visibility of the parsed annotations
+ * @return {@code non-null;} the list of annotation lists read from the attribute
+ * data
+ */
+ private AnnotationsList parseAnnotationsList(
+ AnnotationVisibility visibility) throws IOException {
+ int count = input.readUnsignedByte();
+
+ if (observer != null) {
+ parsed(1, "num_parameters: " + Hex.u1(count));
+ }
+
+ AnnotationsList outerList = new AnnotationsList(count);
+
+ for (int i = 0; i < count; i++) {
+ if (observer != null) {
+ parsed(0, "parameter_annotations[" + i + "]:");
+ changeIndent(1);
+ }
+
+ Annotations annotations = parseAnnotations(visibility);
+ outerList.set(i, annotations);
+
+ if (observer != null) {
+ observer.changeIndent(-1);
+ }
+ }
+
+ outerList.setImmutable();
+ return outerList;
+ }
+
+ /**
+ * Parses an annotation list.
+ *
+ * @param visibility {@code non-null;} visibility of the parsed annotations
+ * @return {@code non-null;} the list of annotations read from the attribute
+ * data
+ */
+ private Annotations parseAnnotations(AnnotationVisibility visibility)
+ throws IOException {
+ int count = input.readUnsignedShort();
+
+ if (observer != null) {
+ parsed(2, "num_annotations: " + Hex.u2(count));
+ }
+
+ Annotations annotations = new Annotations();
+
+ for (int i = 0; i < count; i++) {
+ if (observer != null) {
+ parsed(0, "annotations[" + i + "]:");
+ changeIndent(1);
+ }
+
+ Annotation annotation = parseAnnotation(visibility);
+ annotations.add(annotation);
+
+ if (observer != null) {
+ observer.changeIndent(-1);
+ }
+ }
+
+ annotations.setImmutable();
+ return annotations;
+ }
+
+ /**
+ * Parses a single annotation.
+ *
+ * @param visibility {@code non-null;} visibility of the parsed annotation
+ * @return {@code non-null;} the parsed annotation
+ */
+ private Annotation parseAnnotation(AnnotationVisibility visibility)
+ throws IOException {
+ requireLength(4);
+
+ int typeIndex = input.readUnsignedShort();
+ int numElements = input.readUnsignedShort();
+ CstString typeString = (CstString) pool.get(typeIndex);
+ CstType type = new CstType(Type.intern(typeString.getString()));
+
+ if (observer != null) {
+ parsed(2, "type: " + type.toHuman());
+ parsed(2, "num_elements: " + numElements);
+ }
+
+ Annotation annotation = new Annotation(type, visibility);
+
+ for (int i = 0; i < numElements; i++) {
+ if (observer != null) {
+ parsed(0, "elements[" + i + "]:");
+ changeIndent(1);
+ }
+
+ NameValuePair element = parseElement();
+ annotation.add(element);
+
+ if (observer != null) {
+ changeIndent(-1);
+ }
+ }
+
+ annotation.setImmutable();
+ return annotation;
+ }
+
+ /**
+ * Parses a {@link NameValuePair}.
+ *
+ * @return {@code non-null;} the parsed element
+ */
+ private NameValuePair parseElement() throws IOException {
+ requireLength(5);
+
+ int elementNameIndex = input.readUnsignedShort();
+ CstString elementName = (CstString) pool.get(elementNameIndex);
+
+ if (observer != null) {
+ parsed(2, "element_name: " + elementName.toHuman());
+ parsed(0, "value: ");
+ changeIndent(1);
+ }
+
+ Constant value = parseValue();
+
+ if (observer != null) {
+ changeIndent(-1);
+ }
+
+ return new NameValuePair(elementName, value);
+ }
+
+ /**
+ * Parses an annotation value.
+ *
+ * @return {@code non-null;} the parsed value
+ */
+ private Constant parseValue() throws IOException {
+ int tag = input.readUnsignedByte();
+
+ if (observer != null) {
+ CstString humanTag = new CstString(Character.toString((char) tag));
+ parsed(1, "tag: " + humanTag.toQuoted());
+ }
+
+ switch (tag) {
+ case 'B': {
+ CstInteger value = (CstInteger) parseConstant();
+ return CstByte.make(value.getValue());
+ }
+ case 'C': {
+ CstInteger value = (CstInteger) parseConstant();
+ int intValue = value.getValue();
+ return CstChar.make(value.getValue());
+ }
+ case 'D': {
+ CstDouble value = (CstDouble) parseConstant();
+ return value;
+ }
+ case 'F': {
+ CstFloat value = (CstFloat) parseConstant();
+ return value;
+ }
+ case 'I': {
+ CstInteger value = (CstInteger) parseConstant();
+ return value;
+ }
+ case 'J': {
+ CstLong value = (CstLong) parseConstant();
+ return value;
+ }
+ case 'S': {
+ CstInteger value = (CstInteger) parseConstant();
+ return CstShort.make(value.getValue());
+ }
+ case 'Z': {
+ CstInteger value = (CstInteger) parseConstant();
+ return CstBoolean.make(value.getValue());
+ }
+ case 'c': {
+ int classInfoIndex = input.readUnsignedShort();
+ CstString value = (CstString) pool.get(classInfoIndex);
+ Type type = Type.internReturnType(value.getString());
+
+ if (observer != null) {
+ parsed(2, "class_info: " + type.toHuman());
+ }
+
+ return new CstType(type);
+ }
+ case 's': {
+ return parseConstant();
+ }
+ case 'e': {
+ requireLength(4);
+
+ int typeNameIndex = input.readUnsignedShort();
+ int constNameIndex = input.readUnsignedShort();
+ CstString typeName = (CstString) pool.get(typeNameIndex);
+ CstString constName = (CstString) pool.get(constNameIndex);
+
+ if (observer != null) {
+ parsed(2, "type_name: " + typeName.toHuman());
+ parsed(2, "const_name: " + constName.toHuman());
+ }
+
+ return new CstEnumRef(new CstNat(constName, typeName));
+ }
+ case '@': {
+ Annotation annotation =
+ parseAnnotation(AnnotationVisibility.EMBEDDED);
+ return new CstAnnotation(annotation);
+ }
+ case '[': {
+ requireLength(2);
+
+ int numValues = input.readUnsignedShort();
+ CstArray.List list = new CstArray.List(numValues);
+
+ if (observer != null) {
+ parsed(2, "num_values: " + numValues);
+ changeIndent(1);
+ }
+
+ for (int i = 0; i < numValues; i++) {
+ if (observer != null) {
+ changeIndent(-1);
+ parsed(0, "element_value[" + i + "]:");
+ changeIndent(1);
+ }
+ list.set(i, parseValue());
+ }
+
+ if (observer != null) {
+ changeIndent(-1);
+ }
+
+ list.setImmutable();
+ return new CstArray(list);
+ }
+ default: {
+ throw new ParseException("unknown annotation tag: " +
+ Hex.u1(tag));
+ }
+ }
+ }
+
+ /**
+ * Helper for {@link #parseValue}, which parses a constant reference
+ * and returns the referred-to constant value.
+ *
+ * @return {@code non-null;} the parsed value
+ */
+ private Constant parseConstant() throws IOException {
+ int constValueIndex = input.readUnsignedShort();
+ Constant value = (Constant) pool.get(constValueIndex);
+
+ if (observer != null) {
+ String human = (value instanceof CstString)
+ ? ((CstString) value).toQuoted()
+ : value.toHuman();
+ parsed(2, "constant_value: " + human);
+ }
+
+ return value;
+ }
+
+ /**
+ * Helper which will throw an exception if the given number of bytes
+ * is not available to be read.
+ *
+ * @param requiredLength the number of required bytes
+ */
+ private void requireLength(int requiredLength) throws IOException {
+ if (input.available() < requiredLength) {
+ throw new ParseException("truncated annotation attribute");
+ }
+ }
+
+ /**
+ * Helper which indicates that some bytes were just parsed. This should
+ * only be used (for efficiency sake) if the parse is known to be
+ * observed.
+ *
+ * @param length {@code >= 0;} number of bytes parsed
+ * @param message {@code non-null;} associated message
+ */
+ private void parsed(int length, String message) {
+ observer.parsed(bytes, parseCursor, length, message);
+ parseCursor += length;
+ }
+
+ /**
+ * Convenience wrapper that simply calls through to
+ * {@code observer.changeIndent()}.
+ *
+ * @param indent the amount to change the indent by
+ */
+ private void changeIndent(int indent) {
+ observer.changeIndent(indent);
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/AttributeFactory.java b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeFactory.java
new file mode 100644
index 000000000..f7486eb9c
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeFactory.java
@@ -0,0 +1,134 @@
+/*
+ * 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.dx.cf.direct;
+
+import com.android.dx.cf.attrib.RawAttribute;
+import com.android.dx.cf.iface.Attribute;
+import com.android.dx.cf.iface.ParseException;
+import com.android.dx.cf.iface.ParseObserver;
+import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.util.ByteArray;
+import com.android.dx.util.Hex;
+
+/**
+ * Factory capable of instantiating various {@link Attribute} subclasses
+ * depending on the context and name.
+ */
+public class AttributeFactory {
+ /** context for attributes on class files */
+ public static final int CTX_CLASS = 0;
+
+ /** context for attributes on fields */
+ public static final int CTX_FIELD = 1;
+
+ /** context for attributes on methods */
+ public static final int CTX_METHOD = 2;
+
+ /** context for attributes on code attributes */
+ public static final int CTX_CODE = 3;
+
+ /** number of contexts */
+ public static final int CTX_COUNT = 4;
+
+ /**
+ * Constructs an instance.
+ */
+ public AttributeFactory() {
+ // This space intentionally left blank.
+ }
+
+ /**
+ * Parses and makes an attribute based on the bytes at the
+ * indicated position in the given array. This method figures out
+ * the name, and then does all the setup to call on to {@link #parse0},
+ * which does the actual construction.
+ *
+ * @param cf {@code non-null;} class file to parse from
+ * @param context context to parse in; one of the {@code CTX_*}
+ * constants
+ * @param offset offset into {@code dcf}'s {@code bytes}
+ * to start parsing at
+ * @param observer {@code null-ok;} parse observer to report to, if any
+ * @return {@code non-null;} an appropriately-constructed {@link Attribute}
+ */
+ public final Attribute parse(DirectClassFile cf, int context, int offset,
+ ParseObserver observer) {
+ if (cf == null) {
+ throw new NullPointerException("cf == null");
+ }
+
+ if ((context < 0) || (context >= CTX_COUNT)) {
+ throw new IllegalArgumentException("bad context");
+ }
+
+ CstString name = null;
+
+ try {
+ ByteArray bytes = cf.getBytes();
+ ConstantPool pool = cf.getConstantPool();
+ int nameIdx = bytes.getUnsignedShort(offset);
+ int length = bytes.getInt(offset + 2);
+
+ name = (CstString) pool.get(nameIdx);
+
+ if (observer != null) {
+ observer.parsed(bytes, offset, 2,
+ "name: " + name.toHuman());
+ observer.parsed(bytes, offset + 2, 4,
+ "length: " + Hex.u4(length));
+ }
+
+ return parse0(cf, context, name.getString(), offset + 6, length,
+ observer);
+ } catch (ParseException ex) {
+ ex.addContext("...while parsing " +
+ ((name != null) ? (name.toHuman() + " ") : "") +
+ "attribute at offset " + Hex.u4(offset));
+ throw ex;
+ }
+ }
+
+ /**
+ * Parses attribute content. The base class implements this by constructing
+ * an instance of {@link RawAttribute}. Subclasses are expected to
+ * override this to do something better in most cases.
+ *
+ * @param cf {@code non-null;} class file to parse from
+ * @param context context to parse in; one of the {@code CTX_*}
+ * constants
+ * @param name {@code non-null;} the attribute name
+ * @param offset offset into {@code bytes} to start parsing at; this
+ * is the offset to the start of attribute data, not to the header
+ * @param length the length of the attribute data
+ * @param observer {@code null-ok;} parse observer to report to, if any
+ * @return {@code non-null;} an appropriately-constructed {@link Attribute}
+ */
+ protected Attribute parse0(DirectClassFile cf, int context, String name,
+ int offset, int length,
+ ParseObserver observer) {
+ ByteArray bytes = cf.getBytes();
+ ConstantPool pool = cf.getConstantPool();
+ Attribute result = new RawAttribute(name, bytes, offset, length, pool);
+
+ if (observer != null) {
+ observer.parsed(bytes, offset, length, "attribute data");
+ }
+
+ return result;
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/AttributeListParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeListParser.java
new file mode 100644
index 000000000..2715e6a94
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeListParser.java
@@ -0,0 +1,164 @@
+/*
+ * 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.dx.cf.direct;
+
+import com.android.dx.cf.iface.Attribute;
+import com.android.dx.cf.iface.ParseException;
+import com.android.dx.cf.iface.ParseObserver;
+import com.android.dx.cf.iface.StdAttributeList;
+import com.android.dx.util.ByteArray;
+import com.android.dx.util.Hex;
+
+/**
+ * Parser for lists of attributes.
+ */
+final /*package*/ class AttributeListParser {
+ /** {@code non-null;} the class file to parse from */
+ private final DirectClassFile cf;
+
+ /** attribute parsing context */
+ private final int context;
+
+ /** offset in the byte array of the classfile to the start of the list */
+ private final int offset;
+
+ /** {@code non-null;} attribute factory to use */
+ private final AttributeFactory attributeFactory;
+
+ /** {@code non-null;} list of parsed attributes */
+ private final StdAttributeList list;
+
+ /** {@code >= -1;} the end offset of this list in the byte array of the
+ * classfile, or {@code -1} if not yet parsed */
+ private int endOffset;
+
+ /** {@code null-ok;} parse observer, if any */
+ private ParseObserver observer;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param cf {@code non-null;} class file to parse from
+ * @param context attribute parsing context (see {@link AttributeFactory})
+ * @param offset offset in {@code bytes} to the start of the list
+ * @param attributeFactory {@code non-null;} attribute factory to use
+ */
+ public AttributeListParser(DirectClassFile cf, int context, int offset,
+ AttributeFactory attributeFactory) {
+ if (cf == null) {
+ throw new NullPointerException("cf == null");
+ }
+
+ if (attributeFactory == null) {
+ throw new NullPointerException("attributeFactory == null");
+ }
+
+ int size = cf.getBytes().getUnsignedShort(offset);
+
+ this.cf = cf;
+ this.context = context;
+ this.offset = offset;
+ this.attributeFactory = attributeFactory;
+ this.list = new StdAttributeList(size);
+ this.endOffset = -1;
+ }
+
+ /**
+ * Sets the parse observer for this instance.
+ *
+ * @param observer {@code null-ok;} the observer
+ */
+ public void setObserver(ParseObserver observer) {
+ this.observer = observer;
+ }
+
+ /**
+ * Gets the end offset of this constant pool in the {@code byte[]}
+ * which it came from.
+ *
+ * @return {@code >= 0;} the end offset
+ */
+ public int getEndOffset() {
+ parseIfNecessary();
+ return endOffset;
+ }
+
+ /**
+ * Gets the parsed list.
+ *
+ * @return {@code non-null;} the list
+ */
+ public StdAttributeList getList() {
+ parseIfNecessary();
+ return list;
+ }
+
+ /**
+ * Runs {@link #parse} if it has not yet been run successfully.
+ */
+ private void parseIfNecessary() {
+ if (endOffset < 0) {
+ parse();
+ }
+ }
+
+ /**
+ * Does the actual parsing.
+ */
+ private void parse() {
+ int sz = list.size();
+ int at = offset + 2; // Skip the count.
+
+ ByteArray bytes = cf.getBytes();
+
+ if (observer != null) {
+ observer.parsed(bytes, offset, 2,
+ "attributes_count: " + Hex.u2(sz));
+ }
+
+ for (int i = 0; i < sz; i++) {
+ try {
+ if (observer != null) {
+ observer.parsed(bytes, at, 0,
+ "\nattributes[" + i + "]:\n");
+ observer.changeIndent(1);
+ }
+
+ Attribute attrib =
+ attributeFactory.parse(cf, context, at, observer);
+
+ at += attrib.byteLength();
+ list.set(i, attrib);
+
+ if (observer != null) {
+ observer.changeIndent(-1);
+ observer.parsed(bytes, at, 0,
+ "end attributes[" + i + "]\n");
+ }
+ } catch (ParseException ex) {
+ ex.addContext("...while parsing attributes[" + i + "]");
+ throw ex;
+ } catch (RuntimeException ex) {
+ ParseException pe = new ParseException(ex);
+ pe.addContext("...while parsing attributes[" + i + "]");
+ throw pe;
+ }
+ }
+
+ endOffset = at;
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/ClassPathOpener.java b/dexlib/src/main/java/com/android/dx/cf/direct/ClassPathOpener.java
new file mode 100644
index 000000000..26fbca029
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/direct/ClassPathOpener.java
@@ -0,0 +1,291 @@
+/*
+ * 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.dx.cf.direct;
+
+import com.android.dex.util.FileUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Opens all the class files found in a class path element. Path elements
+ * can point to class files, {jar,zip,apk} files, or directories containing
+ * class files.
+ */
+public class ClassPathOpener {
+
+ /** {@code non-null;} pathname to start with */
+ private final String pathname;
+ /** {@code non-null;} callback interface */
+ private final Consumer consumer;
+ /**
+ * If true, sort such that classes appear before their inner
+ * classes and "package-info" occurs before all other classes in that
+ * package.
+ */
+ private final boolean sort;
+ private FileNameFilter filter;
+
+ /**
+ * Callback interface for {@code ClassOpener}.
+ */
+ public interface Consumer {
+
+ /**
+ * Provides the file name and byte array for a class path element.
+ *
+ * @param name {@code non-null;} filename of element. May not be a valid
+ * filesystem path.
+ *
+ * @param lastModified milliseconds since 1970-Jan-1 00:00:00 GMT
+ * @param bytes {@code non-null;} file data
+ * @return true on success. Result is or'd with all other results
+ * from {@code processFileBytes} and returned to the caller
+ * of {@code process()}.
+ */
+ boolean processFileBytes(String name, long lastModified, byte[] bytes);
+
+ /**
+ * Informs consumer that an exception occurred while processing
+ * this path element. Processing will continue if possible.
+ *
+ * @param ex {@code non-null;} exception
+ */
+ void onException(Exception ex);
+
+ /**
+ * Informs consumer that processing of an archive file has begun.
+ *
+ * @param file {@code non-null;} archive file being processed
+ */
+ void onProcessArchiveStart(File file);
+ }
+
+ /**
+ * Filter interface for {@code ClassOpener}.
+ */
+ public interface FileNameFilter {
+
+ boolean accept(String path);
+ }
+
+ /**
+ * An accept all filter.
+ */
+ public static final FileNameFilter acceptAll = new FileNameFilter() {
+
+ @Override
+ public boolean accept(String path) {
+ return true;
+ }
+ };
+
+ /**
+ * Constructs an instance.
+ *
+ * @param pathname {@code non-null;} path element to process
+ * @param sort if true, sort such that classes appear before their inner
+ * classes and "package-info" occurs before all other classes in that
+ * package.
+ * @param consumer {@code non-null;} callback interface
+ */
+ public ClassPathOpener(String pathname, boolean sort, Consumer consumer) {
+ this(pathname, sort, acceptAll, consumer);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param pathname {@code non-null;} path element to process
+ * @param sort if true, sort such that classes appear before their inner
+ * classes and "package-info" occurs before all other classes in that
+ * package.
+ * @param consumer {@code non-null;} callback interface
+ */
+ public ClassPathOpener(String pathname, boolean sort, FileNameFilter filter,
+ Consumer consumer) {
+ this.pathname = pathname;
+ this.sort = sort;
+ this.consumer = consumer;
+ this.filter = filter;
+ }
+
+ /**
+ * Processes a path element.
+ *
+ * @return the OR of all return values
+ * from {@code Consumer.processFileBytes()}.
+ */
+ public boolean process() {
+ File file = new File(pathname);
+
+ return processOne(file, true);
+ }
+
+ /**
+ * Processes one file.
+ *
+ * @param file {@code non-null;} the file to process
+ * @param topLevel whether this is a top-level file (that is,
+ * specified directly on the commandline)
+ * @return whether any processing actually happened
+ */
+ private boolean processOne(File file, boolean topLevel) {
+ try {
+ if (file.isDirectory()) {
+ return processDirectory(file, topLevel);
+ }
+
+ String path = file.getPath();
+
+ if (path.endsWith(".zip") ||
+ path.endsWith(".jar") ||
+ path.endsWith(".apk")) {
+ return processArchive(file);
+ }
+ if (filter.accept(path)) {
+ byte[] bytes = FileUtils.readFile(file);
+ return consumer.processFileBytes(path, file.lastModified(), bytes);
+ } else {
+ return false;
+ }
+ } catch (Exception ex) {
+ consumer.onException(ex);
+ return false;
+ }
+ }
+
+ /**
+ * Sorts java class names such that outer classes preceed their inner
+ * classes and "package-info" preceeds all other classes in its package.
+ *
+ * @param a {@code non-null;} first class name
+ * @param b {@code non-null;} second class name
+ * @return {@code compareTo()}-style result
+ */
+ private static int compareClassNames(String a, String b) {
+ // Ensure inner classes sort second
+ a = a.replace('$','0');
+ b = b.replace('$','0');
+
+ /*
+ * Assuming "package-info" only occurs at the end, ensures package-info
+ * sorts first.
+ */
+ a = a.replace("package-info", "");
+ b = b.replace("package-info", "");
+
+ return a.compareTo(b);
+ }
+
+ /**
+ * Processes a directory recursively.
+ *
+ * @param dir {@code non-null;} file representing the directory
+ * @param topLevel whether this is a top-level directory (that is,
+ * specified directly on the commandline)
+ * @return whether any processing actually happened
+ */
+ private boolean processDirectory(File dir, boolean topLevel) {
+ if (topLevel) {
+ dir = new File(dir, ".");
+ }
+
+ File[] files = dir.listFiles();
+ int len = files.length;
+ boolean any = false;
+
+ if (sort) {
+ Arrays.sort(files, new Comparatorcom.android.dx.rop.pool
com.android.dx.util
cf.iface.*
based on a direct representation
+of class files as byte[]
s.
+
+
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Attribute.java b/dexlib/src/main/java/com/android/dx/cf/iface/Attribute.java
new file mode 100644
index 000000000..b075251db
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/Attribute.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dx.cf.iface;
+
+/**
+ * Interface representing attributes of class files (directly or indirectly).
+ */
+public interface Attribute {
+ /**
+ * Get the name of the attribute.
+ *
+ * @return {@code non-null;} the name
+ */
+ public String getName();
+
+ /**
+ * Get the total length of the attribute in bytes, including the
+ * header. Since the header is always six bytes, the result of
+ * this method is always at least {@code 6}.
+ *
+ * @return {@code >= 6;} the total length, in bytes
+ */
+ public int byteLength();
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/AttributeList.java b/dexlib/src/main/java/com/android/dx/cf/iface/AttributeList.java
new file mode 100644
index 000000000..f7a1d2705
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/AttributeList.java
@@ -0,0 +1,75 @@
+/*
+ * 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.dx.cf.iface;
+
+/**
+ * Interface for lists of attributes.
+ */
+public interface AttributeList {
+ /**
+ * Get whether this instance is mutable. Note that the
+ * {@code AttributeList} interface itself doesn't provide any means
+ * of mutation, but that doesn't mean that there isn't a non-interface
+ * way of mutating an instance.
+ *
+ * @return {@code true} iff this instance is somehow mutable
+ */
+ public boolean isMutable();
+
+ /**
+ * Get the number of attributes in the list.
+ *
+ * @return the size
+ */
+ public int size();
+
+ /**
+ * Get the {@code n}th attribute.
+ *
+ * @param n {@code n >= 0, n < size();} which attribute
+ * @return {@code non-null;} the attribute in question
+ */
+ public Attribute get(int n);
+
+ /**
+ * Get the total length of this list in bytes, when part of a
+ * class file. The returned value includes the two bytes for the
+ * {@code attributes_count} length indicator.
+ *
+ * @return {@code >= 2;} the total length, in bytes
+ */
+ public int byteLength();
+
+ /**
+ * Get the first attribute in the list with the given name, if any.
+ *
+ * @param name {@code non-null;} attribute name
+ * @return {@code null-ok;} first attribute in the list with the given name,
+ * or {@code null} if there is none
+ */
+ public Attribute findFirst(String name);
+
+ /**
+ * Get the next attribute in the list after the given one, with the same
+ * name, if any.
+ *
+ * @param attrib {@code non-null;} attribute to start looking after
+ * @return {@code null-ok;} next attribute after {@code attrib} with the
+ * same name as {@code attrib}
+ */
+ public Attribute findNext(Attribute attrib);
+}
diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/ClassFile.java b/dexlib/src/main/java/com/android/dx/cf/iface/ClassFile.java
new file mode 100644
index 000000000..d6c9ed03e
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/cf/iface/ClassFile.java
@@ -0,0 +1,123 @@
+/*
+ * 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.dx.cf.iface;
+
+import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.TypeList;
+
+/**
+ * Interface for things which purport to be class files or reasonable
+ * facsimiles thereof.
+ *
+ * com.android.dx.cf.attrib
com.android.dx.cf.iface
com.android.dx.rop.pool
com.android.dx.util
+
+
diff --git a/dexlib/src/main/java/com/android/dx/command/UsageException.java b/dexlib/src/main/java/com/android/dx/command/UsageException.java
new file mode 100644
index 000000000..6809bf45e
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/command/UsageException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.dx.command;
+
+/**
+ * Simple exception class used to communicate that the command-line tool
+ * should print the usage message.
+ */
+public class UsageException extends RuntimeException {
+ // This space intentionally left blank.
+}
diff --git a/dexlib/src/main/java/com/android/dx/command/dexer/DxContext.java b/dexlib/src/main/java/com/android/dx/command/dexer/DxContext.java
new file mode 100644
index 000000000..fe972f93d
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/command/dexer/DxContext.java
@@ -0,0 +1,35 @@
+package com.android.dx.command.dexer;
+
+import com.android.dx.dex.cf.CodeStatistics;
+import com.android.dx.dex.cf.OptimizerOptions;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * State used by a single invocation of {@link Main}.
+ */
+public class DxContext {
+ public final CodeStatistics codeStatistics = new CodeStatistics();
+ public final OptimizerOptions optimizerOptions = new OptimizerOptions();
+ public final PrintStream out;
+ public final PrintStream err;
+
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ final PrintStream noop = new PrintStream(new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ // noop;
+ }
+ });
+
+ public DxContext(OutputStream out, OutputStream err) {
+ this.out = new PrintStream(out);
+ this.err = new PrintStream(err);
+ }
+
+ public DxContext() {
+ this(System.out, System.err);
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/command/dexer/Main.java b/dexlib/src/main/java/com/android/dx/command/dexer/Main.java
new file mode 100644
index 000000000..ad8cd7bff
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/command/dexer/Main.java
@@ -0,0 +1,1953 @@
+/*
+ * 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.dx.command.dexer;
+
+import com.android.dex.Dex;
+import com.android.dex.DexException;
+import com.android.dex.DexFormat;
+import com.android.dex.util.FileUtils;
+import com.android.dx.Version;
+import com.android.dx.cf.code.SimException;
+import com.android.dx.cf.direct.ClassPathOpener;
+import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter;
+import com.android.dx.cf.direct.DirectClassFile;
+import com.android.dx.cf.direct.StdAttributeFactory;
+import com.android.dx.cf.iface.ParseException;
+import com.android.dx.command.UsageException;
+import com.android.dx.dex.DexOptions;
+import com.android.dx.dex.cf.CfOptions;
+import com.android.dx.dex.cf.CfTranslator;
+import com.android.dx.dex.code.PositionList;
+import com.android.dx.dex.file.ClassDefItem;
+import com.android.dx.dex.file.DexFile;
+import com.android.dx.dex.file.EncodedMethod;
+import com.android.dx.merge.CollisionPolicy;
+import com.android.dx.merge.DexMerger;
+import com.android.dx.rop.annotation.Annotation;
+import com.android.dx.rop.annotation.Annotations;
+import com.android.dx.rop.annotation.AnnotationsList;
+import com.android.dx.rop.code.RegisterSpec;
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Prototype;
+import com.android.dx.rop.type.Type;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+/**
+ * Main class for the class file translator.
+ */
+public class Main {
+
+ public static final int CONCURRENCY_LEVEL = 4;
+
+ /**
+ * File extension of a {@code .dex} file.
+ */
+ private static final String DEX_EXTENSION = ".dex";
+
+ /**
+ * File name prefix of a {@code .dex} file automatically loaded in an
+ * archive.
+ */
+ private static final String DEX_PREFIX = "classes";
+
+ /**
+ * {@code non-null;} the lengthy message that tries to discourage
+ * people from defining core classes in applications
+ */
+ private static final String IN_RE_CORE_CLASSES =
+ "Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" +
+ "when not building a core library.\n\n" +
+ "This is often due to inadvertently including a core library file\n" +
+ "in your application's project, when using an IDE (such as\n" +
+ "Eclipse). If you are sure you're not intentionally defining a\n" +
+ "core class, then this is the most likely explanation of what's\n" +
+ "going on.\n\n" +
+ "However, you might actually be trying to define a class in a core\n" +
+ "namespace, the source of which you may have taken, for example,\n" +
+ "from a non-Android virtual machine project. This will most\n" +
+ "assuredly not work. At a minimum, it jeopardizes the\n" +
+ "compatibility of your app with future versions of the platform.\n" +
+ "It is also often of questionable legality.\n\n" +
+ "If you really intend to build a core library -- which is only\n" +
+ "appropriate as part of creating a full virtual machine\n" +
+ "distribution, as opposed to compiling an application -- then use\n" +
+ "the \"--core-library\" option to suppress this error message.\n\n" +
+ "If you go ahead and use \"--core-library\" but are in fact\n" +
+ "building an application, then be forewarned that your application\n" +
+ "will still fail to build or run, at some point. Please be\n" +
+ "prepared for angry customers who find, for example, that your\n" +
+ "application ceases to function once they upgrade their operating\n" +
+ "system. You will be to blame for this problem.\n\n" +
+ "If you are legitimately using some code that happens to be in a\n" +
+ "core package, then the easiest safe alternative you have is to\n" +
+ "repackage that code. That is, move the classes in question into\n" +
+ "your own package namespace. This means that they will never be in\n" +
+ "conflict with core system classes. JarJar is a tool that may help\n" +
+ "you in this endeavor. If you find that you cannot do this, then\n" +
+ "that is an indication that the path you are on will ultimately\n" +
+ "lead to pain, suffering, grief, and lamentation.\n";
+
+ /**
+ * {@code non-null;} name of the standard manifest file in {@code .jar}
+ * files
+ */
+ private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
+
+ /**
+ * {@code non-null;} attribute name for the (quasi-standard?)
+ * {@code Created-By} attribute
+ */
+ private static final Attributes.Name CREATED_BY =
+ new Attributes.Name("Created-By");
+
+ /**
+ * {@code non-null;} list of {@code javax} subpackages that are considered
+ * to be "core". Note:: This list must be sorted, since it
+ * is binary-searched.
+ */
+ private static final String[] JAVAX_CORE = {
+ "accessibility", "crypto", "imageio", "management", "naming", "net",
+ "print", "rmi", "security", "sip", "sound", "sql", "swing",
+ "transaction", "xml"
+ };
+
+ /* Array.newInstance may be added by RopperMachine,
+ * ArrayIndexOutOfBoundsException.com.android.dx.rop.pool
com.android.dx.util
+
+
diff --git a/dexlib/src/main/java/com/android/dx/dex/code/ArrayData.java b/dexlib/src/main/java/com/android/dx/dex/code/ArrayData.java
new file mode 100644
index 000000000..fd2871285
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/dex/code/ArrayData.java
@@ -0,0 +1,201 @@
+/*
+ * 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.dx.dex.code;
+
+import com.android.dx.io.Opcodes;
+import com.android.dx.rop.code.RegisterSpecList;
+import com.android.dx.rop.code.SourcePosition;
+import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.CstLiteral32;
+import com.android.dx.rop.cst.CstLiteral64;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.util.AnnotatedOutput;
+import com.android.dx.util.Hex;
+import java.util.ArrayList;
+
+/**
+ * Pseudo-instruction which holds fill array data.
+ */
+public final class ArrayData extends VariableSizeInsn {
+ /**
+ * {@code non-null;} address representing the instruction that uses this
+ * instance
+ */
+ private final CodeAddress user;
+
+ /** {@code non-null;} initial values to be filled into an array */
+ private final ArrayListcom.android.dx.cf.code
com.android.dx.cf.direct
com.android.dx.cf.iface
com.android.dx.dex.code
com.android.dx.dex.file
com.android.dx.rop.code
com.android.dx.rop.cst
com.android.dx.util
int
and emitted
+ * in little-endian order.
+ *
+ * @param out {@code non-null;} where to write to
+ * @param c0 code unit to write
+ * @param c1c2 code unit pair to write
+ */
+ protected static void write(AnnotatedOutput out, short c0, int c1c2) {
+ write(out, c0, (short) c1c2, (short) (c1c2 >> 16));
+ }
+
+ /**
+ * Writes four code units to the given output destination, where the
+ * second and third are represented as single int
and emitted
+ * in little-endian order.
+ *
+ * @param out {@code non-null;} where to write to
+ * @param c0 code unit to write
+ * @param c1c2 code unit pair to write
+ * @param c3 code unit to write
+ */
+ protected static void write(AnnotatedOutput out, short c0, int c1c2,
+ short c3) {
+ write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3);
+ }
+
+ /**
+ * Writes five code units to the given output destination, where the
+ * second and third are represented as single int
and emitted
+ * in little-endian order.
+ *
+ * @param out {@code non-null;} where to write to
+ * @param c0 code unit to write
+ * @param c1c2 code unit pair to write
+ * @param c3 code unit to write
+ * @param c4 code unit to write
+ */
+ protected static void write(AnnotatedOutput out, short c0, int c1c2,
+ short c3, short c4) {
+ write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3, c4);
+ }
+
+ /**
+ * Writes five code units to the given output destination, where the
+ * second through fifth are represented as single long
+ * and emitted in little-endian order.
+ *
+ * @param out {@code non-null;} where to write to
+ * @param c0 code unit to write
+ * @param c1c2c3c4 code unit quad to write
+ */
+ protected static void write(AnnotatedOutput out, short c0, long c1c2c3c4) {
+ write(out, c0, (short) c1c2c3c4, (short) (c1c2c3c4 >> 16),
+ (short) (c1c2c3c4 >> 32), (short) (c1c2c3c4 >> 48));
+ }
+}
diff --git a/dexlib/src/main/java/com/android/dx/dex/code/LocalList.java b/dexlib/src/main/java/com/android/dx/dex/code/LocalList.java
new file mode 100644
index 000000000..31e3fd080
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/dex/code/LocalList.java
@@ -0,0 +1,944 @@
+/*
+ * 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.dx.dex.code;
+
+import com.android.dx.rop.code.RegisterSpec;
+import com.android.dx.rop.code.RegisterSpecSet;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Type;
+import com.android.dx.util.FixedSizeList;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * List of local variables. Each local variable entry indicates a
+ * range of code which it is valid for, a register number, a name,
+ * and a type.
+ */
+public final class LocalList extends FixedSizeList {
+ /** {@code non-null;} empty instance */
+ public static final LocalList EMPTY = new LocalList(0);
+
+ /** whether to run the self-check code */
+ private static final boolean DEBUG = false;
+
+ /**
+ * Constructs an instance. All indices initially contain {@code null}.
+ *
+ * @param size {@code >= 0;} the size of the list
+ */
+ public LocalList(int size) {
+ super(size);
+ }
+
+ /**
+ * Gets the element at the given index. It is an error to call
+ * this with the index for an element which was never set; if you
+ * do that, this will throw {@code NullPointerException}.
+ *
+ * @param n {@code >= 0, < size();} which index
+ * @return {@code non-null;} element at that index
+ */
+ public Entry get(int n) {
+ return (Entry) get0(n);
+ }
+
+ /**
+ * Sets the entry at the given index.
+ *
+ * @param n {@code >= 0, < size();} which index
+ * @param entry {@code non-null;} the entry to set at {@code n}
+ */
+ public void set(int n, Entry entry) {
+ set0(n, entry);
+ }
+
+ /**
+ * Does a human-friendly dump of this instance.
+ *
+ * @param out {@code non-null;} where to dump
+ * @param prefix {@code non-null;} prefix to attach to each line of output
+ */
+ public void debugPrint(PrintStream out, String prefix) {
+ int sz = size();
+
+ for (int i = 0; i < sz; i++) {
+ out.print(prefix);
+ out.println(get(i));
+ }
+ }
+
+ /**
+ * Disposition of a local entry.
+ */
+ public static enum Disposition {
+ /** local started (introduced) */
+ START,
+
+ /** local ended without being replaced */
+ END_SIMPLY,
+
+ /** local ended because it was directly replaced */
+ END_REPLACED,
+
+ /** local ended because it was moved to a different register */
+ END_MOVED,
+
+ /**
+ * local ended because the previous local clobbered this one
+ * (because it is category-2)
+ */
+ END_CLOBBERED_BY_PREV,
+
+ /**
+ * local ended because the next local clobbered this one
+ * (because this one is a category-2)
+ */
+ END_CLOBBERED_BY_NEXT;
+ }
+
+ /**
+ * Entry in a local list.
+ */
+ public static class Entry implements Comparable
+ *
+ *
+ *
+ *
+ */
+ static final int DBG_ADVANCE_PC = 0x01;
+
+ /**
+ * Advances the line register without emitting
+ * a positions entry.
+ *
+ */
+ static final int DBG_ADVANCE_LINE = 0x02;
+
+ /**
+ * Introduces a local variable at the current address.
+ *
+ */
+ static final int DBG_START_LOCAL = 0x03;
+
+ /**
+ * Introduces a local variable at the current address with a type
+ * signature specified.
+ *
+ */
+ static final int DBG_START_LOCAL_EXTENDED = 0x04;
+
+ /**
+ * Marks a currently-live local variable as out of scope at the
+ * current address.
+ *
+ */
+ static final int DBG_END_LOCAL = 0x05;
+
+ /**
+ * Re-introduces a local variable at the current address. The name
+ * and type are the same as the last local that was live in the specified
+ * register.
+ *
+ */
+ static final int DBG_RESTART_LOCAL = 0x06;
+
+
+ /**
+ * Sets the "prologue_end" state machine register, indicating that the
+ * next position entry that is added should be considered the end of
+ * a method prologue (an appropriate place for a method breakpoint).
+ *
+ */
+ static final int DBG_SET_FILE = 0x09;
+
+ /* IF YOU ADD A NEW OPCODE, increase OPCODE_BASE */
+
+ /*
+ * "special opcode" configuration, essentially what's found in
+ * the line number program header in DWARFv3, Section 6.2.4
+ */
+
+ /** the smallest value a special opcode can take */
+ static final int DBG_FIRST_SPECIAL = 0x0a;
+ static final int DBG_LINE_BASE = -4;
+ static final int DBG_LINE_RANGE = 15;
+ // MIN_INSN_LENGTH is always 1
+}
diff --git a/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoDecoder.java b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoDecoder.java
new file mode 100644
index 000000000..203b0f856
--- /dev/null
+++ b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoDecoder.java
@@ -0,0 +1,596 @@
+/*
+ * 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.dx.dex.file;
+
+import com.android.dex.util.ByteArrayByteInput;
+import com.android.dex.util.ByteInput;
+import com.android.dex.util.ExceptionWithContext;
+import com.android.dex.Leb128;
+import com.android.dx.dex.code.DalvCode;
+import com.android.dx.dex.code.DalvInsnList;
+import com.android.dx.dex.code.LocalList;
+import com.android.dx.dex.code.PositionList;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_EPILOGUE_BEGIN;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_FILE;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL;
+import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED;
+import com.android.dx.rop.cst.CstMethodRef;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.type.Prototype;
+import com.android.dx.rop.type.StdTypeList;
+import com.android.dx.rop.type.Type;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A decoder for the dex debug info state machine format.
+ * This code exists mostly as a reference implementation and test for
+ * for the {@code DebugInfoEncoder}
+ */
+public class DebugInfoDecoder {
+ /** encoded debug info */
+ private final byte[] encoded;
+
+ /** positions decoded */
+ private final ArrayList
+ *
+ */
+public final class DebugInfoEncoder {
+ private static final boolean DEBUG = false;
+
+ /** {@code null-ok;} positions (line numbers) to encode */
+ private final PositionList positions;
+
+ /** {@code null-ok;} local variables to encode */
+ private final LocalList locals;
+
+ private final ByteArrayAnnotatedOutput output;
+ private final DexFile file;
+ private final int codeSize;
+ private final int regSize;
+
+ private final Prototype desc;
+ private final boolean isStatic;
+
+ /** current encoding state: bytecode address */
+ private int address = 0;
+
+ /** current encoding state: line number */
+ private int line = 1;
+
+ /**
+ * if non-null: the output to write annotations to. No normal
+ * output is written to this.
+ */
+ private AnnotatedOutput annotateTo;
+
+ /** if non-null: another possible output for annotations */
+ private PrintWriter debugPrint;
+
+ /** if non-null: the prefix for each annotation or debugPrint line */
+ private String prefix;
+
+ /** true if output should be consumed during annotation */
+ private boolean shouldConsume;
+
+ /** indexed by register; last local alive in register */
+ private final LocalList.Entry[] lastEntryForReg;
+
+ /**
+ * Creates an instance.
+ *
+ * @param positions {@code null-ok;} positions (line numbers) to encode
+ * @param locals {@code null-ok;} local variables to encode
+ * @param file {@code null-ok;} may only be {@code null} if simply using
+ * this class to do a debug print
+ * @param codeSize
+ * @param regSize
+ * @param isStatic
+ * @param ref
+ */
+ public DebugInfoEncoder(PositionList positions, LocalList locals,
+ DexFile file, int codeSize, int regSize,
+ boolean isStatic, CstMethodRef ref) {
+ this.positions = positions;
+ this.locals = locals;
+ this.file = file;
+ this.desc = ref.getPrototype();
+ this.isStatic = isStatic;
+ this.codeSize = codeSize;
+ this.regSize = regSize;
+
+ output = new ByteArrayAnnotatedOutput();
+ lastEntryForReg = new LocalList.Entry[regSize];
+ }
+
+ /**
+ * Annotates or writes a message to the {@code debugPrint} writer
+ * if applicable.
+ *
+ * @param length the number of bytes associated with this message
+ * @param message the message itself
+ */
+ private void annotate(int length, String message) {
+ if (prefix != null) {
+ message = prefix + message;
+ }
+
+ if (annotateTo != null) {
+ annotateTo.annotate(shouldConsume ? length : 0, message);
+ }
+
+ if (debugPrint != null) {
+ debugPrint.println(message);
+ }
+ }
+
+ /**
+ * Converts this (PositionList, LocalList) pair into a state machine
+ * sequence.
+ *
+ * @return {@code non-null;} encoded byte sequence without padding and
+ * terminated with a {@code 0x00} byte
+ */
+ public byte[] convert() {
+ try {
+ byte[] ret;
+ ret = convert0();
+
+ if (DEBUG) {
+ for (int i = 0 ; i < ret.length; i++) {
+ System.err.printf("byte %02x\n", (0xff & ret[i]));
+ }
+ }
+
+ return ret;
+ } catch (IOException ex) {
+ throw ExceptionWithContext
+ .withContext(ex, "...while encoding debug info");
+ }
+ }
+
+ /**
+ * Converts and produces annotations on a stream. Does not write
+ * actual bits to the {@code AnnotatedOutput}.
+ *
+ * @param prefix {@code null-ok;} prefix to attach to each line of output
+ * @param debugPrint {@code null-ok;} if specified, an alternate output for
+ * annotations
+ * @param out {@code null-ok;} if specified, where annotations should go
+ * @param consume whether to claim to have consumed output for
+ * {@code out}
+ * @return {@code non-null;} encoded output
+ */
+ public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint,
+ AnnotatedOutput out, boolean consume) {
+ this.prefix = prefix;
+ this.debugPrint = debugPrint;
+ annotateTo = out;
+ shouldConsume = consume;
+
+ byte[] result = convert();
+
+ return result;
+ }
+
+ private byte[] convert0() throws IOException {
+ ArrayList