Kaynağa Gözat

Fix JSON serialization logic to adhere to latest specification (#820)

Marko Lahma 4 yıl önce
ebeveyn
işleme
c204412140

+ 15 - 0
Jint.Tests.Test262/BuiltIns/JSONTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.BuiltIns
+{
+    public class JSONTests : Test262Test
+    {
+        [Theory(DisplayName = "built-ins\\JSON")]
+        [MemberData(nameof(SourceFiles), "built-ins\\JSON", false)]
+        [MemberData(nameof(SourceFiles), "built-ins\\JSON", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 47 - 60
Jint/Native/Json/JsonInstance.cs

@@ -1,5 +1,6 @@
 using Jint.Collections;
 using Jint.Native.Object;
+using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
@@ -8,8 +9,6 @@ namespace Jint.Native.Json
 {
     public sealed class JsonInstance : ObjectInstance
     {
-        private JsValue _reviver;
-
         private JsonInstance(Engine engine)
             : base(engine, objectClass: ObjectClass.JSON)
         {
@@ -32,107 +31,95 @@ namespace Jint.Native.Json
                 ["stringify"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "stringify", Stringify, 3), true, false, true)
             };
             SetProperties(properties);
+            
+            var symbols = new SymbolDictionary(1)
+            {
+                [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("JSON", false, false, true),
+            };
+            SetSymbols(symbols);
         }
 
-        private JsValue AbstractWalkOperation(ObjectInstance thisObject, JsValue prop)
+        private static JsValue InternalizeJSONProperty(JsValue holder, JsValue name, ICallable reviver)
         {
-            JsValue value = thisObject.Get(prop, thisObject);
-            if (value.IsObject())
+            JsValue temp = holder.Get(name, holder);
+            if (temp is ObjectInstance val)
             {
-                var valueAsObject = value.AsObject();
-                if (valueAsObject.Class == ObjectClass.Array)
+                if (val.IsArray())
                 {
-                    var valAsArray = value.AsArray();
-                    var i = 0;
-                    var arrLen = valAsArray.GetLength();
-                    while (i < arrLen)
+                    var i = 0UL;
+                    var len = TypeConverter.ToLength(val.Get(CommonProperties.Length));
+                    while (i < len)
                     {
-                        var newValue = AbstractWalkOperation(valAsArray, JsString.Create(i));
-                        if (newValue.IsUndefined())
+                        var prop = JsString.Create(i);
+                        var newElement = InternalizeJSONProperty(val, prop, reviver);
+                        if (newElement.IsUndefined())
                         {
-                            valAsArray.Delete(JsString.Create(i));
+                            val.Delete(prop);
                         }
                         else
                         {
-                            valAsArray.DefineOwnProperty
-                            (
-                                JsString.Create(i),
-                                new PropertyDescriptor
-                                (
-                                    value: newValue,
-                                    PropertyFlag.ConfigurableEnumerableWritable
-                                ));
+                            val.CreateDataProperty(prop, newElement);
                         }
                         i = i + 1;
                     }
                 }
                 else
                 {
-                    var keys = valueAsObject.GetOwnProperties();
+                    var keys = val.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Key);
                     foreach (var p in keys)
                     {
-                        var newElement = AbstractWalkOperation(valueAsObject, p.Key);
+                        var newElement = InternalizeJSONProperty(val, p, reviver);
                         if (newElement.IsUndefined())
                         {
-                            valueAsObject.Delete(p.Key);
+                            val.Delete(p);
                         }
                         else
                         {
-                            valueAsObject.DefineOwnProperty(
-                                p.Key,
-                                new PropertyDescriptor
-                                (
-                                    value: newElement,
-                                    PropertyFlag.ConfigurableEnumerableWritable
-                                ));
+                            val.CreateDataProperty(p, newElement);
                         }
                     }
                 }
             }
-            return _reviver.Invoke(thisObject, new[] { prop, value });
+
+            return reviver.Call(holder, new[] { name, temp });
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-json.parse
+        /// </summary>
         public JsValue Parse(JsValue thisObject, JsValue[] arguments)
         {
+            var jsonString = TypeConverter.ToString(arguments.At(0));
+            var reviver = arguments.At(1);
+
             var parser = new JsonParser(_engine);
-            var res = parser.Parse(TypeConverter.ToString(arguments[0]));
-            if (arguments.Length > 1)
+            var unfiltered = parser.Parse(jsonString);
+
+            if (reviver.IsCallable)
             {
-                _reviver = arguments[1];
-                ObjectInstance revRes = _engine.Object.Construct(Arguments.Empty);
-                revRes.SetProperty("", new PropertyDescriptor(value: res, PropertyFlag.ConfigurableEnumerableWritable));
-                return AbstractWalkOperation(revRes, JsString.Empty);
+                var root = _engine.Object.Construct(Arguments.Empty);
+                var rootName = JsString.Empty;
+                root.CreateDataPropertyOrThrow(rootName, unfiltered);
+                return InternalizeJSONProperty(root, rootName, (ICallable) reviver);
+            }
+            else
+            {
+                return unfiltered;
             }
-            return res;
         }
 
         public JsValue Stringify(JsValue thisObject, JsValue[] arguments)
         {
-            JsValue
-                value = Undefined,
-                replacer = Undefined,
-                space = Undefined;
+            var value = arguments.At(0);
+            var replacer = arguments.At(1);
+            var space = arguments.At(2);
 
-            if (arguments.Length > 2)
+            if (value.IsUndefined() && replacer.IsUndefined()) 
             {
-                space = arguments[2];
-            }
-
-            if (arguments.Length > 1)
-            {
-                replacer = arguments[1];
-            }
-
-            if (arguments.Length > 0)
-            {
-                value = arguments[0];
-            }
-
-            var serializer = new JsonSerializer(_engine);
-            if (value.IsUndefined() && replacer.IsUndefined()) {
                 return Undefined;
             }
 
+            var serializer = new JsonSerializer(_engine);
             return serializer.Serialize(value, replacer, space);
         }
     }

+ 7 - 12
Jint/Native/Json/JsonSerializer.cs

@@ -28,7 +28,7 @@ namespace Jint.Native.Json
 
             // 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 (value is ICallable callable && ReferenceEquals(replacer, Undefined.Instance))
+            if (value.IsCallable && ReferenceEquals(replacer, Undefined.Instance))
             {
                 return Undefined.Instance;
             }
@@ -119,9 +119,8 @@ namespace Jint.Native.Json
             return Str(JsString.Empty, wrapper);
         }
 
-        private JsValue Str(JsValue key, ObjectInstance holder)
+        private JsValue Str(JsValue key, JsValue holder)
         {
-
             var value = holder.Get(key, holder);
             if (value.IsObject())
             {
@@ -141,7 +140,6 @@ namespace Jint.Native.Json
                 value = replacerFunctionCallable.Call(holder, Arguments.From(key, value));
             }
 
-
             if (value.IsObject())
             {
                 var valueObj = value.AsObject();
@@ -157,7 +155,7 @@ namespace Jint.Native.Json
                         value = TypeConverter.ToPrimitive(value);
                         break;
                     case ObjectClass.Array:
-                        value = SerializeArray(value.As<ArrayInstance>());
+                        value = SerializeArray(value);
                         return value;
                     case ObjectClass.Object:
                         value = SerializeObject(value.AsObject());
@@ -195,12 +193,9 @@ namespace Jint.Native.Json
 
             if (value.IsObject() && isCallable == false)
             {
-                if (value.AsObject().Class == ObjectClass.Array)
-                {
-                    return SerializeArray(value.As<ArrayInstance>());
-                }
-
-                return SerializeObject(value.AsObject());
+                return value.AsObject().Class == ObjectClass.Array 
+                    ? SerializeArray(value)
+                    : SerializeObject(value.AsObject());
             }
 
             return JsValue.Undefined;
@@ -251,7 +246,7 @@ namespace Jint.Native.Json
             return sb.ToString();
         }
 
-        private string SerializeArray(ArrayInstance value)
+        private string SerializeArray(JsValue value)
         {
             EnsureNonCyclicity(value);
             _stack.Push(value);

+ 1 - 1
Jint/Native/Object/ObjectInstance.cs

@@ -1216,7 +1216,7 @@ namespace Jint.Native.Object
             }
         }
 
-        internal JsValue EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind kind)
+        internal ArrayInstance EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind kind)
         {
             var ownKeys = GetOwnPropertyKeys(Types.String);