Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8154236

Deserialization of lambda causes ClassCastException

    Details

    • Subcomponent:
    • Understanding:
      Fix Understood
    • CPU:
      generic
    • OS:
      generic

      Description

      FULL PRODUCT VERSION :
      java version "1.8.0_73"
      Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
      Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.3.9600]

      A DESCRIPTION OF THE PROBLEM :
      - Two (distinct) classes, B and C, both of which extend the same base class, A, which has a method, String f().
      - Create a Supplier reference to method f() for an object of type B; call this bf [new B()::f].
      - Create a Supplier reference to method f() for an object of type C; cal this cf [new C()::f].
      - Serialize cf (ObjectOutputStream#writeObject)
      - When the serialized cf is deserialized (ObjectInputStream#readObject), a ClassCastException is thrown saying that class C cannot be cast to class B

      The problem seems to be with the generate byte code (using javap to decompile):
      - javac generates a call to invokevirtual that references the shared based class (invokevirtual SerializationTest$A.f:()Ljava/lang/String;)
      - the Eclipse compiler generates a call invokevirtual that references the actual class the lambda was created from

      -- javac generated --

        0: #109 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
          Method arguments:
            #110 ()Ljava/lang/Object;
            #111 invokevirtual SerializationTest$A.f:()Ljava/lang/String;
            #112 ()Ljava/lang/String;
            #113 5
            #114 0
        1: #109 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
          Method arguments:
            #110 ()Ljava/lang/Object;
            #111 invokevirtual SerializationTest$A.f:()Ljava/lang/String;
            #112 ()Ljava/lang/String;
            #113 5
            #114 0

      -- Eclipse compiler generated --

      BootstrapMethods:
        0: #172 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
          Method arguments:
            #173 ()Ljava/lang/Object;
            #176 invokestatic SerializationTest.lambda$0:(LSerializationTest$B;)Ljava/lang/String;
            #177 ()Ljava/lang/String;
            #178 1
        1: #172 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
          Method arguments:
            #179 ()Ljava/lang/Object;
            #182 invokestatic SerializationTest.lambda$1:(LSerializationTest$C;)Ljava/lang/String;
            #183 ()Ljava/lang/String;
            #178 1

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the program below.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      No ClassCastException when the lambda is deserialized.
      ACTUAL -
      See exception below.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.io.IOException: unexpected exception type
              at java.io.ObjectStreamClass.throwMiscException(Unknown Source)
              at java.io.ObjectStreamClass.invokeReadResolve(Unknown Source)
              at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
              at java.io.ObjectInputStream.readObject0(Unknown Source)
              at java.io.ObjectInputStream.readObject(Unknown Source)
              at scratch.SerializationTest.main(SerializationTest.java:31)
      Caused by: java.lang.reflect.InvocationTargetException
              at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
              at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
              at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
              at java.lang.reflect.Method.invoke(Unknown Source)
              at java.lang.invoke.SerializedLambda.readResolve(Unknown Source)
              at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
              at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
              at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
              at java.lang.reflect.Method.invoke(Unknown Source)
              ... 5 more
      Caused by: java.lang.ClassCastException: SerializationTest$C cannot be cast to SerializationTest$B
              at SerializationTest.$deserializeLambda$(SerializationTest.java:10)
              ... 14 more

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.io.ByteArrayInputStream;
      import java.io.ByteArrayOutputStream;
      import java.io.ObjectInputStream;
      import java.io.ObjectOutputStream;
      import java.io.Serializable;
      import java.util.function.Supplier;

      public class SerializationTest {

          static class A implements Serializable {
              public String f() { return toString(); }
          }

          static class B extends A { }

          static class C extends A { }

          public static void main(String[] args) throws Exception {
              Supplier<String> bs = (Supplier<String> & Serializable) new B()::f;
              Supplier<String> cs = (Supplier<String> & Serializable) new C()::f;

              ByteArrayOutputStream caos = new ByteArrayOutputStream();

              try (ObjectOutputStream coos = new ObjectOutputStream(caos);) {
                  coos.writeObject(cs);
              }

              try (ObjectInputStream cis = new ObjectInputStream(new ByteArrayInputStream(caos.toByteArray()));) {
                  Supplier<String> ccs = (Supplier<String>) cis.readObject();
              }

          }

      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Don't use multiple lambdas that reference the same method in a base class and that get serialized and deserialized in the same class.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                mcimadamore Maurizio Cimadamore
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                7 Start watching this issue

                Dates

                • Created:
                  Updated: