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