Back
package com.futureshocked.classloader;
import com.futureshocked.debug.StringUtil;
import org.objectweb.asm.*;
import java.util.List;
import java.util.HashMap;
import java.util.Iterator;
/**
* An <tt>org.objectweb.asm.ClassAdapter</tt> that will modify classes as
* needed. As a class is loaded the appropriate visit*() methods are called
* in this class.<br><br>
*
* By default, an {@link InterceptedMethod} object that requests a static
* debugging call will call <tt>StaticDebugger.printDebugInformation()</tt>.
*
* <br><br>See also: Visitor design pattern.
*/
public class DebuggerClassVisitor extends ClassAdapter
implements Opcodes {
/**
* The name of the class currently being visited.
*/
protected String className;
/**
* A {@link ClassTransforms} that holds {@link InterceptedMethod} objects.
*/
protected ClassTransforms ct;
/**
* <tt>true</tt> if debugging information should be displayed.
*/
protected boolean verbose;
/**
* The message to be printed when the recursion limit is exceeded.
*/
protected String message = "Recursion limit exceeded for : ";
/**
* The name of the class that contains a static method to be called for
* debugging purposes.
*/
protected String staticDebugClass = "com/futureshocked/debug/StaticDebugger";
/**
* The name of the static method to be called for debugging purposes.
*/
protected String staticDebugMethod = "printDebugInformation";
/**
* The description of the static method to be called for debugging. Should
* be in the appropriate ASM format - see {@link ApplicationDebugger} for
* an example.
*/
protected String staticDebugDescription = "(Ljava/lang/Object;I)V";
/**
* Constructor that sets the appropriate instance variables.
*
* @param cv A <tt>ClassVisitor</tt> or <tt>ClassWriter</tt> that has already
* been created.
* @param className The name of the class being visited.
* @param ct Container for {@link InterceptedMethod} objects.
* @param verbose <tt>true</tt> if debugging info should be displayed.
*/
public DebuggerClassVisitor(ClassVisitor cv, String className,
ClassTransforms ct, boolean verbose) {
super(cv);
this.className = className;
this.ct = ct;
this.verbose = verbose;
}
/**
* <tt>visit()</tt> is called when a class is first created. All extra
* fields related to recursion limited are created here.
*
* @param version see ASM documentation.
* @param access Access flags - see ASM documentation.
* @param name The name of the class.
* @param signature The class signature - see ASM documentation.
* @param superName The name of the parent class - see ASM documentation.
* @param interfaces Interfaces implemented by this object.
*/
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
// All overloaded methods with the same name share the same recursion
// counter - this HashMap keeps the fields from being created twice.
HashMap addedFields = new HashMap();
List methods = ct.getMethodsByClass(className);
if (methods != null) {
Iterator i = methods.iterator();
while (i.hasNext()) {
InterceptedMethod im = (InterceptedMethod) i.next();
if (im.needsRecursionLimiter()) {
String fieldName = getFieldNameByMethod(im.getMethodName());
// If the field hasn't already been created, create it
if (!addedFields.containsKey(fieldName)) {
if (verbose)
System.out.println("RecursionLimiterClassVisitor: " +
"creating field " + StringUtil.getShortClassName(className) +
"." + fieldName);
super.visitField(ACC_PRIVATE, fieldName, "I", null, null);
addedFields.put(fieldName, im);
}
}
}
}
}
/**
* Called for every method in a class. If this is called for a method
* we wish to intercept, this method will add the appropriate bytecode.
*
* @param access Access flags for the method - see ASM documentation.
* @param name The name of the method.
* @param desc The description of the method - see {@link ApplicationDebugger}
* or ASM documentation.
* @param signature Method signature - see ASM documentation.
* @param exceptions Exceptions thrown by this method - see ASM documentation.
* @return <tt>MethodVisitor</tt> object
*/
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
InterceptedMethod im = ct.getInterceptedMethod(className, name, desc);
if (im != null) {
if (im.needsRecursionLimiter())
mv = addRecursionLimiter(mv, im, getReturnTypeFromDescription(desc));
if (im.needsStaticDebuggerCall())
mv = addStaticDebuggerCall(mv, im);
}
return mv;
}
/**
* Adds the bytecode to call the static debugger.
*
* @param mv The current <tt>MethodVisitor</tt>
* @param im The {@link InterceptedMethod} object that represents the method
* that will be modified.
* @return <tt>MethodVisitor</tt> object passed in
*/
public MethodVisitor addStaticDebuggerCall(MethodVisitor mv,
InterceptedMethod im) {
// corresponds to: SomeClass.staticMethod(this, int debugDepth);
mv.visitVarInsn(ALOAD, 0);
mv.visitIntInsn(BIPUSH, im.getStaticDebuggerCallDepth());
mv.visitMethodInsn(INVOKESTATIC, staticDebugClass, staticDebugMethod,
staticDebugDescription);
return mv;
}
/**
* Adds a recursion limit to a recursive limit - keeps recursive methods that
* are buggy from causing a stack overflow.
*
* @param mv The current <tt>MethodVisitor</tt>
* @param im The {@link InterceptedMethod} object that represents the method
* that will be modified.
* @param returnType The return type of the method being modified - this is
* the same type as in ASM description, not full class names such as String.
* @return The current <tt>MethodVisitor</tt>
*/
public MethodVisitor addRecursionLimiter(MethodVisitor mv,
InterceptedMethod im, String returnType) {
String field = getFieldNameByMethod(im.getMethodName());
// need to turn com.some.package.Class into com/some/pacakge/Class
String clazz = im.getClassName().replace('.', '/');
// this will execute '(field)++'
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, clazz, field, "I");
mv.visitInsn(ICONST_1);
mv.visitInsn(IADD);
mv.visitFieldInsn(PUTFIELD, clazz, field, "I");
// this will compare field to maxRecursionCount, and jump to below if it's
// less than the max.
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, clazz, field, "I");
mv.visitIntInsn(BIPUSH, im.getMaxRecursionCount());
Label l0 = new Label();
mv.visitJumpInsn(IF_ICMPLE, l0);
// if message is not null print out an error to System.out
if (message != null) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn(message + im.getMethodName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
}
// Executes '(field)--', since the code added to the end of the method will
// not be reached.
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, clazz, field, "I");
mv.visitInsn(ICONST_1);
mv.visitInsn(ISUB);
mv.visitFieldInsn(PUTFIELD, clazz, field, "I");
// Adds a return instruction of the appropriate type. Currently only types
// void, int, and Object are supported.
if (returnType.equals("V")) {
mv.visitInsn(RETURN);
}
else if (returnType.equals("I")) {
// return 0 for return type of int
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
}
else {
// return null for Object return types
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
}
// if recursion count is under the max, the jump from above lands here
mv.visitLabel(l0);
mv = new RecursionLimiterMethodAdapter(mv, clazz, field);
return mv;
}
/**
* Returns the name of the field that holds the current recursion count for
* the method.
*
* @param method The method that needs to know it's recursion count.
* @return A <tt>String</tt> that holds the field name.
*/
private String getFieldNameByMethod(String method) {
return "__" + method + "RecursionCount";
}
/**
* Parses the return type from the method description.
*
* @param desc The method description - see {@link ApplicationDebugger}.
* @return A <tt>String</tt> containing the type.
*/
private String getReturnTypeFromDescription(String desc) {
String type = null;
if (desc != null) {
type = desc.substring(desc.indexOf(')') + 1);
}
return type;
}
/**
* Inner class that adds recursion limiting instructions to the end of
* a method. More specifically, it will execute '(field)--' before every
* RETURN opcode in a method.
*
* Since this will examine every opcode in a class file, it is only
* invoked on methods which are known to require modification before
* RETURN opcodes.
*/
class RecursionLimiterMethodAdapter extends MethodAdapter implements Opcodes {
/**
* The name of the class being visited.
*/
protected String className;
/**
* The name of the field to decrement.
*/
protected String field;
/**
* Constructor which sets the appropriate instance variables.
*
* @param mv <tt>MethodVisitor</tt> for the current method.
* @param className The name of the class.
* @param field The name of the field to decrement.
*/
public RecursionLimiterMethodAdapter(MethodVisitor mv, String className,
String field) {
super(mv);
this.className = className;
this.field = field;
}
/**
* Called for every opcode in a method - if the opcode is a RETURN opcode
* then the field decrementing opcodes will be added.
*
* @param opcode
*/
public void visitInsn(int opcode) {
// Since opcodes are integers this covers the range of all possible
// return opcodes - cleaner looking than a big OR statement, but it does
// require an explanation. See org.objectweb.asm.Opcodes for more
// information.
if ((opcode >= IRETURN) && (opcode <= RETURN)) {
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, className, field, "I");
mv.visitInsn(ICONST_1);
mv.visitInsn(ISUB);
mv.visitFieldInsn(PUTFIELD, className, field, "I");
}
super.visitInsn(opcode);
}
}
}
Top |