From 13accc22bef03d6c130164c3bc8078bfc531b408 Mon Sep 17 00:00:00 2001 From: eirannejad Date: Wed, 31 Jan 2024 13:18:27 -0800 Subject: [PATCH] Fixes RH-80142 marshaller now watches for self referencing objects --- src/runtime/McNeel.PythonEngine.cs | 103 +++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 14 deletions(-) diff --git a/src/runtime/McNeel.PythonEngine.cs b/src/runtime/McNeel.PythonEngine.cs index 8e06cd549..95f3f6d78 100755 --- a/src/runtime/McNeel.PythonEngine.cs +++ b/src/runtime/McNeel.PythonEngine.cs @@ -618,31 +618,59 @@ PyModule PrepareScope(string scopeName, string pythonFile) #endregion #region Marshalling + class MarshContext + { + readonly Stack _parents = new Stack(); + + public bool IsSeen(object parent) => _parents.Contains(parent); + + public void Push(object parent) => _parents.Push(parent); + + public void Pop() => _parents.Pop(); + } + // NOTE: // ensures incoming data is marshalled into // closest-matching Python objects - public PyObject MarshInput(object value) + public PyObject MarshInput(object value) => MarshInput(value, new MarshContext()); + + PyObject MarshInput(object value, MarshContext context) { + if (context.IsSeen(value)) + { + return MarshToPyObject(value); + } + switch (value) { case string str: return MarshToPyObject(value); case IDictionary dict: + context.Push(dict); + PyDict pyDict = new PyDict(); foreach (KeyValuePair pair in dict) { if (pair.Key is string key) - pyDict[key] = MarshInput(pair.Value); + pyDict[key] = MarshInput(pair.Value, context); } + + context.Pop(); + return pyDict; case IList list: + context.Push(list); + PyList pyList = new PyList(); foreach (object obj in list) { - pyList.Append(MarshInput(obj)); + pyList.Append(MarshInput(obj, context)); } + + context.Pop(); + return pyList; case PyObject pyObj: @@ -661,47 +689,80 @@ PyObject MarshToPyObject(object value) // NOTE: // ensures outgoing data is marshalled into // closest-matching dotnet objects - public object MarshOutput(object value) + public object MarshOutput(object value) => MarshOutput(value, new MarshContext()); + + object MarshOutput(object value, MarshContext context) { + if (context.IsSeen(value)) + { + return MarshToPyObject(value); + } + switch (value) { case object[] array: - return array.Select(i => MarshOutput(i)).ToArray(); + return array.Select(i => MarshOutput(i, context)).ToArray(); case PyList list: + context.Push(list); + var fromPylist = new List(); foreach (object obj in list) { - fromPylist.Add(MarshOutput(obj)); + fromPylist.Add(MarshOutput(obj, context)); } + + context.Pop(); + return fromPylist; case List list: + context.Push(list); + var fromClrList = new List(); foreach (object obj in list) { - fromClrList.Add(MarshOutput(obj)); + fromClrList.Add(MarshOutput(obj, context)); } + + context.Pop(); + return fromClrList; case IDictionary dict: - return dict.Select(p => + context.Push(dict); + + var fromClrDict = dict.Select(p => { - return new KeyValuePair(MarshOutput(p.Key), MarshOutput(p.Value)); + return new KeyValuePair(MarshOutput(p.Key, context), MarshOutput(p.Value, context)); }).ToDictionary(p => p.Key, p => p.Value); + context.Pop(); + + return fromClrDict; + case PyObject pyObj: - return MarshFromPyObject(pyObj); + return MarshFromPyObject(pyObj, context); } return value; } - object MarshFromPyObject(PyObject pyObj) + object MarshFromPyObject(PyObject pyObj, MarshContext context) { + if (context.IsSeen(pyObj)) + { + return pyObj; + } + if (ManagedType.GetManagedObject(pyObj) is CLRObject co) { - return MarshOutput(co.inst); + if (context.IsSeen(co)) + { + return pyObj; + } + + return MarshOutput(co.inst, context); } else if (Runtime.PyObject_TYPE(pyObj) == Runtime.PyNoneType) @@ -712,13 +773,27 @@ object MarshFromPyObject(PyObject pyObj) else if (Runtime.PyList_Check(pyObj)) { var l = new PyList(pyObj); - return l.Select(i => MarshFromPyObject(i)).ToList(); + + context.Push(l); + + var toCLRList = l.Select(i => MarshFromPyObject(i, context)).ToList(); + + context.Pop(); + + return toCLRList; } else if (Runtime.PyDict_Check(pyObj)) { var d = new PyDict(pyObj); - return d.Keys().ToDictionary(k => MarshFromPyObject(k), k => MarshFromPyObject(d[k])); + + context.Push(d); + + var toCLRDict = d.Keys().ToDictionary(k => MarshFromPyObject(k, context), k => MarshFromPyObject(d[k], context)); + + context.Pop(); + + return toCLRDict; } else if (Runtime.PyString_CheckExact(pyObj))