Search code examples
javabytecode

java.lang.VerifyError while running modified .class file


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

Solution

  • 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: