Procházet zdrojové kódy

Fix some array issues uncovered by compat-table tests (#1347)

Marko Lahma před 2 roky
rodič
revize
f9423a5e9d

+ 81 - 0
Jint.Tests/Runtime/ArrayTests.cs

@@ -223,4 +223,85 @@ public class ArrayTests
         Assert.True(engine.Evaluate("!iterator.hasOwnProperty(Symbol.iterator)").AsBoolean());
         Assert.True(engine.Evaluate("iterator[Symbol.iterator]() === iterator").AsBoolean());
     }
+
+    [Fact]
+    public void ArrayFrom()
+    {
+        const string Script = @"
+            // Array.from -> Get -> [[Get]]
+            var get = [];
+            var p = new Proxy({length: 2, 0: '', 1: ''}, { get: function(o, k) { get.push(k); return o[k]; }});
+            Array.from(p);";
+
+        var engine = new Engine();
+        engine.Execute(Script);
+
+        Assert.True(engine.Evaluate("get[0] === Symbol.iterator").AsBoolean());
+        Assert.Equal("length,0,1", engine.Evaluate("get.slice(1) + ''").AsString());
+    }
+
+    [Fact]
+    public void Iteration()
+    {
+        const string Script = @"
+            // Array.prototype methods -> Get -> [[Get]]
+            var methods = ['copyWithin', 'every', 'fill', 'filter', 'find', 'findIndex', 'forEach',
+              'indexOf', 'join', 'lastIndexOf', 'map', 'reduce', 'reduceRight', 'some'];
+            var get;
+            var p = new Proxy({length: 2, 0: '', 1: ''}, { get: function(o, k) { get.push(k); return o[k]; }});
+            for(var i = 0; i < methods.length; i+=1) {
+              get = [];
+              Array.prototype[methods[i]].call(p, Function());
+              var actual = get + '';
+              var expected = (
+                methods[i] === 'fill' ? ""length"" :
+                methods[i] === 'every' ? ""length,0"" :
+                methods[i] === 'lastIndexOf' || methods[i] === 'reduceRight' ? ""length,1,0"" :
+                ""length,0,1"");
+
+              if (actual !== expected) {
+                throw methods[i] + ': ' + actual + ' !== ' + expected;
+              }
+            }
+            return true;";
+
+        var engine = new Engine();
+        Assert.True(engine.Evaluate(Script).AsBoolean());
+    }
+
+    [Fact]
+    public void Concat()
+    {
+        const string Script = @"
+            // Array.prototype.concat -> Get -> [[Get]]
+            var get = [];
+            var arr = [1];
+            arr.constructor = void undefined;
+            var p = new Proxy(arr, { get: function(o, k) { get.push(k); return o[k]; }});
+            Array.prototype.concat.call(p,p);";
+
+        var engine = new Engine();
+        engine.Execute(Script);
+
+        Assert.Equal("constructor", engine.Evaluate("get[0]"));
+        Assert.True(engine.Evaluate("get[1] === Symbol.isConcatSpreadable").AsBoolean());
+        Assert.Equal("length", engine.Evaluate("get[2]"));
+        Assert.Equal("0", engine.Evaluate("get[3]"));
+        Assert.True(engine.Evaluate("get[4] === get[1] && get[5] === get[2] && get[6] === get[3]").AsBoolean());
+        Assert.Equal(7, engine.Evaluate("get.length"));
+    }
+
+    [Fact]
+    public void Shift()
+    {
+        const string Script = @"
+// Array.prototype.shift -> Get -> [[Get]]
+var get = [];
+var p = new Proxy([0,1,2,3], { get: function(o, k) { get.push(k); return o[k]; }});
+Array.prototype.shift.call(p);
+return get + '' === ""length,0,1,2,3"";";
+
+        var engine = new Engine();
+        Assert.True(engine.Evaluate(Script).AsBoolean());
+    }
 }

+ 7 - 2
Jint/Native/Array/ArrayConstructor.cs

@@ -46,6 +46,9 @@ namespace Jint.Native.Array
             SetSymbols(symbols);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-array.from
