ClassCastException: cannot assign SerializedLambda to field with ClassLoader

seth lytle seth.lytle at gmail.com
Tue Mar 26 04:03:41 UTC 2019


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