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.
source to share
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.
source to share