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.lev...@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.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); > >>> > > } > >>> > > } > >>> > > } > >>> > > } > >>> > > > >>> > > >>> > >