| /* |
| * 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); |
| } |
| } |
| } |