JDK-8201131 : Deserialization fails for cyclic object graphs, superclassing, and HashSet
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io:serialization
  • Affected Version: 9,10,11
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2018-04-04
  • Updated: 2024-06-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "9.0.4"
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Linux prod-wgba-005.oslo-no0030.xxx.xxx 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Microsoft Windows [Version 6.1.76.01]

EXTRA RELEVANT SYSTEM CONFIGURATION :
Seems to be independent of operating system.


A DESCRIPTION OF THE PROBLEM :
Seems to be similar to the 1.4.2 bug described here
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6208166#

1) Create an object graph (for example object tree) with cyclic dependencies:
- Node class with an Long field and a hashCode() method depending on this field.
- A parent reference.
- A set of children (represented by a HashSet) and a parent reference.
2)
- Create some Node instances and chain them together so that they represent a tree (with cyclic dependencies). 
3) 
- Serialize and de-serialize the object graph from 2)

This works with JDK8 but fails with JDK9.



REGRESSION.  Last worked in version 8u172

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached code with JDK9.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No output.

ACTUAL -
Exception in thread "main" java.lang.RuntimeException: java.lang.NullPointerException
	at wg.crewmanager.scrapbook.ReproduceHashSetSerializationBug.deSerialize(ReproduceHashSetSerializationBug.java:127)
	at wg.crewmanager.scrapbook.ReproduceHashSetSerializationBug.test(ReproduceHashSetSerializationBug.java:89)
	at wg.crewmanager.scrapbook.ReproduceHashSetSerializationBug.main(ReproduceHashSetSerializationBug.java:73)
Caused by: java.lang.NullPointerException
	at wg.crewmanager.scrapbook.ReproduceHashSetSerializationBug$Superclass.hashCode(ReproduceHashSetSerializationBug.java:38)
	at java.base/java.util.HashMap.hash(HashMap.java:339)
	at java.base/java.util.HashMap.put(HashMap.java:612)
	at java.base/java.util.HashSet.readObject(HashSet.java:342)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1160)
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2207)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2078)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1585)
	at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2346)
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2240)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2078)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1585)
	at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2346)
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2240)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2078)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1585)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at java.base/java.util.ArrayList.readObject(ArrayList.java:825)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1160)
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2207)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2078)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1585)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at wg.crewmanager.scrapbook.ReproduceHashSetSerializationBug.deSerialize(ReproduceHashSetSerializationBug.java:123)
	... 2 more


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package wg.crewmanager.scrapbook;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

/**
 * Test serialization of {@link HashSet}.
 *
 */
public class ReproduceHashSetSerializationBug
{

   private static final Random RANDOM = new Random();

   public static class Superclass implements Serializable
   {

      private final Long id;

      public Superclass()
      {
         this.id = RANDOM.nextLong();
      }

      public int hashCode()
      {
         return this.id.hashCode();
      }

      /*
      private void readObject(java.io.ObjectInputStream in)
         throws IOException, ClassNotFoundException
      {
         in.defaultReadObject();
      }
      */

   }


   public static class Node extends Superclass implements Serializable
   {

      private final String name;
      private final Node parent;
      private final Set<Node> children = new HashSet<>();

      public Node(String name, Node parent)
      {
         this.name = name;
         this.parent = parent;
         if (parent != null)
         {
            parent.children.add(this);
         }
      }

   }

   public static void main(String[] args)
   {
      new ReproduceHashSetSerializationBug().test();
   }

   @SuppressWarnings("unchecked")
   private void test()
   {

      Node a = new Node("a", null);
      Node b = new Node("b", a);

      List<Node> elements = new ArrayList<>();
      elements.add(b);
      elements.add(a);

      byte[] bytes = serialize((Serializable) elements);

      List<Node> deserialized = (List<Node>) deSerialize(bytes);
   }

   /**
    * Serializes the given object into a byte array.
    *
    * @param serializable not null
    * @return a byte array, not null
    */
   private static byte[] serialize(Serializable serializable)
   {
      try
      {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
         objectOutputStream.writeObject(serializable);
         return out.toByteArray();
      }
      catch (Exception e)
      {
         throw new RuntimeException(e);
      }
   }

   /**
    * De-serializes the given byte array into an object.
    *
    * @param bytes the byte array to de-serializes, not null
    * @return the de-serialized object
    */
   private static Serializable deSerialize(byte[] bytes)
   {
      try
      {
         return (Serializable) new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
      }
      catch (Exception e)
      {
         throw new RuntimeException(e);
      }
   }

}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Explicit readObject in super class (remove comments and run again).

Not sure why this makes it work.





Comments
This bug covers the problems with deserialization of object graphs using HashSet and subclassing. It differs from JDK-6208166, which involves cycles and HashSet, but not subclassing. These differ because the scenarios described in the current bug worked up through JDK 8 but were broken by changes in JDK 9, namely JDK-8071474. ("Better failure atomicity for default read object"). A workaround for this class of bugs is to add a readObject() method to the superclass. This differs from JDK-6208166, where deserializing a graph with cycles would fail on all JDK releases 1.4 and later. There is no superclass on which to add a readObject() method.
12-06-2024

Seems related to, if not a duplicate of, JDK-8199664.
25-06-2018

To reproduce the issue, run the attached test case. JDK 8u161 - Pass JDK 9.0.4 - Fail JDK 10+46 - Fail JDK 11-ea+7 - Fail java version "11-ea" 2018-09-18 Java(TM) SE Runtime Environment 18.9 (build 11-ea+7) Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11-ea+7, mixed mode) Exception in thread "main" java.lang.RuntimeException: java.lang.NullPointerException at JI9053219.deSerialize(JI9053219.java:121) at JI9053219.test(JI9053219.java:83) at JI9053219.main(JI9053219.java:67) Caused by: java.lang.NullPointerException at JI9053219$Superclass.hashCode(JI9053219.java:32) at java.base/java.util.HashMap.hash(HashMap.java:339) at java.base/java.util.HashMap.put(HashMap.java:612) at java.base/java.util.HashSet.readObject(HashSet.java:342) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1160) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2214) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2085) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1592) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2353) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2247) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2085) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1592) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2353) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2247) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2085) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1592) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at java.base/java.util.ArrayList.readObject(ArrayList.java:823) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1160) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2214) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2085) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1592) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at JI9053219.deSerialize(JI9053219.java:117) ... 2 more
05-04-2018