ClassCastException: cannot assign SerializedLambda to field with ClassLoader

seth lytle seth.lytle at gmail.com
Tue Mar 26 15:04:59 UTC 2019


awesome ! thanks for the workaround


peter wrote:
> 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 :-)






On Tue, Mar 26, 2019 at 3:54 AM Peter Levart <peter.levart at gmail.com> wrote:

> 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