I want to replace method body with new content(sample.class:sayHello method) and then executes sample.class. The original sayHelo declaration is:
public int sayHello(String args){
}
I want to modify its body to be:
System.out.println("sxu says: hello world!");
return 1;
But sample execution result throws exception:
Exception in thread "main" java.lang.VerifyError: Operand stack overflow
Exception Details:
Location:
code/sxu/demo/data/sample.sayHello(Ljava/lang/String;)I @4: ldc
Reason:
Exceeded max stack size.
Current Frame:
bci: @4
flags: { }
locals: { 'code/sxu/demo/data/sample', 'java/lang/String' }
stack: { 'code/sxu/demo/data/sample', 'java/io/PrintStream' }
Bytecode:
0000000: 2ab2 0028 122a b600 1704 acb2 0028 2bb6
0000010: 0017 04ac
I use asm tools and there are three java classes in my code:
public class Adapt extends ClassLoader {
@Override
protected synchronized Class<?> loadClass(final String name,
final boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) {
return super.loadClass(name, resolve);
} else {
System.err.println("Adapt: loading class '" + name
+ "' with on the fly adaptation");
}
// gets an input stream to read the bytecode of the class
String resource = name.replace('.', '/') + ".class";
InputStream is = getResourceAsStream(resource);
byte[] b;
// adapts the class on the fly
try {
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassAdapter(cw);
cr.accept(cv, 0);
b = cw.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
FileOutputStream fos = new FileOutputStream("/tmp/"+resource.substring(resource.lastIndexOf("/")) );
fos.write(b);
fos.close();
// returns the adapted class
return defineClass(name, b, 0, b.length);
}
public static void main(String[] args) {
// loads the application class (in args[0]) with an Adapt class loader
ClassLoader loader = new Adapt();
Class<?> c;
try {
c = loader.loadClass(args[0]);
// calls the 'main' static method of this class with the
// application arguments (in args[1] ... args[n]) as parameter
Method m = c.getMethod("main", new Class<?>[] { String[].class });
String[] applicationArgs = new String[args.length - 1];
System.arraycopy(args, 1, applicationArgs, 0, applicationArgs.length);
m.invoke(null, new Object[] { applicationArgs });
}
}
public class ClassAdapter extends ClassVisitor {
private String owner;
public ClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@Override
public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
owner = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(final int access, final String name,
final String desc, final String signature, final Object value) {
FieldVisitor fv = super
.visitField(access, name, desc, signature, value);
if ((access & Opcodes.ACC_STATIC) == 0) { ...
}
return fv;
}
@Override
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
System.out.println("calling method name: "+ name);
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
//Rewrite sayHello method
if(name.equals("sayHello")){
mv = new MethodModifierAdapter(mv);
return mv;
/*
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
// pushes the "Hello World!" String constant
mv.visitLdcInsn("Sxu says: Hello world!");
// invokes the 'println' method (defined in the PrintStream class)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IRETURN);
// this code uses a maximum of two stack elements and two local
// variables
mv.visitMaxs(5, 5);
mv.visitEnd();
*/
}
return mv == null ? null : new TraceFieldCodeAdapter(Opcodes.ASM4, mv, owner);
}
}
public class MethodModifierAdapter extends MethodVisitor implements Opcodes {
boolean _modified ;
public MethodModifierAdapter(MethodVisitor mv) {
super(Opcodes.ASM4, mv);
_modified = false;
}
@Override
public void visitCode() {
if(!_modified){
_modified = true;
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
// pushes the "Hello World!" String constant
mv.visitLdcInsn("Sxu says: Hello world!");
// invokes the 'println' method (defined in the PrintStream class)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IRETURN);
// this code uses a maximum of two stack elements and two local
// variables
mv.visitMaxs(5, 5);
mv.visitEnd();
}
}
}
In the ClassAdaptor::visitMethod, i created a new MethodModifierAdapter. visitCode of MethodModifierAdapter then rewrites method body.
The javap result of new sample.class is:
public int sayHello(java.lang.String);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: getstatic #40 // Field java/lang/System.out:Ljava/io/PrintStream;
4: ldc #42 // String Sxu says: Hello world!
6: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: iconst_1
10: ireturn
11: getstatic #40 // Field java/lang/System.out:Ljava/io/PrintStream;
14: aload_1
15: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: iconst_1
19: ireturn
LocalVariableTable:
Start Length Slot Name Signature
11 9 0 this Lcode/sxu/demo/data/sample;
11 9 1 args Ljava/lang/String;
LineNumberTable:
line 12: 11
line 13: 18
According to its content, MethodModifierAdapter::visitcode do updates method content.
I guess there some wrong in my way using ASM API, but donot clear which one..
You are inserting code that requires more stack space than the original one. So you have to either update the max stack declaration manually or tell ASM to calculate the required stack space automatically. Replace your construction new ClassWriter(0)
with new ClassWriter(ClassWriter.COMPUTE_MAXS)
. The flag ClassWriter.COMPUTE_MAXS
will tell ASM to (re)calculate the stack requirements.