59

the following code works perfectly fine in a Java 1.8 VM but produces a LambdaConversionException when executed in a Java 11 VM. Wheres the difference and why does it behave like this?


Code:

public void addSomeListener(Component comp){
    if(comp instanceof HasValue) {
        ((HasValue<?,?>) comp).addValueChangeListener(evt -> {
            //do sth with evt
        });
    }
}

HasValue Javadoc

Exception (V11 only):

Caused by: java.lang.invoke.LambdaConversionException: Type mismatch
for instantiated parameter 0: class java.lang.Object is not a subtype
of interface com.vaadin.flow.component.HasValue$ValueChangeEvent
    at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.checkDescriptor(AbstractValidatingLambdaMetafactory.java:308)
    at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:294)
    at java.base/java.lang.invoke.LambdaMetafactory.altMetafactory(LambdaMetafactory.java:503)
    at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:138)
    ... 73 more

Workaround:

ValueChangeListener<ValueChangeEvent<?>> listener = evt -> {
    // do sth with evt
};
((HasValue<?,?>) comp).addValueChangeListener(listener);

System:
OS: Windows 10
IDE: Eclipse 2018-12 (4.10.0)
Java (Compile): ecj
Java (Webserver): JDK 11.0.2
Webserver: Wildfly 15

Gerrit Sedlaczek
  • 1,040
  • 1
  • 11
  • 29
  • 2
    Interesting and yet poor thing there `(HasValue,?>) comp` ... Such validation though agreed didn't exist in the JDK-8 code. – Naman Apr 05 '19 at 09:24
  • When exactly do you get this exception? When you add the listener or when the listener is invoked with an event? If the latter, how do you invoke it with an event (show the code place) – Erwin Bolwidt Apr 05 '19 at 09:40
  • @ErwinBolwidt It happens when the listener is supposed to be added. – Gerrit Sedlaczek Apr 05 '19 at 09:45
  • Hm..I set up a small hello world example using the official vaadin starter project. I added your method and added a TextField which I am passing to your `addSomeListener` method. It works without the mentioned error and when changing the TextField's value the added listener is executed as expected. (Tested it with jetty and Java 11: `jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 11.0.1+13`) – codinghaus Apr 05 '19 at 10:22
  • Which compiler are you using? This looks like a problem with the generated bytecode. – Jorn Vernee Apr 05 '19 at 10:46
  • 8
    That’s an important information, as Eclipse has its own compiler, so problems caused by the compiler (and this looks much like a compiler problem) do not have to apply to `javac` then. – Holger Apr 05 '19 at 10:51
  • You could verify a difference in bytecode by inspecting the generated bytecode with `javap -c `. Since the compiler shouldn't change, unless you're also changing Eclipse versions, it's probably caused by sharpening of BSM type checking in JDK 11 (i.e. in the code that creates the lambda instance) that is exposing a bug in ECJ. – Jorn Vernee Apr 05 '19 at 10:55
  • 2
    @JornVernee changing the language level sometimes causes behavioral changes in the compiler. – Holger Apr 05 '19 at 11:40
  • @Holger Yeah, you're right. I forgot about that. – Jorn Vernee Apr 05 '19 at 11:47

1 Answers1

53

TL;DR The Eclipse compiler generates a method signature for the lambda instance that is invalid according to the specification. Due to additional type checking code added in JDK 9 to better enforce the specification, the incorrect signature is now causing an exception when running on Java 11.


Verified with Eclipse 2019-03 as well with this code:

public class Main {    
    public static void main(String[] args) {
        getHasValue().addValueChangeListener(evt -> {});
    }

    public static HasValue<?, ?> getHasValue() {
        return null;
    }    
}

interface HasValue<E extends HasValue.ValueChangeEvent<V>,V> {    
    public static interface ValueChangeEvent<V> {}    
    public static interface ValueChangeListener<E extends HasValue.ValueChangeEvent<?>> {
        void valueChanged(E event);
    }    
    void addValueChangeListener(HasValue.ValueChangeListener<? super E> listener);
}

Even when using null as the receiver, the code fails when bootstrapping with the same error.

Using javap -v Main we can see where the problem lies. I'm seeing this in the BoostrapMethods table:

BootstrapMethods:
  0: #48 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:
      #50 (Lmain/HasValue$ValueChangeEvent;)V
      #53 REF_invokeStatic main/Main.lambda$0:(Ljava/lang/Object;)V
      #54 (Ljava/lang/Object;)V

Note that the last argument (constant #54) is (Ljava/lang/Object;)V, while javac generates (Lmain/HasValue$ValueChangeEvent;)V. i.e. the method signature that Eclipse wants to use for the lambda is different from what javac wants to use.

If the wanted method signature is the erasure of the target method (which seems to be the case), then the correct method signature is indeed (Lmain/HasValue$ValueChangeEvent;)V since that is the erasure of the target method, which is:

void valueChanged(E event);

Where E is E extends HasValue.ValueChangeEvent<?>, so that would be erased to HasValue.ValueChangeEvent.

The problem seems to be with ECJ, and seems to have been brought to the surface by JDK-8173587 (revision) (Unfortunately this seems to be a private ticket.) which adds extra type checks to verify that the SAM method type is actually compatible with the instantiate method type. According to the documentation of LambdaMetafactory::metafactory the instantiated method type must be the same, or a specialization of the SAM method type:

instantiatedMethodType - The signature and return type that should be enforced dynamically at invocation time. This may be the same as samMethodType, or may be a specialization of it.

which the method type generated by ECJ is evidently not, so this ends up throwing an exception. (though, to be fair, I don't see defined anywhere what constitutes a "specialization" in this case). I've reported this on the Eclipse bugzilla here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161

I'm guessing this change was made somewhere in JDK 9, since source code was already modular at that point, and the date of the revision is fairly early (February 2017).

Since javac generates the correct method signature, you could switch to that for the time being as a workaround.

Jorn Vernee
  • 26,917
  • 3
  • 67
  • 80
  • 9
    I tend to over-complicate these bytecode related answers (being someone who's used to looking at bytecode). It's hard to find the right balance between sufficient explanation, and keeping the answer succinct. If anything is unclear, please feel free to ask for clarification. – Jorn Vernee Apr 05 '19 at 12:18
  • 3
    this is _not_ over-complicate and it's an easy to read and understand answer, there simply isn't a better one. I had a pleasure reading it, thank you for taking the time – Eugene Apr 05 '19 at 13:16
  • Could you please add a link to the corresponding Eclipse bug? – howlger Apr 05 '19 at 13:16
  • @JornVernee Not at all. I think you explain bytecode related answers very well. – Michael Berry Apr 05 '19 at 14:00
  • @howlger [I can't find it](https://bugs.eclipse.org/bugs/buglist.cgi?classification=Eclipse&component=Core&product=JDT&query_format=advanced&short_desc=LambdaConversionException&short_desc_type=allwordssubstr), there doesn't seem to be one yet. – Jorn Vernee Apr 05 '19 at 15:07
  • @JornVernee If it doesn't exist yet, please report it to Eclipse and add the link to bug to your answer. – howlger Apr 05 '19 at 15:10
  • Eugene, MichaelBerry Thanks for the support, btw ;) – Jorn Vernee Apr 05 '19 at 16:33
  • @JornVernee Thanks for analyzing this issue, for the detailed description and for reporting it to Eclipse. – howlger Apr 05 '19 at 18:12
  • Thank you for explaining the issue. – Gerrit Sedlaczek Apr 05 '19 at 18:49