ClassCastException: cannot assign SerializedLambda to field with ClassLoader

David Holmes david.holmes at oracle.com
Tue Mar 26 05:12:53 UTC 2019


Hi Seth,

I think we've both been chasing red herrings :) The classloader is not 
relevant. I can run the test with everything loaded as normal by the app 
loader and it still gets the ClassCastException. Searching JBS it seems 
that serialization of lambdas is broken - ref:

https://bugs.openjdk.java.net/browse/JDK-8154236
https://bugs.openjdk.java.net/browse/JDK-8174865
https://bugs.openjdk.java.net/browse/JDK-8174864

though I'm not clear if your testcase falls under the same 
categorization as above.

David
-----

On 26/03/2019 2:08 pm, David Holmes wrote:
> On 26/03/2019 2:03 pm, 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
> 
> Sorry, hard to see all the diffs inside the email. Not sure what 
> relevance splitting into two files has given the main change was to load 
> the enclosing class in the new loader rather than the nested class.
> 
> Anyway, yes this gets rid of the "nested class in a different 
> classloader" problem.
> 
> I'll take a look and see what's getting generated under the hood.
> 
> David
> -----
> 
>> 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 
>> <mailto: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>
>>      > <mailto: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>>
>>      >      > <mailto: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>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2190) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://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>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectStreamClass.checkObjFieldValueTypes(ObjectStreamClass.java:1407) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.defaultCheckFieldValues(ObjectInputStream.java:2371) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readSerialData(ObjectInputStream.java:2278) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1594) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readArray(ObjectInputStream.java:1993) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1588) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2355) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readSerialData(ObjectInputStream.java:2249) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2087) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://java.io> <http://java.io>
>>      >      >
>>      >      
>>  <http://java.io>.ObjectInputStream.readObject0(ObjectInputStream.java:1594) 
>>
>>      >      >      > at
>>      >      >      > java.base/java.io <http://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