ClassCastException: cannot assign SerializedLambda to field with ClassLoader
David Holmes
david.holmes at oracle.com
Tue Mar 26 05:12:53 UTC 2019
Hi Seth,
I think we've both been chasing red herrings :) The classloader is not
relevant. I can run the test with everything loaded as normal by the app
loader and it still gets the ClassCastException. Searching JBS it seems
that serialization of lambdas is broken - ref:
https://bugs.openjdk.java.net/browse/JDK-8154236
https://bugs.openjdk.java.net/browse/JDK-8174865
https://bugs.openjdk.java.net/browse/JDK-8174864
though I'm not clear if your testcase falls under the same
categorization as above.
David
-----
On 26/03/2019 2:08 pm, David Holmes wrote:
> On 26/03/2019 2:03 pm, 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
>
> Sorry, hard to see all the diffs inside the email. Not sure what
> relevance splitting into two files has given the main change was to load
> the enclosing class in the new loader rather than the nested class.
>
> Anyway, yes this gets rid of the "nested class in a different
> classloader" problem.
>
> I'll take a look and see what's getting generated under the hood.
>
> David
> -----
>
>> 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
>> <mailto: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>
>> > <mailto: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>>
>> > > <mailto: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>
>> > >
>> >
>> <http://java.io>.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2190)
>>
>> > > > at
>> > > > java.base/java.io <http://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>
>> > >
>> >
>> <http://java.io>.ObjectStreamClass.checkObjFieldValueTypes(ObjectStreamClass.java:1407)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.defaultCheckFieldValues(ObjectInputStream.java:2371)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readSerialData(ObjectInputStream.java:2278)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readArray(ObjectInputStream.java:1993)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1588)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2355)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readSerialData(ObjectInputStream.java:2249)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087)
>>
>> > > > at
>> > > > java.base/java.io <http://java.io> <http://java.io>
>> > >
>> >
>> <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
>>
>> > > > at
>> > > > java.base/java.io <http://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