/*
* Copyright (C) 2001 Mika Riekkinen, Joni Suominen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package alt.jiapi.reflect;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import org.apache.log4j.Category;
import alt.jiapi.Runtime;
import alt.jiapi.file.Attribute;
import alt.jiapi.file.ClassFile;
import alt.jiapi.file.CodeAttribute;
import alt.jiapi.file.ConstantPool;
import alt.jiapi.file.ExceptionsAttribute;
import alt.jiapi.file.LineNumberTableAttribute;
import alt.jiapi.file.LocalVariableTableAttribute;
import alt.jiapi.file.Method;
import alt.jiapi.file.SyntheticAttribute;
import alt.jiapi.reflect.instruction.Opcodes;
/*
* NOTE:
* When instructions, exception table, etc. are modified,
* make sure that update() method is called when finished.
* This method call syncs changes back to alt.jiapi.file
* package.
* Currently, JiapiClass.getByteCode() triggers update()
* method.
*/
/**
* This class represents a Method.
*
* @author Mika Riekkinen
* @author Joni Suominen
* @version $Revision: 1.34 $ $Date: 2005/06/13 10:28:27 $
*/
public class JiapiMethod {
private static Category log = Runtime.getLogCategory(JiapiMethod.class);
private Method method;
private Signature signature;
private JiapiClass declaringClass;
private InstructionList instructions;
private TryBlock[] tryBlocks = null;
private List lineNumberTable;
private List exceptionTable;
/**
* Constructor for JiapiMethod.
*/
public JiapiMethod(Method m) {
this.method = m;
this.signature = new Signature(m.getDescriptor());
}
/**
* Get the name of this Method.
*
* @return name of the method
*/
public String getName() {
return method.getName();
}
/**
* Gets modifiers of this Method.
*
* @return Modifiers of this method
* @see java.lang.reflect.Modifier
*/
public int getModifiers() {
return method.getAccessFlags();
}
// public abstract setModifiers(int modifiers);
/**
* Gets the return type of a method.
*
* @return the return type of a method
*/
public String getReturnType() {
return signature.getReturnType();
}
/**
* Gets the signature of this method.
*
* @return method signature
*/
public Signature getSignature() {
return signature;
}
/**
* Get the InstructionFactory.
*/
public InstructionFactory getInstructionFactory() {
return new InstructionFactory(method.getConstantPool());
}
/**
* Get an InstructionList, that represents a byte-code of this method.
*
* @return InstructionList
*/
public InstructionList getInstructionList() {
CodeAttribute ca = null;
if (instructions == null) {
ca = (CodeAttribute)getAttribute("Code");
if (ca == null) {
return null;
}
byte[] byteCode = ca.getByteCode();
instructions = new InstructionList(byteCode,
method.getConstantPool());
instructions.setDeclaringMethod(this);
if (true/* configurable by jiapi.properties */) {
// create private representation of
// line number table, so that we can update
// its offsets, when class is instrumented
lineNumberTable = new LinkedList();
LineNumberTableAttribute lnta = (LineNumberTableAttribute)ca.getAttribute(LineNumberTableAttribute.ATTRIBUTE_NAME);
if (lnta != null) {
List entries = lnta.getEntries();
Iterator i = entries.iterator();
while(i.hasNext()) {
LineNumberTableAttribute.Entry entry =
(LineNumberTableAttribute.Entry)i.next();
Instruction ins =
instructions.instructionAtOffset(entry.getStartPc());
lineNumberTable.add(new LNTableEntry(entry, ins));
}
}
else {
//System.out.println("ERROR: could not get line number table");
}
}
this.exceptionTable = new LinkedList();
List eTable = ca.getExceptionTable();
if (eTable != null) {
Iterator i = eTable.iterator();
while(i.hasNext()) {
CodeAttribute.ExceptionTableEntry entry =
(CodeAttribute.ExceptionTableEntry)i.next();
Instruction start =
instructions.instructionAtOffset(entry.getStartPc());
Instruction end =
instructions.instructionAtOffset(entry.getEndPc());
Instruction handler =
instructions.instructionAtOffset(entry.getHandlerPc());
this.exceptionTable.add(new ETEntry(entry, start, end,
handler));
}
}
else {
//System.out.println("ERROR: could not get exception table");
}
}
return instructions;
}
/**
* Get an array of try blocks within this method.
* If a physical try blocks are constructed so, that one block
* is inside another one, they are both returned separately in
* TryBlock array. It is then the responsibility of the developer
* to check whether or not try blocks have this kind of a relationship.
*
* @return an array of TryBlocks. If this method has no try blocks,
* an Array of length 0 is returned.
*/
TryBlock[] getTryBlocks() {
return null;
}
// Package protected for now!!!
void addTryBlock(InstructionList tryBlock, String exceptionName,
Instruction handlerStart) {
}
// Alternative:
void addTryBlock(TryBlock b) {
}
/**
* Gets the class that declared this JiapiMethod.
*
* @return a JiapiClass which declares this method
*/
public JiapiClass getDeclaringClass() {
return declaringClass;
}
/**
* Sets the declaring class.
*
* @param declaringClass A JiapiClass, that declares this JiapiField
*/
void setDeclaringClass(JiapiClass declaringClass) {
this.declaringClass = declaringClass;
}
/**
* Gets the parameter types in this method's signature. Each
* of the parameters are loaded with the same Loader as
* the declaring class was loaded with.
*
* @return an array of parameter types
* @exception ClassNotFoundException is thrown, if one of the types
* could not be loaded
* @see #getParameterTypeNames()
*/
public JiapiClass[] getParameterTypes() throws ClassNotFoundException {
String[] paramNames = getParameterTypeNames();
JiapiClass[] types = new JiapiClass[paramNames.length];
Loader loader = getDeclaringClass().getLoader();
int i = 0;
try {
for (i = 0; i < paramNames.length; i++) {
types[i] = loader.loadClass(paramNames[i]);
}
}
catch(java.io.IOException ioe) {
throw new ClassNotFoundException(paramNames[i]);
}
return types;
}
/**
* Gets the names of parameter types in this method's signature.
* (e.g. "java.lang.String", "com.Foo", "int")
*
* @return an array of parameter type names
*/
public String[] getParameterTypeNames() {
return signature.getParameters();
}
/**
* Gets the parameter types of exceptions this method can throw.
* Types are loaded with the same Loader as declaring class was loaded
* with.
*
* @return an array of parameter types
* @exception ClassNotFoundException is thrown, if one of the types
* could not be loaded
* @see #getExceptionNames()
*/
public JiapiClass[] getExceptionTypes() throws ClassNotFoundException {
String[] exceptionNames = getExceptionNames();
JiapiClass[] types = new JiapiClass[exceptionNames.length];
Loader loader = getDeclaringClass().getLoader();
int i = 0;
try {
for (i = 0; i < exceptionNames.length; i++) {
types[i] = loader.loadClass(exceptionNames[i]);
}
}
catch(java.io.IOException ioe) {
throw new ClassNotFoundException(exceptionNames[i]);
}
return types;
}
/**
* Gets the names of exceptions this method can throw.
* (e.g. "java.lang.ClassNotFoundException").
*
* @return an array of parameter type names
*/
public String[] getExceptionNames() {
ExceptionsAttribute a =
(ExceptionsAttribute)getAttribute("Exceptions");
if (a == null) {
return new String[0];
}
return a.getExceptionNames();
}
/**
* Adds a local variable for this method.
*
* @param type a type of the local variable
* @param name a name of the local variable
* @return a local variable
*/
public LocalVariable addLocalVariable(String type, String name) {
return null;
}
/*
* CONSIDER adding this method
*
* Copies source JiapiMethod to this JiapiMethod.
* ExceptionTables and all the other metadata is copied.
* As well as InstructionList. InstructionList gets appended
* to InstructionList of this JiapiMethod.
*/
/*public*/ void copy(JiapiMethod source) {
// TBDL
}
/**
* Convert this JiapiMethod to String.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
String ms = Modifier.toString(getModifiers());
sb.append(ms);
if (ms.length() > 0) {
sb.append(' ');
}
sb.append(getReturnType());
sb.append(' ');
sb.append(getName());
sb.append('(');
String[] params = getParameterTypeNames();
for (int i = 0; i < params.length; i++) {
sb.append(params[i]);
if (i < params.length - 1) {
sb.append(',');
}
}
sb.append(')');
String[] eNames = getExceptionNames();
if (eNames.length > 0) {
sb.append(" throws ");
for(int i = 0; i < eNames.length; i++) {
sb.append(eNames[i]);
if (i < eNames.length - 1) {
sb.append(", ");
}
}
}
//getInstructionList();
// if (this.exceptionTable != null) {
// Iterator i = exceptionTable.iterator();
// sb.append("Exception table:\nstart_pc end_pc handler_pc catch_type\n");
// while(i.hasNext()) {
// ETEntry entry = (ETEntry)i.next();
// sb.append("start: " + entry.getStart() + " " +
// "end: " + entry.getEnd() + " " +
// "handler: " +entry.getHandler() + "\n");
// }
// }
// else {
// // System.out.println("Exception table was null");
// }
// sb.append("\nmax-locals: ");
// sb.append(getMaxLocals());
// sb.append(", max-stack: ");
// sb.append(getMaxStack());
return sb.toString();
}
/**
* Gets all the method attributes defined in ClassFile.
*/
private List getAttributes() {
return method.getAttributes();
}
Method getMethod() {
return method;
}
/**
* Gets all the method attributes defined in ClassFile.
*/
Attribute getAttribute(String name) {
List l = method.getAttributes();
Iterator i = l.iterator();
while(i.hasNext()) {
Attribute a = (Attribute)i.next();
if (a != null) { // NOTE: Should never be null
if (a.getName().equals(name)) {
return a;
}
}
}
return null;
}
// This method is called, when bytecode has changed, so that
// we can update Method and CodeAttribute
void update() {
if (instructions != null) {
instructions.updateOffsets();
// Update branch offsets
updateBranchOffsets();
CodeAttribute ca = (CodeAttribute)getAttribute("Code");
ca.setByteCode(instructions.getBytes());
// Update max locals & max stack
updateMaxLocals(ca, instructions);
updateMaxStack(ca, instructions);
}
updateLineNumberTableOffsets();
updateExceptionTableOffsets();
}
//
// Update routines follow.
// Update changes from reflect layer to file layer
//
private short updateMaxLocals(CodeAttribute ca, InstructionList il) {
// maxLocals = locals + (this); // locals + 1;
short maxLocals = (short)(getParameterTypeNames().length);
// if (!Modifier.isStatic(getModifiers())) {
// maxLocals++; // 'this'
// }
LocalVariableTableAttribute lvta = (LocalVariableTableAttribute)getAttribute(LocalVariableTableAttribute.ATTRIBUTE_NAME);
// Add local variables other than ones in method signature
if (lvta != null) {
maxLocals += lvta.getLocalVariables().size();
}
short maxLocalsInInstructions = 0;
for(int i = 0; i < il.size(); i++) {
Instruction ins = il.get(i);
switch(ins.getOpcode()) {
case Opcodes.FLOAD_0:
case Opcodes.FSTORE_0:
case Opcodes.ILOAD_0:
case Opcodes.ISTORE_0:
case Opcodes.ALOAD_0:
case Opcodes.ASTORE_0:
if (maxLocals < 1) {
maxLocals = 1;
}
break;
case Opcodes.FLOAD_1:
case Opcodes.FSTORE_1:
case Opcodes.LSTORE_0: // long occupies two slots
case Opcodes.LLOAD_0:
case Opcodes.DSTORE_0: // double occupies two slots
case Opcodes.DLOAD_0:
case Opcodes.ILOAD_1:
case Opcodes.ISTORE_1:
case Opcodes.ALOAD_1:
case Opcodes.ASTORE_1:
if (maxLocals < 2) {
maxLocals = 2;
}
break;
case Opcodes.FLOAD_2:
case Opcodes.FSTORE_2:
case Opcodes.LSTORE_1: // long occupies two slots
case Opcodes.LLOAD_1:
case Opcodes.DSTORE_1: // double occupies two slots
case Opcodes.DLOAD_1:
case Opcodes.ILOAD_2:
case Opcodes.ISTORE_2:
case Opcodes.ALOAD_2:
case Opcodes.ASTORE_2:
if (maxLocals < 3) {
maxLocals = 3;
}
break;
case Opcodes.FLOAD_3:
case Opcodes.FSTORE_3:
case Opcodes.LSTORE_2: // long occupies two slots
case Opcodes.LLOAD_2:
case Opcodes.DSTORE_2: // double occupies two slots
case Opcodes.DLOAD_2:
case Opcodes.ILOAD_3:
case Opcodes.ISTORE_3:
case Opcodes.ALOAD_3:
case Opcodes.ASTORE_3:
if (maxLocals < 4) {
maxLocals = 4;
}
break;
case Opcodes.DSTORE_3: // double occupies two slots
case Opcodes.DLOAD_3:
case Opcodes.LSTORE_3: // long occupies two slots
case Opcodes.LLOAD_3:
if (maxLocals < 5) {
maxLocals = 5;
}
break;
case Opcodes.DSTORE: // double occupies two slots
case Opcodes.DLOAD:
case Opcodes.LSTORE: // long occupies two slots
case Opcodes.LLOAD:
byte[] bytes_l = ins.getBytes();
int mloc_l = bytes_l[1];
if (maxLocals < mloc_l + 1) {
maxLocals = (short)(mloc_l + 1);
}
break;
case Opcodes.FLOAD:
case Opcodes.FSTORE:
case Opcodes.ILOAD:
case Opcodes.ISTORE:
case Opcodes.ALOAD:
case Opcodes.ASTORE:
byte[] bytes = ins.getBytes();
short mloc = bytes[1];
if (maxLocals < mloc) {
maxLocals = mloc;
}
break;
}
}
maxLocals++;
ca.setMaxLocals(maxLocals);
return maxLocals;
}
/*
* Calculate max stack. If instructions' stack usage is positive,
* increase maxStackUsage.
*/
private short updateMaxStack(CodeAttribute ca, InstructionList il) {
short maxStack = 0;
short su = 0;
if (il != null) { // Abstract methods do not have instructionlist
for(int i = 0; i < il.size(); i++) {
Instruction ins = il.get(i);
su += ins.stackUsage();
if (su > maxStack) {
maxStack = su;
}
// stack usage above says only stack consumption.
// Some instructions both consume stack and produce
// stack. Fix it here.
if (ins.getOpcode() == Opcodes.GETSTATIC ||
ins.getOpcode() == Opcodes.GETFIELD) {
maxStack++;
}
}
ca.setMaxStack(maxStack);
}
return maxStack;
}
/*
* Update branch intructions' offsets.
*/
private void updateBranchOffsets() {
for(int i = 0; i < instructions.size(); i++) {
Instruction ins = instructions.get(i);
if (ins instanceof BranchInstruction) {
BranchInstruction bIns = (BranchInstruction)ins;
// Check, that target actually is found in this list
// If not, Leave offsets as is, and hope for the
// best
if (instructions.indexOf(bIns.getTarget()) != -1) {
int branchOffset = bIns.getOffset();
int targetOffset = bIns.getTarget().getOffset();
// Set offset of the branch to (target - branch)
bIns.setTargetOffset(targetOffset - branchOffset);
}
else {
log.warn("Target instruction for branch was not found. This is most likely because branch-instruction was copied from some other instruction-list, and target was not copied. Branch offset is left as is.");
}
}
}
}
private void updateLineNumberTableOffsets() {
if (lineNumberTable != null) {
Iterator i = lineNumberTable.iterator();
while(i.hasNext()) {
LNTableEntry entry = (LNTableEntry)i.next();
entry.update();
}
}
}
private void updateExceptionTableOffsets() {
if (exceptionTable != null) {
Iterator i = exceptionTable.iterator();
while(i.hasNext()) {
ETEntry entry = (ETEntry)i.next();
entry.update();
}
}
}
public /*private*/ int getMaxLocals() {
CodeAttribute ca = (CodeAttribute)getAttribute("Code");
if (ca == null) {
throw new NullPointerException("No Code attribute found");
}
ca.setByteCode(getInstructionList().getBytes());
return updateMaxLocals(ca, getInstructionList());
}
public /*private*/ int getMaxStack() {
CodeAttribute ca = (CodeAttribute)getAttribute("Code");
//ca.setByteCode(instructions.getBytes());
return updateMaxStack(ca, getInstructionList());
}
/**
* ExceptionTableEntry.
*/
private class ETEntry {
private Instruction start;
private Instruction end;
private Instruction handler;
private CodeAttribute.ExceptionTableEntry entry;
ETEntry(CodeAttribute.ExceptionTableEntry entry, Instruction start,
Instruction end, Instruction handler) {
// System.out.println("Created ETEntry: " + start + ", " + end+
// ", " + handler);
this.entry = entry;
this.start = start;
this.end = end;
this.handler = handler;
}
/**
* Called, when bytecode instrumentation is done.
* This method updates offset of the instruction to reflect
* offset changes.
*/
void update() {
// System.out.println("Updating ETEntry: start " + entry.getStartPc()+
// "-->" + start.getOffset() +
// entry.getEndPc() + "-->" + end.getOffset() +
// entry.getHandlerPc() + "-->"+handler.getOffset()
// );
entry.setStartPc(start.getOffset());
entry.setEndPc(end.getOffset());
entry.setHandlerPc(handler.getOffset());
}
Instruction getStart() {
return start;
}
Instruction getEnd() {
return end;
}
Instruction getHandler() {
return handler;
}
}
/**
* Checks, whether or not this JiapiMethod is synthetic.
*
* @return true, if this JiapiMethod is synthetic
*/
public boolean isSynthetic() {
return (method.getAttribute(SyntheticAttribute.ATTRIBUTE_NAME) !=null);
}
}