ClassCastException: cannot assign SerializedLambda to field with ClassLoader
Peter Levart
peter.levart at gmail.com
Tue Mar 26 07:54:35 UTC 2019
Hi Seth,
I think you stumbled on a "defect" of Java Serialization mechanism.
Namely, the inability for it to correctly deserialize object graphs
containing a cycle when an object in such cycle uses the "readResolve()"
special method to replace the deserialized object with a replacement
object. SerializedLambda is a class with instances that are 1st
deserialized from stream then "replaced" with real lambda objects via
the "readResolve()" method which constructs and returns the "real"
lambda object. The deserialized SerializedLambda object is nevertheless
attempted to be (temporarily) assigned to the field that would hold the
final lambda object, which fails as the filed is of different type than
SerializedLambda (or supertype).
You can check my claims by changing the type of the field holding the
lambda to java.lang.Object and see if it works... (It should if my
memory serves me well :-)
This is a known defect of Java serialization and is not specific to
serialized lambda.
There were some unsuccessful attempts to fix it during (re)discovery
when lambdas were being added to Java.
Regards, Peter
On 3/26/19 5:03 AM, seth lytle wrote:
> it's a nested class, but it's loaded with the same class loader as the
> toplevel.
> which is what i thought you thought was the problem when
>
> you wrote:
>> this test loads a nested type into a different classloader to its
> enclosing type
>
> and:
>> it is a requirement that all nest members be defined in the same
>> package - which implies the same classloader
>
>
>
> On Mon, Mar 25, 2019 at 11:40 PM David Holmes <david.holmes at oracle.com>
> wrote:
>
>> On 26/03/2019 1:06 pm, seth lytle wrote:
>>> i still get the same stack trace after breaking the example out into two
>>> files. does that sufficiently address the nesting issue ?
>> ??
>>
>> public static class MyCode ...
>>
>> is still a nested class.
>>
>> David
>>
>>> file: SerializedLambdaTest
>>> package test;
>>>
>>> import java.io.ByteArrayInputStream;
>>> import java.io.ByteArrayOutputStream;
>>> import java.io.IOException;
>>> import java.io.ObjectInputStream;
>>> import java.io.ObjectOutputStream;
>>> import java.io.Serializable;
>>>
>>> // from: https://bugs.openjdk.java.net/browse/JDK-8008770
>>> public class SerializedLambdaTest implements Runnable {
>>> public void run() { new MyCode().run(); }
>>> public interface SerializableRunnable extends Runnable,Serializable
>> {
>>> }
>>> public static class MyCode implements SerializableRunnable {
>>> SerializableRunnable runnable2 = ()
>>> -> {
>>> System.out.println("HELLO"+this.getClass());
>>> };
>>> private byte[] serialize(Object o) {
>>> ByteArrayOutputStream baos;
>>> try (
>>> ObjectOutputStream oos
>>> = new ObjectOutputStream(baos = new
>>> ByteArrayOutputStream())) {
>>> oos.writeObject(o);
>>> } catch (IOException e) {
>>> throw new RuntimeException(e);
>>> }
>>> return baos.toByteArray();
>>> }
>>> private <T> T deserialize(byte[] bytes) {
>>> try (
>>> ObjectInputStream ois
>>> = new ObjectInputStream(new
>>> ByteArrayInputStream(bytes))) {
>>> return (T) ois.readObject();
>>> } catch (IOException|ClassNotFoundException e) {
>>> throw new RuntimeException(e);
>>> }
>>> }
>>> @Override
>>> public void run() {
>>> System.out.println(" this: "+this);
>>> SerializableRunnable deSerializedThis
>>> = deserialize(serialize(this));
>>> System.out.println(" deSerializedThis: "
>>> +deSerializedThis);
>>>
>>> SerializableRunnable runnable = runnable2;
>>> System.out.println(" runnable: "+runnable);
>>> SerializableRunnable deSerializedRunnable
>>> = deserialize(serialize(runnable));
>>> System.out.println("deSerializedRunnable: "
>>> +deSerializedRunnable);
>>> }
>>> }
>>> }
>>>
>>>
>>> file: MyClassLoader
>>> package test;
>>>
>>> import java.io.IOException;
>>> import java.io.InputStream;
>>>
>>> class MyClassLoader extends ClassLoader {
>>> MyClassLoader(ClassLoader parent) {
>>> super(parent);
>>> }
>>> @Override
>>> protected Class<?> loadClass(String name,boolean resolve) throws
>>> ClassNotFoundException {
>>> if (name.startsWith("test."))
>>> synchronized (getClassLoadingLock(name)) {
>>> Class<?> c = findLoadedClass(name);
>>> if (c==null) c = findClass(name);
>>> if (resolve) resolveClass(c);
>>> return c;
>>> }
>>> else return super.loadClass(name,resolve);
>>> }
>>> @Override
>>> protected Class<?> findClass(String name) throws
>>> ClassNotFoundException {
>>> String path = name.replace('.','/').concat(".class");
>>> try (final InputStream is = getResourceAsStream(path)) {
>>> if (is!=null) {
>>> byte[] bytes = is.readAllBytes();
>>> return defineClass(name,bytes,0,bytes.length);
>>> }
>>> else throw new ClassNotFoundException(name);
>>> } catch (IOException e) {
>>> throw new ClassNotFoundException(name,e);
>>> }
>>> }
>>> public static void main(String[] args) throws Exception {
>>> ClassLoader myCl = new MyClassLoader(
>>> MyClassLoader.class.getClassLoader()
>>> );
>>> Class<?> myCodeClass = Class.forName(
>>> "test.SerializedLambdaTest",
>>> true,
>>> myCl
>>> );
>>> Runnable myCode = (Runnable) myCodeClass.newInstance();
>>> myCode.run();
>>> }
>>> }
>>>
>>>
>>>
>>>
>>> On Mon, Mar 25, 2019 at 10:34 PM David Holmes <david.holmes at oracle.com
>>> <mailto:david.holmes at oracle.com>> wrote:
>>>
>>> Hi Seth,
>>>
>>> On 26/03/2019 12:16 pm, seth lytle wrote:
>>> > i haven't changed the assignment from the original test case
>>> (which was
>>> > accepted as valid at the time). i haven't gone through it in
>>> depth, but
>>> > assume that it's ok and used it since people are already familiar
>>> with
>>> > it. it appears to me that that example uses reflection to
>>> allocate an
>>> > instance using a definitive class loader and then uses a runnable
>>> for
>>> > the serialization/deserialization cycle
>>> >
>>> > the class cast exception happens not at the example level, but
>> deep
>>> > inside `readObject` when it's doing an internal assignment
>>> >
>>> > perhaps my choice of words "into a new classloader" was
>>> insufficient or
>>> > misleading. in both examples that i've come up with so far, the
>>> class
>>> > loader calls defineClass directly without first delegating to
>>> super (for
>>> > a subset of the classes). so "into a definitive classloader"
>>> might have
>>> > been a better choice
>>>
>>> I think the basic problem is that this test loads a nested type into
>> a
>>> different classloader to its enclosing type. I can easily imagine
>> this
>>> messing up the code that gets generated to implement the lambda
>>> expression - but I'd need to examine that code in detail to see
>> exactly
>>> why (others may know this more readily than I do). It's unclear to me
>>> whether this would be considered a bug or a "don't do that"
>> situation.
>>> As of JDK 11, nested types define "nests" at the VM level (see
>> JEP-181)
>>> and it is a requirement that all nest members be defined in the same
>>> package - which implies the same classloader.
>>>
>>> David
>>>
>>> >
>>> >
>>> >
>>> >
>>> >
>>> > On Mon, Mar 25, 2019 at 9:34 PM David Holmes
>>> <david.holmes at oracle.com <mailto:david.holmes at oracle.com>
>>> > <mailto:david.holmes at oracle.com
>>> <mailto:david.holmes at oracle.com>>> wrote:
>>> >
>>> > Hi Seth,
>>> >
>>> > On 26/03/2019 11:22 am, seth lytle wrote:
>>> > > if a lambda is a field and captures `this`, and it's
>>> deserialized
>>> > into a
>>> > > new class loader, it throws a ClassCastException.
>>> >
>>> > Not sure I follow. If you load a class into a different
>>> classloader
>>> > then
>>> > you get a different type. It might appear the same to you but
>>> it is a
>>> > distinct type, so you can't assign across different instances
>>> loaded by
>>> > different classloaders.
>>> >
>>> > Cheers,
>>> > David
>>> > -----
>>> >
>>> > > i came across this bug
>>> > > independently while writing a test, but then found an old
>>> openjdk
>>> > bug with
>>> > > a similar issue and tweaked the test case (source below).
>>> my version
>>> > > differs only in
>>> > > 1. moved the lambda to a field
>>> > > 2. reference `this` in it
>>> > > 3. uses readAllBytes instead of the internal method
>>> > > 4. formatting
>>> > >
>>> > > running with java 11 or 12 (or java 8 using a substitute
>> for
>>> > readAllBytes)
>>> > > results in:
>>> > > this:
>>> test.SerializedLambdaTest$MyCode at 8efb846
>>> > > deSerializedThis:
>>> test.SerializedLambdaTest$MyCode at 2b71fc7e
>>> > > runnable:
>>> > >
>>> >
>>>
>> test.SerializedLambdaTest$MyCode$$Lambda$1/0x0000000801188440 at 37bba400
>>> > > Exception in thread "main" java.lang.ClassCastException:
>>> cannot
>>> > assign
>>> > > instance of java.lang.invoke.SerializedLambda to field
>>> > > test.SerializedLambdaTest$MyCode.runnable2 of type
>>> > > test.SerializedLambdaTest$SerializableRunnable in instance
>> of
>>> > > test.SerializedLambdaTest$MyCode
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2190)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectStreamClass$FieldReflector.checkObjectFieldValueTypes(ObjectStreamClass.java:2153)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectStreamClass.checkObjFieldValueTypes(ObjectStreamClass.java:1407)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.defaultCheckFieldValues(ObjectInputStream.java:2371)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readSerialData(ObjectInputStream.java:2278)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readObject0(ObjectInputStream.java:1594)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readArray(ObjectInputStream.java:1993)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readObject0(ObjectInputStream.java:1588)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.defaultReadFields(ObjectInputStream.java:2355)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readSerialData(ObjectInputStream.java:2249)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readObject0(ObjectInputStream.java:1594)
>>> > > at
>>> > > java.base/java.io <http://java.io>
>>> >
>>> <http://java.io
>>> .ObjectInputStream.readObject(ObjectInputStream.java:430)
>>> > > at
>>> > >
>>> >
>>>
>> test.SerializedLambdaTest$MyCode.deserialize(SerializedLambdaTest.java:35)
>>> > > at
>>> test.SerializedLambdaTest$MyCode.run(SerializedLambdaTest.java:51)
>>> > > at
>>> test.SerializedLambdaTest.main(SerializedLambdaTest.java:66)
>>> > >
>>> > >
>>> > >
>>> > > https://bugs.openjdk.java.net/browse/JDK-8008770
>>> > >
>>> > >
>>> > > package test;
>>> > >
>>> > > import java.io.ByteArrayInputStream;
>>> > > import java.io.ByteArrayOutputStream;
>>> > > import java.io.IOException;
>>> > > import java.io.InputStream;
>>> > > import java.io.ObjectInputStream;
>>> > > import java.io.ObjectOutputStream;
>>> > > import java.io.Serializable;
>>> > >
>>> > > // from: https://bugs.openjdk.java.net/browse/JDK-8008770
>>> > > public class SerializedLambdaTest {
>>> > > public interface SerializableRunnable extends
>>> > Runnable,Serializable {
>>> > > }
>>> > > public static class MyCode implements
>>> SerializableRunnable {
>>> > > SerializableRunnable runnable2 = ()
>>> > > -> {
>>> > > System.out.println("HELLO"+this.getClass());
>>> > > };
>>> > > private byte[] serialize(Object o) {
>>> > > ByteArrayOutputStream baos;
>>> > > try (
>>> > > ObjectOutputStream oos
>>> > > = new ObjectOutputStream(baos = new
>>> > > ByteArrayOutputStream())) {
>>> > > oos.writeObject(o);
>>> > > } catch (IOException e) {
>>> > > throw new RuntimeException(e);
>>> > > }
>>> > > return baos.toByteArray();
>>> > > }
>>> > > private <T> T deserialize(byte[] bytes) {
>>> > > try (
>>> > > ObjectInputStream ois
>>> > > = new ObjectInputStream(new
>>> > > ByteArrayInputStream(bytes))) {
>>> > > return (T) ois.readObject();
>>> > > } catch (IOException|ClassNotFoundException
>> e) {
>>> > > throw new RuntimeException(e);
>>> > > }
>>> > > }
>>> > > @Override
>>> > > public void run() {
>>> > > System.out.println(" this:
>>> "+this);
>>> > > SerializableRunnable deSerializedThis
>>> > > = deserialize(serialize(this));
>>> > > System.out.println(" deSerializedThis: "
>>> > > +deSerializedThis);
>>> > >
>>> > > SerializableRunnable runnable = runnable2;
>>> > > System.out.println(" runnable:
>>> "+runnable);
>>> > > SerializableRunnable deSerializedRunnable
>>> > > = deserialize(serialize(runnable));
>>> > > System.out.println("deSerializedRunnable: "
>>> > > +deSerializedRunnable);
>>> > > }
>>> > > }
>>> > > public static void main(String[] args) throws
>> Exception {
>>> > > ClassLoader myCl = new MyClassLoader(
>>> > >
>> SerializedLambdaTest.class.getClassLoader()
>>> > > );
>>> > > Class<?> myCodeClass = Class.forName(
>>> > >
>>> SerializedLambdaTest.class.getName()+"$MyCode",
>>> > > true,
>>> > > myCl
>>> > > );
>>> > > Runnable myCode = (Runnable)
>>> myCodeClass.newInstance();
>>> > > myCode.run();
>>> > > }
>>> > > static class MyClassLoader extends ClassLoader {
>>> > > MyClassLoader(ClassLoader parent) {
>>> > > super(parent);
>>> > > }
>>> > > @Override
>>> > > protected Class<?> loadClass(String name,boolean
>>> resolve)
>>> > > throws ClassNotFoundException {
>>> > > if (name.startsWith("test."))
>>> > > synchronized (getClassLoadingLock(name)) {
>>> > > Class<?> c = findLoadedClass(name);
>>> > > if (c==null)
>>> > > c = findClass(name);
>>> > > if (resolve)
>>> > > resolveClass(c);
>>> > > return c;
>>> > > }
>>> > > else
>>> > > return super.loadClass(name,resolve);
>>> > > }
>>> > > @Override
>>> > > protected Class<?> findClass(String name) throws
>>> > > ClassNotFoundException {
>>> > > String path =
>>> name.replace('.','/').concat(".class");
>>> > > try ( InputStream is =
>>> getResourceAsStream(path)) {
>>> > > if (is!=null) {
>>> > > byte[] bytes = is.readAllBytes();
>>> > > return
>>> defineClass(name,bytes,0,bytes.length);
>>> > > } else
>>> > > throw new
>> ClassNotFoundException(name);
>>> > > } catch (IOException e) {
>>> > > throw new ClassNotFoundException(name,e);
>>> > > }
>>> > > }
>>> > > }
>>> > > }
>>> > >
>>> >
>>>
More information about the core-libs-dev
mailing list