Browse Source

Check cyclic references when evaluating ToObject (#936)

Marko Lahma 4 years ago
parent
commit
b256b595d3

+ 1 - 0
Jint.Tests/Jint.Tests.csproj

@@ -5,6 +5,7 @@
     <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
     <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
     <SignAssembly>true</SignAssembly>
     <IsPackable>false</IsPackable>
     <IsPackable>false</IsPackable>
+    <LangVersion>latest</LangVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Runtime\Scripts\*.*;Parser\Scripts\*.*" />
     <EmbeddedResource Include="Runtime\Scripts\*.*;Parser\Scripts\*.*" />

+ 28 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -2563,5 +2563,33 @@ namespace Jint.Tests.Runtime
             var wrapper = new ObjectWrapper(_engine, new Dictionary<string, object>());
             var wrapper = new ObjectWrapper(_engine, new Dictionary<string, object>());
             Assert.False(wrapper.IsArrayLike);
             Assert.False(wrapper.IsArrayLike);
         }
         }
+
+        [Fact]
+        public void ShouldHandleCyclicReferences()
+        {
+            var engine=new Engine();
+
+            static void Test(string message, object value) { Console.WriteLine(message); }
+
+            engine.Realm.GlobalObject.FastAddProperty("global", engine.Realm.GlobalObject, true, true, true);
+            engine.Realm.GlobalObject.FastAddProperty("test", new DelegateWrapper(engine, (Action<string, object>) Test), true, true, true);
+
+            var ex = Assert.Throws<JavaScriptException>(() => engine.Realm.GlobalObject.ToObject());
+            Assert.Equal("Cyclic reference detected.", ex.Message);
+
+            ex = Assert.Throws<JavaScriptException>(() =>
+                engine.Execute(@"
+                    var demo={};
+                    demo.value=1;
+                    test('Test 1', demo.value===1);
+                    test('Test 2', demo.value);
+                    demo.demo=demo;
+                    test('Test 3', demo);
+                    test('Test 4', global);"
+                )
+            );
+
+            Assert.Equal("Cyclic reference detected.", ex.Message);
+        }
     }
     }
 }
 }

+ 40 - 0
Jint/Collections/ObjectTraverseStack.cs

@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using Jint.Native;
+using Jint.Runtime;
+
+namespace Jint.Collections
+{
+    /// <summary>
+    /// Helps traversing objects and checks for cyclic references.
+    /// </summary>
+    internal sealed class ObjectTraverseStack
+    {
+        private readonly Engine _engine;
+        private readonly Stack<object> _stack = new();
+
+        public ObjectTraverseStack(Engine engine)
+        {
+            _engine = engine;
+        }
+
+        public void Enter(JsValue value)
+        {
+            if (value is null)
+            {
+                ExceptionHelper.ThrowArgumentNullException(nameof(value));
+            }
+
+            if (_stack.Contains(value))
+            {
+                ExceptionHelper.ThrowTypeError(_engine.Realm, "Cyclic reference detected.");
+            }
+
+            _stack.Push(value);
+        }
+
+        public void Exit()
+        {
+            _stack.Pop();
+        }
+    }
+}

+ 2 - 1
Jint/Native/Iterator/IteratorInstance.cs

@@ -26,7 +26,8 @@ namespace Jint.Native.Iterator
 
 
         public override object ToObject()
         public override object ToObject()
         {
         {
-            throw new System.NotImplementedException();
+            ExceptionHelper.ThrowNotImplementedException();
+            return null;
         }
         }
 
 
         public override bool Equals(JsValue other)
         public override bool Equals(JsValue other)

+ 8 - 22
Jint/Native/Json/JsonSerializer.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using Jint.Collections;
 using Jint.Native.Global;
 using Jint.Native.Global;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime;
@@ -16,14 +17,14 @@ namespace Jint.Native.Json
             _engine = engine;
             _engine = engine;
         }
         }
 
 
