/* Software Name : AsmDex
* Version : 1.0
*
* Copyright © 2012 France Télécom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.ow2.asmdex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.zip.Adler32;
import org.ow2.asmdex.lowLevelUtils.ByteVector;
import org.ow2.asmdex.lowLevelUtils.IDalvikValueReader;
import org.ow2.asmdex.structureWriter.AnnotationDirectoryItem;
import org.ow2.asmdex.structureWriter.AnnotationElement;
import org.ow2.asmdex.structureWriter.AnnotationItem;
import org.ow2.asmdex.structureWriter.AnnotationSetItem;
import org.ow2.asmdex.structureWriter.AnnotationSetRefList;
import org.ow2.asmdex.structureWriter.ClassDefinitionItem;
import org.ow2.asmdex.structureWriter.CodeItem;
import org.ow2.asmdex.structureWriter.ConstantPool;
import org.ow2.asmdex.structureWriter.Field;
import org.ow2.asmdex.structureWriter.IAnnotationsHolder;
import org.ow2.asmdex.structureWriter.Method;
import org.ow2.asmdex.structureWriter.Prototype;
import org.ow2.asmdex.structureWriter.TypeList;
/**
* A {@link ApplicationVisitor} that generates an Android dex file in
* bytecode form. More precisely this visitor generates a byte array conforming
* to the dex file format. It can be used alone, to generate Android classes
* "from scratch", or with one or more {@link ApplicationReader ApplicationReader}
* and adapter Application visitor to generate a modified class from one or more existing
* Java applications.<br /><br />
*
* KNOWN ISSUES
* <ul><li> The line number management from the beginning to the "data" for parse/sparse switch/
* fillArray till the end is wrong. It can't be fixed properly. It shouldn't matter much, as it
* only happen if two or more switches/fillArray are encoded, and if there are line number for
* each. Only the last one will be encoded, at the beginning of the first structure.</li>
* </ul>
*
* DIFFERENCES WITH THE DX BYTECODE :
* <ul><li> TypeList are correctly sorted, but dx put the Annotations TypeList before the others.</li>
*
* <li> AnnotationItems are correctly sorted, but dx seems to put the Throws exception before the others.
* I don't do that, as my collected is ordered naturally.</li>
*
* <li> Annotation_directory_item are not sorted by increasing offset.</li>
*
* <li> First line number isn't perfectly accurate, because we don't have it through the ASM interface.
* So the first one found becomes the first one in the debug_info_item header. It is not perfectly
* accurate, because it doesn't represent the first actual line number of the method. However, it
* works fine and allows to get the same file size.</li>
* </ul>
*
* NOTES:
* <ul><li> The management of several Local Variable that uses the SAME register number isn't accurate, but won't
* be improved (is it useful ?). Each End/Restart is linked to the last Local Variable found (with the
* right register number), till a new one is added. Works fine for all the cases found.
*
* <li> The "type_id" doesn't exist as a Class, because it only contains a string_id. Structures
* referring to "type_id" directly refers to a String (added to the Constant Pool as
* both String and Type). So there is a Type structure, but only present in the Constant Pool.
*
* <li> The Debug Info.
* <ul><li> We only know the instruction addresses when actually writing the Dex file (except for the
* Jump Instruction, but they might change if an Instruction must be resized). So we can't
* write the DBG_ADVANCE_PC/Special Opcodes as we gather the Instructions. It is simpler to write the
* Debug Instructions along with writing effectively the Code Item.</li>
* <li> The Line Number are added to the Instructions directly.</li>
* <li> We can get the Line_start if the first instruction has a line number. Else, we consider it is 1.</li>
* </ul>
* </ul>
*
* @author Julien Névo, based on the ASM Framework.
*/
public class ApplicationWriter extends ApplicationVisitor {
/**
* The Application Constant Pool.
*/
private ConstantPool constantPool = new ConstantPool();
/**
* Buffer representing our output Dex file.
*/
private ByteVector out;
/**
* The ApplicationReader that may be linked to this ApplicationWriter. This is only used for
* the optimization that consists in copying the Constant Pool from the ApplicationReader and
* also the methods if they aren't going to be modified by an Adapter.
*/
private ApplicationReader applicationReader;
/**
* Indicates if the writer information must be displayed.
*/
public static final boolean DISPLAY_WRITER_INFORMATION = false;
/**
* Indicates if the writer must encode the Debug information that it is given.
* If the flag is true, the debug_info_items are not encoded at all, which is
* different from the SKIP_DEBUG from ApplicationReader that just doesn't emit
* {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
* {@link MethodVisitor#visitLineNumber visitLineNumber}, but has no power over the
* creation of the debug_info_items.
*/
public static final boolean SKIP_DEBUG_INFO_ITEMS = false;
/*
* Offsets of the structures. Only computed when the parsing of the Application has been done.
*/
private int stringIdsOffset = HEADER_NOMINAL_SIZE; // The strings always come just after the header.
private int typeIdsOffset;
private int prototypeIdsOffset;
private int fieldIdsOffset;
private int methodIdsOffset;
private int classDefinitionsOffset;
private int dataOffset;
private int annotationSetRefListOffset; // Offset of the first annotation_set_ref_list in the data section.
private int annotationItemsOffset; // Offset of the first annotation_item in the data section.
private int annotationSetItemOffset; // Offset of the first annotation_set_item in the data section.
private int annotationDirectoryItemsOffset; // Offset of the first annotation_directory_item in the data section.
private int encodedArrayItemsOffset; // Offset of the first array_item in the data section.
private int debugInfoItemOffset; // Offset of the first debug_info_item in the data section.
private int typeListOffset;
private int stringDataOffset;
private int classDataItemOffset; // Offset of the first class_data_item, in the data section.
private int codeItemOffset; // Offset of the first code_item, in the data section.
private int debugInfoItemCount = 0; // Global count of the debug_info_items in the data section.
private int encodedArrayItemsCount = 0; // Global count of the array_items in the data section.
private int classDataItemCount = 0; // Global count of the class_data_items in the data section.
private int codeItemCount; // Count of the Code Items of this application.
/**
* The Dex_File_Magic number.
*/
private static final byte[] dexFileMagic = new byte[] {0x64, 0x65, 0x78, 0xa, 0x30, 0x33, 0x35, 0x00 };
private static final int ADLER_OFFSET = 8; // offset in bytes of the Adler32 Checksum.
private static final int ADLER_SIZE = 4; // Size in bytes of the Adler32 Checksum.
private static final int SHA1_SIGNATURE_OFFSET = 12; // offset in bytes of the SHA1 Signature.
private static final int SHA1_SIGNATURE_SIZE = 20; // Size in bytes of the SHA1 Signature.
private static final int HEADER_NOMINAL_SIZE = 0x70; // Fixed in documentation.
private static final int STANDARD_ENDIAN_VALUE = 0x12345678;
private static final int MAP_OFFSET_OFFSET = 0x34; // Offset of the offset of the Map, in header_item.
private static final int FILE_SIZE_OFFSET = 0x20; // Offset of the file_size item, in header_item.
// Sizes in bytes of the different atomic structures linking to the Data sections.
private static final int STRING_ID_ITEM_SIZE = 4;
private static final int TYPE_ID_ITEM_SIZE = 4;
private static final int PROTO_ID_ITEM_SIZE = 4 * 3;
private static final int FIELD_ID_ITEM_SIZE = 2 * 2 + 4;
private static final int METHOD_ID_ITEM_SIZE = 2 * 2 + 4;
private static final int CLASS_DEF_ITEM_SIZE = 8 * 4;
/**
* Constructs an new {@link ClassWriter}.
*/
public ApplicationWriter() {
super(Opcodes.ASM4);
}
/**
* Constructs a new {@link ClassWriter} object and enables optimizations for
* "mostly add" bytecode transformations. These optimizations are the
* following:
*
* <ul> <li>The constant pool from the original application is copied as is in
* the new application, which saves time. New constant pool entries will be added
* if necessary, but unused constant pool entries <i>won't be
* removed</i>.</li> <li>Methods that are not transformed are copied as
* is in the new application, directly from the original application bytecode (i.e.
* without emiting visit events for all the method instructions), which
* saves a <i>lot</i> of time. Untransformed methods are detected by the
* fact that the {@link ApplicationReader} receives {@link MethodVisitor} objects
* that come from a {@link ApplicationWriter} (and not from any other {@link ApplicationVisitor} instance).</li>
* </ul>
*
* @param applicationReader the {@link ApplicationReader} used to read the original
* application. It will be used to copy the entire constant pool from the
* original application and also to copy other fragments of original
* bytecode where applicable.
*/
public ApplicationWriter(final ApplicationReader applicationReader) {
super(Opcodes.ASM4);
applicationReader.copyPool(this);
this.applicationReader = applicationReader;
}
// ------------------------------------------------------------------------
// Implementation of the ClassVisitor interface
// ------------------------------------------------------------------------
@Override
public void visit() {
if (DISPLAY_WRITER_INFORMATION) {
System.out.println("ApplicationWriter : visit.");
}
}
@Override
public ClassVisitor visitClass(int access, String name, String[] signature,
String superName, String[] interfaces) {
if (DISPLAY_WRITER_INFORMATION) {
System.out.print("ApplicationWriter : visitClass. Acces = " + access
+ ", name = " + name
+ ", SuperClassName = " + superName
);
if (interfaces != null) {
System.out.print(". Interfaces = ");
for (int i = 0; i < interfaces.length; i++) {
System.out.print(interfaces[i] + ", ");
}
}
if (signature != null) {
System.out.print(". Signature = ");
for (int i = 0; i < signature.length; i++) {
System.out.print(signature[i] + ", ");
}
}
System.out.println();
}
return new ClassWriter(this, constantPool, access, name, signature, superName, interfaces);
}
@Override
public void visitEnd() {
if (DISPLAY_WRITER_INFORMATION) {
System.out.println("ApplicationWriter : visitEnd.");
}
generateDexFile();
}
// ------------------------------------------------------------------------
// Other public methods
// ------------------------------------------------------------------------
/**
* Returns the bytecode of the class that was built with this
* application writer. The application Dex file must have been parsed before.
*
* @return the bytecode of the class that was build with this application
* writer, or Null if the application has not been parsed yet.
*/
public byte[] toByteArray() {
if (out == null) {
return null;
}
return out.getData();
}
// ------------------------------------------------------------------------
// Getters and Setters.
// ------------------------------------------------------------------------
/**
* Gets the Constant Pool.
* @return the Constant Pool.
*/
public ConstantPool getConstantPool() {
return constantPool;
}
// ------------------------------------------------------------------------
// Private methods
// ------------------------------------------------------------------------
/**
* Generate the Dex output file. The application files must have been parsed before.
*/
private void generateDexFile() {
out = new ByteVector(HEADER_NOMINAL_SIZE);
// Sizes in bytes of the different structures linking to the Data sections.
int stringIdsSize;
int typeIdsSize;
int protoIdsSize;
int fieldIdsSize;
int methodIdsSize;
// Fills the Indexes maps, and sorts the collections.
constantPool.prepareGeneration();
// ------------------------------
// Creates the header.
// ------------------------------
out.putByteArray(dexFileMagic, 0, dexFileMagic.length); // Put dex_file_magic.
out.putInt(0); // Put a fake Checksum.
for (int i = 0; i < 20; i++) { out.putByte(0); } // Put a fake Signature.
out.putInt(0); // Put a fake file_size.
out.putInt(HEADER_NOMINAL_SIZE); // Put header_size.
out.putInt(STANDARD_ENDIAN_VALUE); // Put the endian_tag.
out.putInt(0); // No Link section.
out.putInt(0);
out.putInt(0); // No Map section. Filled later when the Map is written.
// String_ids
stringIdsOffset = HEADER_NOMINAL_SIZE; // The strings always come just after the header.
int nbStrings = constantPool.getStringCount();
out.putInt(nbStrings);
out.putInt(nbStrings == 0 ? 0 : stringIdsOffset);
stringIdsSize = nbStrings * STRING_ID_ITEM_SIZE;
// Type_ids
typeIdsOffset = stringIdsOffset + stringIdsSize;
int nbTypes = constantPool.getTypeCount();
out.putInt(nbTypes);
out.putInt(nbTypes == 0 ? 0 : typeIdsOffset);
typeIdsSize = nbTypes * TYPE_ID_ITEM_SIZE;
// Proto_ids
prototypeIdsOffset = typeIdsOffset + typeIdsSize;
int nbProtos = constantPool.getPrototypeCount();
out.putInt(nbProtos);
out.putInt(nbProtos == 0 ? 0 : prototypeIdsOffset);
protoIdsSize = nbProtos * PROTO_ID_ITEM_SIZE;
// Field_ids
fieldIdsOffset = prototypeIdsOffset + protoIdsSize;
int nbFields = constantPool.getFieldCount();
out.putInt(nbFields);
out.putInt(nbFields == 0 ? 0 : fieldIdsOffset);
fieldIdsSize = nbFields * FIELD_ID_ITEM_SIZE;
// Method_ids
methodIdsOffset = fieldIdsOffset + fieldIdsSize;
int nbMethods = constantPool.getMethodCount();
out.putInt(nbMethods);
out.putInt(nbMethods == 0 ? 0 : methodIdsOffset);
methodIdsSize = nbMethods * METHOD_ID_ITEM_SIZE;
// Class_defs
classDefinitionsOffset = methodIdsOffset + methodIdsSize;
int nbClasses = constantPool.getClassDefinitionCount();
out.putInt(nbClasses);
out.putInt(nbClasses == 0 ? 0 : classDefinitionsOffset);
// Data size is not known for now. We'll come back for it later.
// Data offset could be found, but it is the same as the first code_item, which may need
// padding. We will take care of it when writing the code_items.
int dataSizeOffset = out.getLength(); // Store this offset to get back later.
out.putInt(0); // data_size, unknown for now.
out.putInt(0); // data_off, calculated later.
// ------------------------------
// Fills the string_id, type_id, proto_id, field_id, method_id, class_defs section.
// Some fields can be correctly filled now, but not all. These ones are filled will 0, and
// we will come back here later.
// ------------------------------
// String_ids can't be filled for now.
for (int i = 0; i < nbStrings; i++) { out.putInt(0); }
// Type_ids can be filled. They consists in the index of the String the type refers.
for (String type : constantPool.getTypes()) {
int index = constantPool.getStringIndex(type);
out.putInt(index);
}
// Prototypes. Filled later.
for (int i = 0; i < nbProtos; i++) { out.putInt(0); out.putInt(0); out.putInt(0); }
// Field_ids.
for (Field field : constantPool.getFields()) {
String className = field.getClassName();
String typeName = field.getTypeName();
int classNameIndex = constantPool.getTypeIndex(className);
int typeNameIndex = constantPool.getTypeIndex(typeName);
int nameIndex = constantPool.getStringIndex(field.getFieldName());
out.putShort(classNameIndex);
out.putShort(typeNameIndex);
out.putInt(nameIndex);
}
// Methods_ids.
for (Method method : constantPool.getMethods()) {
String className = method.getClassName();
Prototype prototype = method.getPrototype();
String name = method.getMethodName();
int classNameIndex = constantPool.getTypeIndex(className);
int nameIndex = constantPool.getStringIndex(name);
int prototypeIndex = constantPool.getPrototypeIndex(prototype);
out.putShort(classNameIndex);
out.putShort(prototypeIndex);
out.putInt(nameIndex);
}
// Classes. Filled later.
for (int i = 0; i < nbClasses; i++) {
out.putInt(0); out.putInt(0); out.putInt(0); out.putInt(0);
out.putInt(0); out.putInt(0); out.putInt(0); out.putInt(0);
}
// ------------------------------
// Now comes the Data section, and the filling of previous sections we couldn't do earlier.
// ------------------------------
// The elements are stored in the same order they are generated by the dx compiler, though it
// is absolutely not mandatory.
dataOffset = out.getLength();
prepareAnnotationSetRefLists();
prepareAnnotationSetItems();
writeCodeItems();
prepareAnnotationDirectoryItems();
writeTypeList();
writePrototypeIds();
writeStringDataItemSection();
writeDebugInfoItems();
writeAnnotationItems();
completeAnnotationSetItems();
completeAnnotationSetRefLists();
completeAnnotationDirectoryItems();
writeEncodedArrayItems();
writeClassDataItems();
writeMap();
// Writes the size and offset of the Data Block into the data_size and data_off fields of the header.
int dataSize = out.getLength() - dataOffset;
if ((dataSize % 4) != 0) {
try { throw new Exception("Data Size isn't a multiple of (uint).");
} catch (Exception e) { e.printStackTrace(); }
}
out.putInt(dataSize, dataSizeOffset);
out.putInt(dataOffset, dataSizeOffset + 4);
// Writes the file size, now that we know it.
out.putInt(out.getLength(), FILE_SIZE_OFFSET);
addSHA1Signature();
addAdler32Checksum();
}
/**
* Calculates and adds the Adler32 Checksum at the beginning of the file, in the header.
*/
private void addAdler32Checksum() {
Adler32 adler = new Adler32();
adler.update(out.getBuffer(), ADLER_OFFSET + ADLER_SIZE, out.getLength() - (ADLER_OFFSET + ADLER_SIZE));
out.putInt((int)adler.getValue(), ADLER_OFFSET);
}
/**
* Calculates and adds the SHA1 signature at the beginning of the file, in the header.
*/
private void addSHA1Signature() {
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if (sha != null) {
int sha1PostSignatureOffset = SHA1_SIGNATURE_OFFSET + SHA1_SIGNATURE_SIZE;
sha.update(out.getBuffer(), sha1PostSignatureOffset, out.getLength() - sha1PostSignatureOffset);
byte[] digest = sha.digest();
if (digest.length == SHA1_SIGNATURE_SIZE) {
out.putByteArray(digest, SHA1_SIGNATURE_OFFSET);
} else {
try { throw new Exception("SHA-1 digest has an unexpected size : " + digest.length);
} catch (Exception e) { e.printStackTrace(); }
}
}
}
/**
* Prepares the annotation_set_ref_lists at the end of the file. We can resolve their offset, but not their
* content, because we don't know the offsets of the annotation_set_items yet.
*/
private void prepareAnnotationSetRefLists() {
for (AnnotationSetRefList annotationSetRefList : constantPool.getAnnotationSetRefLists()) {
out.addPadding();
if (annotationSetRefListOffset == 0) {
annotationSetRefListOffset = out.getLength();
}
// Sets the offset.
constantPool.setAnnotationSetRefListOffset(annotationSetRefList, out.getLength());
// Writes fake data.
out.putInt(0); // Fake size.
for (int i = 0, size = annotationSetRefList.getNbAnnotationSetItem(); i < size; i++) {
out.putInt(0); // Fake annotations_off.
}
}
}
/**
* Prepares the Annotations Set Items structure by filling the area with 0, at the end of the Dex file.
* This is needed because this is the second section encoded, and we don't have the offset of the
* Annotation_Items yet. However, we set the annotation_set_item offset inside the Classes, Methods, Fields.
*/
private void prepareAnnotationSetItems() {
for (AnnotationSetItem annotationSetItem : constantPool.getAnnotationSetItems()) {
int size = annotationSetItem.getNbAnnotationItems(); //annotationHolder.getNbAnnotations();
// We do not discard annotation_set_item with size of 0, because Annotation Parameters use them.
out.addPadding();
if (annotationSetItemOffset == 0) {
annotationSetItemOffset = out.getLength();
}
constantPool.setAnnotationSetItemOffset(annotationSetItem, out.getLength());
out.putInt(0); // Fake size.
for (int i = 0; i < size; i++) {
out.putInt(0); // Fake offsets.
}
}
}
/**
* Writes the code items at the end of the buffer, and sets their offset. Each code must be parsed
* once again to map the symbolic elements link to the resolved elements.
*/
private void writeCodeItems() {
// Scans all the methods inside all the classes.
for (ClassDefinitionItem cdi : constantPool.getClasses()) {
// And every direct or virtual methods in these classes.
writeMethodsOfCodeItem(cdi.getDirectMethods());
writeMethodsOfCodeItem(cdi.getVirtualMethods());
}
}
/**
* Write the given Methods. Should only be called by writeCodeItems.
* @param methods List of methods, whether they are direct or virtual.
*/
private void writeMethodsOfCodeItem(List<Method> methods) {
for (Method method : methods) {
if (method.supportsCodeItem()) {
out.addPadding();
if (codeItemOffset == 0) {
codeItemOffset = out.getLength();
}
// Checks if the ConstantPool optimization (Reader-->Writer) is used.
int startByteCodeToCopy = method.getStartBytecodeToCopy();
if (startByteCodeToCopy == 0) {
// No optimization. Parses the code normally.
ByteVector code = method.getCodeItemCode();
if (code.getLength() > 0) {
// First encodes the code itself.
CodeItem codeItem = method.getCodeItem();
codeItem.setOffset(out.getLength());
codeItem.mapResolvedIndexes();
out.putByteVector(code);
// Then encodes the try/catch following the code.
ByteVector tryCatchCode = codeItem.getCodeItemTryCatch();
if (tryCatchCode != null) {
out.putByteVector(tryCatchCode);
}
}
} else {
// Optimization used.
// First, needs to parse the code from the Dex file and adds it to the output file.
CodeItem codeItem = method.getCodeItem();
int outputFileByteCodeOffset = out.getLength();
codeItem.setOffset(outputFileByteCodeOffset);
IDalvikValueReader dexFile = applicationReader.getDexFile();
// Calculates the size of code. We use the size of the source buffer.
// The size of the try/catch is not useful : we will parse it and copy it byte
// per byte.
int lengthByteCode = CodeItem.HEADER_SIZE;
dexFile.seek(startByteCodeToCopy + CodeItem.TRIES_SIZE_FIELD_OFFSET);
int nbTries = dexFile.ushort();
// Reads the debug_info_off. Useful later when copying the debug_info_item.
int startDebugInfoItem = dexFile.uint();
method.setStartDebugInfoToCopy(startDebugInfoItem);
int insnsSizeInWord = dexFile.uint();
lengthByteCode += insnsSizeInWord * 2;
if ((nbTries != 0) && ((insnsSizeInWord % 2) != 0)) { // Padding present ?
lengthByteCode += 2;
}
// Copies the bytecode from the input file to the output.
out.putByteArray(dexFile.getContents(), startByteCodeToCopy, lengthByteCode);
// Maps the resolved symbols in the newly copied bytecode. This doesn't include
// the try/catch that hasn't been copied yet.
codeItem.mapResolvedIndexesByteCode(out, outputFileByteCodeOffset);
// Now takes care of the try/catch. Contrary to the bytecode before, we can't copy
// it and parse it because it will surely not be the same size (as it encodes its
// fields as ULeb128).
if (nbTries != 0) {
ByteVector inputDexFileByteArray = new ByteVector(dexFile.getContents());
ByteVector tryCatch = codeItem.mapResolvedIndexesTryCatch(inputDexFileByteArray,
startByteCodeToCopy + lengthByteCode, nbTries);
out.putByteVector(tryCatch);
}
}
codeItemCount++;
}
}
}
/**
* Prepares the Annotations Directory Items structure by filling the area with mostly 0. This is needed
* because we don't have the offset of the annotation_set_items yet.
*/
private void prepareAnnotationDirectoryItems() {
// Encodes the annotation_directory_items with fake data. This allows however to know the offset
// of each one of them.
for (AnnotationDirectoryItem adi : constantPool.getAnnotationDirectoryItems()) {
out.addPadding();
if (annotationDirectoryItemsOffset == 0) { // Sets the Offset of the first Item.
annotationDirectoryItemsOffset = out.getLength();
}
constantPool.setAnnotationDirectoryItemOffset(adi, out.getLength());
out.putInt(0); // Fake class_annotations_off, or right one if there is none !
int nbAnnotatedFields = adi.getNbAnnotatedFields();
int nbAnnotatedMethods = adi.getNbAnnotatedMethods();
int nbAnnotatedParameters = adi.getNbAnnotatedParameters();
out.putInt(0);
out.putInt(0);
out.putInt(0);
int size = nbAnnotatedFields + nbAnnotatedMethods + nbAnnotatedParameters;
for (int i = 0; i < size; i++) {
out.putInt(0); // Fake index.
out.putInt(0); // Fake annotation_off.
}
}
}
/**
* Writes the type_list section at the end of the output buffer, and fills the typeListToOffset map of
* the Constant Pool.
*/
private void writeTypeList() {
for (TypeList typeList : constantPool.getTypeList()) {
out.addPadding();
if (typeListOffset == 0) {
typeListOffset = out.getLength();
}
// Encodes the offset of the structure in the corresponding map.
constantPool.setTypeListOffset(typeList, out.getLength());
// Encodes the type_list and type_item section.
int size = typeList.size();
if (size > 0) {
out.putInt(size);
for (String type : typeList.getTypeList()) {
out.putShort(constantPool.getTypeIndex(type));
}
}
}
}
/**
* Write the prototype_ids section in the beginning of the buffer. This has to be done only after the
* type_list has been encoded and their offset found.
*/
private void writePrototypeIds() {
int prototypeIdsOffset = this.prototypeIdsOffset;
for (Prototype prototype : constantPool.getPrototypes()) {
String shortyDescriptor = prototype.getShortyDescriptor();
out.putInt(constantPool.getStringIndex(shortyDescriptor), prototypeIdsOffset); // Shorty_idx.
prototypeIdsOffset += 4;
String returnType = prototype.getReturnType();
out.putInt(constantPool.getTypeIndex(returnType), prototypeIdsOffset); // Return_type_idx.
prototypeIdsOffset += 4;
// Parameters_off. May be 0 if the Prototype has no parameter.
TypeList typeList = prototype.getParameterTypes();
int typeListOffset = (typeList.size() == 0) ? 0 : constantPool.getTypeListOffset(typeList);
out.putInt(typeListOffset, prototypeIdsOffset);
prototypeIdsOffset += 4;
}
}
/**
* Writes the string_data_item section at the end of the output buffer, and completes the string_id
* section.
*/
private void writeStringDataItemSection() {
if (constantPool.getStringCount() > 0) {
// The string_id_item section is always padded after the header.
int stringIdsOffset = this.stringIdsOffset;
stringDataOffset = out.getLength();
for (String string : constantPool.getStrings()) {
// Encodes the offset in the string_id section.
out.putInt(out.getLength(), stringIdsOffset);
stringIdsOffset += 4;
// Encodes the size and data in the string_data_item.
out.putUleb128(string.length());
out.putMUTF8(string);
}
}
}
/**
* Writes the debug_info_items at the end of the output buffer. Also sets their offset inside the
* Methods previously encoded.
*/
private void writeDebugInfoItems() {
if (SKIP_DEBUG_INFO_ITEMS) {
return;
}
for (Method method : constantPool.getMethods()) {
// If the Method is "Unknown" (referred to but not encoded), or is Abstract or Interface
// don't write its Debug Information.
if (!method.isUnknown() && method.supportsCodeItem()) {
CodeItem codeItem = method.getCodeItem();
// "Constant Pool" optimization enabled ?
int startDebugInfoToCopy = method.getStartDebugInfoToCopy();
if (startDebugInfoToCopy == 0) {
// No optimization. Writes the Debug Info Item we created.
ByteVector debugInfoItemCode = codeItem.getDebugInfoItemCode();
if (debugInfoItemCode != null) {
//ByteVector debugInfoItemHeader = codeItem.getDebugInfoItemHeader();
int currentDebugInfoItemOffset = out.getLength();
// Sets the offset of the Debug Info Item inside the related Code Item.
int currentCodeItemOffset = codeItem.getOffset();
if (currentCodeItemOffset != 0) {
// Sets the offset of the first Debug Info item in the data section.
if (debugInfoItemOffset == 0) {
debugInfoItemOffset = currentDebugInfoItemOffset;
}
// Sets the offset of the debug_info_item to the current Code Item encoded bytes.
codeItem.setDebugInfoItemOffset(out, currentDebugInfoItemOffset);
// Encodes the debug_info_item code.
// But first we may have to resolve its indexes as it has been encoded with
// symbolic indexes. However, if no index was used in this debug_info_item, we
// can use it as-is.
if (codeItem.areSymbolicIndexesUsedInDebugCodeItem()) {
debugInfoItemCode = codeItem.mapResolvedIndexesForDebug(debugInfoItemCode, 0);
}
out.putByteVector(debugInfoItemCode);
debugInfoItemCount++;
}
}
} else {
// "Constant Pool" optimization. We need to parse the input file to map the
// symbolic indexes to resolved indexes. We can't directly copy the debug_info_item
// to the destination and change the indexes from here because they are encoded
// as ULeb128, so the new indexes may have different size.
// First, get the input Dex file.
IDalvikValueReader dexFile = applicationReader.getDexFile();
ByteVector inputDexFileByteArray = new ByteVector(dexFile.getContents());
int currentDebugInfoItemOffset = out.getLength();
// Sets the offset of the first Debug Info item in the data section.
if (debugInfoItemOffset == 0) {
debugInfoItemOffset = currentDebugInfoItemOffset;
}
ByteVector debugInfoItem = codeItem.mapResolvedIndexesForDebug(inputDexFileByteArray,
startDebugInfoToCopy);
out.putByteVector(debugInfoItem);
// Sets the offset of the debug_info_item to the current Code Item encoded bytes.
codeItem.setDebugInfoItemOffset(out, currentDebugInfoItemOffset);
debugInfoItemCount++;
}
}
}
}
/**
* Writes the annotation_item section at the end of the output buffer. Stores their offset in the
* Constant Pool.
*/
private void writeAnnotationItems() {
annotationItemsOffset = out.getLength();
// Encodes the Annotation Items found. They are all unique and ordered.
for (AnnotationItem annotationItem : constantPool.getAnnotationItems()) {
constantPool.setAnnotationItemOffset(annotationItem, out.getLength());
out.putByte(annotationItem.getVisibility());
// Encoding the encoded_annotation format.
out.putUleb128(constantPool.getTypeIndex(annotationItem.getAnnotationType())); // type_idx.
out.putUleb128(annotationItem.getNbAnnotationElements()); // size.
// Encodes the elements (values) of the annotation.
PriorityQueue<AnnotationElement> annotationElements = annotationItem.getAnnotationElements();
//ArrayList<AnnotationElement> annotationElements = annotationItem.getAnnotationElements();
for (AnnotationElement annotationElement : annotationElements) {
out.putUleb128(constantPool.getStringIndex(annotationElement.getElementName())); // name_idx.
out.putByteArray(annotationElement.getEncodedValue().encode(constantPool)); // element.
}
}
}
/**
* Completes the annotation_set_items we filled with 0 before, now that we have the annotation_item offsets.
* Only call this method after writeAnnotationItems().
*/
private void completeAnnotationSetItems() {
// Encodes all the annotation_set_items.
if ((annotationSetItemOffset != 0) && (constantPool.getAnnotationSetItemCount() > 0)) {
int offset = annotationSetItemOffset;
for (AnnotationSetItem annotationSetItem : constantPool.getAnnotationSetItems()) {
out.putInt(annotationSetItem.getNbAnnotationItems(), offset); // size.
offset += 4;
for (AnnotationItem annotationItem : annotationSetItem.getAnnotationItems()) {
out.putInt(constantPool.getAnnotationItemOffset(annotationItem), offset);
offset += 4;
}
}
}
}
/**
* Completes the annotation_set_ref_lists we filled with 0 before, now that was have the
* annotation_set_items offsets. Only call this method after completeAnnotationSetItems().
*/
private void completeAnnotationSetRefLists() {
int offset = annotationSetRefListOffset;
for (AnnotationSetRefList annotationSetRefList : constantPool.getAnnotationSetRefLists()) {
int size = annotationSetRefList.getNbAnnotationSetItem();
out.putInt(size, offset); // Encodes size of the list, in entries.
offset += 4;
// Encodes the offsets to the annotation_set_items.
for (int i = 0; i < size; i++) {
AnnotationSetItem annotationSetItem = annotationSetRefList.getAnnotationSetItem(i);
out.putInt(constantPool.getAnnotationSetItemOffset(annotationSetItem), offset);
offset += 4;
}
}
}
/**
* Completes the annotation_directory_item section previously prepared. The annotation_set_item
* section must have been completed.
*/
private void completeAnnotationDirectoryItems() {
for (AnnotationDirectoryItem adi : constantPool.getAnnotationDirectoryItems()) {
// Encodes only if there is actually Annotations in this Class.
int directoryItemOffset = constantPool.getAnnotationDirectoryItemOffset(adi);
if (directoryItemOffset != 0) {
AnnotationSetItem annotationSetItem = adi.getClassAnnotationSetItem();
// There may be no Class Annotations.
if (annotationSetItem.getNbAnnotationItems() > 0) {
int annotationOffset = constantPool.getAnnotationSetItemOffset(annotationSetItem);
out.putInt(annotationOffset, directoryItemOffset); // class_annotation_off.
}
out.putInt(adi.getNbAnnotatedFields(), directoryItemOffset + AnnotationDirectoryItem.FIELDS_SIZE_OFFSET);
out.putInt(adi.getNbAnnotatedMethods(), directoryItemOffset + AnnotationDirectoryItem.ANNOTATED_METHODS_SIZE_OFFSET);
out.putInt(adi.getNbAnnotatedParameters(), directoryItemOffset + AnnotationDirectoryItem.ANNOTATED_PARAMETERS_SIZE_OFFSET);
directoryItemOffset += 4 + 3 * 4;
// Encodes the field_annotation format.
if (adi.getNbAnnotatedFields() > 0) {
TreeMap<Integer,IAnnotationsHolder> orderedContent = new TreeMap<Integer,IAnnotationsHolder> ();
for (Field field : adi.getAnnotatedFields()) {
orderedContent.put(constantPool.getFieldIndex(field),(IAnnotationsHolder)field);
}
for (Map.Entry<Integer, IAnnotationsHolder> entry: orderedContent.entrySet()) {
directoryItemOffset = writeFieldMethodIndexAnnotation(entry.getValue(),
entry.getKey(), directoryItemOffset);
}
}
// Encodes the method_annotation format.
if (adi.getNbAnnotatedMethods() > 0) {
TreeMap<Integer,IAnnotationsHolder> orderedContent = new TreeMap<Integer,IAnnotationsHolder> ();
for (Method method : adi.getAnnotatedMethods()) {
orderedContent.put(constantPool.getMethodIndex(method),(IAnnotationsHolder) method);
}
for (Map.Entry<Integer, IAnnotationsHolder> entry: orderedContent.entrySet()) {
directoryItemOffset = writeFieldMethodIndexAnnotation(entry.getValue(),
entry.getKey(), directoryItemOffset);
}
}
// Encodes the parameter_annotation format.
if (adi.getNbAnnotatedParameters() > 0) {
TreeMap<Integer,AnnotationSetRefList> orderedContent = new TreeMap<Integer,AnnotationSetRefList> ();
for (AnnotationSetRefList annotatedParameters : adi.getAnnotatedParameters()) {
Method method = annotatedParameters.getMethod();
orderedContent.put(constantPool.getMethodIndex(method), method.getAnnotatedParameterSetRefList());
}
for (Map.Entry<Integer, AnnotationSetRefList> entry : orderedContent.entrySet()) {
out.putInt(entry.getKey(), directoryItemOffset);
out.putInt(constantPool.getAnnotationSetRefListOffset(entry.getValue()),
directoryItemOffset + 4);
directoryItemOffset += 8;
}
}
}
}
}
/**
* Writes the field_annotation, method_annotation or parameter_annotation format at the given offset of the
* Dex file. This works for all these structures because its format is the same.
* On return, the new offset after what has been written is given.
* If the Holder has no Annotations, nothing happens.
* @param annotationsHolder holder of the Annotations.
* @param index index of the Method or Field (for Parameter, the Index is from the Method it is from).
* @param offset offset on the Dex file where to write.
* @return the new offset after what has been written.
*/
private int writeFieldMethodIndexAnnotation(IAnnotationsHolder annotationsHolder, int index, int offset) {
if (annotationsHolder.getNbAnnotations() > 0) {
out.putInt(index, offset);
out.putInt(constantPool.getAnnotationSetItemOffset(annotationsHolder.getAnnotationSetItem())
, offset + 4);
return (offset + 8);
} else {
return offset;
}
}
/**
* Writes the encoded_array_item section at the end of the output buffer. It must be done before
* writing the class_def_items.
*/
private void writeEncodedArrayItems() {
/**
* Tiny class to hold the offset and size of the previously encoded structure.
*
* @author Julien Névo
*/
class Place {
public int offset;
public int size;
public Place(int offset, int size) {
this.offset = offset;
this.size = size;
}
}
encodedArrayItemsOffset = out.getLength();
// Each encoded_array_item must be unique. So we store the offset and size of each one encoded
// to be able to compare the new one with the encoded ones.
List<Place> places = new ArrayList<Place>();
// Encodes the array of static values of each Class.
for (ClassDefinitionItem cdi : constantPool.getClasses()) {
// According to the output of a compiled Class, only Final Static Fields have their value
// encoded. The Static only Fields may be encoded, with their value set to 0 or null, ONLY
// if a Final Static Field is after them. The last Final Static Field taken in account must have
// its value set, i.e. it is not a reference type.
// Primitive type may NOT have a value, in case they are set by a Static Constructor.
if (cdi.getNbStaticFields() > 0) {
ArrayList<Field> fields = cdi.getStaticFields();
// Then, we look for the last Final Static Field inside.
boolean found = false;
int lastFinalStaticIndex = fields.size() - 1;
while (!found && (lastFinalStaticIndex >= 0)) {
Field field = fields.get(lastFinalStaticIndex);
found = (field.isFinalStatic() && (field.getValue() != null) && (field.getValue().getType() != Opcodes.VALUE_NULL));
if (!found) {
lastFinalStaticIndex--;
}
}
if (found) {
// We store the offset where the item might be encoded. But we might discard it if it is
// a duplicate.
int possibleOffset = out.getLength();
// Used to store the complete encoded Array Item. We may discard it if it happens to be a duplicate.
ByteVector bv = new ByteVector();
// Encodes the encoded_array. First, the size.
bv.putUleb128(lastFinalStaticIndex + 1);
// Encodes the array. Iterates forward.
int index = 0;
while (index <= lastFinalStaticIndex) {
Field field = fields.get(index);
// To comply the output of a compiled Class, Fields with no value are set
// to 0 or Null, because they are "before" the last Final Static Field.
if (field.getValue() == null) {
field.setNoValue();
}
byte[] bytes = field.encodeValue(constantPool);
if (bytes != null) bv.putByteArray(bytes);
index++;
}
// Checks if the newly created item must be encoded. Is it a duplicate ?
boolean foundDuplicate = false;
int indexPlace = 0;
int size = places.size();
int scannedStructureOffset = 0;
// Scans all the encoded items.
while (!foundDuplicate && (indexPlace < size)) {
byte[] bytesPending = bv.getBuffer(); // We take the whole buffer, to avoid making copies.
int bytesPendingSize = bv.getLength();
Place currentPlace = places.get(indexPlace);
int placeSize = currentPlace.size;
if (placeSize == bytesPendingSize) {
// They have the same size. We have to check byte by byte.
int posPending = 0;
scannedStructureOffset = currentPlace.offset;
boolean difference = false;
while (!difference && posPending < placeSize) {
difference = bytesPending[posPending] != out.getBuffer()[scannedStructureOffset + posPending];
posPending++;
}
// Difference ? If no, we found a duplicate and we can stop here.
// Else, we continue to compare with the next elements.
foundDuplicate = !difference;
}
indexPlace++;
}
// Found no duplicate ? Then encodes the structure.
if (!foundDuplicate) {
encodedArrayItemsCount++;
places.add(new Place(possibleOffset, bv.getLength()));
// Saves the offset to the array we're going to encode.
out.putByteVector(bv);
} else {
// We found a duplicate. We don't encode the structure, but the Class must refer to
// the already encoded structure !
possibleOffset = scannedStructureOffset;
}
constantPool.addOffsetForStaticValuesEncodedArrayItemOfClass(cdi, possibleOffset);
}
}
}
}
/**
* Writes the class_data_item section at the end of the output buffer, and completes the class_defs
* section.
*/
private void writeClassDataItems() {
int currentClassDefinitionOffset = classDefinitionsOffset;
classDataItemOffset = out.getLength();
for (ClassDefinitionItem cdi : constantPool.getClasses()) {
// Writes the class_def_item.
out.putInt(constantPool.getTypeIndex(cdi.getClassName()), currentClassDefinitionOffset); // Class_idx.
out.putInt(cdi.getAccessFlags(), currentClassDefinitionOffset + ClassDefinitionItem.ACCESS_FLAGS_OFFSET); // Access_flags.
// Superclass_idx. If no super class, encodes the NO_INDEX value.
String superClassName = cdi.getSuperClassName();
int superClassIndex = superClassName == null ? Opcodes.NO_INDEX_SIGNED :
constantPool.getTypeIndex(superClassName);
out.putInt(superClassIndex, currentClassDefinitionOffset + ClassDefinitionItem.SUPERCLASS_IDX_OFFSET);
// Interfaces. 0 if none.
TypeList interfaces = cdi.getInterfaces();
int indexTypeListInterface = (interfaces.size() == 0) ? 0 :
constantPool.getTypeListOffset(interfaces);
out.putInt(indexTypeListInterface, currentClassDefinitionOffset + ClassDefinitionItem.INTERFACES_OFFSET);
// Source_file_idx. NO_INDEX if none.
String sourceFile = cdi.getSourceFileName();
int indexSourceFile = (sourceFile == null) ? Opcodes.NO_INDEX_SIGNED :
constantPool.getStringIndex(sourceFile);
out.putInt(indexSourceFile, currentClassDefinitionOffset + ClassDefinitionItem.SOURCE_FILE_IDX_OFFSET);
// Annotations_off.
out.putInt(constantPool.getAnnotationDirectoryItemOffset(cdi.getAnnotationDirectoryItem()),
currentClassDefinitionOffset + ClassDefinitionItem.ANNOTATIONS_OFF_OFFSET);
// Class_data_off. We point at the end of the file, as that's where we're going to add the
// Class Data, just below.
// If the class is an Interface, and has no fields or methods, then it is considered "marker
// interface" and the class_data_item isn't encoded.
int classDataOffset;
if ((cdi.isInterface() && (cdi.getNbStaticFields() == 0)
&& (cdi.getNbInstanceFields() == 0))
&& ((cdi.getNbDirectMethods() == 0)
&& (cdi.getNbVirtualMethods() == 0))) {
classDataOffset = 0;
} else {
classDataOffset = out.getLength();
}
out.putInt(classDataOffset, currentClassDefinitionOffset + ClassDefinitionItem.CLASS_DATA_OFF_OFFSET);
// Static_values_off.
out.putInt(constantPool.getOffsetOfStaticValuesEncodedArrayItemOfClass(cdi), currentClassDefinitionOffset + 4 * 7);
currentClassDefinitionOffset += CLASS_DEF_ITEM_SIZE;
// Writes the class_data_item at the end of the file, only if the class isn't a "marker interface".
if (classDataOffset != 0) {
out.putUleb128(cdi.getNbStaticFields());
out.putUleb128(cdi.getNbInstanceFields());
out.putUleb128(cdi.getNbDirectMethods());
out.putUleb128(cdi.getNbVirtualMethods());
writeFieldsInClassDataItem(cdi.getStaticFields());
writeFieldsInClassDataItem(cdi.getInstanceFields());
writeMethodsInClassDataItem(cdi.getDirectMethods());
writeMethodsInClassDataItem(cdi.getVirtualMethods());
classDataItemCount++;
}
}
}
/**
* Writes the given Methods according to the "direct/virtual_method" field format in the class_data_item
* at the end of the output file. Requires code_items to have their offset resolved.
* @param methods the Methods to encode.
*/
private void writeMethodsInClassDataItem(ArrayList<Method> methods) {
if (methods.size() == 0) {
return;
}
// We must sort the Array in order to get a growing index number.
TreeSet<Method> sortedMethods = new TreeSet<Method>(methods);
boolean isFirstMethod = true;
int previousIndex = 0;
int indexToEncode = 0;
for (Method method : sortedMethods) {
// Index is coded as a difference from the previous Index, except for the first occurrence.
int methodIndex = constantPool.getMethodIndex(method); // constantPool.getMethodsToIndexesMap().get(method);
if (isFirstMethod) {
indexToEncode = methodIndex;
isFirstMethod = false;
} else {
indexToEncode = methodIndex - previousIndex;
}
out.putUleb128(indexToEncode);
out.putUleb128(method.getAccess());
CodeItem codeItem = method.getCodeItem();
out.putUleb128(codeItem == null ? 0 : codeItem.getOffset());
previousIndex = methodIndex;
}
}
/**
* Writes the given Fields according to the "static/instance_method" field format in the class_data_item
* at the end of the output file.
* @param fields the Fields to encode.
*/
private void writeFieldsInClassDataItem(ArrayList<Field> fields) {
if (fields.size() == 0) {
return;
}
// We must sort the Array in order to get a growing index number.
TreeSet<Field> sortedFields = new TreeSet<Field>(fields);
boolean isFirstField = true;
int previousIndex = 0;
int indexToEncode = 0;
for (Field field : sortedFields) {
// Index is coded as a difference from the previous Index, except for the first occurrence.
int fieldIndex = constantPool.getFieldIndex(field);
if (isFirstField) {
indexToEncode = fieldIndex;
isFirstField = false;
} else {
indexToEncode = fieldIndex - previousIndex;
}
out.putUleb128(indexToEncode);
out.putUleb128(field.getAccess());
previousIndex = fieldIndex;
}
}
/**
* Writes the Map section at the end of the output buffer. All the elements must have been parsed and
* their offset resolved.
*/
private void writeMap() {
out.addPadding();
// We don't know yet how many entries there are, so we save it to write it for later.
int mapOffset = out.getLength();
out.putInt(0);
out.putInt(mapOffset, MAP_OFFSET_OFFSET); // Sets the Map Offset in the header.
// The Map elements are stored in the same order they are generated in the file, though
// this is absolutely not mandatory.
// Header_item.
int nbEntries = writeMapItem(Opcodes.TYPE_HEADER_ITEM, 1, 0, 0);
nbEntries = writeMapItem(Opcodes.TYPE_STRING_ID_ITEM, constantPool.getStringCount(),
stringIdsOffset, nbEntries); // String_id_item.
nbEntries = writeMapItem(Opcodes.TYPE_TYPE_ID_ITEM, constantPool.getTypeCount(),
typeIdsOffset, nbEntries); // Type_id_item.
nbEntries = writeMapItem(Opcodes.TYPE_PROTO_ID_ITEM, constantPool.getPrototypeCount(),
prototypeIdsOffset, nbEntries); // Proto_id_item.
nbEntries = writeMapItem(Opcodes.TYPE_FIELD_ID_ITEM, constantPool.getFieldCount(),
fieldIdsOffset, nbEntries); // Field_id_item.
nbEntries = writeMapItem(Opcodes.TYPE_METHOD_ID_ITEM, constantPool.getMethodCount(),
methodIdsOffset, nbEntries); // Method_id_item.
nbEntries = writeMapItem(Opcodes.TYPE_CLASS_DEF_ITEM, constantPool.getClassDefinitionCount(),
classDefinitionsOffset, nbEntries); // Class_def_item.
// Data items.
nbEntries = writeMapItem(Opcodes.TYPE_ANNOTATION_SET_REF_LIST, constantPool.getAnnotationSetRefListsCount(),
annotationSetRefListOffset, nbEntries); // Annotation_set_ref_list.
nbEntries = writeMapItem(Opcodes.TYPE_ANNOTATION_SET_ITEM, constantPool.getAnnotationSetItemCount(),
annotationSetItemOffset, nbEntries); // Annotation_set_item.
nbEntries = writeMapItem(Opcodes.TYPE_CODE_ITEM, codeItemCount,
codeItemOffset, nbEntries); // Code_item.
nbEntries = writeMapItem(Opcodes.TYPE_ANNOTATIONS_DIRECTORY_ITEM, constantPool.getAnnotationDirectoryItemCount(),
annotationDirectoryItemsOffset, nbEntries); // Annotation_directory_item.
nbEntries = writeMapItem(Opcodes.TYPE_TYPE_LIST, constantPool.getTypeListCount(),
typeListOffset, nbEntries); // Type_list.
nbEntries = writeMapItem(Opcodes.TYPE_STRING_DATA_ITEM, constantPool.getStringCount(),
stringDataOffset, nbEntries); // String_data_item.
nbEntries = writeMapItem(Opcodes.TYPE_DEBUG_INFO_ITEM, debugInfoItemCount,
debugInfoItemOffset, nbEntries); // Debug_info_item.
nbEntries = writeMapItem(Opcodes.TYPE_ANNOTATION_ITEM, constantPool.getAnnotationItemCount(),
annotationItemsOffset, nbEntries); // Annotation_item.
nbEntries = writeMapItem(Opcodes.TYPE_ENCODED_ARRAY_ITEM, encodedArrayItemsCount,
encodedArrayItemsOffset, nbEntries); // Encoded_array_item.
nbEntries = writeMapItem(Opcodes.TYPE_CLASS_DATA_ITEM, classDataItemCount,
classDataItemOffset, nbEntries); // Class_data_item.
nbEntries = writeMapItem(Opcodes.TYPE_MAP_LIST, 1, mapOffset, nbEntries); // map_list.
// We can now set the right number of entries at the beginning of the structure.
out.putInt(nbEntries, mapOffset);
}
/**
* Convenient method to write, at the end of the file, a map_item item. It is not written if the size is 0.
* @param type type of the item (see {@link Opcodes#TYPE_HEADER_ITEM}).
* @param size count of the number of items.
* @param offset offset from the start of the file to the items.
* @param nbEntries the current number of entries in the Map.
* @return the number of entries in the Map, plus the size given.
*/
private int writeMapItem(int type, int size, int offset, int nbEntries) {
if (size > 0) {
out.putShort(type);
out.putShort(0);
out.putInt(size);
out.putInt(offset);
nbEntries++;
}
return nbEntries;
}
// ---------------------------------------------------------
// Code and ConstantPool copy optimization methods.
// ---------------------------------------------------------
/**
* Returns the ApplicationReader that may be linked to this ApplicationWriter. May be null.
*/
public ApplicationReader getApplicationReader() {
return applicationReader;
}
/**
* When the optimization about the ApplicationReader/ApplicationWriter is enabled, the
* ApplicationReader will want to register Strings into the Constant Pool of the Writer.
* This method is used for that. Note that the String added should be considered by the Writer
* as symbolic, as more of them can be added later.
* @param string the String to add to the Constant Pool.
*/
public void addStringFromApplicationReader(String string) {
constantPool.addStringToConstantPool(string);
}
/**
* When the optimization about the ApplicationReader/ApplicationWriter is enabled, the
* ApplicationReader will want to register Types into the Constant Pool of the Writer.
* This method is used for that. Note that the Type added should be considered by the Writer
* as symbolic, as more of them can be added later.
* @param type the Type to add to the Constant Pool.
*/
public void addTypeNameFromApplicationReader(String type) {
constantPool.addTypeToConstantPool(type);
}
/**
* When the optimization about the ApplicationReader/ApplicationWriter is enabled, the
* ApplicationReader will want to register Fields into the Constant Pool of the Writer.
* This method is used for that. Note that the Field added should be considered by the Writer
* as symbolic, as more of them can be added later.
* @param className the Class owner name.
* @param type the type of the Field.
* @param fieldName the name of the Field.
*/
public void addFieldFromApplicationReader(String className, String type, String fieldName) {
constantPool.addFieldToConstantPool(fieldName, type, className, Opcodes.ACC_UNKNOWN, null, null);
}
/**
* When the optimization about the ApplicationReader/ApplicationWriter is enabled, the
* ApplicationReader will want to register Methods into the Constant Pool of the Writer.
* This method is used for that. Note that the Method added should be considered by the Writer
* as symbolic, as more of them can be added later.
* @param className the Class owner name.
* @param prototype the prototype of the method, in TypeDescriptor format.
* @param methodName the name of the Method.
*/
public void addMethodFromApplicationReader(String className, String prototype, String methodName) {
constantPool.addMethodToConstantPool(methodName, className, prototype, Opcodes.ACC_UNKNOWN,
null, null);
}
}