Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8337558

Regression in lambda serialization

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P3 P3
    • 24
    • tools
    • None
    • binary, behavioral
    • low
    • Hide
      There is a chance that code might depend on the unspecified names of generated non-serializable lambda methods (e.g. via reflection). Also, the fixes in `EnclosingMethod` can also lead to behavioral incompatibility - e.g. when using `Class::getEnclosingMethod` (although, the old classfile seemed broken).
      Show
      There is a chance that code might depend on the unspecified names of generated non-serializable lambda methods (e.g. via reflection). Also, the fixes in `EnclosingMethod` can also lead to behavioral incompatibility - e.g. when using `Class::getEnclosingMethod` (although, the old classfile seemed broken).
    • Language construct
    • Implementation

      Summary

      The fix for JDK-8334037 created some compatibility issues with serializable lambdas defined inside local or anonymous non-serializable classes. The fix associated with this CSR reverts those changes.

      Problem

      The fix for JDK-8334037 made changes to the compiler backend pipeline. Most notably, the step for translating lambda expressions is now ran after the step for translating inner classes. While this fixes several anomalies, it introduced an unwanted regression for serializable lambdas. Consider this case:

      class Outer {
          void m(String s) {
               new Object() {
                   void g() {
                        Supplier<String> ss = (Supplier<String> & Serializable)() -> s;
                   }
               };
          }
      }

      In the above, a lambda expression is nested inside an anonymous class. The lambda expression returns a captured variable defined in the enclosing method m.

      Before the fix for JDK-8334037, the following synthetic lambda method was generated:

        private static java.lang.String lambda$g$5b985cd5$1(java.lang.String);

      But, after the fix, the method changed to this:

        private java.lang.String lambda$g$c930465a$1();

      Note the differences:

      • The method no longer captures s directly;
      • The method is no longer static (as it needs access to the enclosing instance which has the captured value for s);
      • The name of the method is changed.

      All these changes would make a previously serialized lambda fail to deserialize. And, even worse, given that there's now a dependency between the lambda and its enclosing class, and given the enclosing class is not serializable, the new lambda would actually fail to serialize.

      Solution

      The solution is to revert lambda translation to the former behavior. More specifically, captured variables should never be accessed through the enclosing class. They should instead be re-captured by the generated lambda method.

      In order to fix this incompatibility, a relatively large rewrite of the LambdaToMethod javac class was necessary. This resulted in few minor incompatibilities, which we'd like to list here:

      • the name of synthetic methods for non-serializable lambdas has been changed, and made more consistent. E.g. instead of lambda$m$0 and lambda$g$1, we now generate lambda$m$0 and lambda$g$0 (e.g. we only increase the counter when there are ambiguities).
      • when fixing the LambdaToMethod code we realized that existing code was causing incorrect EnclosingMethod attribute to be generated.

      To illustrate the latter issue, consider the following example:

      class Outer {
          Runnable lambda = () -> { class Inner { } };
      }

      The EnclosingMethod attribute associated with the Inner class used to be like this:

      EnclosingMethod: #16.#3                 // Outer.<init>

      Note how this is incorrect, and not compliant with JVMS 4.7.7 which states:

      In particular, method_index must be zero if the current class was immediately enclosed in source code by an instance initializer, static initializer, instance variable initializer, or class variable initializer. (The first two concern both local classes and anonymous classes, while the last two concern anonymous classes declared on the right hand side of a field assignment.)

      After the fix associated with this CSR, the EnclosingMethod attribute for Inneris as follows:

      EnclosingMethod: #16.#0                 // Outer

      Which is correct.

            mcimadamore Maurizio Cimadamore
            cushon Liam Miller-Cushon
            Vicente Arturo Romero Zaldivar
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: