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>> 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>.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2190)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectStreamClass$FieldReflector.checkObjectFieldValueTypes(ObjectStreamClass.java:2153)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectStreamClass.checkObjFieldValueTypes(ObjectStreamClass.java:1407)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectInputStream.defaultCheckFieldValues(ObjectInputStream.java:2371)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectInputStream.readSerialData(ObjectInputStream.java:2278)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087)
     > at
     > java.base/java.io
    <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
     > at
     > java.base/java.io
    <http://java.io>.ObjectInputStream.readArray(ObjectInputStream.java:1993)
     > at
     > java.base/java.io
    <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1588)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2355)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectInputStream.readSerialData(ObjectInputStream.java:2249)
     > at
     > java.base/java.io
    
<http://java.io>.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087)
     > at
     > java.base/java.io
    <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
     > at
     > java.base/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