Delay writing typei fields of Nodes.
First Node read/written is responsible for writing/reading the typei fields for
all of its children.
This avoids deep recursion when TypeI objects refer to not-yet-written Node objects.
@@ -52,9 +52,11 @@ import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
+import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.CheckReturnValue;
@@ -3285,6 +3287,11 @@ public class Node implements Serializable {
return this.token == Token.YIELD;
}
+ // see writeObject() and readObject() for how this field is used in (de)serialization.
+ // TODO(bradfordcsmith): We are assuming that we will never have multiple (de)serializations
+ // happening at the same time.
+ private static List<Node> incompleteNodes = null;
+
@GwtIncompatible("ObjectOutputStream")
private void writeObject(java.io.ObjectOutputStream out) throws Exception {
// Do not call out.defaultWriteObject() as all the fields and transient and this class does not
@@ -3296,6 +3303,17 @@ public class Node implements Serializable {
writeEncodedInt(out, sourcePosition);
writeEncodedInt(out, length);
+ boolean isStartingNode = false;
+ if (incompleteNodes == null) {
+ // The first node to get serialized is responsible for completing serialization
+ // of all the other nodes whose serialization it triggers.
+ // This allows us to avoid deep recursion that would happen otherwise due to
+ // node -> type obj -> another node -> type obj...
+ isStartingNode = true;
+ incompleteNodes = new ArrayList<>();
+ }
+ incompleteNodes.add(this);
+
// Serialize the embedded children linked list here to limit the depth of recursion (and avoid
// serializing redundant information like the previous reference)
Node currentChild = first;
@@ -3305,8 +3323,15 @@ public class Node implements Serializable {
}
// Null marks the end of the children.
out.writeObject(null);
- out.writeObject(typei);
out.writeObject(propListHead);
+
+ if (isStartingNode) {
+ List<Node> nodeList = Node.incompleteNodes;
+ Node.incompleteNodes = null;
+ for (Node n : nodeList) {
+ out.writeObject(n.typei);
+ }
+ }
}
@GwtIncompatible("ObjectInputStream")
@@ -3318,6 +3343,17 @@ public class Node implements Serializable {
sourcePosition = readEncodedInt(in);
length = readEncodedInt(in);
+ boolean isStartingNode = false;
+ if (incompleteNodes == null) {
+ // The first node to get deserialized is responsible for completing deserialization
+ // of all the other nodes whose deserialization it triggers.
+ // This allows us to avoid deep recursion that would happen otherwise due to
+ // node -> type obj -> another node -> type obj...
+ isStartingNode = true;
+ incompleteNodes = new ArrayList<>();
+ }
+ incompleteNodes.add(this);
+
// Deserialize the children list restoring the value of the previous reference.
first = (Node) in.readObject();
if (first != null) {
@@ -3341,8 +3377,15 @@ public class Node implements Serializable {
checkState(first.previous == null);
first.previous = lastChild;
}
- typei = (TypeI) in.readObject();
propListHead = (PropListItem) in.readObject();
+
+ if (isStartingNode) {
+ List<Node> nodeList = Node.incompleteNodes;
+ Node.incompleteNodes = null;
+ for (Node n : nodeList) {
+ n.typei = (TypeI) in.readObject();
+ }
+ }
}
/**