-        private Stack<object> _stack;
+        private ObjectTraverseStack _stack;
         private string _indent, _gap;
         private string _indent, _gap;
         private List<JsValue> _propertyList;
         private List<JsValue> _propertyList;
         private JsValue _replacerFunction = Undefined.Instance;
         private JsValue _replacerFunction = Undefined.Instance;
 
 
         public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
         public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
         {
         {
-            _stack = new Stack<object>();
+            _stack = new ObjectTraverseStack(_engine);
 
 
             // for JSON.stringify(), any function passed as the first argument will return undefined
             // for JSON.stringify(), any function passed as the first argument will return undefined
             // if the replacer is not defined. The function is not called either.
             // if the replacer is not defined. The function is not called either.
@@ -247,8 +248,7 @@ namespace Jint.Native.Json
 
 
         private string SerializeArray(JsValue value)
         private string SerializeArray(JsValue value)
         {
         {
-            EnsureNonCyclicity(value);
-            _stack.Push(value);
+            _stack.Enter(value);
             var stepback = _indent;
             var stepback = _indent;
             _indent = _indent + _gap;
             _indent = _indent + _gap;
             var partial = new List<string>();
             var partial = new List<string>();
@@ -262,7 +262,7 @@ namespace Jint.Native.Json
             }
             }
             if (partial.Count == 0)
             if (partial.Count == 0)
             {
             {
-                _stack.Pop();
+                _stack.Exit();
                 return "[]";
                 return "[]";
             }
             }
 
 
@@ -280,30 +280,16 @@ namespace Jint.Native.Json
                 final = "[\n" + _indent + properties + "\n" + stepback + "]";
                 final = "[\n" + _indent + properties + "\n" + stepback + "]";
             }
             }
 
 
-            _stack.Pop();
+            _stack.Exit();
             _indent = stepback;
             _indent = stepback;
             return final;
             return final;
         }
         }
 
 
-        private void EnsureNonCyclicity(object value)
-        {
-            if (value == null)
-            {
-                ExceptionHelper.ThrowArgumentNullException(nameof(value));
-            }
-
-            if (_stack.Contains(value))
-            {
-                ExceptionHelper.ThrowTypeError(_engine.Realm, "Cyclic reference detected.");
-            }
-        }
-
         private string SerializeObject(ObjectInstance value)
         private string SerializeObject(ObjectInstance value)
         {
         {
             string final;
             string final;
 
 
-            EnsureNonCyclicity(value);
-            _stack.Push(value);
+            _stack.Enter(value);
             var stepback = _indent;
             var stepback = _indent;
             _indent += _gap;
             _indent += _gap;
 
 
@@ -346,7 +332,7 @@ namespace Jint.Native.Json
                     final = "{\n" + _indent + properties + "\n" + stepback + "}";
                     final = "{\n" + _indent + properties + "\n" + stepback + "}";
                 }
                 }
             }
             }
-            _stack.Pop();
+            _stack.Exit();
             _indent = stepback;
             _indent = stepback;
             return final;
             return final;
         }
         }

+ 34 - 21
Jint/Native/Object/ObjectInstance.cs

@@ -891,11 +891,19 @@ namespace Jint.Native.Object
 
 
         public override object ToObject()
         public override object ToObject()
         {
         {
+            return ToObject(new ObjectTraverseStack(_engine));
+        }
+
+        private object ToObject(ObjectTraverseStack stack)
+        {
+            stack.Enter(this);
             if (this is IObjectWrapper wrapper)
             if (this is IObjectWrapper wrapper)
             {
             {
+                stack.Exit();
                 return wrapper.Target;
                 return wrapper.Target;
             }
             }
 
 
+            object converted = null;
             switch (Class)
             switch (Class)
             {
             {
                 case ObjectClass.Array:
                 case ObjectClass.Array:
@@ -910,49 +918,47 @@ namespace Jint.Native.Object
                             if (kpresent)
                             if (kpresent)
                             {
                             {
                                 var kvalue = arrayInstance.Get(k);
                                 var kvalue = arrayInstance.Get(k);
-                                result[k] = kvalue.ToObject();
+                                var value = kvalue is ObjectInstance oi
+                                    ? oi.ToObject(stack)
+                                    : kvalue.ToObject();
+                                result[k] = value;
                             }
                             }
                             else
                             else
                             {
                             {
                                 result[k] = null;
                                 result[k] = null;
                             }
                             }
                         }
                         }
-
-                        return result;
+                        converted = result;
                     }
                     }
