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);
> }
> }
> }
> }
>