diff options
Diffstat (limited to 'dx/src/com/android/dx/dex/file/ValueEncoder.java')
-rw-r--r-- | dx/src/com/android/dx/dex/file/ValueEncoder.java | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/dex/file/ValueEncoder.java b/dx/src/com/android/dx/dex/file/ValueEncoder.java new file mode 100644 index 0000000..9c433a3 --- /dev/null +++ b/dx/src/com/android/dx/dex/file/ValueEncoder.java @@ -0,0 +1,527 @@ +/* + * 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.file; + +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.NameValuePair; +import com.android.dx.rop.cst.Constant; +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.CstFieldRef; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstKnownNull; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstShort; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.Collection; + +/** + * Handler for writing out {@code encoded_values} and parts + * thereof. + */ +public final class ValueEncoder { + /** annotation value type constant: {@code byte} */ + private static final int VALUE_BYTE = 0x00; + + /** annotation value type constant: {@code short} */ + private static final int VALUE_SHORT = 0x02; + + /** annotation value type constant: {@code char} */ + private static final int VALUE_CHAR = 0x03; + + /** annotation value type constant: {@code int} */ + private static final int VALUE_INT = 0x04; + + /** annotation value type constant: {@code long} */ + private static final int VALUE_LONG = 0x06; + + /** annotation value type constant: {@code float} */ + private static final int VALUE_FLOAT = 0x10; + + /** annotation value type constant: {@code double} */ + private static final int VALUE_DOUBLE = 0x11; + + /** annotation value type constant: {@code string} */ + private static final int VALUE_STRING = 0x17; + + /** annotation value type constant: {@code type} */ + private static final int VALUE_TYPE = 0x18; + + /** annotation value type constant: {@code field} */ + private static final int VALUE_FIELD = 0x19; + + /** annotation value type constant: {@code method} */ + private static final int VALUE_METHOD = 0x1a; + + /** annotation value type constant: {@code enum} */ + private static final int VALUE_ENUM = 0x1b; + + /** annotation value type constant: {@code array} */ + private static final int VALUE_ARRAY = 0x1c; + + /** annotation value type constant: {@code annotation} */ + private static final int VALUE_ANNOTATION = 0x1d; + + /** annotation value type constant: {@code null} */ + private static final int VALUE_NULL = 0x1e; + + /** annotation value type constant: {@code boolean} */ + private static final int VALUE_BOOLEAN = 0x1f; + + /** {@code non-null;} file being written */ + private final DexFile file; + + /** {@code non-null;} output stream to write to */ + private final AnnotatedOutput out; + + /** + * Construct an instance. + * + * @param file {@code non-null;} file being written + * @param out {@code non-null;} output stream to write to + */ + public ValueEncoder(DexFile file, AnnotatedOutput out) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + if (out == null) { + throw new NullPointerException("out == null"); + } + + this.file = file; + this.out = out; + } + + /** + * Writes out the encoded form of the given constant. + * + * @param cst {@code non-null;} the constant to write + */ + public void writeConstant(Constant cst) { + int type = constantToValueType(cst); + int arg; + + switch (type) { + case VALUE_BYTE: + case VALUE_SHORT: + case VALUE_INT: + case VALUE_LONG: { + long value = ((CstLiteralBits) cst).getLongBits(); + writeSignedIntegralValue(type, value); + break; + } + case VALUE_CHAR: { + long value = ((CstLiteralBits) cst).getLongBits(); + writeUnsignedIntegralValue(type, value); + break; + } + case VALUE_FLOAT: { + // Shift value left 32 so that right-zero-extension works. + long value = ((CstFloat) cst).getLongBits() << 32; + writeRightZeroExtendedValue(type, value); + break; + } + case VALUE_DOUBLE: { + long value = ((CstDouble) cst).getLongBits(); + writeRightZeroExtendedValue(type, value); + break; + } + case VALUE_STRING: { + int index = file.getStringIds().indexOf((CstString) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_TYPE: { + int index = file.getTypeIds().indexOf((CstType) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_FIELD: { + int index = file.getFieldIds().indexOf((CstFieldRef) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_METHOD: { + int index = file.getMethodIds().indexOf((CstMethodRef) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_ENUM: { + CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef(); + int index = file.getFieldIds().indexOf(fieldRef); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_ARRAY: { + out.writeByte(type); + writeArray((CstArray) cst, false); + break; + } + case VALUE_ANNOTATION: { + out.writeByte(type); + writeAnnotation(((CstAnnotation) cst).getAnnotation(), + false); + break; + } + case VALUE_NULL: { + out.writeByte(type); + break; + } + case VALUE_BOOLEAN: { + int value = ((CstBoolean) cst).getIntBits(); + out.writeByte(type | (value << 5)); + break; + } + default: { + throw new RuntimeException("Shouldn't happen"); + } + } + } + + /** + * Gets the value type for the given constant. + * + * @param cst {@code non-null;} the constant + * @return the value type; one of the {@code VALUE_*} constants + * defined by this class + */ + private static int constantToValueType(Constant cst) { + /* + * TODO: Constant should probable have an associated enum, so this + * can be a switch(). + */ + if (cst instanceof CstByte) { + return VALUE_BYTE; + } else if (cst instanceof CstShort) { + return VALUE_SHORT; + } else if (cst instanceof CstChar) { + return VALUE_CHAR; + } else if (cst instanceof CstInteger) { + return VALUE_INT; + } else if (cst instanceof CstLong) { + return VALUE_LONG; + } else if (cst instanceof CstFloat) { + return VALUE_FLOAT; + } else if (cst instanceof CstDouble) { + return VALUE_DOUBLE; + } else if (cst instanceof CstString) { + return VALUE_STRING; + } else if (cst instanceof CstType) { + return VALUE_TYPE; + } else if (cst instanceof CstFieldRef) { + return VALUE_FIELD; + } else if (cst instanceof CstMethodRef) { + return VALUE_METHOD; + } else if (cst instanceof CstEnumRef) { + return VALUE_ENUM; + } else if (cst instanceof CstArray) { + return VALUE_ARRAY; + } else if (cst instanceof CstAnnotation) { + return VALUE_ANNOTATION; + } else if (cst instanceof CstKnownNull) { + return VALUE_NULL; + } else if (cst instanceof CstBoolean) { + return VALUE_BOOLEAN; + } else { + throw new RuntimeException("Shouldn't happen"); + } + } + + /** + * Writes out the encoded form of the given array, that is, as + * an {@code encoded_array} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param array {@code non-null;} array instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeArray(CstArray array, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + CstArray.List list = ((CstArray) array).getList(); + int size = list.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUleb128(size); + + for (int i = 0; i < size; i++) { + Constant cst = list.get(i); + if (annotates) { + out.annotate(" [" + Integer.toHexString(i) + "] " + + constantToHuman(cst)); + } + writeConstant(cst); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Writes out the encoded form of the given annotation, that is, + * as an {@code encoded_annotation} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param annotation {@code non-null;} annotation instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeAnnotation(Annotation annotation, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + + CstType type = annotation.getType(); + int typeIdx = typeIds.indexOf(type); + + if (annotates) { + out.annotate(" type_idx: " + Hex.u4(typeIdx) + " // " + + type.toHuman()); + } + + out.writeUleb128(typeIds.indexOf(annotation.getType())); + + Collection<NameValuePair> pairs = annotation.getNameValuePairs(); + int size = pairs.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUleb128(size); + + int at = 0; + for (NameValuePair pair : pairs) { + CstString name = pair.getName(); + int nameIdx = stringIds.indexOf(name); + Constant value = pair.getValue(); + + if (annotates) { + out.annotate(0, " elements[" + at + "]:"); + at++; + out.annotate(" name_idx: " + Hex.u4(nameIdx) + " // " + + name.toHuman()); + } + + out.writeUleb128(nameIdx); + + if (annotates) { + out.annotate(" value: " + constantToHuman(value)); + } + + writeConstant(value); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Gets the colloquial type name and human form of the type of the + * given constant, when used as an encoded value. + * + * @param cst {@code non-null;} the constant + * @return {@code non-null;} its type name and human form + */ + public static String constantToHuman(Constant cst) { + int type = constantToValueType(cst); + + if (type == VALUE_NULL) { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + + sb.append(cst.typeName()); + sb.append(' '); + sb.append(cst.toHuman()); + + return sb.toString(); + } + + /** + * Helper for {@link #writeConstant}, which writes out the value + * for any signed integral type. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeSignedIntegralValue(int type, long value) { + /* + * Figure out how many bits are needed to represent the value, + * including a sign bit: The bit count is subtracted from 65 + * and not 64 to account for the sign bit. The xor operation + * has the effect of leaving non-negative values alone and + * unary complementing negative values (so that a leading zero + * count always returns a useful number for our present + * purpose). + */ + int requiredBits = + 65 - Long.numberOfLeadingZeros(value ^ (value >> 63)); + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Helper for {@link #writeConstant}, which writes out the value + * for any unsigned integral type. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeUnsignedIntegralValue(int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfLeadingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Helper for {@link #writeConstant}, which writes out a + * right-zero-extended value. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeRightZeroExtendedValue(int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfTrailingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + // Scootch the first bits to be written down to the low-order bits. + value >>= 64 - (requiredBytes * 8); + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular {@link Annotation}, calling itself + * recursively should it encounter a nested annotation. + * + * @param file {@code non-null;} the file to add to + * @param annotation {@code non-null;} the annotation to add contents for + */ + public static void addContents(DexFile file, Annotation annotation) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(annotation.getType()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + stringIds.intern(pair.getName()); + addContents(file, pair.getValue()); + } + } + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular constant, calling itself recursively + * should it encounter a {@link CstArray} and calling {@link + * #addContents(DexFile,Annotation)} recursively should it + * encounter a {@link CstAnnotation}. + * + * @param file {@code non-null;} the file to add to + * @param cst {@code non-null;} the constant to add contents for + */ + public static void addContents(DexFile file, Constant cst) { + if (cst instanceof CstAnnotation) { + addContents(file, ((CstAnnotation) cst).getAnnotation()); + } else if (cst instanceof CstArray) { + CstArray.List list = ((CstArray) cst).getList(); + int size = list.size(); + for (int i = 0; i < size; i++) { + addContents(file, list.get(i)); + } + } else { + file.internIfAppropriate(cst); + } + } +} |