+        /// </summary>
         private JsValue From(JsValue thisObj, JsValue[] arguments)
         {
             var source = arguments.At(0);
@@ -61,7 +64,7 @@ namespace Jint.Native.Array
             if (source is JsString jsString)
             {
                 var a = _realm.Intrinsics.Array.ArrayCreate((uint) jsString.Length);
-                for (int i = 0; i < jsString._value.Length; i++)
+                for (var i = 0; i < jsString._value.Length; i++)
                 {
                     a.SetIndexValue((uint) i, JsString.Create(jsString._value[i]), updateLength: false);
                 }
@@ -108,13 +111,15 @@ namespace Jint.Native.Array
             ICallable? callable,
             JsValue thisArg)
         {
+            var iterator = objectInstance.Get(GlobalSymbolRegistry.Iterator);
             var source = ArrayOperations.For(objectInstance);
             var length = source.GetLength();
 
             ObjectInstance a;
             if (thisObj is IConstructor constructor)
             {
-                var argumentsList = objectInstance.Get(GlobalSymbolRegistry.Iterator).IsNullOrUndefined()
+                var isNullOrUndefined = iterator.IsNullOrUndefined();
+                var argumentsList = isNullOrUndefined
                     ? new JsValue[] { length }
                     : null;
 

+ 2 - 3
Jint/Native/Array/ArrayOperations.cs

@@ -190,9 +190,8 @@ namespace Jint.Native.Array
             public override bool TryGetValue(ulong index, out JsValue value)
             {
                 var propertyName = JsString.Create(index);
-                var property = _target.GetProperty(propertyName);
-                var kPresent = property != PropertyDescriptor.Undefined;
-                value = kPresent ? _target.UnwrapJsValue(property) : JsValue.Undefined;
+                var kPresent = _target.HasProperty(propertyName);
+                value = kPresent ? _target.Get(propertyName) : JsValue.Undefined;
                 return kPresent;
             }
 

+ 10 - 1
Jint/Native/Array/ArrayPrototype.cs

@@ -681,6 +681,9 @@ namespace Jint.Native.Array
             return target.FindWithCallback(arguments, out _, out _, false);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-array.prototype.every
+        /// </summary>
         private JsValue Every(JsValue thisObj, JsValue[] arguments)
         {
             var o = ArrayOperations.For(_realm, thisObj);
@@ -781,6 +784,9 @@ namespace Jint.Native.Array
             return -1;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-array.prototype.find
+        /// </summary>
         private JsValue Find(JsValue thisObj, JsValue[] arguments)
         {
             var target = TypeConverter.ToObject(_realm, thisObj);
@@ -788,6 +794,9 @@ namespace Jint.Native.Array
             return value;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-array.prototype.findindex
+        /// </summary>
         private JsValue FindIndex(JsValue thisObj, JsValue[] arguments)
         {
             var target = TypeConverter.ToObject(_realm, thisObj);
@@ -1311,7 +1320,7 @@ namespace Jint.Native.Array
             for (var i = 0; i < items.Count; i++)
             {
                 var e = items[i];
-                if (e is ObjectInstance oi && oi.IsConcatSpreadable)
+                if (e is ObjectInstance { IsConcatSpreadable: true } oi)
                 {
                     if (e is ArrayInstance eArray && a is ArrayInstance a2)
                     {

+ 4 - 22
Jint/Native/Object/ObjectInstance.cs

@@ -1023,26 +1023,8 @@ namespace Jint.Native.Object
         {
             long GetLength()
             {
-                var desc = GetProperty(CommonProperties.Length);
-                var descValue = desc.Value;
-                double len;
-                if (desc.IsDataDescriptor() && !ReferenceEquals(descValue, null))
-                {
-                    len = TypeConverter.ToNumber(descValue);
-                }
-                else
-                {
-                    var getter = desc.Get ?? Undefined;
-                    if (getter.IsUndefined())
-                    {
-                        len = 0;
-                    }
-                    else
-                    {
-                        // if getter is not undefined it must be ICallable
-                        len = TypeConverter.ToNumber(((ICallable) getter).Call(this, Arguments.Empty));
-                    }
-                }
+                var descValue = Get(CommonProperties.Length);
+                var len = TypeConverter.ToNumber(descValue);
 
                 return (long) System.Math.Max(
                     0,
@@ -1057,7 +1039,8 @@ namespace Jint.Native.Object
                 return kPresent;
             }
 
-            if (GetLength() == 0)
+            var length = GetLength();
+            if (length == 0)
             {
                 index = 0;
                 value = Undefined;
@@ -1070,7 +1053,6 @@ namespace Jint.Native.Object
 
             var args = _engine._jsValueArrayPool.RentArray(3);
             args[2] = this;
-            var length = GetLength();
             for (uint k = 0; k < length; k++)
             {
                 if (TryGetValue(k, out var kvalue) || visitUnassigned)