Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't crash when printing a recursive structure #2882

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/source/commands/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ They allow to transform data structures into JSON objects and store them in a fi
It serializes messages currently stored on message queues to ConfigNode (KSP data format) and adds them to KSP save files.

It is **important** to remember that any data that you supply to :ref:`WRITEJSON` and :meth:`Connection:SENDMESSAGE` must be serializable.

.. note::

It's not possible to serialize structures that loop on themselves. Only `Directed Acyclical Graphs <https://en.wikipedia.org/wiki/Directed_acyclic_graph>`_ are supported.

An example of a looping structure is::

SET a TO LIST().
SET b TO LIST(a).
a:ADD(b).
WRITEJSON(a, "test"). // <-- This will fail
33 changes: 25 additions & 8 deletions src/kOS.Safe/Serialization/SafeSerializationMgr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,49 @@ public static bool IsPrimitiveStructure(object serialized)
return serialized is PrimitiveStructure;
}

private object DumpValue(object value, bool includeType)
private object DumpValue(object value, bool includeType, bool allowTruncatedRecursion, List<object> seenList)
{
var valueDumper = value as IDumper;

if (!(value is string) && seenList.Contains(value))
{
if (!allowTruncatedRecursion)
throw new KOSSerializationException("Trying to serialize a structure that loops on itself. Only Directed Acyclical Graphs are supported.");
return "...recurse...";
}

if (valueDumper != null) {
return Dump(valueDumper, includeType);
return Dump(valueDumper, includeType, allowTruncatedRecursion, seenList);
} else if (value is Dump) {
return value;
} else if (value is List<object>) {
return (value as List<object>).Select((v) => DumpValue(v, includeType)).ToList();
var nextSeenList = new List<object>(seenList);
nextSeenList.Add(value);
return (value as List<object>).Select((v) => DumpValue(v, includeType, allowTruncatedRecursion, nextSeenList)).ToList();
} else if (IsSerializablePrimitive(value)) {
return Structure.ToPrimitive(value);
} else {
return value.ToString();
}
}

public Dump Dump(IDumper dumper, bool includeType = true)
public Dump Dump(IDumper dumper, bool includeType = true, bool allowTruncatedRecursion = false, List<object> seenList = null)
{
// We want to allow DAG-like structure serialization (so nodes can occur in the output more than once.
// Cyclical graphs crash us with a stackoverflow however. To protect from this we check wether we're already
// in the list of objects that are between us and the root.
if (seenList == null)
seenList = new List<object>();
seenList = new List<object>(seenList);
seenList.Add(dumper);

var dump = dumper.Dump();

List<object> keys = new List<object>(dump.Keys);

foreach (object key in keys)
{
dump[key] = DumpValue(dump[key], includeType);
dump[key] = DumpValue(dump[key], includeType, allowTruncatedRecursion, seenList);
}

if (includeType)
Expand All @@ -72,9 +89,9 @@ public Dump Dump(IDumper dumper, bool includeType = true)
return dump;
}

public string Serialize(IDumper serialized, IFormatWriter formatter, bool includeType = true)
public string Serialize(IDumper serialized, IFormatWriter formatter, bool includeType = true, bool allowTruncatedRecursion = false)
{
return formatter.Write(Dump(serialized, includeType));
return formatter.Write(Dump(serialized, includeType, allowTruncatedRecursion));
}

public object CreateValue(object value)
Expand Down Expand Up @@ -221,7 +238,7 @@ public IDumper Deserialize(string input, IFormatReader formatter)

public string ToString(IDumper dumper)
{
return Serialize(dumper, TerminalFormatter.Instance, false);
return Serialize(dumper, TerminalFormatter.Instance, false, true);
}
}
}
Expand Down