ClassCastException: cannot assign SerializedLambda to field with ClassLoader

David Holmes david.holmes at oracle.com
Tue Mar 26 03:40:24 UTC 2019


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