/* 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.structureWriter;
import java.util.ArrayList;
import java.util.List;
import org.ow2.asmdex.Opcodes;
import org.ow2.asmdex.lowLevelUtils.ByteVector;
import org.ow2.asmdex.structureCommon.LocalVariable;
/**
* Simple class representing a Method. It's a mix between the method_id_item, but also encoded_method
* structures.
*
* The equals and hashCode methods have been overridden in order to detect easily
* duplicates in HashMaps.
*
* A Method is identified by its methodName, className and prototype. Access and ExceptionIndexes
* are NOT taken in account to differentiate the methods.
*
* Access may be ACC_UNKNOWN if the method created is referenced, but not yet parsed (like when an
* instruction refers to a not yet parsed method).
*
* A Method has a code_item. However, it may not if the method is abstract or interface.
*
* @author Julien Névo
*/
public class Method implements Comparable<Method>, IAnnotationsHolder {
/**
* Name of the Class owning the Method.
*/
final private String className;
/**
* Prototype of the Method, in TypeDescriptor format.
*/
final private Prototype prototype;
/**
* The name of the Method.
*/
final private String methodName;
/**
* The access flags of the Field.
*/
private int access;
/**
* The names of the exceptions. May be Null.
*/
private String[] exceptionNames;
/**
* Start in bytes from the beginning of the Dex file, of the bytecode to copy, in case the
* "ConstantPool" optimization is used if the Writer is directly linked to the Reader.
* It includes the code_item header.
*/
private int startBytecodeToCopy;
/**
* Start in bytes from the beginning of the Dex file, of the debug_info_item to copy, in case the
* "ConstantPool" optimization is used if the Writer is directly linked to the Reader.
*/
private int startDebugInfoToCopy;
/**
* The code of the Method, its Instructions. May be Null if abstract/interface.
*/
private CodeItem codeItem;
/**
* Annotation_set_item, representing all the annotation_items for this Method.
*/
private AnnotationSetItem annotationSetItem = new AnnotationSetItem();
/**
* Annotation_set_ref_item, representing the annotation_set_item for the Annotated Parameters
* of this Method.
*/
private AnnotationSetRefList annotatedParameterSetRefList;
/**
* The name of the parameters (return parameter not included).
*/
private String[] parameters;
/**
* The Local Variables of this Method.
*/
private List<LocalVariable> localVariables = new ArrayList<LocalVariable>();
/**
* The Signature of the Field. May be Null.
*/
private String[] signature;
/**
* The hashcode is cached.
*/
final private int hashcode;
/**
*
* @param methodName name of the Method.
* @param className name of the Class owning the Method.
* @param methodDescriptor Prototype of the Method, in TypeDescriptor format.
*/
public Method(String methodName, String className, Prototype methodDescriptor) {
this.methodName = methodName;
this.className = className;
prototype = methodDescriptor;
hashcode = calculateHashCode(methodName, className, prototype);
}
/**
* Full init if this is the witness kept.
* @param access The access flags of the Field.
* @param signature the signature of the method. May be Null.
* @param exceptionNames the names of the exceptions. May be Null.
* @param constantPool the Constant Pool of the Application.
*/
public void init(int access, String[] signature, String[] exceptionNames, ConstantPool constantPool) {
this.access = access;
this.exceptionNames = exceptionNames;
this.signature = signature;
if (!isUnknown() && supportsCodeItem()) {
codeItem = new CodeItem(this, constantPool);
}
annotatedParameterSetRefList = new AnnotationSetRefList(prototype.getNbParameters(), this);
}
// ----------------------------------------------
// Public methods.
// ----------------------------------------------
/**
* Adds a Local Variable to this Method.
*/
public void addLocalVariable(LocalVariable localVariable) {
localVariables.add(localVariable);
}
/**
* Adds information to the current Method. This is useful <i>only</i> if this
* Method has the ACC_UNKNOWN flag, which means Instructions referred to it while it has not yet been
* visited, declaring it but not giving all the information it should have.
*/
public void completeInformation(int access, String[] exceptionNames, String[] signature, ConstantPool constantPool) {
this.access = access;
this.exceptionNames = exceptionNames;
this.signature = signature;
if ((codeItem == null) && !isUnknown() && supportsCodeItem()) { // It SHOULD be Null in all cases.
codeItem = new CodeItem(this, constantPool);
}
}
/**
* Calculates the hashcode of the Method according to its given identifiers.
* @param methodName the name of the Method.
* @param className the Class it belongs to.
* @param prototype the Prototype of the Method.
* @return the hashcode of the Method.
*/
public static int calculateHashCode(String methodName, String className, Prototype prototype) {
return methodName.hashCode() + className.hashCode() + prototype.hashCode();
}
/**
* Calculates the hashcode of the Method according to its given identifiers.
* @param methodName the name of the Method.
* @param className the Class it belongs to.
* @param prototypeHashCode the hashcode of the Prototype.
* @return the hashcode of the Method.
*/
public static int calculateHashCode(String methodName, String className, int prototypeHashCode) {
return methodName.hashCode() + className.hashCode() + prototypeHashCode;
}
/**
* Indicates whether the Method is virtual (not static, private, or constructor).
* @return true if the Method is virtual.
*/
public boolean isVirtual() {
return ((access & Opcodes.ACC_STATIC) + (access & Opcodes.ACC_PRIVATE)
+ (access & Opcodes.ACC_CONSTRUCTOR)) == 0;
}
/**
* Indicates whether the Method is direct (static and/or private and/or constructor).
* @return true if the Method is direct.
*/
public boolean isDirect() {
return !isVirtual();
}
/**
* Indicates whether the Method is static.
* @return true if the Method is static.
*/
public boolean isStatic() {
return (access & Opcodes.ACC_STATIC) != 0;
}
/**
* Indicates whether the Method is abstract or interface.
* @return true if the Method is abstract or interface.
*/
public boolean isAbstractOrInterface() {
return ((access & Opcodes.ACC_ABSTRACT) | (access & Opcodes.ACC_INTERFACE)) != 0;
}
/**
* Indicates whether the Method is native.
* @return true if the Method is static.
*/
public boolean isNative() {
return (access & Opcodes.ACC_NATIVE) != 0;
}
/**
* Indicates whether the Method is unknown (referred to, but not yet parsed).
* @return true if the Method is unknown.
*/
public boolean isUnknown() {
return (access & Opcodes.ACC_UNKNOWN) != 0;
}
/**
* Indicates whether a method contains java code (a CodeItem).
* @return true if the Method has code.
*/
public boolean supportsCodeItem() {
return (access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE | Opcodes.ACC_NATIVE)) == 0;
}
/**
* Indicates whether the Method uses the "this" parameter.
* @return true if the Method uses the "this" parameter.
*/
public boolean isUsingThis() {
// "This" is used if the Method is not static, and is not a Constructor and not abstract.
// Patch for "not abstract" by Panxiaobo #316341 - patch 16.
return (!isStatic()) && ((access & Opcodes.ACC_CONSTRUCTOR) == 0) && supportsCodeItem();
}
/**
* Adds an annotation_item to the annotations_set_items.
* @param annotationItem the Annotation Item to add.
*/
public void addAnnotationItem(AnnotationItem annotationItem) {
annotationSetItem.addAnnotationItem(annotationItem);
}
/**
* Adds an annotation_item to a parameter of this Method.
* @param parameterIndex zero-based index of the argument.
* @param annotationItem the Annotation Item to add.
*/
public void addParameterAnnotationItem(int parameterIndex, AnnotationItem annotationItem) {
annotatedParameterSetRefList.addAnnotationItem(parameterIndex, annotationItem);
}
/**
* Closes and registers the annotation_set_items and annotation_set_ref_items of this Method.
* This must only be done once when the Method has been fully visited.
* @param constantPool the Constant Pool of the Application.
*/
public void closeAnnotations(ConstantPool constantPool) {
annotatedParameterSetRefList.close();
// Now that the annotation_set_item are fully known, we can register it to the Constant Pool.
constantPool.addAnnotationSetItemToConstantPool(annotationSetItem);
// We do the same for the annotation_set_ref_list.
constantPool.addAnnotationSetRefListToConstantPool(annotatedParameterSetRefList);
}
/**
* Generates the Instructions code, as Dalvik bytecode, in the codeItem buffer, as well as its
* header. It uses <i>symbolic</i> references, so must be parsed again to link them to the "real"
* elements.
* Also fills the debug_code_item in this instance. Note however that the debug_info_offset field
* in the code_item header is not set, because we don't know where the debug_info_item is encoded
* for now. Note that the debug_info_item is not written here, only built.
* The alignment is not managed here, but must be by the calling method.
*/
public void generateCodeItemCode() {
codeItem.generateCodeItemCode();
}
/**
* Frees all the structures (list of instructions...) of the code_item and the debug_info_item
* so that they don't consume memory. This <i>MUST</i> be done after having generated the bytecode, once
* the method has been parsed and its end visited.
*/
public void free() {
codeItem.free();
}
// ----------------------------------------------
// Getters and setters.
// ----------------------------------------------
/**
* Returns the code_item code (including the code_item header and the bytecode), <i>without</i> the
* try/catch fields after the insns field.
* It uses <i>symbolic</i> references, so must be parsed again to link them to the "real"
* elements.
* @return the code_item code, without the try/catch fields.
*/
public ByteVector getCodeItemCode() {
return codeItem.getCodeItemCode();
}
/**
* Returns the try/catch section of the code_item, beginning by the possible padding after the insns field,
* or Null if no try/catch is present.
* It uses <i>symbolic</i> references, so must be parsed again to link them to the "real"
* elements.
* @return the try/catch code, or Null.
*/
public ByteVector getCodeItemTryCatch() {
return codeItem.getCodeItemTryCatch();
}
/**
* Sets the start of the bytecode to copy from the input Dex file to the
* output. This is only useful when using the optimization that consists in copying part of
* the Constant Pool and the bytecode of methods that doesn't change, if the Reader is linked
* to the Writer with no Adapter to modify the methods in between.
* @param start start in bytes from the beginning of the Dex file where the bytecode is. This
* includes the code_item header.
*/
public void setStartBytecodeToCopy(int start) {
startBytecodeToCopy = start;
}
/**
* Gets the start in byte of the bytecode to copy from the input Dex file.
* This is only useful when using the optimization that consists in copying part of
* the Constant Pool and the bytecode of methods that doesn't change, if the Reader is linked
* to the Writer with no Adapter to modify the methods in between.
* @return the start in byte of the bytecode, or 0 if the optimization is not used.
*/
public int getStartBytecodeToCopy() {
return startBytecodeToCopy;
}
/**
* Sets the start of the debug_info_item to copy from the input Dex file to the
* output. This is only useful when using the optimization that consists in copying part of
* the Constant Pool and the bytecode of methods that doesn't change, if the Reader is linked
* to the Writer with no Adapter to modify the methods in between.
* @param start start in bytes from the beginning of the Dex file where the debug_info_item is.
*/
public void setStartDebugInfoToCopy(int start) {
startDebugInfoToCopy = start;
}
/**
* Gets the start in byte of the debug_info_item to copy from the input Dex file.
* This is only useful when using the optimization that consists in copying part of
* the Constant Pool and the bytecode of methods that doesn't change, if the Reader is linked
* to the Writer with no Adapter to modify the methods in between.
* @return the start in byte of the debug_info_item, or 0 if the optimization is not used.
*/
public int getStartDebugInfoToCopy() {
return startDebugInfoToCopy;
}
// ----------------------------------------------
// Getters and Setters.
// ----------------------------------------------
/**
* Returns the name of the Class owning the Method.
* @return the name of the Class owning the Method.
*/
public String getClassName() {
return className;
}
/**
* Returns the prototype of the Method, in TypeDescriptor format.
* @return the prototype of the Method, in TypeDescriptor format.
*/
public Prototype getPrototype() {
return prototype;
}
/**
* Returns the name of the Method.
* @return the name of the Method.
*/
public String getMethodName() {
return methodName;
}
/**
* Returns the access flags.
* @return the access flags.
*/
public int getAccess() {
return access;
}
/**
* Returns the exception names. May be Null.
* @return the exception names, or Null.
*/
public String[] getExceptionNames() {
return exceptionNames;
}
/**
* Returns the Code Item linked to this Method. May be Null if it hasn't any (if abstract or interface).
* @return the Code Item linked to this Method or Null.
*/
public CodeItem getCodeItem() {
return codeItem;
}
/**
* Returns the annotation_set_item this structure currently contains.
* @return the annotation_set_item this structure currently contains.
*/
@Override
public AnnotationSetItem getAnnotationSetItem() {
return annotationSetItem;
}
/**
* Returns the number of annotation_items this structure currently contains. Does not concern the
* parameter annotations.
* @return the number of annotation_items this structure currently contains.
*/
@Override
public int getNbAnnotations() {
return annotationSetItem.getNbAnnotationItems();
}
/**
* Return the annotation_set_ref_list of this Method, indicating what are the annotations on each
* parameters.
* @return the annotation_set_ref_list of this Method.
*/
public AnnotationSetRefList getAnnotatedParameterSetRefList() {
return annotatedParameterSetRefList;
}
/**
* Returns the number of annotation_set_items the Method has. They are only used by the
* parameter annotations (one annotation_set_item per argument, even if this one in particular is
* not annotated).
* @return the number of annotation_set_items the Method has.
*/
public int getNbParameterAnnotations() {
return annotatedParameterSetRefList.getNbAnnotationSetItemsUsed();
}
/**
* Returns the number of parameters of this Method (doesn't count the Return parameter). May be 0.
* @return the number of parameters of this Method.
*/
public int getNbParameters() {
return prototype.getNbParameters();
}
/**
* Returns the name of the parameters of this Method (excluding the Return parameter). May be null if
* the Debug Info didn't provide them or was not visited.
* @return the name of the parameters of this Method, or null.
*/
public String[] getParameters() {
return parameters;
}
/**
* Sets the name of the parameters used by this Method.
* @param parameters name of the parameters.
*/
public void setParameters(String[] parameters) {
this.parameters = parameters;
}
/**
* Sets the first line number of the Method, but only if none has been found before.
* @param firstLineNumber the first line number of this Method.
*/
public void setFirstLineNumberIfNeeded(int firstLineNumber) {
codeItem.setFirstLineNumber(firstLineNumber);
}
/**
* Returns the Signature of the Method. May be Null.
* @return the Signature of the Method. May be Null.
*/
public String[] getSignature() {
return signature;
}
/**
* Returns the Local Variable list. It may be empty.
* @return the Local Variable list.
*/
public List<LocalVariable> getLocalVariables() {
return localVariables;
}
// ----------------------------------------------
// Overridden methods.
// ----------------------------------------------
@Override
public int hashCode() {
return hashcode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Method) {
Method mii = (Method)obj;
return methodName.equals(mii.methodName) &&
(className.equals(mii.className) && prototype.equals(mii.prototype));
}
return false;
}
@Override
public int compareTo(Method method) {
if (this == method) {
return 0;
}
// Tests class owning name first.
int compare = className.compareTo(method.className);
if (compare != 0) {
return compare;
}
// Tests the names.
compare = methodName.compareTo(method.methodName);
if (compare != 0) {
return compare;
}
// Tests the prototype.
return prototype.compareTo(method.prototype);
}
}