[11u]: RFR 8227650: EnumSet.class serialization broken in JDK 9+
Stuart Marks
stuart.marks at oracle.com
Tue Jul 16 18:09:34 UTC 2019
On 7/13/19 9:09 AM, Peter Levart wrote:
> This is a review request for JDK-8227368 [1] originally applied to JDK 13, to
> be backported to JDK 11 update release, tracked by JDK-8227650 [2].
>
> The proposed patch is unchanged at first [3], but since the original patch
> required CSR [4], it might be necessary to change it a bit for JDK 11 update
> release. Changes introduced in JDK 9 timeframe broke serialization of
> EnumSet.class (java.lang.Class<EnumSet>) objects, so this patch is trying to
> bring back the compatibility across all currently maintained releases (JDK 8,
> JDK 11 u, JDK 13). There will be no backport to JDK 8 u, since we are
> restoring the behavior of that release. Nevertheless this is a change to the
> serialization format and the published specification of it
> (serialized-form.html), so I'm asking if this is allowed in an update release.
> If the published specification matters, the patch could be tweaked so that the
> published serialized-form.html wouldn't change, only the behavior would. But
> this sounds like cheating. Please advise.
>
> Regards, Peter
>
> [1] https://bugs.openjdk.java.net/browse/JDK-8227368
> [2] https://bugs.openjdk.java.net/browse/JDK-8227650
> [3]
> http://cr.openjdk.java.net/~plevart/jdk11u-dev/8227650_EnumSet.class_serialization/webrev.01/
> [4] https://bugs.openjdk.java.net/browse/JDK-8227432
>
Hi Peter,
First, thanks for finding and fixing this bug in JDK 13. I think we all learned
a bunch more than we ever expected to about serialVersionUID!
I've been doing some investigation about how this fix can be applied to the
backports. A straight backport as you've posted in [3] is indeed a specification
change. It's not so much a matter of whether it's allowed or disallowed; it's
indeed possible to make specification changes in update releases. However, doing
so requires a JCP Maintenance Release (MR) of the corresponding Java SE
specification JSR.
My understanding is that in the past we (Oracle) have only done MRs when it's
been absolutely necessary and when there's no alternative. The actual decision
though is in the hands of the update leads (Andrew Haley for JDK 11, and Rob
McKenna for JDK 12).
In this message I'll make the case that an MR is not absolutely necessary, and
I'll outline an alternative.
First, a bit more background.
The essence of the JDK 13 fix is that it adds the line
private static final long serialVersionUID = 1009687484059888093L;
to EnumSet.java. This changes the javadoc output of serialized-form.html. In an
earlier message [5] I had suggested a "sleazy hack" (or, as Peter put it more
gently, "cheating"), doing something like the following in order to avoid
changing serialized-form.html:
private static final long serialVersionUID; static { serialVersionUID =
1009687484059888093L; }
Indeed, this avoids changing serialized-form.html. However, there's still a
problem. I ran JCK 11 with this patch applied to my JDK 11 build, and the result
is that the api/serializabletypes test failed. So, cheating or sleazy hack, this
approach doesn't really work. Although this technique avoids changing the /text/
of the specification output, it really is changing the specification, as far as
the JCK is concerned.
The good news is that it's possible to change EnumSet's serialVersionUID back to
what it was in JDK 8, without changing the specification. In effect, I reverted
the post-JDK 8 changes that had caused changes to the svuid. These were the
following:
1) A couple fields had been made transient.
I removed the transient modifiers. This has no practical effect on the
serialized output, since EnumSet uses a serialization proxy. However, it had the
effect of making those fields documented in serialized-form.html. I was able to
suppress that by adding a serialPersistentFields field with a zero-length array.
2) The <clinit> method was removed.
The svuid computation includes the presence of a <clinit> (static initializer)
method on the class. A non-constant expression as the initializer of a static
field effectively creates a <clinit> even though there's no static block. The
JDK 8 code initialized one static field with a non-constant expression. A
post-JDK 8 change removed this, thereby removing <clinit>, thus affecting the
svuid. The addition of the zero-length array creation for serialPersistentFields
(above) is sufficient to reintroduce <clinit>.
3) The svuid computation includes synthetic methods, including one named access$000.
The JDK 8 code defined a private field in EnumSet that was referenced from a
nested class, the serialization proxy. This required the addition of a bridge
method, in this case named access$000. (This predates nestmates, which removes
the need for such bridge methods.) A post-JDK 8 change moved this private field
into the nested class, which removed the need for the bridge method. I was able
to "revert" the effect of this change by adding a no-op method named access$000.
Yes, another sleazy hack, my specialty!
**
The webrev for this changeset is [6]. I've adjusted the BogusEnumSet test
accordingly. I didn't change the EnumSetClassSerialization test from the JDK 13
patch. This passes the regression tests, the JCK tests, and the
serialized-form.html is unchanged as far as I can see (except for some
incidental markup). And of course the svuid matches that of JDK 8 -- which is
tested by the EnumSetClassSerialization test.
This gives up some of the performance gain introduced by the intervening
changesets. This probably causes loading of ObjectStreamField.class and
ObjectStreamField[].class, and it (re-)introduces a static initializer. This is
unfortunate, but it doesn't seem too terrible to me. (I haven't measured it though.)
What do you think?
s'marks
[5] http://mail.openjdk.java.net/pipermail/core-libs-dev/2019-June/061114.html
[6] http://cr.openjdk.java.net/~smarks/reviews/8227368.jdk11u/webrev.0/
More information about the jdk-updates-dev
mailing list