Mirror of the BLOAT repository
https://www.cs.purdue.edu/homes/hosking/bloat/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1757 lines
43 KiB
1757 lines
43 KiB
/*
|
|
* All files in the distribution of BLOAT (Bytecode Level Optimization and
|
|
* Analysis tool for Java(tm)) are Copyright 1997-2001 by the Purdue
|
|
* Research Foundation of Purdue University. All rights reserved.
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package EDU.purdue.cs.bloat.editor;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
|
|
import EDU.purdue.cs.bloat.reflect.*;
|
|
import EDU.purdue.cs.bloat.tree.*;
|
|
import EDU.purdue.cs.bloat.util.*;
|
|
|
|
/**
|
|
* <tt>MethodEditor</tt> provides a means to edit a method of a class. A
|
|
* <tt>MethodEditor</tt> gathers information from a <tt>MethodInfo</tt>
|
|
* object. It then goes through the bytecodes of the method and extracts
|
|
* information about the method. Along the way it creates an array of
|
|
* <tt>Instruction</tt> and <tt>Label</tt> objects that represent the code.
|
|
* Additionally, it models the try-catch blocks in the method and their
|
|
* associated exception handlers.
|
|
*
|
|
* @see EDU.purdue.cs.bloat.reflect.MethodInfo
|
|
* @see Label
|
|
* @see Instruction
|
|
*
|
|
* @author Nate Nystrom (<a
|
|
* href="mailto:nystrom@cs.purdue.edu">nystrom@cs.purdue.edu</a>)
|
|
*/
|
|
public class MethodEditor implements Opcode {
|
|
public static boolean PRESERVE_DEBUG = true;
|
|
|
|
public static boolean UNIQUE_HANDLERS = false;
|
|
|
|
public static boolean OPT_STACK_2 = false; // byte-code level stack opt
|
|
|
|
private ClassEditor editor; // The editor that "owns" this MethodEditor
|
|
|
|
private MethodInfo methodInfo; // Representation of this method
|
|
|
|
private String name; // The name of this method
|
|
|
|
private Type type; // Type variable representing the class's
|
|
|
|
// descriptor
|
|
private LinkedList code; // Label and Instruction objects representing
|
|
// this
|
|
|
|
// method's bytecode
|
|
private LinkedList tryCatches; // Info about the try-catch blocks in this
|
|
// method
|
|
|
|
private LinkedList lineNumbers;
|
|
|
|
private LocalVariable[] params; // The parameters to this method
|
|
|
|
private int maxStack; // Max size of stack while running this method
|
|
|
|
private int maxLabel; // Label pointing to the end of the code
|
|
|
|
private int maxLocals; // Maximum number of local variables
|
|
|
|
private boolean isDirty; // Has the method been modified?
|
|
|
|
private Map locals; // Maps indices to that LocalVariable
|
|
|
|
private Type[] paramTypes; // Types of parameters (accounts for wides)
|
|
|
|
public UseMap uMap; // Structure for remembering use/def info
|
|
|
|
private boolean isDeleted = false;
|
|
|
|
public MethodEditor(final ClassEditor editor, final int modifiers,
|
|
final Class returnType, final String methodName,
|
|
final Class[] paramTypes, final Class[] exceptionTypes) {
|
|
|
|
this(editor, modifiers, (returnType == null ? null : Type
|
|
.getType(returnType)), methodName, MethodEditor
|
|
.convertTypes(paramTypes), MethodEditor
|
|
.convertTypes(exceptionTypes));
|
|
}
|
|
|
|
private static Type[] convertTypes(final Class[] classes) {
|
|
if (classes == null) {
|
|
return (null);
|
|
}
|
|
|
|
final Type[] types = new Type[classes.length];
|
|
for (int i = 0; i < types.length; i++) {
|
|
types[i] = Type.getType(classes[i]);
|
|
}
|
|
return (types);
|
|
}
|
|
|
|
/**
|
|
* Creates a new <code>MethodEditor</code> for editing a method in a given
|
|
* class with the given modifiers, return type, name, parameter types, and
|
|
* exception types.
|
|
*
|
|
* @param modifiers
|
|
* The {@link EDU.purdue.cs.bloat.reflect.Modifiers modifiers}
|
|
* for the new method
|
|
* @param returnType
|
|
* The return type of the method. If, <code>returnType</code>
|
|
* is null, the return type is assumed to be <code>void</code>.
|
|
* @param methodName
|
|
* The name of the method
|
|
* @param paramTypes
|
|
* The types of the parameters to the new method. If
|
|
* <code>paramTypes</code> is <code>null</code>, then we
|
|
* assume that there are no arguments.
|
|
* @param exceptionTypes
|
|
* The types of exceptions that may be thrown by the new method.
|
|
* If <code>exceptionTypes</code> is <code>null</code>, then
|
|
* we assume that no exceptions are declared.
|
|
*/
|
|
public MethodEditor(final ClassEditor editor, final int modifiers,
|
|
Type returnType, final String methodName, Type[] paramTypes,
|
|
Type[] exceptionTypes) {
|
|
|
|
// if(ClassEditor.DEBUG) {
|
|
// System.out.println("Creating MethodEditor " +
|
|
// System.identityHashCode(this));
|
|
// Thread.dumpStack();
|
|
// }
|
|
|
|
this.editor = editor;
|
|
this.name = methodName;
|
|
|
|
if (returnType == null) {
|
|
returnType = Type.VOID;
|
|
}
|
|
|
|
if (paramTypes == null) {
|
|
paramTypes = new Type[0];
|
|
}
|
|
|
|
if (exceptionTypes == null) {
|
|
exceptionTypes = new Type[0];
|
|
}
|
|
|
|
// Get the indices in the constant pool for all sorts of
|
|
// interesting information
|
|
final ConstantPool cp = editor.constants();
|
|
final int nameIndex = cp.getUTF8Index(methodName);
|
|
this.type = Type.getType(paramTypes, returnType);
|
|
Assert.isTrue(this.type.isMethod(), "Method type not method: "
|
|
+ this.type);
|
|
final int typeIndex = cp.getTypeIndex(this.type);
|
|
final int exceptionIndex = cp.getUTF8Index("Exceptions");
|
|
|
|
final int[] exceptionTypeIndices = new int[exceptionTypes.length];
|
|
for (int i = 0; i < exceptionTypes.length; i++) {
|
|
final Type eType = exceptionTypes[i];
|
|
exceptionTypeIndices[i] = cp.getTypeIndex(eType);
|
|
}
|
|
|
|
final int codeIndex = cp.getUTF8Index("Code");
|
|
|
|
final ClassInfo classInfo = editor.classInfo();
|
|
this.methodInfo = classInfo.addNewMethod(modifiers, typeIndex,
|
|
nameIndex, exceptionIndex, exceptionTypeIndices, codeIndex);
|
|
|
|
// Initialize other parts of this MethodEditor as best we can
|
|
this.code = new LinkedList();
|
|
this.tryCatches = new LinkedList();
|
|
this.lineNumbers = new LinkedList();
|
|
this.locals = new HashMap();
|
|
|
|
// Be sure to include space for the this pointer.
|
|
if (!isStatic()) {
|
|
this.params = new LocalVariable[type.stackHeight() + 1];
|
|
|
|
} else {
|
|
this.params = new LocalVariable[type.stackHeight()];
|
|
}
|
|
|
|
// Initalize the params to hold LocalVariables representing the
|
|
// parameters
|
|
this.paramTypes = new Type[this.params.length];
|
|
final Type[] indexedParams = this.type().indexedParamTypes();
|
|
if (!isStatic()) {
|
|
// First parameter is the this pointer
|
|
this.paramTypes[0] = this.declaringClass().type();
|
|
for (int q = 1; q < this.paramTypes.length; q++) {
|
|
this.paramTypes[q] = indexedParams[q - 1];
|
|
}
|
|
|
|
} else {
|
|
for (int q = 0; q < this.paramTypes.length; q++) {
|
|
this.paramTypes[q] = indexedParams[q];
|
|
}
|
|
}
|
|
|
|
for (int q = 0; q < this.params.length; q++) {
|
|
this.params[q] = new LocalVariable(null, this.paramTypes[q], q);
|
|
}
|
|
|
|
this.maxLocals = this.paramTypes.length;
|
|
|
|
this.isDirty = true;
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param editor
|
|
* The class containing the method.
|
|
* @param methodInfo
|
|
* The method to edit.
|
|
*
|
|
* @see ClassEditor
|
|
* @see EDU.purdue.cs.bloat.reflect.MethodInfo MethodInfo
|
|
*/
|
|
public MethodEditor(final ClassEditor editor, final MethodInfo methodInfo) {
|
|
// if(ClassEditor.DEBUG) {
|
|
// System.out.println("Creating MethodEditor " +
|
|
// System.identityHashCode(this));
|
|
// Thread.dumpStack();
|
|
// }
|
|
|
|
final ConstantPool cp = editor.constants();
|
|
|
|
this.methodInfo = methodInfo;
|
|
this.editor = editor;
|
|
this.isDirty = false;
|
|
|
|
maxLabel = 0;
|
|
maxLocals = methodInfo.maxLocals();
|
|
maxStack = methodInfo.maxStack();
|
|
locals = new HashMap();
|
|
|
|
int index;
|
|
int i;
|
|
int j;
|
|
|
|
index = methodInfo.nameIndex();
|
|
name = (String) cp.constantAt(index);
|
|
|
|
index = methodInfo.typeIndex();
|
|
final String typeName = (String) cp.constantAt(index);
|
|
type = Type.getType(typeName);
|
|
|
|
code = new LinkedList();
|
|
tryCatches = new LinkedList();
|
|
lineNumbers = new LinkedList();
|
|
|
|
// Be sure to include space for the this pointer.
|
|
if (!isStatic()) {
|
|
params = new LocalVariable[type.stackHeight() + 1];
|
|
} else {
|
|
params = new LocalVariable[type.stackHeight()];
|
|
}
|
|
|
|
// Initalize the params to hold LocalVariables representing the
|
|
// parameters
|
|
paramTypes = new Type[params.length];
|
|
final Type[] indexedParams = this.type().indexedParamTypes();
|
|
if (!isStatic()) {
|
|
// First parameter is the this pointer
|
|
paramTypes[0] = this.declaringClass().type();
|
|
for (int q = 1; q < paramTypes.length; q++) {
|
|
paramTypes[q] = indexedParams[q - 1];
|
|
}
|
|
|
|
} else {
|
|
for (int q = 0; q < paramTypes.length; q++) {
|
|
paramTypes[q] = indexedParams[q];
|
|
}
|
|
}
|
|
for (int q = 0; q < params.length; q++) {
|
|
params[q] = new LocalVariable(null, paramTypes[q], q);
|
|
}
|
|
|
|
// Get the byte code for this method
|
|
final byte[] array = methodInfo.code();
|
|
|
|
if ((array == null) || (array.length == 0)) {
|
|
return;
|
|
}
|
|
|
|
// Build the array of Instructions (and Labels).
|
|
//
|
|
// next[i] contains the index of the instruction following i.
|
|
// targets[i] contains an array of the branch targets of i.
|
|
// lookups[i] contains an array of the switch lookup values of i.
|
|
// label[i] contains a label if a label should be inserted before i.
|
|
// lines[i] contains the line number of instruction i (or 0).
|
|
//
|
|
final int[] next = new int[array.length];
|
|
final int[][] targets = new int[array.length][];
|
|
final int[][] lookups = new int[array.length][];
|
|
final Label[] label = new Label[array.length + 1];
|
|
LocalVariable[][] localVars;
|
|
|
|
if (MethodEditor.PRESERVE_DEBUG && (array.length < 0x10000)) {
|
|
// LocalDebugInfo maps a local variable in the generated code
|
|
// back to the name of a local variable in the original Java
|
|
// source file.
|
|
final LocalDebugInfo[] locals = methodInfo.locals();
|
|
int max = 0;
|
|
|
|
// Find the maximum local variable index for the code.
|
|
for (i = 0; i < locals.length; i++) {
|
|
if (max <= locals[i].index()) {
|
|
max = locals[i].index() + 1;
|
|
}
|
|
}
|
|
|
|
// localVars[i][j] contains the a LocalVariable, j, for
|
|
// instruction i
|
|
localVars = new LocalVariable[array.length][max];
|
|
|
|
// Create LocalVariables for those locals with debug info
|
|
// and set the params array so the name and type will be returned
|
|
// be paramAt.
|
|
//
|
|
for (i = 0; i < locals.length; i++) {
|
|
final int start = locals[i].startPC();
|
|
final int end = start + locals[i].length();
|
|
|
|
final String localName = (String) cp.constantAt(locals[i]
|
|
.nameIndex());
|
|
final String localType = (String) cp.constantAt(locals[i]
|
|
.typeIndex());
|
|
|
|
final LocalVariable var = new LocalVariable(localName, Type
|
|
.getType(localType), locals[i].index());
|
|
|
|
for (int pc = start; pc <= end; pc++) {
|
|
if (pc < localVars.length) {
|
|
localVars[pc][locals[i].index()] = var;
|
|
}
|
|
}
|
|
|
|
if ((start == 0) && (locals[i].index() < params.length)) {
|
|
params[locals[i].index()] = var;
|
|
}
|
|
}
|
|
|
|
// Create a list of line number entries and add a label at the
|
|
// start PC for each entry.
|
|
final LineNumberDebugInfo[] lineNumbers = methodInfo.lineNumbers();
|
|
|
|
for (i = 0; i < lineNumbers.length; i++) {
|
|
final int start = lineNumbers[i].startPC();
|
|
|
|
if (label[start] == null) {
|
|
label[start] = new Label(start, false);
|
|
}
|
|
|
|
addLineNumberEntry(label[start], lineNumbers[i].lineNumber());
|
|
}
|
|
} else {
|
|
// We're not preserving debugging information. So, we don't
|
|
// need to worry about which local variables are live at
|
|
// which instructions.
|
|
|
|
localVars = new LocalVariable[array.length][0];
|
|
}
|
|
|
|
// Create a label for the beginning of the code and for each
|
|
// branch target. Also set next[i] for all instructions i.
|
|
//
|
|
label[0] = new Label(0, true);
|
|
|
|
int numInst = 0;
|
|
|
|
for (i = 0; i < array.length; i = next[i]) {
|
|
// Examine an instruction and extract its target labels
|
|
// and switch lookups
|
|
next[i] = munchCode(array, i, targets, lookups);
|
|
numInst++;
|
|
|
|
// Generate Labels for all the targets local to the code
|
|
if (targets[i] != null) {
|
|
for (j = 0; j < targets[i].length; j++) {
|
|
if (targets[i][j] < array.length) {
|
|
label[targets[i][j]] = new Label(targets[i][j], true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a label for the beginning and end of protected blocks and the
|
|
// beginning of catch blocks. Add a TryCatch entry for each
|
|
// exception handler in the method.
|
|
//
|
|
final Catch[] exc = methodInfo.exceptionHandlers();
|
|
|
|
for (i = 0; i < exc.length; i++) {
|
|
final int start = exc[i].startPC();
|
|
final int end = exc[i].endPC();
|
|
final int handler = exc[i].handlerPC();
|
|
|
|
label[start] = new Label(start, true);
|
|
label[end] = new Label(end, true);
|
|
label[handler] = new Label(handler, true);
|
|
|
|
final Type catchType = (Type) cp
|
|
.constantAt(exc[i].catchTypeIndex());
|
|
|
|
addTryCatch(new TryCatch(label[start], label[end], label[handler],
|
|
catchType));
|
|
}
|
|
|
|
// Go through the bytecode and create Instructions and build the
|
|
// code linked list.
|
|
// Add a label for instructions following branches.
|
|
for (i = 0; i < array.length; i = next[i]) {
|
|
final Instruction inst = new Instruction(array, i, targets[i],
|
|
lookups[i], localVars[i], cp);
|
|
|
|
if (label[i] != null) {
|
|
code.add(label[i]);
|
|
}
|
|
|
|
code.add(inst);
|
|
|
|
if (inst.isJump() || inst.isReturn() || inst.isJsr()
|
|
|| inst.isRet() || inst.isThrow() || inst.isSwitch()) {
|
|
|
|
// Add a label for the next instruction after a branch.
|
|
if (next[i] < array.length) {
|
|
label[next[i]] = new Label(next[i], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a label at the end. This label must start a block.
|
|
label[array.length] = new Label(array.length, true);
|
|
code.add(label[array.length]);
|
|
|
|
maxLabel = array.length + 1;
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("Editing method " + name + " " + type);
|
|
}
|
|
|
|
if (MethodEditor.OPT_STACK_2) {
|
|
uMap = new UseMap(); // structure for remembering use/def info.
|
|
}
|
|
|
|
this.setDirty(false);
|
|
}
|
|
|
|
/**
|
|
* Returns the <tt>Type</tt>s of exceptions that this method may throw.
|
|
*/
|
|
public Type[] exceptions() {
|
|
final ConstantPool cp = editor.constants();
|
|
final int[] indices = methodInfo.exceptionTypes();
|
|
final Type[] types = new Type[indices.length];
|
|
|
|
for (int i = 0; i < indices.length; i++) {
|
|
types[i] = (Type) cp.constantAt(indices[i]);
|
|
}
|
|
|
|
return (types);
|
|
}
|
|
|
|
/**
|
|
* Returns <tt>true</tt> if this method has been modified.
|
|
*/
|
|
public boolean isDirty() {
|
|
return (this.isDirty);
|
|
}
|
|
|
|
/**
|
|
* Sets the dirty flag of this method. The dirty flag is <tt>true</tt> if
|
|
* the method has been modified.
|
|
*/
|
|
public void setDirty(final boolean dirty) {
|
|
this.isDirty = dirty;
|
|
if (isDirty == true) {
|
|
this.editor.setDirty(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Marks this method for deletion. Once a method has been marked for
|
|
* deletion all attempts to change it will throw an
|
|
* <code>IllegalStateException</code>.
|
|
*/
|
|
public void delete() {
|
|
this.setDirty(true);
|
|
this.isDeleted = true;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of <tt>Type</tt>s representing the types of the
|
|
* parameters of this method. It's really used to figure out the type of the
|
|
* local variables that hold the parameters. So, wide data is succeeded by
|
|
* an empty slot. Also, for virtual methods, the first element in the array
|
|
* is the receiver.
|
|
*/
|
|
public Type[] paramTypes() {
|
|
return (this.paramTypes);
|
|
}
|
|
|
|
/**
|
|
* Get the LocalVariable for the parameter at the given index.
|
|
*
|
|
* @param index
|
|
* The index into the params (0 is the this pointer or the first
|
|
* argument, if static).
|
|
* @return The LocalVariable for the parameter at the given index.
|
|
*
|
|
*/
|
|
public LocalVariable paramAt(final int index) {
|
|
if ((index >= params.length) || (params[index] == null)) {
|
|
final LocalVariable local = new LocalVariable(index);
|
|
if (index < params.length) {
|
|
params[index] = local;
|
|
}
|
|
return (local);
|
|
}
|
|
|
|
return params[index];
|
|
}
|
|
|
|
/**
|
|
* Returns the raw MethodInfo of the method being edited.
|
|
*/
|
|
public MethodInfo methodInfo() {
|
|
return methodInfo;
|
|
}
|
|
|
|
/**
|
|
* Returns the class which declared the method.
|
|
*/
|
|
public ClassEditor declaringClass() {
|
|
return editor;
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum number of locals used by the method.
|
|
*/
|
|
public int maxLocals() {
|
|
return maxLocals;
|
|
}
|
|
|
|
public boolean isPublic() {
|
|
return (methodInfo.modifiers() & Modifiers.PUBLIC) != 0;
|
|
}
|
|
|
|
public boolean isPrivate() {
|
|
return (methodInfo.modifiers() & Modifiers.PRIVATE) != 0;
|
|
}
|
|
|
|
public boolean isProtected() {
|
|
return (methodInfo.modifiers() & Modifiers.PROTECTED) != 0;
|
|
}
|
|
|
|
/**
|
|
* Returns true is the method has package level visibility
|
|
*/
|
|
public boolean isPackage() {
|
|
return (!isPublic() && !isPrivate() && !isProtected());
|
|
}
|
|
|
|
public boolean isStatic() {
|
|
return (methodInfo.modifiers() & Modifiers.STATIC) != 0;
|
|
}
|
|
|
|
public boolean isFinal() {
|
|
return (methodInfo.modifiers() & Modifiers.FINAL) != 0;
|
|
}
|
|
|
|
public boolean isSynchronized() {
|
|
return (methodInfo.modifiers() & Modifiers.SYNCHRONIZED) != 0;
|
|
}
|
|
|
|
public boolean isNative() {
|
|
return (methodInfo.modifiers() & Modifiers.NATIVE) != 0;
|
|
}
|
|
|
|
public boolean isAbstract() {
|
|
return (methodInfo.modifiers() & Modifiers.ABSTRACT) != 0;
|
|
}
|
|
|
|
/**
|
|
* Returns <tt>true</tt> if this method's class is an interface.
|
|
*/
|
|
public boolean isInterface() {
|
|
return (editor.isInterface());
|
|
}
|
|
|
|
// TODO: Only change the methodInfo at commit time.
|
|
// TODO: Add similar methods to field and class editors.
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setPublic(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.PUBLIC;
|
|
} else {
|
|
mod &= ~Modifiers.PUBLIC;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setPrivate(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.PRIVATE;
|
|
} else {
|
|
mod &= ~Modifiers.PRIVATE;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setProtected(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.PROTECTED;
|
|
} else {
|
|
mod &= ~Modifiers.PROTECTED;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setStatic(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.STATIC;
|
|
} else {
|
|
mod &= ~Modifiers.STATIC;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setFinal(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.FINAL;
|
|
} else {
|
|
mod &= ~Modifiers.FINAL;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setSynchronized(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.SYNCHRONIZED;
|
|
} else {
|
|
mod &= ~Modifiers.SYNCHRONIZED;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setNative(final boolean flag) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.NATIVE;
|
|
} else {
|
|
mod &= ~Modifiers.NATIVE;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
public void setAbstract(final boolean flag) {
|
|
int mod = methodInfo.modifiers();
|
|
|
|
if (flag) {
|
|
mod |= Modifiers.ABSTRACT;
|
|
} else {
|
|
mod &= ~Modifiers.ABSTRACT;
|
|
}
|
|
|
|
methodInfo.setModifiers(mod);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Scan the raw bytes of a single instruction, saving the indices of branch
|
|
* targets and the values of switch lookups. That is, gather information
|
|
* needed for creating <tt>Instruction</tt> instances.
|
|
*
|
|
* @param code
|
|
* The byte code array.
|
|
* @param index
|
|
* The index into the code array.
|
|
* @param targets
|
|
* Branch targets for the instruction scanned. This is set by the
|
|
* method.
|
|
* @param lookups
|
|
* Switch lookups for the instruction scanned. This is set by the
|
|
* method.
|
|
* @return The index of the next instruction in the code array.
|
|
*/
|
|
private int munchCode(final byte[] code, final int index,
|
|
final int[][] targets, final int[][] lookups) {
|
|
final int opcode = Instruction.toUByte(code[index]);
|
|
int next = index + Opcode.opcSize[opcode];
|
|
|
|
switch (opcode) {
|
|
case opc_ifeq:
|
|
case opc_ifne:
|
|
case opc_iflt:
|
|
case opc_ifge:
|
|
case opc_ifgt:
|
|
case opc_ifle:
|
|
case opc_if_icmpeq:
|
|
case opc_if_icmpne:
|
|
case opc_if_icmplt:
|
|
case opc_if_icmpge:
|
|
case opc_if_icmpgt:
|
|
case opc_if_icmple:
|
|
case opc_if_acmpeq:
|
|
case opc_if_acmpne:
|
|
case opc_ifnull:
|
|
case opc_ifnonnull: {
|
|
// Branch target
|
|
final int target = Instruction.toShort(code[index + 1],
|
|
code[index + 2]);
|
|
targets[index] = new int[1];
|
|
targets[index][0] = index + target;
|
|
break;
|
|
}
|
|
case opc_goto:
|
|
case opc_jsr: {
|
|
// Branch target
|
|
final int target = Instruction.toShort(code[index + 1],
|
|
code[index + 2]);
|
|
targets[index] = new int[1];
|
|
targets[index][0] = index + target;
|
|
break;
|
|
}
|
|
case opc_goto_w:
|
|
case opc_jsr_w: {
|
|
// Branch target
|
|
final int target = Instruction.toInt(code[index + 1],
|
|
code[index + 2], code[index + 3], code[index + 4]);
|
|
targets[index] = new int[1];
|
|
targets[index][0] = index + target;
|
|
break;
|
|
}
|
|
case opc_ret: {
|
|
// Unconditional branch to the address in a local variable.
|
|
// Work on finding branch targets later.
|
|
break;
|
|
}
|
|
case opc_tableswitch: {
|
|
int target;
|
|
int lo;
|
|
int hi;
|
|
int j;
|
|
|
|
// The targets and low and high values are aligned on
|
|
// 4-byte boundaries.
|
|
for (j = index + 1; j % 4 != 0; j++) {
|
|
// Empty statement.
|
|
}
|
|
|
|
// Read the default target.
|
|
target = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
lo = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
hi = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
lookups[index] = new int[2];
|
|
lookups[index][0] = lo;
|
|
lookups[index][1] = hi;
|
|
|
|
targets[index] = new int[hi - lo + 2];
|
|
|
|
int k = 0;
|
|
targets[index][k++] = index + target;
|
|
|
|
next = j + (hi - lo + 1) * 4;
|
|
|
|
while (j < next) {
|
|
target = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
targets[index][k++] = index + target;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case opc_lookupswitch: {
|
|
int target;
|
|
int value;
|
|
int npairs;
|
|
int j;
|
|
|
|
// The targets and pairs are aligned on 4-byte boundaries.
|
|
for (j = index + 1; j % 4 != 0; j++) {
|
|
// Empty statement.
|
|
}
|
|
|
|
// Read the default target.
|
|
target = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
npairs = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
lookups[index] = new int[npairs];
|
|
targets[index] = new int[npairs + 1];
|
|
|
|
int k = 0;
|
|
targets[index][k++] = index + target;
|
|
|
|
next = j + npairs * 8;
|
|
|
|
while (j < next) {
|
|
value = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
target = Instruction.toInt(code[j], code[j + 1], code[j + 2],
|
|
code[j + 3]);
|
|
j += 4;
|
|
|
|
lookups[index][k - 1] = value;
|
|
targets[index][k++] = index + target;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case opc_wide: {
|
|
if (code[index + 1] == (byte) Opcode.opc_iinc) {
|
|
next = index + 6;
|
|
} else {
|
|
next = index + 4;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
/**
|
|
* Remove all the instructions in preparation for the instructions being
|
|
* added back after a control flow graph edit.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void clearCode() {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("Clearing code");
|
|
Thread.dumpStack();
|
|
}
|
|
|
|
code.clear();
|
|
tryCatches.clear();
|
|
maxLocals = 0;
|
|
maxStack = 0;
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Like clear code, but doesn't reset the maxLocals. I'm not really sure why
|
|
* this works, but it stops certain parts of code that is generated and then
|
|
* re-cfg'd from being eliminated as dead
|
|
*/
|
|
|
|
public void clearCode2() {
|
|
code.clear();
|
|
tryCatches.clear();
|
|
maxStack = 0;
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the method.
|
|
*/
|
|
public String name() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Returns <tt>true</tt> if the method being edited is a constructor.
|
|
*/
|
|
public boolean isConstructor() {
|
|
return (name.equals("<init>"));
|
|
}
|
|
|
|
/**
|
|
* Returns the type of the method.
|
|
*/
|
|
public Type type() {
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Returns the <Tt>NameAndType</tt> of the method.
|
|
*/
|
|
public NameAndType nameAndType() {
|
|
return (new NameAndType(this.name(), this.type()));
|
|
}
|
|
|
|
/**
|
|
* Returns a <tt>MemberRef</tt> for the method.
|
|
*/
|
|
public MemberRef memberRef() {
|
|
return (new MemberRef(this.declaringClass().type(), this.nameAndType()));
|
|
}
|
|
|
|
/**
|
|
* Get the length of the code array.
|
|
*
|
|
* @return The length of the code array.
|
|
*/
|
|
public int codeLength() {
|
|
return code.size();
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void setCode(final List v) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("Setting code to " + v.size() + " instructions");
|
|
Thread.dumpStack();
|
|
}
|
|
code = new LinkedList(v);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Returns the code (<tt>Instruction</tt>s and <Tt>Label</tt>s) in
|
|
* the method.
|
|
*/
|
|
public List code() {
|
|
return code;
|
|
}
|
|
|
|
/**
|
|
* Get the label of the first block.
|
|
*/
|
|
public Label firstBlock() {
|
|
final Iterator iter = code.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Object obj = iter.next();
|
|
|
|
if (obj instanceof Label) {
|
|
final Label l = (Label) obj;
|
|
if (l.startsBlock()) {
|
|
return l;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the label of the next block after the parameter.
|
|
*
|
|
* @param label
|
|
* The label at which to begin.
|
|
* @return The label.
|
|
*/
|
|
public Label nextBlock(final Label label) {
|
|
boolean seen = false;
|
|
|
|
final Iterator iter = code.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final Object obj = iter.next();
|
|
|
|
if (obj instanceof Label) {
|
|
if (seen) {
|
|
final Label l = (Label) obj;
|
|
if (l.startsBlock()) {
|
|
return l;
|
|
}
|
|
} else if (label.equals(obj)) {
|
|
seen = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Removes a Label or Instruction from the code array.
|
|
*
|
|
* @param i
|
|
* The index of the element to remove.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void removeCodeAt(final int i) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
code.remove(i);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Inserts a Label or Instruction into the code array.
|
|
*
|
|
* @param i
|
|
* The index of the element to insert before.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void insertCodeAt(final Object obj, final int i) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
code.add(i, obj);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Replace a Label or Instruction in the code array.
|
|
*
|
|
* @param obj
|
|
* The new element.
|
|
* @param i
|
|
* The index of the element to replace
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void replaceCodeAt(final Object obj, final int i) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
code.set(i, obj);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Returns a Label or Instruction in the code array.
|
|
*
|
|
* @param i
|
|
* The index into the code array.
|
|
* @return The element at the index.
|
|
*/
|
|
public Object codeElementAt(final int i) {
|
|
return code.get(i);
|
|
}
|
|
|
|
/**
|
|
* Add a line number entry.
|
|
*
|
|
* @param label
|
|
* The label beginning the range of instructions for this line
|
|
* number.
|
|
* @param lineNumber
|
|
* The line number.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void addLineNumberEntry(final Label label, final int lineNumber) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
lineNumbers.add(new LineNumberEntry(label, lineNumber));
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of exception handlers in the method.
|
|
*/
|
|
public int numTryCatches() {
|
|
return tryCatches.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the exception handlers (<tt>TryCatch</tt>) in the method.
|
|
*/
|
|
public Collection tryCatches() {
|
|
return tryCatches;
|
|
}
|
|
|
|
/**
|
|
* Add an exception handler.
|
|
*
|
|
* @param tryCatch
|
|
* An exception handler.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void addTryCatch(final TryCatch tryCatch) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("add " + tryCatch);
|
|
}
|
|
|
|
tryCatches.add(tryCatch);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
class LineNumberEntry {
|
|
Label label;
|
|
|
|
int lineNumber;
|
|
|
|
public LineNumberEntry(final Label label, final int lineNumber) {
|
|
this.label = label;
|
|
this.lineNumber = lineNumber;
|
|
}
|
|
}
|
|
|
|
class LocalInfo {
|
|
LocalVariable var;
|
|
|
|
Type type;
|
|
|
|
public LocalInfo(final LocalVariable var, final Type type) {
|
|
this.var = var;
|
|
this.type = type;
|
|
}
|
|
|
|
public boolean equals(final Object obj) {
|
|
return (obj != null) && (obj instanceof LocalInfo)
|
|
&& ((LocalInfo) obj).var.equals(var)
|
|
&& ((LocalInfo) obj).type.equals(type);
|
|
}
|
|
|
|
public int hashCode() {
|
|
return var.hashCode() ^ type.hashCode();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new local variable.
|
|
*/
|
|
public LocalVariable newLocal(final Type type) {
|
|
final int index = maxLocals;
|
|
|
|
maxLocals += type.stackHeight();
|
|
this.setDirty(true);
|
|
|
|
final LocalVariable local = new LocalVariable(index);
|
|
|
|
locals.put(new Integer(index), local);
|
|
|
|
return (local);
|
|
}
|
|
|
|
/**
|
|
* Creates a new local variable of an undertermined type.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public LocalVariable newLocal(final boolean isWide) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
final int index = maxLocals;
|
|
|
|
maxLocals += (isWide ? 2 : 1);
|
|
this.setDirty(true);
|
|
|
|
final LocalVariable local = new LocalVariable(index);
|
|
|
|
locals.put(new Integer(index), local);
|
|
|
|
return (local);
|
|
}
|
|
|
|
/**
|
|
* Returns the <tt>LocalVariable</tt> with the given index. If there is no
|
|
* local variable at that index, a new one is created at that index. We
|
|
* assume that this variable is not wide.
|
|
*/
|
|
public LocalVariable localAt(int index) {
|
|
LocalVariable local = (LocalVariable) locals.get(new Integer(index));
|
|
|
|
if (local == null) {
|
|
local = new LocalVariable(index);
|
|
locals.put(new Integer(index), local);
|
|
if (index >= maxLocals) {
|
|
maxLocals = index++; // Dangerous?
|
|
}
|
|
}
|
|
|
|
return (local);
|
|
}
|
|
|
|
/**
|
|
* Add an instruction.
|
|
*
|
|
* @param opcodeClass
|
|
* The instruction to add.
|
|
*/
|
|
public void addInstruction(final int opcodeClass) {
|
|
addInstruction(new Instruction(opcodeClass));
|
|
}
|
|
|
|
/**
|
|
* Add an instruction.
|
|
*
|
|
* @param opcodeClass
|
|
* The instruction to add.
|
|
*/
|
|
public void addInstruction(final int opcodeClass, final Object operand) {
|
|
addInstruction(new Instruction(opcodeClass, operand));
|
|
}
|
|
|
|
/**
|
|
* Add an instruction to the end of the code array.
|
|
*
|
|
* @param inst
|
|
* The instruction to add.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void addInstruction(final Instruction inst) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println(" " + inst + " to "
|
|
+ System.identityHashCode(this) + ":"
|
|
+ System.identityHashCode(this.code));
|
|
}
|
|
|
|
code.add(inst);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Get the next available label. That is the Label after the final
|
|
* Instruction in the code array.
|
|
*
|
|
* @return A new label.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public Label newLabel() {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
this.setDirty(true);
|
|
return new Label(maxLabel++);
|
|
}
|
|
|
|
public Label newLabelTrue() {
|
|
return new Label(maxLabel++, true);
|
|
}
|
|
|
|
/**
|
|
* Add a label to the code array to the end of the code array.
|
|
*
|
|
* @param label
|
|
* The label to add.
|
|
*
|
|
* @throws IllegalStateException This field has been marked for deletion
|
|
*/
|
|
public void addLabel(final Label label) {
|
|
if (this.isDeleted) {
|
|
final String s = "Cannot change a field once it has been marked "
|
|
+ "for deletion";
|
|
throw new IllegalStateException(s);
|
|
}
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println(" " + label + " to "
|
|
+ System.identityHashCode(this) + ":"
|
|
+ System.identityHashCode(this.code));
|
|
}
|
|
|
|
if (label.index() >= maxLabel) {
|
|
maxLabel = label.index() + 1;
|
|
}
|
|
|
|
code.add(label);
|
|
this.setDirty(true);
|
|
}
|
|
|
|
class LocalVarEntry {
|
|
LocalVariable var;
|
|
|
|
Label start;
|
|
|
|
Label end;
|
|
|
|
public LocalVarEntry(final LocalVariable var, final Label start,
|
|
final Label end) {
|
|
this.var = var;
|
|
this.start = start;
|
|
this.end = end;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Commits changes made to this MethodEditor back to the MethodInfo on which
|
|
* it is based. Note that committal will take place regardless of whether or
|
|
* not the method is dirty.
|
|
*/
|
|
public void commit() {
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("Committing method " + this.name + " "
|
|
+ this.type + ": " + this.code.size() + " insts "
|
|
+ System.identityHashCode(this) + ":"
|
|
+ System.identityHashCode(this.code));
|
|
}
|
|
|
|
final ConstantPool cp = editor.constants();
|
|
|
|
if (this.isDeleted) {
|
|
final int nameIndex = cp.getUTF8Index(this.name);
|
|
final int typeIndex = cp.getTypeIndex(this.type);
|
|
this.editor.classInfo().deleteMethod(nameIndex, typeIndex);
|
|
|
|
} else {
|
|
methodInfo.setNameIndex(cp.addConstant(Constant.UTF8, name));
|
|
methodInfo.setTypeIndex(cp.addConstant(Constant.UTF8, type
|
|
.descriptor()));
|
|
|
|
if (isNative() || isAbstract()) {
|
|
return;
|
|
}
|
|
|
|
final List vars = new ArrayList();
|
|
final List copy = new LinkedList();
|
|
|
|
Iterator iter = code.iterator();
|
|
|
|
CODE: while (iter.hasNext()) {
|
|
final Object ce = iter.next();
|
|
|
|
if (ce instanceof Label) {
|
|
copy.add(ce);
|
|
continue CODE;
|
|
}
|
|
|
|
final Instruction inst = (Instruction) ce;
|
|
|
|
LocalVariable var = null;
|
|
|
|
if (inst.operand() instanceof LocalVariable) {
|
|
var = (LocalVariable) inst.operand();
|
|
} else if (inst.operand() instanceof IncOperand) {
|
|
var = ((IncOperand) inst.operand()).var();
|
|
}
|
|
|
|
if ((var == null) || (var.name() == null)
|
|
|| (var.type() == null)) {
|
|
copy.add(ce);
|
|
continue CODE;
|
|
}
|
|
|
|
for (int j = vars.size() - 1; j >= 0; j--) {
|
|
final LocalVarEntry v = (LocalVarEntry) vars.get(j);
|
|
|
|
// Same variable, extend the range of the variable and
|
|
// go to the next instruction.
|
|
if (v.var.equals(var)) {
|
|
v.end = newLabel();
|
|
|
|
// Add a label after the instruction.
|
|
copy.add(ce);
|
|
copy.add(v.end);
|
|
continue CODE;
|
|
}
|
|
|
|
// Different variable, same index. We have to add an entry
|
|
// for the variable since we know the live range for this
|
|
// index starts here.
|
|
if (v.var.index() == var.index()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
final Label start = newLabel();
|
|
final Label end = newLabel();
|
|
|
|
vars.add(new LocalVarEntry(var, start, end));
|
|
|
|
// Add labels before and after the instruction.
|
|
copy.add(start);
|
|
copy.add(ce);
|
|
copy.add(end);
|
|
}
|
|
|
|
final HashSet seen = new HashSet();
|
|
final ArrayList dup = new ArrayList();
|
|
|
|
iter = tryCatches.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final TryCatch tc = (TryCatch) iter.next();
|
|
|
|
if (!seen.contains(tc.handler())) {
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("See " + tc.handler());
|
|
}
|
|
seen.add(tc.handler());
|
|
} else {
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("See " + tc.handler() + " again");
|
|
}
|
|
dup.add(tc);
|
|
}
|
|
}
|
|
|
|
if (dup.size() != 0) {
|
|
final ListIterator liter = copy.listIterator();
|
|
|
|
while (liter.hasNext()) {
|
|
final Object ce = liter.next();
|
|
|
|
if (ce instanceof Label) {
|
|
final Iterator d = dup.iterator();
|
|
|
|
while (d.hasNext()) {
|
|
final TryCatch tc = (TryCatch) d.next();
|
|
|
|
if (tc.handler().equals(ce)) {
|
|
// Split the exception handler.
|
|
//
|
|
// Handler:
|
|
// nop <-- nop needed to prevent TowerJ
|
|
// goto L2 from removing the goto
|
|
// Handler2:
|
|
// nop
|
|
// goto L2
|
|
// Code:
|
|
// handler code
|
|
|
|
Instruction jump;
|
|
Instruction nop;
|
|
|
|
final Label handler2 = newLabel();
|
|
final Label code = newLabel();
|
|
|
|
nop = new Instruction(Opcode.opcx_nop);
|
|
liter.add(nop);
|
|
|
|
jump = new Instruction(Opcode.opcx_goto, code);
|
|
liter.add(jump);
|
|
|
|
liter.add(handler2);
|
|
|
|
nop = new Instruction(Opcode.opcx_nop);
|
|
liter.add(nop);
|
|
|
|
jump = new Instruction(Opcode.opcx_goto, code);
|
|
liter.add(jump);
|
|
|
|
liter.add(code);
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("Insert " + jump);
|
|
System.out.println("Insert " + handler2);
|
|
System.out.println("Insert " + jump);
|
|
System.out.println("Insert " + code);
|
|
}
|
|
|
|
tc.setHandler(handler2);
|
|
|
|
d.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final CodeArray array = new CodeArray(this, cp, copy);
|
|
final byte[] arr = array.array();
|
|
|
|
methodInfo.setCode(arr);
|
|
|
|
methodInfo.setMaxLocals(array.maxLocals());
|
|
methodInfo.setMaxStack(array.maxStack());
|
|
|
|
if (MethodEditor.PRESERVE_DEBUG && (arr.length < 0x10000)) {
|
|
final LocalDebugInfo[] locals = new LocalDebugInfo[vars.size()];
|
|
|
|
for (int i = 0; i < vars.size(); i++) {
|
|
final LocalVarEntry entry = (LocalVarEntry) vars.get(i);
|
|
|
|
final int start = array.labelIndex(entry.start);
|
|
final int end = array.labelIndex(entry.end);
|
|
|
|
if (start < end) {
|
|
locals[i] = new LocalDebugInfo(start, end - start, cp
|
|
.addConstant(Constant.UTF8, entry.var.name()),
|
|
cp.addConstant(Constant.UTF8, entry.var.type()
|
|
.descriptor()), entry.var.index());
|
|
}
|
|
}
|
|
|
|
methodInfo.setLocals(locals);
|
|
|
|
final LineNumberDebugInfo[] lines = new LineNumberDebugInfo[lineNumbers
|
|
.size()];
|
|
int i = 0;
|
|
|
|
iter = lineNumbers.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final LineNumberEntry line = (LineNumberEntry) iter.next();
|
|
lines[i++] = new LineNumberDebugInfo(array
|
|
.labelIndex(line.label), line.lineNumber);
|
|
}
|
|
|
|
methodInfo.setLineNumbers(lines);
|
|
} else {
|
|
methodInfo.setLineNumbers(null);
|
|
methodInfo.setLocals(null);
|
|
}
|
|
|
|
final List c = new LinkedList();
|
|
|
|
iter = tryCatches.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
final TryCatch tc = (TryCatch) iter.next();
|
|
|
|
final int start = array.labelIndex(tc.start());
|
|
final int end = array.labelIndex(tc.end());
|
|
|
|
if (start < end) {
|
|
c.add(new Catch(start, end, array.labelIndex(tc.handler()),
|
|
cp.addConstant(Constant.CLASS, tc.type())));
|
|
}
|
|
}
|
|
|
|
final Object[] a = c.toArray();
|
|
final Catch[] catches = new Catch[a.length];
|
|
System.arraycopy(a, 0, catches, 0, a.length);
|
|
|
|
methodInfo.setExceptionHandlers(catches);
|
|
|
|
}
|
|
|
|
if (ClassEditor.DEBUG) {
|
|
System.out.println("MethodInfo after commit: " + methodInfo);
|
|
}
|
|
|
|
// Method is no longer dirty
|
|
this.isDirty = false;
|
|
}
|
|
|
|
/**
|
|
* Print the method.
|
|
*
|
|
* @param out
|
|
* Stream to which to print.
|
|
*/
|
|
public void print(final PrintStream out) {
|
|
out.println(name + "." + type + (isDirty ? " (dirty) " : "") + ":");
|
|
|
|
Iterator iter;
|
|
|
|
iter = code.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
out.println(" " + iter.next());
|
|
}
|
|
|
|
iter = tryCatches.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
out.println(" " + iter.next());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Two <tt>MethodEditor</tt>s are equal if they edit the same method in
|
|
* the same class.
|
|
*/
|
|
public boolean equals(final Object o) {
|
|
if (o instanceof MethodEditor) {
|
|
final MethodEditor other = (MethodEditor) o;
|
|
|
|
if (!other.declaringClass().equals(this.declaringClass())) {
|
|
return (false);
|
|
}
|
|
if (!other.name().equals(this.name())) {
|
|
return (false);
|
|
}
|
|
if (!other.type().equals(this.type())) {
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
/**
|
|
* A <tt>MethodEditor</tt>'s hash code is based on the hash codes for its
|
|
* class, name, and type.
|
|
*/
|
|
public int hashCode() {
|
|
return (this.declaringClass().hashCode() + this.name().hashCode() + this
|
|
.type().hashCode());
|
|
}
|
|
|
|
public String toString() {
|
|
return (editor.type() + "." + name + type);
|
|
}
|
|
|
|
public UseMap uMap() {
|
|
return uMap;
|
|
}
|
|
|
|
public void rememberDef(final LocalExpr e) {
|
|
if (MethodEditor.OPT_STACK_2) {
|
|
uMap.add(e, (Instruction) code.get(code.size() - 1));
|
|
}
|
|
}
|
|
|
|
}
|
|
|