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.hol...@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.hol...@oracle.com
<mailto:david.hol...@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.hol...@oracle.com <mailto:david.hol...@oracle.com>
      > <mailto:david.hol...@oracle.com
     <mailto:david.hol...@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@8efb846
      >      >      deSerializedThis:
     test.SerializedLambdaTest$MyCode@2b71fc7e
      >      >              runnable:
      >      >
      >

  test.SerializedLambdaTest$MyCode$$Lambda$1/0x0000000801188440@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);
      >      >              }
      >      >          }
      >      >      }
      >      > }
      >      >
      >


Reply via email to