Different behaviors between Unsafe defineAnonymousClass and ClassLoader
Asked Answered
L

0

6

I used classloader and Unsafe::definedAnonymous() to load generated bytecode byte[]. The usage of Class returned by classLoader.loadClass() succeeds while it fails with c.getMethod() in which c=Unsafe.defineAnonymousClass() API. So is the generated bytecode WRONG?

My code:

        MainInliner loader = new MainInliner();
        Class<?> c =null;
        byte[] bytes = ...
        if(args[0].equals("0")){
            c = loader.loadClass(name, bytes, 0, bytes.length);  // Classloader.loadClass.
        }else{
            c = Unsafe.defineAnonymousClass(Hoster, bytes, null);
        }

        Method m = c.getMethod("main", new Class<?>[] { String[].class    });

The error is:

Exception Details:
  Location:
    code/sxu/asm/example/Caller.test(II)V @98: lload_3
  Reason:
    Type integer (current frame, locals[3]) is not assignable to long
  Current Frame:
    bci: @98
    flags: { }
    locals: { 'code/sxu/asm/example/Caller', integer, integer, integer, integer, integer, integer, integer, integer, integer }
    stack: { 'java/io/PrintStream' }
  Bytecode:
    0000000: 1b85 2ab4 000e 1b1c 3e36 043a 0537 0619
    0000010: 05b4 002a b600 3019 05b4 0033 b600 3060
    0000020: 3608 1508 3609 1606 1509 a700 0385 6542
    0000030: 1b2a b400 0e1c 033e 3604 3a05 3606 1905
    0000040: b400 2ab6 0030 1905 b400 33b6 0030 6036
    0000050: 0715 0736 0815 0615 08a7 0003 6036 05b2
    0000060: 003d 21b6 0043 b1                      
  Stackmap Table:
    full_frame(@45,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Long,Integer,Integer},{Long,Integer})
    full_frame(@92,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer,Integer,Integer,Integer},{Integer,Integer})

and the generated bytecode is:

 public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=10, args_size=3
         0: iload_1       
         1: i2l   

         2: aload_0       
         3: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
         6: iload_1       
         7: iload_2       
         8: istore_3      
         9: istore        4
        11: astore        5
        13: lstore        6

        15: aload         5
        17: getfield      #42                 // Field code/sxu/asm/example/Callee._a:Ljava/lang/String;
        20: invokevirtual #48                 // Method java/lang/String.length:()I
        23: aload         5
        25: getfield      #51                 // Field code/sxu/asm/example/Callee._b:Ljava/lang/String;
        28: invokevirtual #48                 // Method java/lang/String.length:()I
        31: iadd          
        32: istore        8
        34: iload         8
        36: istore        9
        38: lload         6
        40: iload         9
        42: goto          45


        45: i2l           
        46: lsub          
        47: lstore_3        //r..

        48: iload_1       

        49: aload_0       
        50: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
        53: iload_2       
        54: iconst_0      
        55: istore_3      
        56: istore        4
        58: astore        5
        60: istore        6   //The backup stack bottom variable.

        62: aload         5
        64: getfield      #42                 // Field code/sxu/asm/example/Callee._a:Ljava/lang/String;
        67: invokevirtual #48                 // Method java/lang/String.length:()I
        70: aload         5
        72: getfield      #51                 // Field code/sxu/asm/example/Callee._b:Ljava/lang/String;
        75: invokevirtual #48                 // Method java/lang/String.length:()I
        78: iadd          
        79: istore        7
        81: iload         7
        83: istore        8
        85: iload         6     //Push back 6
        87: iload         8     //Repush 8
        89: goto          92
                                //return calculate()
        92: iadd          
        93: istore        5
        95: getstatic     #61                 // Field java/lang/System.out:Ljava/io/PrintStream;
        98: lload_3       
        99: invokevirtual #67                 // Method java/io/PrintStream.println:(J)V
       102: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              15      30     5  this   Lcode/sxu/asm/example/Callee;
              15      30     4     t   I
              15      30     3     p   I
              34      11     8   tmp   I
              62      30     5  this   Lcode/sxu/asm/example/Callee;
              62      30     4     t   I
              62      30     3     p   I
              81      11     7   tmp   I
               0     103     0  this   Lcode/sxu/asm/example/Caller;
               0     103     1     a   I
               0     103     2     b   I
              48      55     3     r   J
              95       8     5     c   I
      LineNumberTable:
        line 19: 0
        line 16: 15
        line 23: 34
        line 21: 48
        line 16: 62
        line 23: 81
        line 23: 95
        line 30: 102
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 45
          locals = [ class code/sxu/asm/example/Caller, int, int, int, int, class code/sxu/asm/example/Callee, long, int, int ]
          stack = [ long, int ]
           frame_type = 255 /* full_frame */
          offset_delta = 46
          locals = [ class code/sxu/asm/example/Caller, int, int, int, int, class code/sxu/asm/example/Callee, int, int, int, int ]
          stack = [ int, int ]

}

This bytecode corresponding to inline a Callee method.

I checked this bytecode sequence, and it seems there is no error it. The variable 3 in the error message occurs 4 times, in which:

8: istore_3        //Popo up existing parameters for inlining. 
47: lstore_3        //r. The local variable in the Caller method
55: istore_3        // Similiar to the label 8, pops stack paras to local variable. 
98: lload_3         //Println parameter

The corresponding method is:

public class Caller{
public void test(int a, int b){
    long r = (long)a- _callee.calculate(a, b);
    int c = a +_callee.calculate(b, 0);
    System.out.println(r);
}

}

  public class Callee{
    public int calculate(int t, int p){
        int tmp=_a.length()+_b.length();
        return tmp;
    }}
Leid answered 27/5, 2015 at 19:18 Comment(7)
What does loader.loadClass(bytes) mean? There is no such ClassLoader.loadClass(byte[]) method. How did you generate the bytecode? Did you start with an existing method and modify it?Antietam
Yes.The bytecode is generated based on the Class of Caller and Callee, with ASM libraryLeid
What does loader.loadClass(name, bytes, 0, bytes.length) mean? There is no such ClassLoader.loadClass(String, byte[], int, int) method. <- you didn't answer the first part of @bkail's ocmment.Beltane
Typo error here. loader.loadClass(String) will finally invoke defineClass(name, byte, 0, length). I have got the answer for my own question. There is wrong variable index since 55: istore_3Leid
Of course, if you store an int into variable #3 @55, you can’t read a long from the same variable @98. It might be that the verification has been deferred when the class has been defined via an ordinary class loader. Besides that, I recommend not to use Reflection on classes created via Unsafe.defineAnonymousClass, unless you want to play Russian roulette. Use MethodHandles to access the generated code…Ruphina
Using MethodHandle instead of Reflection is a good suggestion for performance.Leid
I didn’t suggest it for performance reason. The problem is that some implementations have problems due to Reflections incompatible self-optimizations. Read, after some successful invocations, Method will try to optimize itself by compiling an accessor class, but since anonymous classes can’t be accessed by other classes, you suddenly receive Errors when calling invoke. That might happen after twenty invocations or so. As said, that’s dependent on the implementation, hence, jre version. That’s why I said Russian roulette.Ruphina

© 2022 - 2024 — McMap. All rights reserved.