3

This line

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

written anywhere in a class, and compiled with Eclipse Kepler, will cause a failure when that line is reached in 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

On its own this is nothing very noteworthy, yet another bug in Eclipse's Java 8 compiler. However, I am intrigued by the details of the failure. If we enable the jdk.internal.lambda.dumpProxyClasses system property and retrieve the generated lambda class code, parsing it with javap will reveal that the class has two identical apply methods defined, one of them flagged 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 backwards compatibility; however, I cannot understand how a bug in Eclipse could force the JDK into synthesizing a defective method pair.

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

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

then we get just one, non-bridge method, and no failures:

{
  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
}

Could it be that this is in fact a bug in the JDK, but it isn't provoked by javac?

I am using javac 1.8.0_20 on OS X and Eclipse Kepler SR2 with the Java 8 patch.

Update: bootstrap method invocation

Eclipse compiler is responsible for emitting the correct invokedynamic bootstrap method invocation (the lambda metafactory). This is how the bootstrap method arguments look like for the failing 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;

Owing to Brian's help it is now clear to me that the last two lines above inflict the error:

  • number 1 on #47 means "there is one bridge method";
  • (Ljava/lang/Object;)Ljava/lang/Object; on #48 describes the bridge method's signature, which is obviously the same as the main signature.

For comparison, this is the working case:

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 the simpler metafactory method is used and no bridge methods are created.

1 Answer 1

6

Based on the stack trace you posted, this is almost certainly a bug in Eclipse's code generation, not the JDK. You can find this out from the javap listing of the code that captures the lambda (generated by ecj.) I think what you'll find is that it invokes the alternate metafactory (altMetafactory) which handles the unusual cases such as serializable lambdas, additional marker interfaces implemented by the lambda object, or bridge methods not handled by the target interface.

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

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

Both are serious corner cases and should happen exceedingly rarely.

Sign up to request clarification or add additional context in comments.

5 Comments

Yes, I have already ascertained that the altMetaFactory is used, and only in the failing case. I still can't find the code that provides the method implementation; I don't know where even to look for that. Is it in the constant pool? Also, I expected the lambda factory mechanism to be high-level enough not to allow creating invalid classes. Basically, I am trying to gain insight into the inner workings of the lambda (meta)factory.
If you post the javap listing for the static bootstrap args (bootstrap method table in the constant pool, javap -v) to the altMetaFactory bootstrap, I can tell you what it should say. The bootstrap method listing is right after the constant pool in a javap -v -c listing; find the right entry in the table by looking at the invokedynamic instruction that captures the desired lambda.
I have pasted it, but I can already thank you for answering my question. Clearly, the issue happens on the entry labeled as #48. I have cross-checked with the signature of altMetaFactory, this is the "bridges" argument.
Yep, eclipse is asking for a redundant bridge.
This had never been reported to Eclipse. Trying to close the loop now, I cannot find any released version of ecj that creates code involving altMetaFactory for this example. Sounds like ecj and javac have been agreeing here ever since Java 8 GA (unless there was more to the example than shown above).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.