-
                     break;
                     break;
 
 
                 case ObjectClass.String:
                 case ObjectClass.String:
                     if (this is StringInstance stringInstance)
                     if (this is StringInstance stringInstance)
                     {
                     {
-                        return stringInstance.PrimitiveValue.ToString();
+                        converted = stringInstance.PrimitiveValue.ToString();
                     }
                     }
-
                     break;
                     break;
 
 
                 case ObjectClass.Date:
                 case ObjectClass.Date:
                     if (this is DateInstance dateInstance)
                     if (this is DateInstance dateInstance)
                     {
                     {
-                        return dateInstance.ToDateTime();
+                        converted = dateInstance.ToDateTime();
                     }
                     }
-
                     break;
                     break;
 
 
                 case ObjectClass.Boolean:
                 case ObjectClass.Boolean:
                     if (this is BooleanInstance booleanInstance)
                     if (this is BooleanInstance booleanInstance)
                     {
                     {
-                        return ((JsBoolean) booleanInstance.PrimitiveValue)._value
-                             ? JsBoolean.BoxedTrue
-                             : JsBoolean.BoxedFalse;
+                        converted = ((JsBoolean) booleanInstance.PrimitiveValue)._value
+                            ? JsBoolean.BoxedTrue
+                            : JsBoolean.BoxedFalse;
                     }
                     }
-
                     break;
                     break;
 
 
                 case ObjectClass.Function:
                 case ObjectClass.Function:
                     if (this is FunctionInstance function)
                     if (this is FunctionInstance function)
                     {
                     {
-                        return (Func<JsValue, JsValue[], JsValue>) function.Call;
+                        converted = (Func<JsValue, JsValue[], JsValue>) function.Call;
                     }
                     }
 
 
                     break;
                     break;
@@ -960,17 +966,15 @@ namespace Jint.Native.Object
                 case ObjectClass.Number:
                 case ObjectClass.Number:
                     if (this is NumberInstance numberInstance)
                     if (this is NumberInstance numberInstance)
                     {
                     {
-                        return numberInstance.NumberData._value;
+                        converted = numberInstance.NumberData._value;
                     }
                     }
-
                     break;
                     break;
 
 
                 case ObjectClass.RegExp:
                 case ObjectClass.RegExp:
                     if (this is RegExpInstance regeExpInstance)
                     if (this is RegExpInstance regeExpInstance)
                     {
                     {
-                        return regeExpInstance.Value;
+                        converted = regeExpInstance.Value;
                     }
                     }
-
                     break;
                     break;
 
 
                 case ObjectClass.Arguments:
                 case ObjectClass.Arguments:
@@ -983,14 +987,23 @@ namespace Jint.Native.Object
                             continue;
                             continue;
                         }
                         }
 
 
-                        o.Add(p.Key.ToString(), Get(p.Key, this).ToObject());
+                        var key = p.Key.ToString();
+                        var propertyValue = Get(p.Key, this);
+                        var value = propertyValue is ObjectInstance oi
+                            ? oi.ToObject(stack)
+                            : propertyValue.ToObject();
+                        o.Add(key, value);
                     }
                     }
 
 
-                    return o;
+                    converted = o;
+                    break;
+                default:
+                    converted = this;
+                    break;
             }
             }
 
 
-
-            return this;
+            stack.Exit();
+            return converted;
         }
         }
 
 
         /// <summary>
         /// <summary>