Duplicate Methods Generated for Lambda with Eclipse Compiler

This line

((UnaryOperator<Integer>)o->o).toString();

      

written anywhere in the class and compiled with Eclipse Kepler will crash when this line is reached on execution:

java.lang.BootstrapMethodError: call site initialization exception
at java.lang.invoke.CallSite.makeSite(CallSite.java:328)
at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:296)
at test.Test.main(Test.java:7)
Caused by: java.lang.ClassFormatError: Duplicate method name&signature in class file test/Test$$Lambda$1
at sun.misc.Unsafe.defineAnonymousClass(Native Method)
at java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:324)
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:194)
at java.lang.invoke.LambdaMetafactory.altMetafactory(LambdaMetafactory.java:474)
at java.lang.invoke.CallSite.makeSite(CallSite.java:301)
... 2 more

      

Nothing remarkable in itself, but another bug in the Eclipse Java 8 compiler. However, I'm intrigued by the details of the failure. If we enable the system property jdk.internal.lambda.dumpProxyClasses

and get the generated code for the lambda class, parsing it using it javap

will show that the class has two identical methods apply

, one of which is marked as a bridge method:

{
  public java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #14                 // class java/lang/Integer
         4: invokestatic  #20                 // Method test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
         7: areturn

  public java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #14                 // class java/lang/Integer
         4: invokestatic  #20                 // Method test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
         7: areturn
}

      

I understand that bridge methods are needed with Generics in order to maintain backward compatibility; however, I can't figure out how a bug in Eclipse can cause the JDK to synthesize a defective pair of methods.

For comparison, if we slightly change our Java string to:

((Object)((UnaryOperator<Integer>)o->o)).toString();

      

then we only get one, non-bridged method and no glitches:

{
  public java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #14                 // class java/lang/Integer
         4: invokestatic  #20                 // Method test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
         7: areturn
}

      

Maybe this is actually a bug in the JDK but is not being caused javac

?

I am using javac 1.8.0_20

for OS X and Eclipse Kepler SR2 with Java 8 patch.

Update: calling the bootstrap method

The Eclipse compiler is responsible for issuing the correct invokedynamic bootstrap (lambda metafactory) method call. Here's what the bootstrap method arguments look like for the unfortunate case:

BootstrapMethods:
      0: #39 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;
        Method arguments:
          #41 (Ljava/lang/Object;)Ljava/lang/Object;
          #44 invokestatic test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
          #45 (Ljava/lang/Integer;)Ljava/lang/Integer;
          #46 4
          #47 1
          #48 (Ljava/lang/Object;)Ljava/lang/Object;

      

Thanks to Brian's help, it is now clear that the last two lines above result in an error:

  • number 1 on #47

    means "there is one bridge method";
  • (Ljava/lang/Object;)Ljava/lang/Object;

    on #48

    describes the signature of the bridge method, which is obviously the same as the main signature.

For comparison, this is a working example:

BootstrapMethods:
      0: #53 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:
          #55 (Ljava/lang/Object;)Ljava/lang/Object;
          #58 invokestatic test/Test.lambda$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
          #59 (Ljava/lang/Integer;)Ljava/lang/Integer;

      

Here, a simpler method is used metafactory

and no bridge methods are created.

+3


source to share


1 answer


Based on the stack trace you posted, this is almost certainly a bug in the Eclipse code generation, not the JDK. You can find this from the list of javap

code that captures the lambda (generated by ecj.). I think you will find that it calls an alternative to metafile ( altMetafactory

) that handles unusual cases like serializable lambdas, additional marker interfaces implemented by a lambda object, or bridge methods that are not handled by the target interface.

For reference, the only cases where additional bridges are explicitly required are



  • when the target interface requires bridges, but was compiled with an older javac and therefore the bridges are not present in the interface itself, or
  • when interactions between the target type and additional interfaces require bridges.

Both are serious corner cases and should be extremely rare.

+6


source







All Articles