/* 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.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.List; import org.ow2.asmdex.lowLevelUtils.DexFileReader; import org.ow2.asmdex.lowLevelUtils.IDalvikValueReader; import org.ow2.asmdex.specificAnnotationParser.DefaultAnnotationSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.EnclosingClassSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.EnclosingMethodSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.ExceptionSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.ISpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.InnerClassSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.MemberClassesSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationParser.SignatureSpecificAnnotationParser; import org.ow2.asmdex.specificAnnotationVisitors.DefaultAnnotationInformation; import org.ow2.asmdex.specificAnnotationVisitors.DefaultAnnotationVisitor; import org.ow2.asmdex.specificAnnotationVisitors.EnclosingClassAnnotationVisitor; import org.ow2.asmdex.specificAnnotationVisitors.EnclosingMethodAnnotationVisitor; import org.ow2.asmdex.specificAnnotationVisitors.ExceptionAnnotationVisitor; import org.ow2.asmdex.specificAnnotationVisitors.InnerClassAnnotationVisitor; import org.ow2.asmdex.specificAnnotationVisitors.MemberClassesAnnotationVisitor; import org.ow2.asmdex.specificAnnotationVisitors.SignatureAnnotationVisitor; import org.ow2.asmdex.structureReader.ClassDefinitionItem; import org.ow2.asmdex.structureReader.FieldIdItem; import org.ow2.asmdex.structureReader.MethodIdItem; /** * An Android Application parser for dex files to make an {@link ApplicationVisitor} * visit an existing application. * This class parses a byte array conforming to the APK/dex class file format and * calls the appropriate visit methods of a given class visitor for each class, field, * method and bytecode instruction encountered.<br /> * <br /> * About Inner/Outer Classes : * * <ul><li> Inner Class of an Outer Class can be visited when visiting this Outer Class, but the * Outer Name isn't encoded, as well as the access flags (this one can be retrieved * elsewhere, though).</li> * <li> Method Inner Class aren't encoded at all in the Outer Class or the Outer Method.</li> * <li> Visiting an Inner Class calls visitInnerClass, which gives the internal * name of the inner class (BUILT !), the class that contains it, the accessflags.</li> * <li> Visiting a Method Inner Class calls visitOuterClass AND visitInnerClass (ASM does that).</li> * * <li> Visiting an Inner Class from a normal class having an inner class doesn't provide the outerName.</li> * * <li> The outerName of an inner class when visiting an inner class * from a normal class is not given.</li> * * <li> When calling visitInnerClass, the internalName is reconstructed from the outer * name and inner name.</li> * * <li> Also done for the visitOuterClass.</li> * * CHOICES : * <ul><li> Line number don't always have a Label, because it's not always given by the * Debug Info. As ASM requires it in visitLineNumber, we systematically create a * label whenever a Line Number is found.</li></ul> * * @author Julien Névo, based on the ASM Framework. */ public class ApplicationReader { /** * The bytecode to be parsed. <i>The content of this array must not be modified.</i> */ public final byte[] byteCode; /** * A reader to the DexFile to parse. */ private DexFileReader dexFile; /* * Maps linking fields, methods and parameters indexes to their annotation offset, * for the class being currently parsed. This corresponds to what is encoded in * the field_annotation structure. */ private HashMap<Integer, Integer> fieldAnnotationOffsetsOfClass; private HashMap<Integer, Integer> methodAnnotationOffsetsOfClass; private HashMap<Integer, Integer> parameterAnnotationOffsetsOfClass; /** * Map to link a Type_Id index to an actual Class Index. This is useful because * some structures (like dalvik.annotation.MemberClasses) refers to classes * thanks to the type_id of their internal name. */ private HashMap<Integer, Integer> typeIdToClassIndexMap; /** * Map linking a Class Name to an actual Class Index. */ private HashMap<String, Integer> classNameToIndex; /** * List of Default Annotations for the current Class. They are stored because * even though they are read in the Class, they must be visited for each Method * that has one, when we encounter them. * In order to have a fast look-up, we link the Method name its possible data about * the Default Annotation. */ private HashMap<String, DefaultAnnotationInformation> defaultAnnotations = new HashMap<String, DefaultAnnotationInformation>(); /** * If true, displays the structures of the Decoded Application. */ public static final boolean DEBUG_DISPLAY_STRUCTURES = false; /** * True to enable annotations support. */ private static final boolean ANNOTATIONS = true; /** * True to enable bytecode writing support. This consists in copying the Constant Pool and the bytecode * of the methods directly to the output if the Reader is linked to the Writer. That is, if the methods * aren't modified by an Adapter. */ private static final boolean WRITER = true; /** * Flag to skip method code. If this class is set <code>CODE</code> * attribute won't be visited. This can be used, for example, to retrieve * annotations for methods and method parameters. */ public static final int SKIP_CODE = 1; /** * Flag to skip the debug information in the class. If this flag is set the * debug information of the class is not visited, i.e. the * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be * called. However, note that in AsmDex, debug_info_items may still be * created as it contains the prologue and end_sequence. In order to completely * prevent the debug_info_items to be created, use the * {@link ApplicationWriter#SKIP_DEBUG_INFO_ITEMS} flag. */ public static final int SKIP_DEBUG = 2; /** * Little enumeration to help identify a Visitor. * * @author Julien Névo */ private static enum VisitorType { classVisitor, methodVisitor, fieldVisitor }; /** * API level */ protected int api; // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Constructs a new {@link ApplicationReader} object. * @param api the ASMDEX api level * @param byteCode the bytecode of the application to be read. */ public ApplicationReader(final int api, final byte[] byteCode) { this(api, byteCode, 0, byteCode.length); } /** * Constructs a new {@link ApplicationReader} object. * @param api the ASMDEX api level * @param byteCode the bytecode of the application to be read. * @param startOffset the start offset of the application data. * @param length the length of the application data. */ public ApplicationReader(final int api, final byte[] byteCode, final int startOffset, final int length) { // Uses the byteCode array as-is for parsing the header, unless an // offset and different length were requested, in which case we // work on a copy of the array. if ((startOffset != 0) || (length != byteCode.length)) { this.byteCode = Arrays.copyOfRange(byteCode, startOffset, length + startOffset); } else { this.byteCode = byteCode; } // Opens the Dex file to check the validity of its header and parses it. dexFile = new DexFileReader(); try { dexFile.parse(this.byteCode); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * Constructs a new {@link ApplicationReader} object. * @param api the ASMDEX api level * @param inputStream an input stream from which to read the application. * @throws IOException if a problem occurs during reading. */ public ApplicationReader(final int api, final InputStream inputStream ) throws IOException { this(api, readApplication(inputStream)); } /** * Constructs a new {@link ApplicationReader} object. * @param api the ASMDEX api level * @param fileName name and path of the application (DEX) to be read. * @throws IOException if an exception occurs during reading. */ public ApplicationReader(int api, final String fileName) throws IOException { // Opens as an InputStream the file whose name is given. this(api, readApplication(new FileInputStream(new File(fileName)))); } /** * Constructs a new {@link ApplicationReader} object. * @param api the ASMDEX api level * @param file the dex file to be read. * @throws IOException if an exception occurs during reading. */ public ApplicationReader(int api, final File file) throws IOException { this(api, readApplication(new FileInputStream(file))); } /** * Returns the Dex file. * @return the Dex file. */ public IDalvikValueReader getDexFile() { return dexFile; } /** * Reads and stores the bytecode of an application. * @param inputStream an input stream from which to read the application. * @return the bytecode read from the given input stream. * @throws IOException if a problem occurs during reading. */ private static byte[] readApplication(final InputStream inputStream) throws IOException { byte[] result = null; if (inputStream == null) { throw new IOException("Application not found"); } else { ByteArrayOutputStream buffer = null; try { buffer = new ByteArrayOutputStream(); byte[] dataRead = new byte[16384]; int nbBytesRead; while ((nbBytesRead = inputStream.read(dataRead)) != -1) { buffer.write(dataRead, 0, nbBytesRead); } buffer.flush(); result = buffer.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { if (buffer != null) { buffer.close(); } inputStream.close(); } } if (result == null) throw new IOException("Application bytecode not initialized"); return result; } // ------------------------------------------------------------------------ // Public methods // ------------------------------------------------------------------------ /** * Makes the given visitor visit the Dex Application of this {@link ApplicationReader}. * All the classes, or only several, can be visited. * This application is the one specified in the constructor (see * {@link #ApplicationReader(int, byte[]) ApplicationReader}). * * @param applicationVisitor the visitor that must visit this class. * @param classesToVisit the names of the classes to visit, or Null to visit * all the classes of the application. * @param flags option flags that can be used to modify the default behavior * of this class. See {@link #SKIP_DEBUG}, {@link #SKIP_CODE}. */ public void accept(final ApplicationVisitor applicationVisitor, final String[] classesToVisit, final int flags) { // Attributes are not supported. accept(applicationVisitor, classesToVisit, null, flags); } /** * Makes the given visitor visit the Dex Application of this {@link ApplicationReader}. * All the classes are visited. * This application is the one specified in the constructor (see * {@link #ApplicationReader(int, byte[]) ApplicationReader}). * * @param applicationVisitor the visitor that must visit this class. * @param flags option flags that can be used to modify the default behavior * of this class. See {@link #SKIP_DEBUG}, {@link #SKIP_CODE}. */ public void accept(final ApplicationVisitor applicationVisitor, final int flags) { // Attributes are not supported. accept(applicationVisitor, null, null, flags); } /** * Makes the given visitor visit the Java class of this {@link ClassVisitor}. * This class is the one specified in the constructor * * @param applicationVisitor the visitor that must visit this Application. * @param classesToVisit the names of the classes to visit, or null to visit * all the classes of the application. * @param attrs Attributes are <i>NOT</i> supported by AsmDex, ignored. * Use annotations instead. * @param flags option flags that can be used to modify the default behavior * of this class. See {@link #SKIP_DEBUG}, {@link #SKIP_CODE}. */ public void accept( final ApplicationVisitor applicationVisitor, final String[] classesToVisit, final Object attrs, final int flags) { // The header has been parsed before, there's no need to parse it again. applicationVisitor.visit(); // Parses the Classes (class_defs structure). int classDefinitionsSize = dexFile.getClassDefinitionsSize(); // Just a little test to display all the classes of the dex file. if (DEBUG_DISPLAY_STRUCTURES) { System.out.println("----------------------- NB classes = " + classDefinitionsSize); for (int testI = 0; testI < classDefinitionsSize; testI++) { ClassDefinitionItem cdi = dexFile.getClassDefinitionItem(testI); System.out.println(dexFile.getStringItemFromTypeIndex(cdi.getClassIndex())); } System.out.println("-----------------------\n"); // Just a little test to display all the methods of the dex file. System.out.println("----------------------- NB methods = " + dexFile.getMethodIdsSize()); for (int testI = 0; testI < dexFile.getMethodIdsSize(); testI++) { System.out.println( dexFile.getStringItemFromStringIndex(dexFile.getMethodIdItem(testI).getNameIndex()) + ", " + dexFile.getStringItemFromTypeIndex(dexFile.getMethodIdItem(testI).getClassIndex()) + ", " + dexFile.getShortyStringFromProtoIndex(dexFile.getMethodIdItem(testI).getPrototypeIndex())); } System.out.println("-----------------------\n"); // Just a little test to display all the strings of the dex file. System.out.println("----------------------- NB strings = " + dexFile.getStringIdsSize()); for (int testI = 0; testI < dexFile.getStringIdsSize(); testI++) { System.out.println(dexFile.getStringItemFromStringIndex(testI)); } System.out.println("-----------------------\n"); // Just a little test to display all the types of the dex file. System.out.println("----------------------- NB types = " + dexFile.getTypeIdsSize()); for (int testI = 0; testI < dexFile.getTypeIdsSize(); testI++) { System.out.println(dexFile.getStringItemFromTypeIndex(testI)); } System.out.println("-----------------------\n"); // Just a little test to display all the prototypes of the dex file. System.out.println("----------------------- NB proto = " + dexFile.getProtoIdsSize()); for (int testI = 0; testI < dexFile.getProtoIdsSize(); testI++) { //System.out.println(dexFile.getShortyStringFromProtoIndex(testI)); System.out.println(dexFile.getDescriptorFromPrototypeIndex(testI)); } System.out.println("-----------------------\n"); // Just a little test to display all the fields of the dex file. System.out.println("----------------------- NB fields = " + dexFile.getFieldIdsSize()); for (int testI = 0; testI < dexFile.getFieldIdsSize(); testI++) { System.out.println(dexFile.getNameFromFieldIndex(testI)); } System.out.println("-----------------------\n"); } // Builds the typeIdToClassIndexMap, in order to associate the Type_Ids to their // Class Index. Builds as well the classNameToIndex in order to reach a Class quickly according to // its name. typeIdToClassIndexMap = new HashMap<Integer, Integer>(classDefinitionsSize); classNameToIndex = new HashMap<String, Integer>(classDefinitionsSize); for (int index = 0; index < classDefinitionsSize; index++) { dexFile.seek(dexFile.getClassDefinitionOffset(index)); int classIndexRead = dexFile.uint(); typeIdToClassIndexMap.put(classIndexRead, index); String className = dexFile.getStringItemFromTypeIndex(classIndexRead); classNameToIndex.put(className, index); } // Visit all the Classes, or some ? if (classesToVisit == null) { // Visit all the Classes. // We couldn't do it inside the loop before, because we wouldn't have filled the // typeIdToClassIndexMap before visiting the Classes. // We also take car of reading the Classes in the order they were encoded. for (int index = 0; index < classDefinitionsSize; index++) { dexFile.seek(dexFile.getClassDefinitionOffset(index)); int classIndexRead = dexFile.uint(); String className = dexFile.getStringItemFromTypeIndex(classIndexRead); visitClass(applicationVisitor, className, flags); } } else { // Visits only some of the Classes ? for (String className : classesToVisit) { visitClass(applicationVisitor, className, flags); } } // Once everything is parsed, call the VisitEnd of the Application. applicationVisitor.visitEnd(); } // ------------------------------------------------------------------------ // Private methods // ------------------------------------------------------------------------ /** * Visits a Class whose name is given. * @param applicationVisitor the Application visitor. * @param className the name of the Class. * @param flags option flags that can be used to modify the default behavior * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}, * {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. */ private void visitClass(final ApplicationVisitor applicationVisitor, final String className, final int flags) { boolean skipCode = (flags & SKIP_CODE) != 0; boolean skipDebug = (flags & SKIP_DEBUG) != 0; // Finds the Class Index from its name. int classIndex = classNameToIndex.get(className); // Parsing the class itself (class_def_item structure). ClassDefinitionItem classDefinitionItem = dexFile.getClassDefinitionItem(classIndex); int accessFlags = classDefinitionItem.getAccessFlags(); int superclassIndex = classDefinitionItem.getSuperclassIndex(); int interfacesOffset = classDefinitionItem.getInterfacesOffset(); int sourceFileIndex = classDefinitionItem.getSourceFileIndex(); int annotationsOffset = classDefinitionItem.getAnnotationsOffset(); int classDataOffset = classDefinitionItem.getClassDataOffset(); int staticValuesOffset = classDefinitionItem.getStaticValuesOffset(); defaultAnnotations.clear(); // Must be reset for each class class. // Finds the Interfaces, if any. String[] interfaces = null; if (interfacesOffset != 0) { dexFile.seek(interfacesOffset); int interfacesListSize = dexFile.uint(); interfaces = new String[interfacesListSize]; for (int noInterface = 0; noInterface < interfacesListSize; noInterface++) { int typeIndex = dexFile.ushort(); // Gets the right descriptor in the Type_Ids, and the string related. String interfaceName = dexFile.getStringItemFromTypeIndex(typeIndex); interfaces[noInterface] = interfaceName; } } // Finds the Super Class. String superName; if (superclassIndex == Opcodes.NO_INDEX_SIGNED) { // If no Index, it is considered a Root class. superName = Constants.OBJECT_STRING; } else { superName = dexFile.getStringItemFromTypeIndex(superclassIndex); } String[] signature = null; // BEFORE visiting the Class, we have to know if there's a Signature in the Class Annotation. if (ANNOTATIONS && (annotationsOffset != 0)) { dexFile.seek(annotationsOffset); // Gets to the annotations_directory_item. int classAnnotationsOffset = dexFile.uint(); if (classAnnotationsOffset != 0) { // Visits the Signature Annotation, if any. dexFile.seek(classAnnotationsOffset); // Gets to the annotation_set_item. signature = readSignatureAnnotation(); // Visits the Signature annotations. } } // We can now visit the Class. ClassVisitor classVisitor = applicationVisitor.visitClass(accessFlags, className, signature, superName, interfaces); // Gets the values from the static fields. Only Final Static fields are // encoded here. The non-static fields are initialized in the // constructor. Object[] staticValues = null; if (staticValuesOffset != 0) { dexFile.seek(staticValuesOffset); staticValues = decodedEncodedArrayAsObjects(); } // Visits the Class. if (classVisitor != null) { classVisitor.visit(0, accessFlags, className, signature, superName, interfaces); // Calls the visitSource method. if (sourceFileIndex != Opcodes.NO_INDEX_SIGNED) { String sourceFile = dexFile.getStringItemFromStringIndex(sourceFileIndex); classVisitor.visitSource(sourceFile, null); } // Reads the annotations. The Class annotations are parsed and visited directly. // The field, method and parameter annotations are stored into three maps, to // to speed up the search when parsing these elements later. int classAnnotationsOffset = 0; if (ANNOTATIONS && (annotationsOffset != 0)) { dexFile.seek(annotationsOffset); // Get to the annotations_directory_item. classAnnotationsOffset = dexFile.uint(); if (classAnnotationsOffset != 0) { // Visit the outer class. dexFile.seek(classAnnotationsOffset); // Get to the annotation_set_item. readOuterClassAnnotations(classVisitor); // Visit the Outer Class annotations. // Visit annotations of the Class only. dexFile.seek(classAnnotationsOffset); // Get to the annotation_set_item. readAndVisitAnnotations(classVisitor, 0, VisitorType.classVisitor); // Visit the Class annotations. // Visit Default Annotation. They are declared in the Class but ASM // wants them in the Methods they are linked to, so we have to store them and // use them later. dexFile.seek(classAnnotationsOffset); // Get to the annotation_set_item. readDefaultAnnotations(); dexFile.seek(annotationsOffset + 4); // Get to the annotations_directory_item, fields_size field. } // Now pointing on the annotations_directory_item, fields_size field. // We build the annotations maps for use later. int fieldsSize = dexFile.uint(); int annotatedMethodsSize = dexFile.uint(); int annotatedParametersSize = dexFile.uint(); // These instructions must be consecutive, because the fields are // encoded sequentially. fieldAnnotationOffsetsOfClass = dexFile.fillOffsetHashMap(fieldsSize); methodAnnotationOffsetsOfClass = dexFile.fillOffsetHashMap(annotatedMethodsSize); parameterAnnotationOffsetsOfClass = dexFile.fillOffsetHashMap(annotatedParametersSize); // Visits the inner class, if any. if (classAnnotationsOffset != 0) { dexFile.seek(classAnnotationsOffset); // Get to the annotation_set_item. readInnerClassAnnotations(className, classVisitor); dexFile.seek(classAnnotationsOffset); readMemberClassesAnnotations(classVisitor); } } // Gets the four fields from the class_data_item. // The class_data_offset can be 0 in case of a "marker interface", in which case // the class has no field and method. if (classDataOffset != 0) { dexFile.seek(classDataOffset); int nbStaticFields = dexFile.uleb128(); int nbInstanceFields = dexFile.uleb128(); int nbDirectMethods = dexFile.uleb128(); int nbVirtualMethods = dexFile.uleb128(); // Visits the fields. // First, searches for the static fields, then the instance fields. // They are directly after the static fields, so the reader must // not move between the two calls. visitFields(classVisitor, nbStaticFields, staticValues, annotationsOffset); visitFields(classVisitor, nbInstanceFields, null, annotationsOffset); // Visits the methods. First, the direct methods, then the virtual ones. // Same remark as above. visitMethods(classVisitor, nbDirectMethods, annotationsOffset, skipDebug, skipCode); visitMethods(classVisitor, nbVirtualMethods, annotationsOffset, skipDebug, skipCode); } classVisitor.visitEnd(); } } /** * Method that visits the fields, whether they are static or instance fields. * Should only be called by the accept method. The dex file reader must point * on the right structure (5th or 6th structure item of class_data_item). It will point * at the end of the structure on return. * @param classVisitor visitor to call whenever a field is found. * @param nbFields number of fields there are. * @param staticValues array of the values of the Final Static values. Must be * Null if the fields to be encountered are non-Final Static. * @param annotationsOffset offset to the annotations_directory_item. Used to parse * the annotations of the fields. May be 0 if no annotation is present. */ private void visitFields(ClassVisitor classVisitor, int nbFields, Object[] staticValues, int annotationsOffset) { int fieldIndex = 0; int nbStaticValues = (staticValues != null ? staticValues.length : 0); for (int i = 0; i < nbFields; i++) { int readFieldIndex = dexFile.uleb128(); // The field index read is a diff, unless it's the first read. fieldIndex = (i == 0) ? readFieldIndex : fieldIndex + readFieldIndex; int fieldAccessFlags = dexFile.uleb128(); int saveDexFilePosition = dexFile.getPos(); // Save the position to come back there later. dexFile.seek(dexFile.getOffsetFieldIdItem(fieldIndex)); // Gets the fields from the field_it_item structure of this field. dexFile.skipShort(); // Skip ClassIndex, not useful. int typeIndexInFieldId = dexFile.ushort(); int nameIndexInFieldId = dexFile.uint(); // Gets Name and Type. String fieldName = dexFile.getStringItemFromStringIndex(nameIndexInFieldId); String fieldType = dexFile.getStringItemFromTypeIndex(typeIndexInFieldId); // Gets the initialization value, previously parsed. // Only for Final Static fields. Object fieldValue = null; if (i < nbStaticValues) { fieldValue = staticValues[i]; } // BEFORE visiting the Field, we have to know if a Signature Annotation is linked to it, // because it is needed by the visitField method. String[] signature = null; if (ANNOTATIONS && (annotationsOffset != 0)) { if (fieldAnnotationOffsetsOfClass.containsKey(fieldIndex)) { dexFile.seek(fieldAnnotationOffsetsOfClass.get(fieldIndex)); // Now pointing on anotation_set_item. signature = readSignatureAnnotation(); // Visits the Signature annotations. } } FieldVisitor fieldVisitor = classVisitor.visitField(fieldAccessFlags, fieldName, fieldType, signature, fieldValue); // Visits the Field. if (fieldVisitor != null) { // Finds if our current field is linked to an annotation. if (ANNOTATIONS && (annotationsOffset != 0)) { if (fieldAnnotationOffsetsOfClass.containsKey(fieldIndex)) { dexFile.seek(fieldAnnotationOffsetsOfClass.get(fieldIndex)); // Now pointing on anotation_set_item. readAndVisitAnnotations(fieldVisitor, 0, VisitorType.fieldVisitor); } } fieldVisitor.visitEnd(); // Each Field visited must be "ended". } dexFile.seek(saveDexFilePosition); // Recovers position in the fields list. } } /** * Method that visits the methods, whether they are direct or virtual. * Should only be called by the accept method. * The dex file reader must point on the right structure (7th or 8th item * of class_data_item). It will point at the end of the structure on return. * @param classVisitor Visitor to call whenever a method is found. * @param nbMethods Number of methods there are. * @param annotationsOffset offset to the annotations_directory_item. Used to parse * the annotations of the fields. May be 0 if no annotation is present. * @param skipDebug indicates if the debug information must be skipped. * @param skipCode indicates if the code information must be skipped. */ private void visitMethods(ClassVisitor classVisitor, int nbMethods, int annotationsOffset, boolean skipDebug, boolean skipCode) { int methodIndex = 0; for (int i = 0; i < nbMethods; i++) { int readMethodIndex = dexFile.uleb128(); // The method index read may be a diff, unless it's the first read. methodIndex = (i == 0) ? readMethodIndex : methodIndex + readMethodIndex; int methodAccessFlags = dexFile.uleb128(); int codeOffset = dexFile.uleb128(); int saveDexFilePosition = dexFile.getPos(); // Saves the position to come back there later. dexFile.seek(dexFile.getOffsetMethodIdItem(methodIndex)); // Gets the fields from the method_it_item structure of this method. dexFile.skipShort(); // Skips ClassIndex, not useful. int protoIndexInFieldId = dexFile.ushort(); int nameIndexInFieldId = dexFile.uint(); // Gets Name String methodName = dexFile.getStringItemFromStringIndex(nameIndexInFieldId); // Gets Descriptor String methodDescriptor = dexFile.getDescriptorFromPrototypeIndex(protoIndexInFieldId); // BEFORE visiting the method and its annotations, we want to display the // exceptions and signature it may produce. We have to make a first pass of the annotations. String[] exceptions = null; String[] signature = null; if (ANNOTATIONS && (annotationsOffset != 0)) { // Finds if our current method is linked to an exception annotation. if (methodAnnotationOffsetsOfClass.containsKey(methodIndex)) { dexFile.seek(methodAnnotationOffsetsOfClass.get(methodIndex)); // Now pointing on annotation_set_item. List<String> exceptionList = readExceptionAnnotations(); if (exceptionList != null) { exceptions = exceptionList.toArray(new String[exceptionList.size()]); } dexFile.seek(methodAnnotationOffsetsOfClass.get(methodIndex)); // Now pointing on annotation_set_item. signature = readSignatureAnnotation(); // Visits the Signature annotations. } } MethodVisitor methodVisitor = classVisitor.visitMethod(methodAccessFlags, methodName, methodDescriptor, signature, exceptions); // Visits the Method. if (methodVisitor != null) { // Visits the annotations and parameter annotations. if (ANNOTATIONS && (annotationsOffset != 0)) { // First of all, check if there's an Annotation Default. // They've been already parsed in the Class, and stored, so that they // can be read later in the right Method. if (defaultAnnotations.containsKey(methodName)) { AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault(); // As stated in the ASM visitDefaultAnnotation(), it consists only in one // visit(), and one visitEnd(). if (annotationVisitor != null) { Object value = defaultAnnotations.get(methodName).getValue(); visitDefaultAnnotationValue(annotationVisitor,methodName,value); annotationVisitor.visitEnd(); } } // Finds if our current Method is linked to an annotation. if (methodAnnotationOffsetsOfClass.containsKey(methodIndex)) { dexFile.seek(methodAnnotationOffsetsOfClass.get(methodIndex)); // Now pointing on annotation_set_item. readAndVisitAnnotations(methodVisitor, -1, VisitorType.methodVisitor); } // Visits the parameter annotations. if (parameterAnnotationOffsetsOfClass.containsKey(methodIndex)) { dexFile.seek(parameterAnnotationOffsetsOfClass.get(methodIndex)); // Now pointing on anotation_set_ref_list. int nbAnnotations = dexFile.uint(); for (int annotationIndex = 0; annotationIndex < nbAnnotations; annotationIndex++) { int annotationSetItemOffset = dexFile.uint(); int saveReaderPosition = dexFile.getPos(); dexFile.seek(annotationSetItemOffset); readAndVisitAnnotations(methodVisitor, annotationIndex, VisitorType.methodVisitor); dexFile.seek(saveReaderPosition); } } } boolean isOptimizationUsed = false; // Visits the code of this method, or not if optimization possible. if (!skipCode) { /* * If the returned MethodVisitor is in fact a MethodWriter, it * means there is no method adapter between the reader and the * writer. If, in addition, the writer's constant pool was * copied from this reader (mw.cw.aw.ar == this), and the signature * and exceptions of the method have not been changed, then it * is possible to skip all visit events and just copy the * original code of the method to the writer (the access, name * and descriptor can have been changed, this is not important * since they are not copied as is from the reader). */ if (WRITER && (methodVisitor instanceof MethodWriter)) { MethodWriter methodWriter = (MethodWriter)methodVisitor; if (methodWriter.getClassWriter().getApplicationWriter().getApplicationReader() == this) { // Same signature ? String[] otherSignature = methodWriter.getMethod().getSignature(); if (Arrays.equals(signature, otherSignature)) { // Same exceptions ? String[] otherExceptions = methodWriter.getMethod().getExceptionNames(); if (Arrays.equals(exceptions, otherExceptions)) { // The bytecode can be copied. However, we don't do it now. We simply // store the offset the bytecode (code_item actually), and it will be copied // when the Dex file is written. Is can only be copied at this moment // because we might add some more Fields, Methods etc. which will modify // the indexes and will make the copied code use wrong indexes. isOptimizationUsed = true; methodWriter.setStartBytecodeToCopy(codeOffset); } } } } // Else, we manage the visit of the code in a normal way, sending all the events // through the visit. if (!isOptimizationUsed) { MethodCodeReader methodCodeReader = new MethodCodeReader( dexFile, methodVisitor, codeOffset, skipDebug); methodCodeReader.visitMethodCode(); } } // Visits the end of the method, even if the code was skipped or optimization was used. // Even abstract methods have a visitEnd(). methodVisitor.visitEnd(); } dexFile.seek(saveDexFilePosition); // Recovers position in the methods list. } } private void visitDefaultAnnotationValue(AnnotationVisitor annotationVisitor, String name, Object value) { if (value instanceof DefaultAnnotationVisitor) { DefaultAnnotationVisitor dav = (DefaultAnnotationVisitor) value; String desc = dav.getDesc(); if (desc == null) { // Here we have an array AnnotationVisitor av = annotationVisitor.visitArray(name); if (av != null) { for(DefaultAnnotationInformation info : dav.getDefaultAnnotationInformationList()) { visitDefaultAnnotationValue(av, info.getName(), info.getValue()); } av.visitEnd(); } } else { // Here we have an annotation with its description type AnnotationVisitor av = annotationVisitor.visitAnnotation(name,desc); if (av != null) { for(DefaultAnnotationInformation info : dav.getDefaultAnnotationInformationList()) { visitDefaultAnnotationValue(av, info.getName(), info.getValue()); } av.visitEnd(); } } } else if (value instanceof DefaultAnnotationInformation.EnumInfo) { DefaultAnnotationInformation.EnumInfo enumInfo = (DefaultAnnotationInformation.EnumInfo) value; annotationVisitor.visitEnum(name,enumInfo.enumDesc, enumInfo.enumValue); } else if (value instanceof DefaultAnnotationInformation.ClassInfo) { DefaultAnnotationInformation.ClassInfo classInfo = (DefaultAnnotationInformation.ClassInfo) value; annotationVisitor.visitClass(name,classInfo.className); } else { // Primitive type case. annotationVisitor.visit(name, value); } } /** * Reads an encoded value, as described in the encoded_value format. The * parsing may be recursive. * The dex file reader must point on the annotation element to visit. On * return, the reader points on the structure next to it. * @param annotationVisitor Visitor to visit the annotation element. May be null. * @param valueName the name of the value. */ private void readEncodedValue( AnnotationVisitor annotationVisitor, String valueName) { // Decodes the format. int value = dexFile.ubyte(); int valueType = value & 0x1f; int valueArg = (value >> 5) & 0x7; switch (valueType) { case Opcodes.VALUE_ENUM: { int enumIndex = (int)dexFile.sizedLong(valueArg); String enumValue = dexFile.getNameFromFieldIndex(enumIndex); String enumDesc = dexFile.getTypeNameFromFieldIndex(enumIndex); if (annotationVisitor != null) { annotationVisitor.visitEnum(valueName, enumDesc, enumValue); } break; } case Opcodes.VALUE_ARRAY: { readEncodedArray(annotationVisitor, valueName); break; } case Opcodes.VALUE_ANNOTATION: { readEncodedAnnotation(annotationVisitor, valueName); break; } case Opcodes.VALUE_TYPE: { int typeIndex = (int)dexFile.sizedLong(valueArg); String type = dexFile.getStringItemFromTypeIndex(typeIndex); if (annotationVisitor != null) { annotationVisitor.visitClass(valueName, type); } break; } default: { Object val = interpretEncodedValue(valueType, valueArg); if (annotationVisitor != null) { annotationVisitor.visit(valueName, val); } break; } } } /** * Interpret the given valueType/valueArg and returns the corresponding object. * The dex file reader must point after the byte containing the valueType/valueArg. * On return, the reader points after the last byte of the structure. * @param valueType the low order five bits of the first byte of encoded_value encoding. * @param valueArg the high order three bits of the first byte of encoded_value encoding. * @return Object of the right type (Byte, Short...). */ private Object interpretEncodedValue(int valueType, int valueArg) { Object result = null; // Remember that the valueArg, if used as a Size, is encoded as Size-1, // like described in the dex documentation. // VALUE_ENUM, VALUE_ARRAY, VALUE_TYPE and VALUE_ANNOTATION are treated before. switch (valueType) { case Opcodes.VALUE_BOOLEAN: result = Boolean.valueOf(valueArg == 0 ? false : true); break; case Opcodes.VALUE_BYTE: result = Byte.valueOf((byte)dexFile.completeSignSizedLong(dexFile.sizedLong(valueArg), valueArg)); break; case Opcodes.VALUE_SHORT: result = Short.valueOf((short)dexFile.completeSignSizedLong(dexFile.sizedLong(valueArg), valueArg)); break; case Opcodes.VALUE_CHAR: result = Character.valueOf((char)dexFile.sizedLong(valueArg)); break; case Opcodes.VALUE_INT: result = Integer.valueOf((int)dexFile.completeSignSizedLong(dexFile.sizedLong(valueArg), valueArg)); break; case Opcodes.VALUE_LONG: result = Long.valueOf(dexFile.completeSignSizedLong(dexFile.sizedLong(valueArg), valueArg)); break; case Opcodes.VALUE_FLOAT: result = Float.valueOf(Float.intBitsToFloat((int)dexFile.sizedLong(valueArg) << (8 * (3 - valueArg)))); break; case Opcodes.VALUE_DOUBLE: { long temp = dexFile.sizedLong(valueArg) << (8 * (7 - valueArg)); result = Double.valueOf(Double.longBitsToDouble(temp)); } break; case Opcodes.VALUE_FIELD: result = Integer.valueOf((int)dexFile.sizedLong(valueArg)); break; case Opcodes.VALUE_METHOD: result = Integer.valueOf((int)dexFile.sizedLong(valueArg)); break; case Opcodes.VALUE_STRING: int stringIndex = (int)dexFile.sizedLong(valueArg); result = dexFile.getStringItemFromStringIndex(stringIndex); break; case Opcodes.VALUE_NULL: break; default: try { throw new Exception("Unknown value format : 0x" + Integer.toHexString(valueType)); } catch (Exception e) { e.printStackTrace(); } } return result; } /** * Decodes an encoded_array structure and returns an array of Objects. This method is useful when * the elements of the array must be then parsed and sent separately. If the array must be used as * a whole and unique Object, later to be parsed, better use the decodeEncodedArray method. * The dex file reader must point on the encoded_array. On return, the reader points * after the last byte of the structure. * @return an array of Objects. */ private Object[] decodedEncodedArrayAsObjects() { int arraySize = dexFile.uleb128(); Object[] result = new Object[arraySize]; for (int i = 0; i < arraySize; i++) { int value = dexFile.ubyte(); int valueType = value & 0x1f; int valueArg = (value >> 5) & 0x7; result[i] = interpretEncodedValue(valueType, valueArg); } return result; } /** * Decodes an encoded_array structure and returns an Object that contains an array of elements. This * method must be used over decodedEncodedArrayAsObjects if we need to send one single Object to * be parsed later. * The dex file reader must point on the encoded_array. On return, the reader points * after the last byte of the structure. * @return an Object containing an array of elements. */ private Object decodeEncodedArray() { // Decodes the encoded_array format. int arraySize = dexFile.uleb128(); // Empty arrays can be of any type, it doesn't matter as nothing will be put inside. if (arraySize == 0) { return new int[0]; } int value = dexFile.ubyte(); int valueType = value & 0x1f; int valueArg = (value >> 5) & 0x7; // We have to create a typed array according to the type. switch (valueType) { case Opcodes.VALUE_INT: { int[] array = new int[arraySize]; array[0] = (Integer)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Integer)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_BOOLEAN: { boolean[] array = new boolean[arraySize]; array[0] = (Boolean)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Boolean)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_BYTE: { byte[] array = new byte[arraySize]; array[0] = (Byte)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Byte)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_SHORT: { short[] array = new short[arraySize]; array[0] = (Short)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Short)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_CHAR: { char[] array = new char[arraySize]; array[0] = (Character)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Character)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_LONG: { long[] array = new long[arraySize]; array[0] = (Long)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Long)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_FLOAT: { float[] array = new float[arraySize]; array[0] = (Float)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Float)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_DOUBLE: { double[] array = new double[arraySize]; array[0] = (Double)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (Double)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_STRING: { String[] array = new String[arraySize]; array[0] = (String)interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = (String)interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } case Opcodes.VALUE_TYPE: case Opcodes.VALUE_FIELD: case Opcodes.VALUE_METHOD: case Opcodes.VALUE_NULL: { Object[] array = new Object[arraySize]; array[0] = interpretEncodedValue(valueType, valueArg); for (int i = 1; i < arraySize; i++) { value = dexFile.ubyte(); array[i] = interpretEncodedValue(value & 0x1f, (value >> 5) & 0x7); } return array; } default: try { throw new Exception("Unhandled value format : " + Integer.toHexString(valueType)); } catch (Exception e) { e.printStackTrace(); } return null; } } /** * Reads an encoded array, as described in the encoded_array format and * visits it with the given visitor. We make the distinction between primitive * and non-primitive Arrays. On the first case, we only call the visit method on * an array composed on the primitive values. On the second case, we call the * visitArray method, and visit each elements. * The dex file reader must point on the encoded array to visit. On return, the reader * points on the structure next to it. * @param annotationVisitor Visitor to visit the encoded elements. May be null. * @param valueName the name of the value. */ private void readEncodedArray( AnnotationVisitor annotationVisitor, String valueName) { // If primitive array, must build the array, and visit it (visit method, // NOT visitArray, specialized for non primitive types). if (isEncodedArrayPrimitive()) { Object primitiveArray = decodeEncodedArray(); if (annotationVisitor != null) { annotationVisitor.visit(valueName, primitiveArray); } } else { // Non primitive array. We visit each elements, as well as calling the // visitArray method. int nbElements = dexFile.uleb128(); AnnotationVisitor arrayElementsAnnotationVisitor = (annotationVisitor == null) ? null : annotationVisitor.visitArray(valueName); for (int elementIndex = 0; elementIndex < nbElements; elementIndex++) { readEncodedValue(arrayElementsAnnotationVisitor, null); } if (arrayElementsAnnotationVisitor != null) arrayElementsAnnotationVisitor.visitEnd(); } } /** * The dex file reader must point on the annotation element to test. On return, the * reader position <i>is</i> saved. * @return true if the next Encoded Value is a primitive type. */ private boolean isEncodedValuePrimitive() { boolean isPrimitive = true; int saveReaderPosition = dexFile.getPos(); int value = dexFile.ubyte(); int valueType = value & 0x1f; switch (valueType) { case Opcodes.VALUE_ENUM: case Opcodes.VALUE_ANNOTATION: case Opcodes.VALUE_ARRAY: case Opcodes.VALUE_STRING: case Opcodes.VALUE_TYPE: isPrimitive = false; break; } dexFile.seek(saveReaderPosition); return isPrimitive; } /** * Indicates if an encoded_array structure whether composed of Primitive type. * Only the first element is tested, though. * The dex file reader must point on the encoded_array element to test. On return, * the reader position <i>is</i> saved. * @return true if the encoded_array structure is composed of Primitive types. */ private boolean isEncodedArrayPrimitive() { boolean isPrimitive = true; int saveReaderPosition = dexFile.getPos(); int nbElements = dexFile.uleb128(); // Just a security, probably useless. if (nbElements > 0) { isPrimitive = isEncodedValuePrimitive(); } dexFile.seek(saveReaderPosition); return isPrimitive; } // ---------------------------------------- // Annotations management // ---------------------------------------- /** * Reads the Annotations given by the reader, and calls the given visitor to * visit it. Several annotations can be encoded. Annotations about inner/outer class, Throws, * Default, Signature are skipped, as Signature are handled when visiting the Classes, Methods * and Fields (before, actually). * The dex file reader must point on annotation_set_item. * @param visitor visitor that must visit the values. Can be either a ClassVisitor, * FieldVisitor or MethodVisitor. * @param parameterNumber only used for MethodVisitor to call visitParameterAnnotation instead * of visitAnnotation, but only if this number is >=0. MethodVisitor wanting to call * visitAnnotation must use -1 for example. * @param visitorType indicates the type of the visitor (class, field or method visitor). */ private void readAndVisitAnnotations(Object visitor, int parameterNumber, VisitorType visitorType) { int[] annotationOffsets = dexFile.getAnnotationItemOffsetsFromAnnotationSetItem(); for (int annotationIndex = 0, nbAnnotations = annotationOffsets.length; annotationIndex < nbAnnotations; annotationIndex++) { dexFile.seek(annotationOffsets[annotationIndex]); // Treats the Annotation Item. short classVisibility = dexFile.ubyte(); boolean isAnnotationVisible = (classVisibility == Opcodes.VISIBILITY_RUNTIME); // Now pointing on encoded_annotation format. String annotationType = dexFile.getStringItemFromTypeIndex(dexFile.uleb128()); // Skips the Throws, Inner/Outer class, Default, Signature annotations. if (((((annotationType.equals(Constants.EXCEPTION_ANNOTATION_INTERNAL_NAME)) || (annotationType.equals(Constants.ENCLOSING_CLASS_ANNOTATION_INTERNAL_NAME))) || ((annotationType.equals(Constants.INNER_CLASS_ANNOTATION_INTERNAL_NAME))) || (annotationType.equals(Constants.ENCLOSING_METHOD_ANNOTATION_INTERNAL_NAME))) || (annotationType.equals(Constants.MEMBER_CLASSES_ANNOTATION_INTERNAL_NAME))) || ((annotationType.equals(Constants.ANNOTATION_DEFAULT_INTERNAL_NAME))) || (annotationType.equals(Constants.SIGNATURE_ANNOTATION_INTERNAL_NAME))) { continue; } AnnotationVisitor annotationVisitor = null; if (visitorType == VisitorType.classVisitor) { annotationVisitor = ((ClassVisitor)visitor).visitAnnotation(annotationType, isAnnotationVisible); } else if (visitorType == VisitorType.fieldVisitor) { annotationVisitor = ((FieldVisitor)visitor).visitAnnotation(annotationType, isAnnotationVisible); } else if (visitorType == VisitorType.methodVisitor) { if (parameterNumber < 0) { annotationVisitor = ((MethodVisitor)visitor).visitAnnotation(annotationType, isAnnotationVisible); } else { annotationVisitor = ((MethodVisitor)visitor).visitParameterAnnotation(parameterNumber, annotationType, isAnnotationVisible); } } // Visits annotation and its elements. if (annotationVisitor != null) { int nbAnnotationElements = dexFile.uleb128(); for (int i = 0; i < nbAnnotationElements; i++) { readAnnotationElement(annotationVisitor); } annotationVisitor.visitEnd(); } } } /** * Reads the Annotations, check only the Exception annotations, and returns * their internal names. * The dex file reader must point on an annotation_set_item. * @return the internal name of each exception, or Null if none were found. */ private List<String> readExceptionAnnotations() { // Creates the Exception Annotation Parser and makes the search for this // specific annotation in the annotations. ISpecificAnnotationParser specificAnnotationParser = new ExceptionSpecificAnnotationParser(Constants.EXCEPTION_ANNOTATION_INTERNAL_NAME); boolean foundAnnotation = parseSpecificAnnotations(new ExceptionAnnotationVisitor(api), specificAnnotationParser); return foundAnnotation ? ((ExceptionSpecificAnnotationParser)specificAnnotationParser).getExceptions() : null; } /** * Reads the Default Annotations, if any, and sets the DefaultAnnotations field. * The dex file reader must point on an annotation_set_item. */ private void readDefaultAnnotations() { // Creates the Default Annotation Parser and makes the search for this // specific annotation in the annotations. ISpecificAnnotationParser specificAnnotationParser = new DefaultAnnotationSpecificAnnotationParser(Constants.ANNOTATION_DEFAULT_INTERNAL_NAME); boolean foundAnnotation = parseSpecificAnnotations(new DefaultAnnotationVisitor(api, Constants.ANNOTATION_DEFAULT_INTERNAL_NAME), specificAnnotationParser); if (foundAnnotation) { // We found a Default Annotation. However, this method was called as we were // visiting a Class. But ASM calls the visitDefaultAnnotation for each Method // which has a Default Annotation linked to it. So we store our result and will // use it for each Method having a Default Annotation. DefaultAnnotationVisitor dav = (DefaultAnnotationVisitor)((DefaultAnnotationSpecificAnnotationParser)specificAnnotationParser).getAnnotationVisitor(); List <DefaultAnnotationInformation> entries = dav.getDefaultAnnotationInformationList(); if (entries.size() == 1) { dav = (DefaultAnnotationVisitor) entries.get(0).getValue(); for (DefaultAnnotationInformation info : dav.getDefaultAnnotationInformationList()) { String methodName = info.getName(); defaultAnnotations.put(methodName, info); } } } } /** * Reads the Signature Annotation, check only the Signature annotations. This is * useful to find Signature linked to Classes, Methods and Fields. * The dex file reader must point on an annotation_set_item. * @return an Array of Strings describing the Signature, or Null if none were found. */ private String[] readSignatureAnnotation() { String[] result = null; // Creates the Signature Annotation Parser and makes the search for this // specific annotation in the annotations. ISpecificAnnotationParser specificAnnotationParser = new SignatureSpecificAnnotationParser(Constants.SIGNATURE_ANNOTATION_INTERNAL_NAME); boolean foundAnnotation = parseSpecificAnnotations(new SignatureAnnotationVisitor(api), specificAnnotationParser); if (foundAnnotation) { result = ((SignatureSpecificAnnotationParser)specificAnnotationParser).getSignature(); } return result; } /** * Reads the Annotations, check only the MemberClasses annotations. This is * useful to find Inner Classes. * The dex file reader must point on an annotation_set_item. */ private void readMemberClassesAnnotations(ClassVisitor classVisitor) { // Creates the Member Classes Annotation Parser and makes the search for this // specific annotation in the annotations. ISpecificAnnotationParser specificAnnotationParser = new MemberClassesSpecificAnnotationParser(Constants.MEMBER_CLASSES_ANNOTATION_INTERNAL_NAME); boolean foundAnnotation = parseSpecificAnnotations(new MemberClassesAnnotationVisitor(api), specificAnnotationParser); if (foundAnnotation) { MemberClassesSpecificAnnotationParser parser = (MemberClassesSpecificAnnotationParser)specificAnnotationParser; // Get the informations from the parser. List<String> classes = parser.getInnerClasses(); for (String name : classes) { int i = name.lastIndexOf('$'); // Reconstruction of the innerName and outerName. String innerName = name.substring(i + 1, name.length() - 1); // Removes also the ";" at the end. // Obfuscators may remove mention of the inner name. We must cope with it. String outerName = (i<0) ? null : name.substring(0, i) + ";"; // Adds the ";" at the end. // classVisitor.visitInnerClass(name, outerName, innerName, accessFlags); classVisitor.visitMemberClass(name, outerName, innerName); } } } /** * Reads the Annotations, check only the ones related to inner classes * (EnclosingClass, InnerClasses), and call the visitInnerClass accordingly. * The dex file reader must point on an annotation_set_item. * @param className * @param classVisitor the visitor to visit the inner class. */ private void readInnerClassAnnotations(String className, ClassVisitor classVisitor) { // Creates the InnerClass and EnclosingClass Parsers and makes the search for these // specific annotations in the annotations. // First, the parser for the Inner Class annotation. ISpecificAnnotationParser innerClassSpecificAnnotationParser = new InnerClassSpecificAnnotationParser(Constants.INNER_CLASS_ANNOTATION_INTERNAL_NAME); // Then, the parser for the Enclosing Class annotation. ISpecificAnnotationParser enclosingClassSpecificAnnotationParser = new EnclosingClassSpecificAnnotationParser(Constants.ENCLOSING_CLASS_ANNOTATION_INTERNAL_NAME); boolean foundFirstAnnotation = parseSpecificAnnotations(new InnerClassAnnotationVisitor(api), innerClassSpecificAnnotationParser); boolean foundSecondAnnotation = parseSpecificAnnotations(new EnclosingClassAnnotationVisitor(api), enclosingClassSpecificAnnotationParser); if (foundFirstAnnotation && foundSecondAnnotation) { InnerClassSpecificAnnotationParser innerParser = (InnerClassSpecificAnnotationParser)innerClassSpecificAnnotationParser; EnclosingClassSpecificAnnotationParser enclosingParser = (EnclosingClassSpecificAnnotationParser)enclosingClassSpecificAnnotationParser; // Gets the information. String outerClassName = enclosingParser.getClassName(); String simpleNameInnerClass = innerParser.getSimpleNameInnerClass(); classVisitor.visitInnerClass(className, outerClassName, simpleNameInnerClass, innerParser.getAccessFlagsInnerClass()); } } /** * Reads the Annotations, checks only the ones related to outer classes * (EnclosingClass), and calls the visitOuterClass accordingly. * The InnerClass annotation is also needed to follow the ASM behavior (outer, then inner * visitor called). * The dex file reader must point on an annotation_set_item. * @param classVisitor the visitor to visit the inner class. */ private void readOuterClassAnnotations(ClassVisitor classVisitor) { // Create the Enclosing Method and InnerClass Annotation Parsers and makes the search for these // specific annotations. ISpecificAnnotationParser enclosingMethodSpecificAnnotationParser = new EnclosingMethodSpecificAnnotationParser(Constants.ENCLOSING_METHOD_ANNOTATION_INTERNAL_NAME); // Then, the parser for the Inner Class annotation. Even though // we're looking for Outer class, the Inner Class annotation is encoded, and // gives information about the class that's actually embedded. ASM always calls // visitInnerClass after visitOuterClass. ISpecificAnnotationParser innerClassSpecificAnnotationParser = new InnerClassSpecificAnnotationParser(Constants.INNER_CLASS_ANNOTATION_INTERNAL_NAME); boolean foundFirstAnnotation = parseSpecificAnnotations(new EnclosingMethodAnnotationVisitor(api), enclosingMethodSpecificAnnotationParser); boolean foundSecondAnnotation = parseSpecificAnnotations(new InnerClassAnnotationVisitor(api), innerClassSpecificAnnotationParser); if (foundFirstAnnotation && foundSecondAnnotation) { int methodId = ((EnclosingMethodSpecificAnnotationParser)enclosingMethodSpecificAnnotationParser).getClassId(); // Reaches the outer method. MethodIdItem methodIdItem = dexFile.getMethodIdItem(methodId); String nameEnclosingClass = dexFile.getStringItemFromTypeIndex(methodIdItem.getClassIndex()); // Get name from class_idx. String methodDescriptor = dexFile.getDescriptorFromPrototypeIndex(methodIdItem.getPrototypeIndex()); String methodName = dexFile.getStringItemFromStringIndex(methodIdItem.getNameIndex()); // visitOuterClass must only be called if an enclosing class exists. if (nameEnclosingClass != null) { classVisitor.visitOuterClass(nameEnclosingClass, methodName, methodDescriptor); } // Visits the InnerClass. InnerClassSpecificAnnotationParser innerParser = (InnerClassSpecificAnnotationParser)innerClassSpecificAnnotationParser; String simpleNameInnerClass = innerParser.getSimpleNameInnerClass(); String outerClassName = dexFile.getNameFromMethodIndex(methodId); String nameInnerClass = null; // Reconstruction the name of the inner class. /* if ((nameEnclosingClass != null) && (simpleNameInnerClass != null) && (outerClassName != null)) { nameInnerClass = nameEnclosingClass.replace(';', '$') + '1' + simpleNameInnerClass; } classVisitor.visitInnerClass(nameInnerClass, outerClassName, simpleNameInnerClass, innerParser.getAccessFlagsInnerClass()); */ if ((nameEnclosingClass != null) && (simpleNameInnerClass != null) && (outerClassName != null)) { nameInnerClass = nameEnclosingClass.replace(';', '$') + '1' + simpleNameInnerClass + ";"; } classVisitor.visitInnerClass(nameInnerClass, nameEnclosingClass, simpleNameInnerClass, innerParser.getAccessFlagsInnerClass()); } } /** * Parses a specific annotation and make an annotation visitor visit it. * This method is made to allow different visitors to visit * the same annotations for different purposes. * The dex file reader must point on an annotation_set_item. Its position is saved. * @param av the annotation visitor. * @param specificAnnotationParser annotation parser to perform an operation when * finding the desired annotation. * @return true if the annotation has been found. */ private boolean parseSpecificAnnotations(AnnotationVisitor av, ISpecificAnnotationParser specificAnnotationParser) { int savePositionreader = dexFile.getPos(); boolean foundAnnotation = false; int[] annotationOffsets = dexFile.getAnnotationItemOffsetsFromAnnotationSetItem(); for (int annotationIndex = 0, nbAnnotations = annotationOffsets.length; annotationIndex < nbAnnotations; annotationIndex++) { dexFile.seek(annotationOffsets[annotationIndex]); // Treats the Annotation Item. dexFile.skipByte(); // Skip visibility. // Now pointing on encoded_annotation format. String annotationType = dexFile.getStringItemFromTypeIndex(dexFile.uleb128()); // Search if the annotation is what we are looking for. if (specificAnnotationParser.getAnnotationName().equals(annotationType)) { foundAnnotation = true; // Visits the annotations. // There should be only one, but we use a loop, just in case. int nbAnnotationElements = dexFile.uleb128(); for (int i = 0; i < nbAnnotationElements; i++) { readAnnotationElement(av); } specificAnnotationParser.treat(dexFile, this, av); } } dexFile.seek(savePositionreader); return foundAnnotation; } /** * Reads an annotation element, as described by the annotation_element format, * and visit it with the given visitor. On return, the reader points on the structure next * to it. This is useful because the annotation_element items are ordered one after the other * in the encoded_annotation format. * The dex file reader must point on the annotation element to visit. * @param annotationVisitor Visitor to visit the annotation element. May be null. */ private void readAnnotationElement(AnnotationVisitor annotationVisitor) { String annotationName = dexFile.getStringItemFromStringIndex(dexFile.uleb128()); readEncodedValue(annotationVisitor, annotationName); } /** * Reads an Encoded Annotation structure, as described by the encoded_annotation * format, and visit it with the given visitor. On return, the reader points * on the structure next to it. * The dex file reader must point on the annotation element to visit. * @param annotationVisitor Visitor to visit the annotation element. * @param valueName name of the value. */ private void readEncodedAnnotation(AnnotationVisitor annotationVisitor, String valueName) { String annotationType = dexFile.getStringItemFromTypeIndex(dexFile.uleb128()); AnnotationVisitor nestedAnnotationVisitor = annotationVisitor.visitAnnotation(valueName, annotationType); // It is important to read the elements even if the given visitor is null. // Because the data is stored linearly, we must continue to parse it to reach the // data "behind" the ones we want to skip. We simply don't have to visit the elements // if the visitor is null. int nbElements = dexFile.uleb128(); for (int elementIndex = 0; elementIndex < nbElements; elementIndex++) { readAnnotationElement(nestedAnnotationVisitor); } if (nestedAnnotationVisitor != null) { nestedAnnotationVisitor.visitEnd(); } } // -------------------------------------------------- // "Constant Pool optimization" methods // -------------------------------------------------- /** * Copies the constant pool data into the given {@link ApplicationWriter}. Should * be called before the {@link #accept(ApplicationVisitor,int)} method. * * However, contrary to ASM, only the Strings, Types, Fields and Methods indexes are actually copied, * because that's the only things the methods are referring to. Also note that the elements given to * the ApplicationWriter are stored as symbolic indexes. Indeed, more Fields, Strings, Types or * Methods could be added, thus making the references already used wrong. * * @param applicationWriter the {@link ApplicationWriter} to copy constant pool into. */ public void copyPool(ApplicationWriter applicationWriter) { // Copy the Strings. for (int i = 0, size = dexFile.getStringIdsSize(); i < size; i++) { String string = dexFile.getStringItemFromStringIndex(i); applicationWriter.addStringFromApplicationReader(string); } // Copy the Types. for (int i = 0, size = dexFile.getTypeIdsSize(); i < size; i++) { String type = dexFile.getStringItemFromTypeIndex(i); applicationWriter.addTypeNameFromApplicationReader(type); } // Copy the Fields. for (int i = 0, size = dexFile.getFieldIdsSize(); i < size; i++) { FieldIdItem fii = dexFile.getFieldIdItem(i); String className = dexFile.getStringItemFromTypeIndex(fii.getClassIndex()); String type = dexFile.getStringItemFromTypeIndex(fii.getTypeIndex()); String fieldName = dexFile.getStringItemFromStringIndex(fii.getNameIndex()); applicationWriter.addFieldFromApplicationReader(className, type, fieldName); } // Copy the Methods. for (int i = 0, size = dexFile.getMethodIdsSize(); i < size; i++) { MethodIdItem mii = dexFile.getMethodIdItem(i); String className = dexFile.getStringItemFromTypeIndex(mii.getClassIndex()); String methodName = dexFile.getStringItemFromStringIndex(mii.getNameIndex()); String prototype = dexFile.getDescriptorFromPrototypeIndex(mii.getPrototypeIndex()); applicationWriter.addMethodFromApplicationReader(className, prototype, methodName); } } }