Search code examples
javagroovyjava-bytecode-asminvokedynamic

How to bootstrap interface method reference with ObjectWeb2 ASM


I am trying to fix the metafactory call for an interface method reference in Groovy: https://issues.apache.org/jira/browse/GROOVY-9853

Given small Java program

public class J {
  public static void main(String[] args) {
    java.util.function.ToIntFunction<CharSequence> f = CharSequence::length;
    f.applyAsInt("");
  }
}

the compiler writes this bootstrap method:

Bootstrap methods:
  0 : # 58 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
        #59 (Ljava/lang/Object;)I
        #66 null
        #68 (Ljava/lang/CharSequence;)I

Given a very similar Groovy program

class G {
  @groovy.transform.CompileStatic
  static main(args) {
    java.util.function.ToIntFunction<CharSequence> f = CharSequence::length
    f.applyAsInt("")
  }
}

the groovy compiler writes this bootstrap method:

Bootstrap methods:
  0 : # 47 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
        #31 (Ljava/lang/Object;)I
        #38 java/lang/CharSequence.length:()I
        #40 (Ljava/lang/CharSequence;)I

The second constant pool entry is "null" for java and "java/lang/CharSequence.length:()I" for groovy. And this is the constant pool entry that is causing the ClassFormatError mentioned in the linked issue. I'm trying to change the bootstrap method output, which is done using ASM like this:

        methodVisitor.visitInvokeDynamicInsn(
                "applyAsInt",
                "()Ljava/util/function/ToIntFunction;",
                new Handle(
                    Opcodes.H_INVOKESTATIC,
                    "java/lang/invoke/LambdaMetafactory",
                    "metafactory",
                    "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
                    isInterface // false because enclosing class "G" is not an interface
                ),
                createBootstrapMethodArguments(
                        "()Ljava/util/function/ToIntFunction;",
                        Opcodes.H_INVOKEVIRTUAL,
                        typeOrTargetRefType, // CharSequence
                        methodRefMethod, // CharSequence#length
                        false
                ) // 3 arguments: [Type("(Ljava/lang/Object;)I"), Handle("java/lang/CharSequence.length()I (5 itf)"), Type("(Ljava/lang/CharSequence;)I")]
        );

    Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode, boolean serializable) {
        List<Object> arguments = new ArrayList<>(5);
        arguments.add(Type.getType(abstractMethodDesc));

        arguments.add(new Handle(
                insn,
                BytecodeHelper.getClassInternalName(methodOwnerClassNode.getName()),
                methodNode.getName(),
                BytecodeHelper.getMethodDescriptor(methodNode),
                methodOwnerClassNode.isInterface()));

        arguments.add(Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(),
                (Parameter[]) methodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE))));

        if (serializable) {
            arguments.add(5);
            arguments.add(0);
        }

        return arguments.toArray();
    }

Is it bootstrap method arguments that needs to be different? I can't quite make the connection between the "null" that java has in the constant table and what is sent to ASM in terms of Types and Handles.

Here is the javap output:


