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