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 fors
); - 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
andlambda$g$1
, we now generatelambda$m$0
andlambda$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 incorrectEnclosingMethod
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 Inner
is as follows:
EnclosingMethod: #16.#0 // Outer
Which is correct.
- csr of
-
JDK-8336492 Regression in lambda serialization
- Resolved
- relates to
-
JDK-8334037 Local class creation in lambda in pre-construction context crashes javac
- Resolved