class J
  minor version: 0
  major version: 52
  flags: (0x0020) ACC_SUPER
  this_class: #1                          // J
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 3
Constant pool:
   #1 = Class              #2             // J
   #2 = Utf8               J
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LJ;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = InvokeDynamic      #0:#17         // #0:applyAsInt:()Ljava/util/function/ToIntFunction;
  #17 = NameAndType        #18:#19        // applyAsInt:()Ljava/util/function/ToIntFunction;
  #18 = Utf8               applyAsInt
  #19 = Utf8               ()Ljava/util/function/ToIntFunction;
  #20 = String             #21            //
  #21 = Utf8
  #22 = InterfaceMethodref #23.#25        // java/util/function/ToIntFunction.applyAsInt:(Ljava/lang/Object;)I
  #23 = Class              #24            // java/util/function/ToIntFunction
  #24 = Utf8               java/util/function/ToIntFunction
  #25 = NameAndType        #18:#26        // applyAsInt:(Ljava/lang/Object;)I
  #26 = Utf8               (Ljava/lang/Object;)I
  #27 = Utf8               args
  #28 = Utf8               [Ljava/lang/String;
  #29 = Utf8               f
  #30 = Utf8               Ljava/util/function/ToIntFunction;
  #31 = Utf8               LocalVariableTypeTable
  #32 = Utf8               Ljava/util/function/ToIntFunction<Ljava/lang/CharSequence;>;
  #33 = Utf8               MethodParameters
  #34 = Utf8               SourceFile
  #35 = Utf8               Temporary.java
  #36 = Utf8               BootstrapMethods
  #37 = Methodref          #38.#40        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #38 = Class              #39            // java/lang/invoke/LambdaMetafactory
  #39 = Utf8               java/lang/invoke/LambdaMetafactory
  #40 = NameAndType        #41:#42        // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #41 = Utf8               metafactory
  #42 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #43 = MethodHandle       6:#37          // REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #44 = MethodType         #26            //  (Ljava/lang/Object;)I
  #45 = InterfaceMethodref #46.#48        // java/lang/CharSequence.length:()I
  #46 = Class              #47            // java/lang/CharSequence
  #47 = Utf8               java/lang/CharSequence
  #48 = NameAndType        #49:#50        // length:()I
  #49 = Utf8               length
  #50 = Utf8               ()I
  #51 = MethodHandle       9:#45          // REF_invokeInterface java/lang/CharSequence.length:()I
  #52 = Utf8               (Ljava/lang/CharSequence;)I
  #53 = MethodType         #52            //  (Ljava/lang/CharSequence;)I
  #54 = Utf8               InnerClasses
  #55 = Class              #56            // java/lang/invoke/MethodHandles$Lookup
  #56 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #57 = Class              #58            // java/lang/invoke/MethodHandles
  #58 = Utf8               java/lang/invoke/MethodHandles
  #59 = Utf8               Lookup
{
  J();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LJ;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #16,  0             // InvokeDynamic #0:applyAsInt:()Ljava/util/function/ToIntFunction;
         5: astore_1
         6: aload_1
         7: ldc           #20                 // String
         9: invokeinterface #22,  2           // InterfaceMethod java/util/function/ToIntFunction.applyAsInt:(Ljava/lang/Object;)I
        14: pop
        15: return
      LineNumberTable:
        line 5: 0
        line 6: 6
        line 25: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  args   [Ljava/lang/String;
            6      10     1     f   Ljava/util/function/ToIntFunction;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            6      10     1     f   Ljava/util/function/ToIntFunction<Ljava/lang/CharSequence;>;
    MethodParameters:
      Name                           Flags
      args
}

BootstrapMethods:
  0: #43 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #44 (Ljava/lang/Object;)I
      #51 REF_invokeInterface java/lang/CharSequence.length:()I
      #53 (Ljava/lang/CharSequence;)I
InnerClasses:
  public static final #59= #55 of #57;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
public class Groovy9853 implements groovy.lang.GroovyObject
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // Groovy9853
  super_class: #4                         // java/lang/Object
  interfaces: 1, fields: 3, methods: 6, attributes: 2
Constant pool:
    #1 = Utf8               Groovy9853
    #2 = Class              #1            // Groovy9853
    #3 = Utf8               java/lang/Object
    #4 = Class              #3            // java/lang/Object
    #5 = Utf8               groovy/lang/GroovyObject
    #6 = Class              #5            // groovy/lang/GroovyObject
    #7 = Utf8               Scratch.groovy
    #8 = Utf8               $staticClassInfo
    #9 = Utf8               Lorg/codehaus/groovy/reflection/ClassInfo;
   #10 = Utf8               __$stMC
   #11 = Utf8               Z
   #12 = Utf8               metaClass
   #13 = Utf8               Lgroovy/lang/MetaClass;
   #14 = Utf8               <init>
   #15 = Utf8               ()V
   #16 = Utf8               Lgroovy/transform/Generated;
   #17 = NameAndType        #14:#15       // "<init>":()V
   #18 = Methodref          #4.#17        // java/lang/Object."<init>":()V
   #19 = Utf8               $getStaticMetaClass
   #20 = Utf8               ()Lgroovy/lang/MetaClass;
   #21 = NameAndType        #19:#20       // $getStaticMetaClass:()Lgroovy/lang/MetaClass;
   #22 = Methodref          #2.#21        // Groovy9853.$getStaticMetaClass:()Lgroovy/lang/MetaClass;
   #23 = NameAndType        #12:#13       // metaClass:Lgroovy/lang/MetaClass;
   #24 = Fieldref           #2.#23        // Groovy9853.metaClass:Lgroovy/lang/MetaClass;
   #25 = Utf8               this
   #26 = Utf8               LGroovy9853;
   #27 = Utf8               main
   #28 = Utf8               ([Ljava/lang/String;)V
   #29 = Utf8               args
   #30 = Utf8               (Ljava/lang/Object;)I
   #31 = MethodType         #30           //  (Ljava/lang/Object;)I
   #32 = Utf8               java/lang/CharSequence
   #33 = Class              #32           // java/lang/CharSequence
   #34 = Utf8               length
   #35 = Utf8               ()I
   #36 = NameAndType        #34:#35       // length:()I
   #37 = InterfaceMethodref #33.#36       // java/lang/CharSequence.length:()I
   #38 = MethodHandle       5:#37         // REF_invokeVirtual java/lang/CharSequence.length:()I
   #39 = Utf8               (Ljava/lang/CharSequence;)I
   #40 = MethodType         #39           //  (Ljava/lang/CharSequence;)I
   #41 = Utf8               java/lang/invoke/LambdaMetafactory
   #42 = Class              #41           // java/lang/invoke/LambdaMetafactory
   #43 = Utf8               metafactory
   #44 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #45 = NameAndType        #43:#44       // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #46 = Methodref          #42.#45       // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #47 = MethodHandle       6:#46         // REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #48 = Utf8               applyAsInt
   #49 = Utf8               ()Ljava/util/function/ToIntFunction;
   #50 = NameAndType        #48:#49       // applyAsInt:()Ljava/util/function/ToIntFunction;
   #51 = InvokeDynamic      #0:#50        // #0:applyAsInt:()Ljava/util/function/ToIntFunction;
   #52 = Utf8
   #53 = String             #52           //
   #54 = Utf8               java/util/function/ToIntFunction
   #55 = Class              #54           // java/util/function/ToIntFunction
   #56 = NameAndType        #48:#30       // applyAsInt:(Ljava/lang/Object;)I
   #57 = InterfaceMethodref #55.#56       // java/util/function/ToIntFunction.applyAsInt:(Ljava/lang/Object;)I
   #58 = Utf8               [Ljava/lang/String;
   #59 = Utf8               f
   #60 = Utf8               Ljava/util/function/ToIntFunction;
   #61 = Utf8               getClass
   #62 = Utf8               ()Ljava/lang/Class;
   #63 = NameAndType        #61:#62       // getClass:()Ljava/lang/Class;
   #64 = Methodref          #4.#63        // java/lang/Object.getClass:()Ljava/lang/Class;
   #65 = Utf8               org/codehaus/groovy/runtime/ScriptBytecodeAdapter
   #66 = Class              #65           // org/codehaus/groovy/runtime/ScriptBytecodeAdapter
   #67 = Utf8               initMetaClass
   #68 = Utf8               (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
   #69 = NameAndType        #67:#68       // initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
   #70 = Methodref          #66.#69       // org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
   #71 = NameAndType        #8:#9         // $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
   #72 = Fieldref           #2.#71        // Groovy9853.$staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
   #73 = Utf8               org/codehaus/groovy/reflection/ClassInfo
   #74 = Class              #73           // org/codehaus/groovy/reflection/ClassInfo
   #75 = Utf8               getClassInfo
   #76 = Utf8               (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
   #77 = NameAndType        #75:#76       // getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
   #78 = Methodref          #74.#77       // org/codehaus/groovy/reflection/ClassInfo.getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
   #79 = Utf8               getMetaClass
   #80 = NameAndType        #79:#20       // getMetaClass:()Lgroovy/lang/MetaClass;
   #81 = Methodref          #74.#80       // org/codehaus/groovy/reflection/ClassInfo.getMetaClass:()Lgroovy/lang/MetaClass;
   #82 = Utf8               Lgroovy/transform/Internal;
   #83 = Utf8               Ljava/beans/Transient;
   #84 = Utf8               groovy/lang/MetaClass
   #85 = Class              #84           // groovy/lang/MetaClass
   #86 = Utf8               setMetaClass
   #87 = Utf8               (Lgroovy/lang/MetaClass;)V
   #88 = Utf8               mc
   #89 = Utf8               $getLookup
   #90 = Utf8               ()Ljava/lang/invoke/MethodHandles$Lookup;
   #91 = Utf8               java/lang/invoke/MethodHandles
   #92 = Class              #91           // java/lang/invoke/MethodHandles
   #93 = Utf8               lookup
   #94 = NameAndType        #93:#90       // lookup:()Ljava/lang/invoke/MethodHandles$Lookup;
   #95 = Methodref          #92.#94       // java/lang/invoke/MethodHandles.lookup:()Ljava/lang/invoke/MethodHandles$Lookup;
   #96 = Utf8               Code
   #97 = Utf8               LocalVariableTable
   #98 = Utf8               RuntimeVisibleAnnotations
   #99 = Utf8               LineNumberTable
  #100 = Utf8               MethodParameters
  #101 = Utf8               StackMapTable
  #102 = Utf8               SourceFile
  #103 = Utf8               BootstrapMethods
{
  private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo;
    descriptor: Lorg/codehaus/groovy/reflection/ClassInfo;
    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC

  public static transient boolean __$stMC;
    descriptor: Z
    flags: (0x1089) ACC_PUBLIC, ACC_STATIC, ACC_TRANSIENT, ACC_SYNTHETIC

  private transient groovy.lang.MetaClass metaClass;
    descriptor: Lgroovy/lang/MetaClass;
    flags: (0x1082) ACC_PRIVATE, ACC_TRANSIENT, ACC_SYNTHETIC

  public Groovy9853();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: invokespecial #18                 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: invokevirtual #22                 // Method $getStaticMetaClass:()Lgroovy/lang/MetaClass;
         8: astore_1
         9: aload_1
        10: aload_0
        11: swap
        12: putfield      #24                 // Field metaClass:Lgroovy/lang/MetaClass;
        15: aload_1
        16: pop
        17: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      17     0  this   LGroovy9853;
    RuntimeVisibleAnnotations:
      0: #16()
        groovy.transform.Generated

  public static void main(java.lang.String...);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0089) ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #51,  0             // InvokeDynamic #0:applyAsInt:()Ljava/util/function/ToIntFunction;
         5: astore_1
         6: aload_1
         7: pop
         8: aload_1
         9: ldc           #53                 // String
        11: invokeinterface #57,  2           // InterfaceMethod java/util/function/ToIntFunction.applyAsInt:(Ljava/lang/Object;)I
        16: pop
        17: return
      LineNumberTable:
        line 7: 0
        line 8: 8
        line 9: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      17     0  args   [Ljava/lang/String;
            6      11     1     f   Ljava/util/function/ToIntFunction;
    MethodParameters:
      Name                           Flags
      args

  protected groovy.lang.MetaClass $getStaticMetaClass();
    descriptor: ()Lgroovy/lang/MetaClass;
    flags: (0x1004) ACC_PROTECTED, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: invokevirtual #64                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
         4: ldc           #2                  // class Groovy9853
         6: if_acmpeq     14
         9: aload_0
        10: invokestatic  #70                 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
        13: areturn
        14: getstatic     #72                 // Field $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
        17: astore_1
        18: aload_1
        19: ifnonnull     34
        22: aload_0
        23: invokevirtual #64                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
        26: invokestatic  #78                 // Method org/codehaus/groovy/reflection/ClassInfo.getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
        29: dup
        30: astore_1
        31: putstatic     #72                 // Field $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
        34: aload_1
        35: invokevirtual #81                 // Method org/codehaus/groovy/reflection/ClassInfo.getMetaClass:()Lgroovy/lang/MetaClass;
        38: areturn
      StackMapTable: number_of_entries = 2
        frame_type = 14 /* same */
        frame_type = 252 /* append */
          offset_delta = 19
          locals = [ class org/codehaus/groovy/reflection/ClassInfo ]

  public groovy.lang.MetaClass getMetaClass();
    descriptor: ()Lgroovy/lang/MetaClass;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #24                 // Field metaClass:Lgroovy/lang/MetaClass;
         4: dup
         5: ifnull        9
         8: areturn
         9: pop
        10: aload_0
        11: dup
        12: invokevirtual #22                 // Method $getStaticMetaClass:()Lgroovy/lang/MetaClass;
        15: putfield      #24                 // Field metaClass:Lgroovy/lang/MetaClass;
        18: aload_0
        19: getfield      #24                 // Field metaClass:Lgroovy/lang/MetaClass;
        22: areturn
      StackMapTable: number_of_entries = 1
        frame_type = 73 /* same_locals_1_stack_item */
          stack = [ class groovy/lang/MetaClass ]
    RuntimeVisibleAnnotations:
      0: #16()
        groovy.transform.Generated
      1: #82()
        groovy.transform.Internal
      2: #83()
        java.beans.Transient

  public void setMetaClass(groovy.lang.MetaClass);
    descriptor: (Lgroovy/lang/MetaClass;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #24                 // Field metaClass:Lgroovy/lang/MetaClass;
         5: return
    RuntimeVisibleAnnotations:
      0: #16()
        groovy.transform.Generated
      1: #82()
        groovy.transform.Internal
    MethodParameters:
      Name                           Flags
      mc

  public static java.lang.invoke.MethodHandles$Lookup $getLookup();
    descriptor: ()Ljava/lang/invoke/MethodHandles$Lookup;
    flags: (0x1009) ACC_PUBLIC, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=0, args_size=0
         0: invokestatic  #95                 // Method java/lang/invoke/MethodHandles.lookup:()Ljava/lang/invoke/MethodHandles$Lookup;
         3: areturn
}

BootstrapMethods:
  0: #47 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #31 (Ljava/lang/Object;)I
      #38 REF_invokeVirtual java/lang/CharSequence.length:()I
      #40 (Ljava/lang/CharSequence;)I

Solution

  • You are right, this is a bug in the Groovy compiler, caused by the wrong use of the tag and isInterface parameter.

    Let's start with the isInterface parameter:

    This parameter is true if and only if the owner class (also a parameter of the Handle constructor) is an interface.

    Let's take a look at some of Groovy's code:

    default Handle createBootstrapMethod(boolean isInterface, boolean serializable) {
        if (serializable) {
            return new Handle(
                    Opcodes.H_INVOKESTATIC,
                    "java/lang/invoke/LambdaMetafactory",
                    "altMetafactory",
                    "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
                    isInterface
            );
        }
    
        return new Handle(
                Opcodes.H_INVOKESTATIC,
                "java/lang/invoke/LambdaMetafactory",
                "metafactory",
                "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
                isInterface
        );
    }
    

    (Called from here)

    java.lang.invoke.LambdaMetafactory is not an interface. And will never be.
    So the right thing to pass to the Handle constructor is a constant false.

    Next is the handle for the target method itself:

     createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), H_INVOKEVIRTUAL, lambdaClass, lambdaMethod, expression.isSerializable())
    

    The Handle that you want to create is for the implementation method - often a private method in the same class. Again, isInterface is true if the owner class is an interface.

    You have to use the H_* constant with a similar name as to which instruction you would use to invoke the method.

    From a design perspective, it might be possible to have a helper method that turns any MethodNode into an Handle