I am working on ASM framework for some time, where i successfully edited a .class file. I am in a situation where I need to replace a method call with another method call. I have achieved this by overriding super.visitMethodInsn()
Am posting the coding here:
Unmodified class: UserSampleClass.java
package checking;
public class UserSampleClass {
public static void main(String[] args) {
UserSampleClass usc= new UserSampleClass();
usc.display();
}
public String display() {
System.out.println("Hello Method: "+hello());
System.out.println("Bye Method: "+bye());
System.out.println("Hello Method: "+hello());
return "i'm display";
}
private String bye() {
return "Bye";
}
private String hello() {
return "Hello";
}
}
The output of the above program will be:
Hello Method: Hello
Bye Method: Bye
Hello Method: Hello
Now I have modified the original class file and changed the the three method calls (i.e., hello(), bye(), hello()) to other 3 method calls as mentioned below:
to replace first hello method: replaceFirstHelloInvoke.java
.
package chekingDepend;
public class replaceFirstHelloInvoke {
public replaceFirstHelloInvoke()
{}
public static String replaceFirstHello() {
return "one";
}
}
to replace the bye method: replaceByeInvoke.java
package chekingDepend;
public class replaceByeInvoke {
public replaceByeInvoke()
{}
public static String replaceBye() {
return "two";
}
}
to replace second hello: replaceSecondHelloInvoke.java
package chekingDepend;
public class replaceSecondHelloInvoke {
public replaceSecondHelloInvoke()
{}
public static String replaceSecondHello() {
return "three";
}
}
I have created all these methods as static
because they can be directly referred through class name.
I am able to successfully generate the modified class through ASM.
Now the problem is that The modified class is not running. When I tried to run it am getting this error:
Exception in thread "main" java.lang.VerifyError: (class: checking/UserSampleClass, method: display signature: ()Ljava/lang/String;) Incompatible object argument for function call
Could not find the main class: UserSampleClass. Program will exit.
I have checked the package dependency also. everything seems to be correct.
I also used the bytecode comparison tool, provided by ASM to check the modifications at byte code level.
All the changes seems to be correct.
I will post the byte codes here:
Unmodified byte code: UserSampleClass.class
// class version 49.0 (49)
// access flags 0x21
public class checking/UserSampleClass {
// compiled from: UserSampleClass.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
NEW checking/UserSampleClass
DUP
INVOKESPECIAL checking/UserSampleClass.<init> ()V
ASTORE 1
L1
LINENUMBER 7 L1
ALOAD 1
INVOKEVIRTUAL checking/UserSampleClass.display ()Ljava/lang/String;
POP
L2
LINENUMBER 8 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE usc Lchecking/UserSampleClass; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public display()Ljava/lang/String;
L0
LINENUMBER 12 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
LDC "hello:"
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ALOAD 0
INVOKESPECIAL checking/UserSampleClass.hello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "bye:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESPECIAL checking/UserSampleClass.bye ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "hello:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESPECIAL checking/UserSampleClass.hello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 13 L1
LDC "i'm display"
ARETURN
L2
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L2 0
MAXSTACK = 4
MAXLOCALS = 1
// access flags 0x2
private bye()Ljava/lang/String;
L0
LINENUMBER 18 L0
LDC "Bye"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x2
private hello()Ljava/lang/String;
L0
LINENUMBER 23 L0
LDC "Hello"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
Modified byte code: UserSampleClass.class
// class version 49.0 (49)
// access flags 0x21
public class checking/UserSampleClass {
// compiled from: UserSampleClass.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
NEW checking/UserSampleClass
DUP
INVOKESPECIAL checking/UserSampleClass.<init> ()V
ASTORE 1
L1
LINENUMBER 7 L1
ALOAD 1
INVOKEVIRTUAL checking/UserSampleClass.display ()Ljava/lang/String;
POP
L2
LINENUMBER 8 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE usc Lchecking/UserSampleClass; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public display()Ljava/lang/String;
L0
LINENUMBER 12 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
LDC "hello:"
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ALOAD 0
INVOKESTATIC chekingDepend/replaceFirstHelloInvoke.replaceFirstHello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "bye:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESTATIC chekingDepend/replaceByeInvoke.replaceBye ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "hello:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESTATIC chekingDepend/replaceSecondHelloInvoke.replaceSecondHello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 13 L1
LDC "i'm display"
ARETURN
L2
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L2 0
MAXSTACK = 6
MAXLOCALS = 1
// access flags 0x2
private bye()Ljava/lang/String;
L0
LINENUMBER 18 L0
LDC "Bye"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x2
private hello()Ljava/lang/String;
L0
LINENUMBER 23 L0
LDC "Hello"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
The overloaded visitMethodInsn()
method
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
MetaData executionUnitMetaData = new MetaData();
String[] methodInvocationToReplace = {"hello", "bye", "hello"};
String[] replacableClassName = {"chekingDepend.replaceFirstHelloInvoke","chekingDepend.replaceByeInvoke","chekingDepend.replaceSecondHelloInvoke"};
String[] replacableMethodName = {"replaceFirstHello","replaceBye","replaceSecondHello"};
if(i<methodInvocationToReplace.length && name.equals(methodInvocationToReplace[i])) {
super.visitMethodInsn(Opcodes.INVOKESTATIC, replacableClassName[i].replace('.', '/'), replacableMethodName[i], desc); // replacableMethodDesc[i]);
i++;
}else {
super.visitMethodInsn(opcode, owner, name, desc);
}
}
}
Here I copy the file from to another directory with same directory structure before executing, so that the original .class file is not modified. the modified .class file is created in a seprate folder with same package structure.
But am clue less why am getting lava.lang.VerifyError
.
Please help me to sort out this issue.
I use Java 1.6 and ASM 4.0
If the program runs correctly, my output should be:
Hello Method: one
Bye Method: two
Hello Method: three
When your original code calls the private instance methods, it does it like this:
ALOAD 0 # push `this`
INVOKESPECIAL checking/UserSampleClass.hello ()Ljava/lang/String; # call the method.
... and the INVOKESPECIAL is going to pop the first operand as the invocation target.
In your modified code, we have this instead:
ALOAD 0 # push `this`
INVOKESTATIC chekingDepend/replaceFirstHelloInvoke.replaceFirstHello ()Ljava/lang/String;
... but the INVOKESTATIC does not pop the opstack element.
The verifier (correctly) figures that this is wrong.
Compare the operand stack requirements for the respective bytecode instructions: