Marko Lahma 6 лет назад
Родитель
Сommit
e4055925c6
40 измененных файлов с 1764 добавлено и 796 удалено
  1. 39 0
      Jint.Tests.Ecma/EcmaTest.cs
  2. 1 1
      Jint.Tests.Ecma/SingleTest.cs
  3. 353 353
      Jint.Tests.Ecma/TestCases/alltests.json
  4. 1 1
      Jint.Tests.Test262/ArrayTests.cs
  5. 14 2
      Jint.Tests.Test262/SingleTest.cs
  6. 40 11
      Jint.Tests.Test262/Test262Test.cs
  7. 191 12
      Jint.Tests.Test262/test/skipped.json
  8. 1 1
      Jint/Engine.cs
  9. 1 1
      Jint/Jint.csproj
  10. 37 22
      Jint/Native/Argument/ArgumentsInstance.cs
  11. 120 6
      Jint/Native/Array/ArrayConstructor.cs
  12. 59 8
      Jint/Native/Array/ArrayInstance.cs
  13. 460 147
      Jint/Native/Array/ArrayPrototype.cs
  14. 0 7
      Jint/Native/Array/IArrayLike.cs
  15. 5 8
      Jint/Native/Boolean/BooleanPrototype.cs
  16. 4 7
      Jint/Native/Function/EvalFunctionInstance.cs
  17. 3 3
      Jint/Native/Function/FunctionConstructor.cs
  18. 7 7
      Jint/Native/Function/FunctionPrototype.cs
  19. 5 7
      Jint/Native/Function/ScriptFunctionInstance.cs
  20. 24 3
      Jint/Native/Iterator/IteratorConstructor.cs
  21. 53 2
      Jint/Native/Iterator/IteratorInstance.cs
  22. 81 0
      Jint/Native/Iterator/IteratorProtocol.cs
  23. 4 4
      Jint/Native/JsString.cs
  24. 29 6
      Jint/Native/JsValue.cs
  25. 43 71
      Jint/Native/Map/MapConstructor.cs
  26. 0 2
      Jint/Native/Map/MapPrototype.cs
  27. 8 4
      Jint/Native/Number/NumberPrototype.cs
  28. 1 1
      Jint/Native/Object/ObjectConstructor.cs
  29. 41 10
      Jint/Native/Object/ObjectInstance.cs
  30. 27 59
      Jint/Native/Set/SetConstructor.cs
  31. 8 4
      Jint/Native/String/StringPrototype.cs
  32. 11 11
      Jint/Native/Symbol/GlobalSymbolRegistry.cs
  33. 26 7
      Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs
  34. 5 0
      Jint/Runtime/Environments/EnvironmentRecord.cs
  35. 4 0
      Jint/Runtime/Environments/ObjectEnvironmentRecord.cs
  36. 16 0
      Jint/Runtime/ExceptionHelper.cs
  37. 9 7
      Jint/Runtime/ExpressionIntepreter.cs
  38. 16 1
      Jint/Runtime/StatementInterpreter.cs
  39. 3 0
      Jint/Runtime/TypeConverter.cs
  40. 14 0
      Jint/Runtime/TypeErrorException.cs

+ 39 - 0
Jint.Tests.Ecma/EcmaTest.cs

@@ -109,10 +109,41 @@ namespace Jint.Tests.Ecma
 
     public class Chapter15 : EcmaTest
     {
+        // couple of tests are really slow, run in parallel
+        internal const string SlowTest1 = "ch15/15.1/15.1.3/15.1.3.1/S15.1.3.1_A2.5_T1.js";
+        internal const string SlowTest2 = "ch15/15.1/15.1.3/15.1.3.1/S15.1.3.1_A2.5_T1.js";
+
         [Theory(DisplayName = "Ecma Chapter 15")]
         [MemberData(nameof(SourceFiles), parameters: new object[] {"ch15", false })]
         [MemberData(nameof(SourceFiles), parameters: new object[] {"ch15", true }, Skip = "Skipped")]
         protected void RunTest(SourceFile sourceFile)
+        {
+            if (sourceFile.Source == SlowTest1 || sourceFile.Source == SlowTest2)
+            {
+                return;
+            }
+            
+            RunTestInternal(sourceFile);
+        }
+    }
+    
+    public class Chapter15_SlowTest1 : EcmaTest
+    {
+        [Theory(DisplayName = "Ecma Chapter 15 Slow Test 1")]
+        [MemberData(nameof(SourceFiles), parameters: new object[] {Chapter15.SlowTest1, false })]
+        [MemberData(nameof(SourceFiles), parameters: new object[] {Chapter15.SlowTest1, true }, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+        
+    public class Chapter15_SlowTest2 : EcmaTest
+    {
+        [Theory(DisplayName = "Ecma Chapter 15 Slow Test 2")]
+        [MemberData(nameof(SourceFiles), parameters: new object[] {Chapter15.SlowTest2, false })]
+        [MemberData(nameof(SourceFiles), parameters: new object[] {Chapter15.SlowTest2, true }, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
         {
             RunTestInternal(sourceFile);
         }
@@ -220,6 +251,14 @@ namespace Jint.Tests.Ecma
                     continue;
                 }
 
+                if (sourceFile.Skip
+                    && (sourceFile.Reason == "part of new test suite"
+                        || sourceFile.Reason.IndexOf("configurable", StringComparison.OrdinalIgnoreCase) > -1))
+                {
+                    // we consider this obsolete and we don't need to process at all
+                    continue;
+                }
+
                 if (skipped == sourceFile.Skip)
                 {
                     results.Add(new object [] { sourceFile });

+ 1 - 1
Jint.Tests.Ecma/SingleTest.cs

@@ -21,7 +21,7 @@ namespace Jint.Tests.Ecma
         [RunnableInDebugOnly]
         public void TestSingle()
         {
-            const string Target = @"ch15/15.4/15.4.4/15.4.4.17/15.4.4.17-4-10.js";
+            const string Target = @"ch15/15.4/15.4.4/15.4.4.10/S15.4.4.10_A3_T3.js";
             var sourceFile = SourceFiles(Target, false)
                 .SelectMany(x => x)
                 .Cast<SourceFile>()

Разница между файлами не показана из-за своего большого размера
+ 353 - 353
Jint.Tests.Ecma/TestCases/alltests.json


+ 1 - 1
Jint.Tests.Test262/ArrayTests.cs

@@ -4,7 +4,7 @@ namespace Jint.Tests.Test262
 {
     public class ArrayTests : Test262Test
     {
-        [Theory(Skip = "A lot to do", DisplayName = "built-ins\\Array")]
+        [Theory(DisplayName = "built-ins\\Array")]
         [MemberData(nameof(SourceFiles), "built-ins\\Array", false)]
         [MemberData(nameof(SourceFiles), "built-ins\\Array", true, Skip = "Skipped")]
         protected void RunTest(SourceFile sourceFile)

+ 14 - 2
Jint.Tests.Test262/SingleTest.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
@@ -15,20 +16,31 @@ namespace Jint.Tests.Test262
             }
         }
     }
+
     public class SingleTest : Test262Test
     {
         // helper to test single test case
         [RunnableInDebugOnly]
         public void TestSingle()
         {
-            const string Target = @"built-ins/Map/iterator-close-after-set-failure.js";
+            const string Target = @"built-ins/Array/prototype/every/15.4.4.16-5-23.js";
+            //const string Target = @"built-ins/Array/from/calling-from-valid-2.js";
             var sourceFile = SourceFiles("built-ins", false)
                 .SelectMany(x => x)
                 .Cast<SourceFile>()
                 .First(x => x.Source == Target);
 
             var code = File.ReadAllText(sourceFile.FullPath);
-            RunTestCode(code, strict: true);
+
+            if (code.IndexOf("onlyStrict", StringComparison.Ordinal) < 0)
+            {
+                RunTestCode(code, strict: false);
+            }
+
+            if (code.IndexOf("noStrict", StringComparison.Ordinal) < 0)
+            {
+                RunTestCode(code, strict: true);
+            }
         }
     }
 }

+ 40 - 11
Jint.Tests.Test262/Test262Test.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Reflection;
+using System.Text.RegularExpressions;
 using Jint.Runtime;
 using Newtonsoft.Json.Linq;
 using Xunit;
@@ -83,14 +84,7 @@ namespace Jint.Tests.Test262
 
         protected void RunTestInternal(SourceFile sourceFile)
         {
-            var fullName = sourceFile.FullPath;
-            if (!File.Exists(fullName))
-            {
-                throw new ArgumentException("Could not find source file: " + fullName);
-            }
-
-            string code = File.ReadAllText(fullName);
-            RunTestCode(code);
+            RunTestCode(sourceFile.Code);
         }
 
         private void RunTestCode(string code)
@@ -118,11 +112,44 @@ namespace Jint.Tests.Test262
                 var name = file.Substring(fixturesPath.Length + 1).Replace("\\", "/");
                 bool skip = _skipReasons.TryGetValue(name, out var reason);
 
+                var code = skip ? "" : File.ReadAllText(file);
+
+                var features = Regex.Match(code, "features: \\[(.+?)\\]");
+                if (features.Success)
+                {
+                    var items = features.Groups[1].Captures[0].Value.Split(",");
+                    foreach (var item in items)
+                    {
+                        // TODO implement
+                        if (item == "cross-realm")
+                        {
+                            skip = true;
+                            reason = "realms not implemented";
+                        }
+                        else if (item == "Symbol.species")
+                        {
+                            skip = true;
+                            reason = "Symbol.species not implemented";
+                        }
+                        else if (item == "Proxy")
+                        {
+                            skip = true;
+                            reason = "Proxies not implemented";
+                        }
+                        else if (item == "Symbol.unscopables")
+                        {
+                            skip = true;
+                            reason = "Symbol.unscopables not implemented";
+                        }
+                    }
+                }
+                
                 var sourceFile = new SourceFile(
                     name,
                     file,
                     skip,
-                    reason);
+                    reason,
+                    code);
 
                 if (skipped == sourceFile.Skip)
                 {
@@ -143,23 +170,25 @@ namespace Jint.Tests.Test262
             string source,
             string fullPath,
             bool skip,
-            string reason)
+            string reason,
+            string code)
         {
             Skip = skip;
             Source = source;
             Reason = reason;
             FullPath = fullPath;
+            Code = code;
         }
 
         public string Source { get; }
         public bool Skip { get; }
         public string Reason { get; }
         public string FullPath { get; }
+        public string Code { get; }
 
         public override string ToString()
         {
             return Source;
         }
     }
-
 }

+ 191 - 12
Jint.Tests.Test262/test/skipped.json

@@ -1,11 +1,107 @@
 [
   {
-    "source": "built-ins/Boolean/proto-from-ctor-realm.js",
-    "reason": "realms not implemented"
+    "source": "built-ins/Array/prototype/concat/create-ctor-non-object.js",
+    "reason": "Constructor functions not implemented"
   },
   {
-    "source": "built-ins/Map/proto-from-ctor-realm.js",
-    "reason": "realms not implemented"
+    "source": "built-ins/Array/prototype/map/create-ctor-non-object.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/slice/create-ctor-non-object.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/splice/create-ctor-non-object.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/of/construct-this-with-the-number-of-arguments.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/iter-set-length-err",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/Array.from_forwards-length-for-array-likes.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/iter-cstm-ctor.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/iter-cstm-ctor-err.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/source-object-constructor.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/of/return-abrupt-from-contructor.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/concat/create-ctor-poisoned.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/map/create-ctor-poisoned.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/slice/create-ctor-poisoned.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/splice/create-ctor-poisoned.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/of/return-a-custom-instance.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/of/return-abrupt-from-data-property.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/of/return-abrupt-from-setting-length.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/of/sets-length.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/iter-set-length-err.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/filter/create-ctor-non-object.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/filter/create-ctor-poisoned.js",
+    "reason": "Constructor functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/from/items-is-arraybuffer.js",
+    "reason": "ArrayBuffer not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/concat/Array.prototype.concat_non-array.js",
+    "reason": "class keyword not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/concat/Array.prototype.concat_large-typed-array.js",
+    "reason": "Uint8Array not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/concat/Array.prototype.concat_small-typed-array.js",
+    "reason": "Uint8Array not implemented"
   },
   {
     "source": "built-ins/Map/prototype/clear/context-is-weakmap-object-throws.js",
@@ -23,10 +119,6 @@
     "source": "built-ins/Map/prototype/forEach/does-not-have-mapdata-internal-slot-weakmap.js",
     "reason": "WeakMap not implemented"
   },
-  {
-    "source": "built-ins/Map/prototype/forEach/callback-this-strict.js",
-    "reason": "global strict mode not working as expected"
-  },
   {
     "source": "built-ins/Map/prototype/forEach/iterates-values-deleted-then-readded.js",
     "reason": "delete/add detection not implemented for map iterator during iteration"
@@ -83,10 +175,6 @@
     "source": "built-ins/Set/prototype/forEach/iterates-values-revisits-after-delete-re-add.js",
     "reason": "delete/add detection not implemented for set iterator during iteration"
   },
-  {
-    "source": "built-ins/Set/prototype/forEach/this-strict.js",
-    "reason": "global strict mode not working as expected"
-  },
   {
     "source": "built-ins/Set/prototype/has/does-not-have-setdata-internal-slot-weakset.js",
     "reason": "WeakSet not implemented"
@@ -98,5 +186,96 @@
   {
     "source": "built-ins/Set/prototype/forEach/this-arg-explicit-cannot-override-lexical-this-arrow.js",
     "reason": "arrow functions not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/reverse/length-exceeding-integer-limit-with-proxy.js",
+    "reason": "proxies not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js",
+    "reason": "proxies not implemented"
+  },
+  {
+    "source": "built-ins/Array/prototype/splice/create-non-array-invalid-len.js",
+    "reason": "requires constructor functions"
+  },
+  {
+    "source": "built-ins/Array/from/iter-set-elem-prop-err.js",
+    "reason": "requires constructor functions"
+  },
+  {
+    "source": "built-ins/Array/prototype/toLocaleString/primitive_this_value.js",
+    "reason": "requires toLocaleString changes"
+  },
+  {
+    "source": "built-ins/Array/prototype/toLocaleString/primitive_this_value_getter.js",
+    "reason": "requires toLocaleString changes"
+  },
+
+  // experimenta
+
+  {
+    "source": "built-ins/Array/prototype/flat/array-like-objects.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/bound-function-call.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/empty-array-elements.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/empty-object-elements.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/length.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/name.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/non-numeric-depth-should-not-throw.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/null-undefined-elements.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/positive-infinity.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flat/prop-desc.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flatMap/array-like-objects.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flatMap/bound-function-argument.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flatMap/depth-always-one.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flatMap/length.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flatMap/name.js",
+    "reason": "experimental"
+  },
+  {
+    "source": "built-ins/Array/prototype/flatMap/thisArg-argument.js",
+    "reason": "experimental"
   }
 ]

+ 1 - 1
Jint/Engine.cs

@@ -51,7 +51,7 @@ namespace Jint
 
         // cached access
         private readonly bool _isDebugMode;
-        private readonly bool _isStrict;
+        internal readonly bool _isStrict;
         private readonly int _maxStatements;
         private readonly long _memoryLimit;
         private readonly bool _runBeforeStatementChecks;

+ 1 - 1
Jint/Jint.csproj

@@ -7,6 +7,6 @@
     <LangVersion>latest</LangVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Esprima" Version="1.0.0-beta-1066" />
+    <PackageReference Include="Esprima" Version="1.0.0-beta-1068" />
   </ItemGroup>
 </Project>

+ 37 - 22
Jint/Native/Argument/ArgumentsInstance.cs

@@ -19,11 +19,11 @@ namespace Jint.Native.Argument
 
         private FunctionInstance _func;
         private string[] _names;
-        private JsValue[] _args;
+        internal JsValue[] _args;
         private EnvironmentRecord _env;
         private bool _strict;
 
-        private bool _initialized;
+        internal bool _initialized;
 
         internal ArgumentsInstance(Engine engine) : base(engine, objectClass: "Arguments")
         {
@@ -56,49 +56,51 @@ namespace Jint.Native.Argument
 
             _initialized = true;
 
-            var self = this;
-            var len = _args.Length;
-            self.SetOwnProperty("length", new PropertyDescriptor(len, PropertyFlag.NonEnumerable));
-            if (_args.Length > 0)
+            BuildProperties();
+        }
+
+        private void BuildProperties()
+        {
+            var args = _args;
+            SetOwnProperty("length", new PropertyDescriptor(args.Length, PropertyFlag.NonEnumerable));
+
+            ObjectInstance map = null;
+            if (args.Length > 0)
             {
-                var map = Engine.Object.Construct(Arguments.Empty);
                 var mappedNamed = _mappedNamed.Value;
                 mappedNamed.Clear();
-                for (var indx = 0; indx < len; indx++)
+                for (var i = 0; i < (uint) args.Length; i++)
                 {
-                    var indxStr = TypeConverter.ToString(indx);
-                    var val = _args[indx];
-                    self.SetOwnProperty(indxStr, new PropertyDescriptor(val, PropertyFlag.ConfigurableEnumerableWritable));
-                    if (indx < _names.Length)
+                    var indxStr = TypeConverter.ToString(i);
+                    var val = args[i];
+                    SetOwnProperty(indxStr, new PropertyDescriptor(val, PropertyFlag.ConfigurableEnumerableWritable));
+                    if (i < _names.Length)
                     {
-                        var name = _names[indx];
+                        var name = _names[i];
                         if (!_strict && !mappedNamed.Contains(name))
                         {
+                            map = map ?? Engine.Object.Construct(Arguments.Empty);
                             mappedNamed.Add(name);
                             map.SetOwnProperty(indxStr, new ClrAccessDescriptor(_env, Engine, name));
                         }
                     }
                 }
-
-                // step 12
-                if (mappedNamed.Count > 0)
-                {
-                    self.ParameterMap = map;
-                }
             }
 
+            ParameterMap = map;
+
             // step 13
             if (!_strict)
             {
-                self.SetOwnProperty("callee", new PropertyDescriptor(_func, PropertyFlag.NonEnumerable));
+                SetOwnProperty("callee", new PropertyDescriptor(_func, PropertyFlag.NonEnumerable));
             }
             // step 14
             else
             {
                 var thrower = Engine.Function.ThrowTypeError;
                 const PropertyFlag flags = PropertyFlag.EnumerableSet | PropertyFlag.ConfigurableSet;
-                self.DefineOwnProperty("caller", new GetSetPropertyDescriptor(get: thrower, set: thrower, flags), false);
-                self.DefineOwnProperty("callee", new GetSetPropertyDescriptor(get: thrower, set: thrower, flags), false);
+                DefineOwnProperty("caller", new GetSetPropertyDescriptor(get: thrower, set: thrower, flags), false);
+                DefineOwnProperty("callee", new GetSetPropertyDescriptor(get: thrower, set: thrower, flags), false);
             }
         }
 
@@ -232,5 +234,18 @@ namespace Jint.Native.Argument
 
             return base.Delete(propertyName, throwOnError);
         }
+
+        internal void PersistArguments()
+        {
+            EnsureInitialized();
+
+            var args = _args;
+            var copiedArgs = new JsValue[args.Length];
+            System.Array.Copy(args, copiedArgs, args.Length);
+            _args = copiedArgs;
+
+            // should no longer expose arguments which is special name
+            ParameterMap = null;
+        }
     }
 }

+ 120 - 6
Jint/Native/Array/ArrayConstructor.cs

@@ -1,6 +1,7 @@
 using System.Collections;
 using System.Runtime.CompilerServices;
 using Jint.Native.Function;
+using Jint.Native.Iterator;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
@@ -27,7 +28,7 @@ namespace Jint.Native.Array
             obj.Prototype = engine.Function.PrototypeObject;
             obj.PrototypeObject = ArrayPrototype.CreatePrototypeObject(engine, obj);
 
-            obj.SetOwnProperty("length", new PropertyDescriptor(1, PropertyFlag.AllForbidden));
+            obj.SetOwnProperty("length", new PropertyDescriptor(1, PropertyFlag.Configurable));
 
             // The initial value of Array.prototype is the Array prototype object
             obj.SetOwnProperty("prototype", new PropertyDescriptor(obj.PrototypeObject, PropertyFlag.AllForbidden));
@@ -45,22 +46,135 @@ namespace Jint.Native.Array
         {
             SetOwnProperty("from",new PropertyDescriptor(new ClrFunctionInstance(Engine, "from", From, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
             SetOwnProperty("isArray", new PropertyDescriptor(new ClrFunctionInstance(Engine, "isArray", IsArray, 1), PropertyFlag.NonEnumerable));
-            SetOwnProperty("of", new PropertyDescriptor(new ClrFunctionInstance(Engine, "of", Of, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
+            SetOwnProperty("of", new PropertyDescriptor(new ClrFunctionInstance(Engine, "of", Of, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
         }
 
         private JsValue From(JsValue thisObj, JsValue[] arguments)
         {
             var source = arguments.At(0);
-            if (source is IArrayLike arrayLike)
+            var mapFunction = arguments.At(1);
+            var callable = !mapFunction.IsUndefined() ? GetCallable(mapFunction) : null;
+            var thisArg = arguments.At(2);
+
+            if (source.IsNullOrUndefined())
+            {
+                ExceptionHelper.ThrowTypeError(_engine, "Cannot convert undefined or null to object");
+            }
+
+            if (source is JsString jsString)
+            {
+                var a = _engine.Array.ConstructFast((uint) jsString.Length);
+                for (int i = 0; i < jsString._value.Length; i++)
+                {
+                    a.SetIndexValue((uint) i, JsString.Create(jsString._value[i]), updateLength: false);
+                }
+                return a;
+            }
+
+            if (thisObj.IsNull() || !(source is ObjectInstance objectInstance))
+            {
+                return _engine.Array.ConstructFast(0);
+            }
+
+            if (objectInstance.IsArrayLike)
+            {
+                var operations = ArrayPrototype.ArrayOperations.For(objectInstance);
+
+                var length = operations.GetLength();
+
+                var a = _engine.Array.ConstructFast(length);
+                var args = !ReferenceEquals(callable, null)
+                    ? _engine._jsValueArrayPool.RentArray(2)
+                    : null;
+
+                uint n = 0;
+                for (uint i = 0; i < length; i++)
+                {
+                    JsValue jsValue;
+                    operations.TryGetValue(i, out var value);
+                    if (!ReferenceEquals(callable, null))
+                    {
+                        args[0] = value;
+                        args[1] = i;
+                        jsValue = callable.Call(thisArg, args);
+
+                        // function can alter data
+                        length = operations.GetLength();
+                    }
+                    else
+                    {
+                        jsValue = value;
+                    }
+                    a.SetIndexValue(i, jsValue, updateLength: false);
+                    n++;
+                }
+
+                if (!ReferenceEquals(callable, null))
+                {
+                    _engine._jsValueArrayPool.ReturnArray(args);
+                }
+
+                a.SetLength(length);
+                return a;
+            }
+
+            var instance = _engine.Array.ConstructFast(0);
+            if (objectInstance.TryGetIterator(_engine, out var iterator))
+            {
+                var protocol = new ArrayProtocol(_engine, thisArg, instance, iterator, callable);
+                protocol.Execute();
+            }
+
+            return instance;
+        }
+
+        private sealed class ArrayProtocol : IteratorProtocol
+        {
+            private readonly JsValue _thisArg;
+            private readonly ArrayInstance _instance;
+            private readonly ICallable _callable;
+            private long _index = -1;
+
+            public ArrayProtocol(
+                Engine engine, 
+                JsValue thisArg,
+                ArrayInstance instance,
+                IIterator iterator,
+                ICallable callable) : base(engine, iterator, 2)
+            {
+                _thisArg = thisArg;
+                _instance = instance;
+                _callable = callable;
+            }
+
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                _index++;
+                var sourceValue = ExtractValueFromIteratorInstance(currentValue);
+                JsValue jsValue;
+                if (!ReferenceEquals(_callable, null))
+                {
+                    args[0] = sourceValue;
+                    args[1] = _index; 
+                    jsValue = _callable.Call(_thisArg, args);
+                }
+                else
+                {
+                    jsValue = sourceValue;
+                }
+
+                _instance.SetIndexValue((uint) _index, jsValue, updateLength: false);
+            }
+
+            protected override void IterationEnd()
             {
-                arrayLike.ToArray(_engine);
+                _instance.SetLength((uint) (_index + 1));
             }
-            return Undefined;
         }
 
         private JsValue Of(JsValue thisObj, JsValue[] arguments)
         {
-            return Undefined;
+            return _engine.Array.Construct(arguments);
         }
 
         private static JsValue Species(JsValue thisObject, JsValue[] arguments)

+ 59 - 8
Jint/Native/Array/ArrayInstance.cs

@@ -58,6 +58,10 @@ namespace Jint.Native.Array
             _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
         }
 
+        internal override bool IsConcatSpreadable => !TryGetIsConcatSpreadable(out var isConcatSpreadable) || isConcatSpreadable;
+
+        internal override bool IsArrayLike => true;
+
         /// Implementation from ObjectInstance official specs as the one
         /// in ObjectInstance is optimized for the general case and wouldn't work
         /// for arrays
@@ -520,6 +524,27 @@ namespace Jint.Native.Array
             return smallest;
         }
 
+        internal uint GetLargestIndex()
+        {
+            if (_dense != null)
+            {
+                return (uint) (_dense.Length - 1);
+            }
+
+            uint largest = uint.MaxValue;
+            // only try to help if collection reasonable small
+            if (_sparse.Count > 0 && _sparse.Count < 100)
+            {
+                largest = 0;
+                foreach (var key in _sparse.Keys)
+                {
+                    largest = System.Math.Max(key, largest);
+                }
+            }
+
+            return largest;
+        }
+
         public bool TryGetValue(uint index, out JsValue value)
         {
             value = Undefined;
@@ -615,7 +640,7 @@ namespace Jint.Native.Array
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal void EnsureCapacity(uint capacity)
         {
-            if (capacity > (uint) _dense.Length)
+            if (capacity <= MaxDenseArrayLength && capacity > (uint) _dense.Length)
             {
                 // need to grow
                 var newArray = new PropertyDescriptor[capacity];
@@ -656,7 +681,7 @@ namespace Jint.Native.Array
                 var desc = new PropertyDescriptor(arguments[i], PropertyFlag.ConfigurableEnumerableWritable);
                 if (_dense != null && n < _dense.Length)
                 {
-                    _dense[(int) n] = desc;
+                    _dense[(uint) n] = desc;
                 }
                 else if (n < uint.MaxValue)
                 {
@@ -720,8 +745,13 @@ namespace Jint.Native.Array
         internal override bool FindWithCallback(
             JsValue[] arguments,
             out uint index,
-            out JsValue value)
+            out JsValue value,
+            bool visitUnassigned)
         {
+            var thisArg = arguments.At(1);
+            var callbackfn = arguments.At(0);
+            var callable = GetCallable(callbackfn);
+
             var len = GetLength();
             if (len == 0)
             {
@@ -730,15 +760,11 @@ namespace Jint.Native.Array
                 return false;
             }
 
-            var callbackfn = arguments.At(0);
-            var thisArg = arguments.At(1);
-            var callable = GetCallable(callbackfn);
-
             var args = _engine._jsValueArrayPool.RentArray(3);
             args[2] = this;
             for (uint k = 0; k < len; k++)
             {
-                if (TryGetValue(k, out var kvalue))
+                if (TryGetValue(k, out var kvalue) || visitUnassigned)
                 {
                     args[0] = kvalue;
                     args[1] = k;
@@ -758,5 +784,30 @@ namespace Jint.Native.Array
             value = Undefined;
             return false;
         }
+        
+        public uint Length => GetLength();
+
+        public JsValue this[uint index]
+        {
+            get
+            {
+                TryGetValue(index, out var kValue);
+                return kValue;
+            }
+        }
+
+        internal ArrayInstance ToArray(Engine engine)
+        {
+            var length = GetLength();
+            var array = _engine.Array.ConstructFast(length);
+            for (uint i = 0; i < length; i++)
+            {
+                if (TryGetValue(i, out var kValue))
+                {
+                    array.SetIndexValue(i, kValue, updateLength: false);
+                }
+            }
+            return array;
+        }
     }
 }

Разница между файлами не показана из-за своего большого размера
+ 460 - 147
Jint/Native/Array/ArrayPrototype.cs


+ 0 - 7
Jint/Native/Array/IArrayLike.cs

@@ -1,7 +0,0 @@
-namespace Jint.Native.Array
-{
-    internal interface IArrayLike
-    {
-        ArrayInstance ToArray(Engine engine);
-    }
-}

+ 5 - 8
Jint/Native/Boolean/BooleanPrototype.cs

@@ -33,20 +33,17 @@ namespace Jint.Native.Boolean
 
         private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
         {
-            var B = thisObj;
-            if (B.IsBoolean())
+            if (thisObj._type == Types.Boolean)
             {
-                return B;
+                return thisObj;
             }
 
-            var o = B.TryCast<BooleanInstance>();
-            if (!ReferenceEquals(o, null))
+            if (thisObj is BooleanInstance bi)
             {
-                return o.PrimitiveValue;
+                return bi.PrimitiveValue;
             }
 
-            ExceptionHelper.ThrowTypeError(Engine);
-            return null;
+            return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
         }
 
         private JsValue ToBooleanString(JsValue thisObj, JsValue[] arguments)

+ 4 - 7
Jint/Native/Function/EvalFunctionInstance.cs

@@ -1,5 +1,4 @@
 using Esprima;
-using Jint.Native.Argument;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
@@ -49,7 +48,7 @@ namespace Jint.Native.Function
                                 Engine.EnterExecutionContext(Engine.GlobalEnvironment, Engine.GlobalEnvironment, Engine.Global);
                             }
 
-                            var lexicalEnvironment = Engine.ExecutionContext.LexicalEnvironment;
+                            var lexicalEnvironment = _engine.ExecutionContext.LexicalEnvironment;
                             if (StrictModeScope.IsStrictModeCode)
                             {
                                 strictVarEnv = LexicalEnvironment.NewDeclarativeEnvironment(Engine, lexicalEnvironment);
@@ -66,12 +65,10 @@ namespace Jint.Native.Function
                             var result = _engine.ExecuteStatement(program);
                             var value = result.GetValueOrDefault();
 
-                            // we can safely release arguments if they don't escape the scope
-                            if (argumentInstanceRented
-                                && lexicalEnvironment?._record is DeclarativeEnvironmentRecord der
-                                && !(result.Value is ArgumentsInstance))
+                            if (argumentInstanceRented)
                             {
-                                der.ReleaseArguments();
+                                lexicalEnvironment?._record?.FunctionWasCalled();
+                                _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled();
                             }
 
                             if (result.Type == CompletionType.Throw)

+ 3 - 3
Jint/Native/Function/FunctionConstructor.cs

@@ -136,12 +136,12 @@ namespace Jint.Native.Function
             var thisArg = arguments[0];
             var argArray = arguments[1];
 
-            if (func == null)
+            if (func is null)
             {
-                ExceptionHelper.ThrowTypeError(Engine);
+                return ExceptionHelper.ThrowTypeError<object>(Engine);
             }
 
-            if (argArray.IsNull() || argArray.IsUndefined())
+            if (argArray.IsNullOrUndefined())
             {
                 return func.Call(thisArg, Arguments.Empty);
             }

+ 7 - 7
Jint/Native/Function/FunctionPrototype.cs

@@ -90,20 +90,20 @@ namespace Jint.Native.Function
             var thisArg = arguments.At(0);
             var argArray = arguments.At(1);
 
-            if (func == null)
+            if (func is null)
             {
-                ExceptionHelper.ThrowTypeError(Engine);
+                return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
             }
 
-            if (argArray.IsNull() || argArray.IsUndefined())
+            if (argArray.IsNullOrUndefined())
             {
                 return func.Call(thisArg, Arguments.Empty);
             }
 
             var argArrayObj = argArray.TryCast<ObjectInstance>();
-            if (ReferenceEquals(argArrayObj, null))
+            if (argArrayObj is null)
             {
-                ExceptionHelper.ThrowTypeError(Engine);
+                return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
             }
 
             var len = ((JsNumber) argArrayObj.Get("length"))._value;
@@ -126,9 +126,9 @@ namespace Jint.Native.Function
         public JsValue CallImpl(JsValue thisObject, JsValue[] arguments)
         {
             var func = thisObject.TryCast<ICallable>();
-            if (func == null)
+            if (func is null)
             {
-                ExceptionHelper.ThrowTypeError(Engine);
+                return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
             }
 
             return func.Call(arguments.At(0), arguments.Length == 0 ? arguments : arguments.Skip(1));

+ 5 - 7
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using Esprima.Ast;
-using Jint.Native.Argument;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
@@ -86,7 +85,8 @@ namespace Jint.Native.Function
         /// <returns></returns>
         public override JsValue Call(JsValue thisArg, JsValue[] arguments)
         {
-            using (new StrictModeScope(Strict, true))
+            var strict = Strict || _engine._isStrict;
+            using (new StrictModeScope(strict, true))
             {
                 // setup new execution context http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.3
                 JsValue thisBinding;
@@ -124,12 +124,10 @@ namespace Jint.Native.Function
                     
                     var value = result.GetValueOrDefault();
                     
-                    // we can safely release arguments if they don't escape the scope
-                    if (argumentInstanceRented
-                        && _engine.ExecutionContext.LexicalEnvironment?._record is DeclarativeEnvironmentRecord der
-                        && !(result.Value is ArgumentsInstance))
+                    if (argumentInstanceRented)
                     {
-                        der.ReleaseArguments();
+                        _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled();
+                        _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled();
                     }
 
                     if (result.Type == CompletionType.Throw)

+ 24 - 3
Jint/Native/Iterator/IteratorConstructor.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using System.Linq;
-using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.Map;
 using Jint.Native.Object;
@@ -71,9 +70,9 @@ namespace Jint.Native.Iterator
             return instance;
         }
 
-        internal ObjectInstance Construct(ArrayInstance array)
+        internal ObjectInstance Construct(ObjectInstance array)
         {
-            var instance = new IteratorInstance.ArrayIterator(Engine, array)
+            var instance = new IteratorInstance.ArrayLikeIterator(Engine, array)
             {
                 Prototype = PrototypeObject,
                 Extensible = true
@@ -114,5 +113,27 @@ namespace Jint.Native.Iterator
 
             return instance;
         }
+
+        internal ObjectInstance ConstructArrayLikeKeyIterator(ObjectInstance array)
+        {
+            var instance = new IteratorInstance.ArrayLikeKeyIterator(Engine, array)
+            {
+                Prototype = PrototypeObject,
+                Extensible = true
+            };
+
+            return instance;
+        }
+
+        internal ObjectInstance ConstructArrayLikeValueIterator(ObjectInstance array)
+        {
+            var instance = new IteratorInstance.ArrayLikeValueIterator(Engine, array)
+            {
+                Prototype = PrototypeObject,
+                Extensible = true
+            };
+
+            return instance;
+        }
     }
 }

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

@@ -109,13 +109,13 @@ namespace Jint.Native.Iterator
             }
         }
 
-        public class ArrayIterator : IteratorInstance
+        public class ArrayLikeIterator : IteratorInstance
         {
             private readonly ArrayPrototype.ArrayOperations _array;
             private uint? _end;
             private uint _position;
 
-            public ArrayIterator(Engine engine, JsValue target) : base(engine)
+            public ArrayLikeIterator(Engine engine, JsValue target) : base(engine)
             {
                 if (!(target is ObjectInstance objectInstance))
                 {
@@ -213,6 +213,57 @@ namespace Jint.Native.Iterator
                     return new  ValueIteratorPosition(_engine, value);
                 }
 
+                _closed = true;
+                return ValueIteratorPosition.Done;
+            }
+        } 
+
+        public class ArrayLikeKeyIterator : IteratorInstance
+        {
+            private readonly ArrayPrototype.ArrayOperations _operations;
+            private uint _position;
+            private bool _closed;
+
+            public ArrayLikeKeyIterator(Engine engine, ObjectInstance objectInstance) : base(engine)
+            {
+                _operations = ArrayPrototype.ArrayOperations.For(objectInstance);
+                _position = 0;
+            }
+
+            public override ObjectInstance Next()
+            {
+                var length = _operations.GetLength();
+                if (!_closed && _position < length)
+                {
+                    return new  ValueIteratorPosition(_engine, _position++);
+                }
+
+                _closed = true;
+                return ValueIteratorPosition.Done;
+            }
+        }
+
+        public class ArrayLikeValueIterator : IteratorInstance
+        {
+            private readonly ArrayPrototype.ArrayOperations _operations;
+            private uint _position;
+            private bool _closed;
+
+            public ArrayLikeValueIterator(Engine engine, ObjectInstance objectInstance) : base(engine)
+            {
+                _operations = ArrayPrototype.ArrayOperations.For(objectInstance);
+                _position = 0;
+            }
+
+            public override ObjectInstance Next()
+            {
+                var length = _operations.GetLength();
+                if (!_closed && _position < length)
+                {
+                    _operations.TryGetValue(_position++, out var value);
+                    return new ValueIteratorPosition(_engine, value);
+                }
+
                 _closed = true;
                 return ValueIteratorPosition.Done;
             }

+ 81 - 0
Jint/Native/Iterator/IteratorProtocol.cs

@@ -0,0 +1,81 @@
+using Jint.Native.Array;
+
+namespace Jint.Native.Iterator
+{
+    /// <summary>
+    /// Handles looping of iterator values, sub-classes can use to implement wanted actions.
+    /// </summary>
+    internal abstract class IteratorProtocol
+    {
+        protected readonly Engine _engine;
+        private readonly IIterator _iterator;
+        private readonly int _argCount;
+
+        protected IteratorProtocol(
+            Engine engine,
+            IIterator iterator,
+            int argCount)
+        {
+            _engine = engine;
+            _iterator = iterator;
+            _argCount = argCount;
+        }
+
+        internal void Execute()
+        {
+            var args = _engine._jsValueArrayPool.RentArray(_argCount);
+            try
+            {
+                do
+                {
+                    var item = _iterator.Next();
+                    if (item.TryGetValue("done", out var done) && done.AsBoolean())
+                    {
+                        break;
+                    }
+
+                    if (!item.TryGetValue("value", out var currentValue))
+                    {
+                        currentValue = JsValue.Undefined;
+                    }
+
+                    ProcessItem(args, currentValue);
+                } while (true);
+            }
+            catch
+            {
+                _iterator.Return();
+                throw;
+            }
+            finally
+            {
+                _engine._jsValueArrayPool.ReturnArray(args);
+            }
+
+            IterationEnd();
+        }
+
+        protected virtual void IterationEnd()
+        {
+        }
+
+        protected abstract void ProcessItem(JsValue[] args, JsValue currentValue);
+
+        protected JsValue ExtractValueFromIteratorInstance(JsValue jsValue)
+        {
+            if (jsValue is ArrayInstance ai)
+            {
+                uint index = 0;
+                if (ai.GetLength() > 1)
+                {
+                    index = 1;
+                }
+
+                ai.TryGetValue(index, out var value);
+                return value;
+            }
+
+            return jsValue;
+        }
+    }
+}

+ 4 - 4
Jint/Native/JsString.cs

@@ -5,7 +5,7 @@ using Jint.Runtime;
 
 namespace Jint.Native
 {
-    public class JsString : JsValue, IEquatable<JsString>, IArrayLike
+    public class JsString : JsValue, IEquatable<JsString>
     {
         private const int AsciiMax = 126;
         private static readonly JsString[] _charToJsValue;
@@ -99,10 +99,10 @@ namespace Jint.Native
 
         public ArrayInstance ToArray(Engine engine)
         {
-            var array = engine.Array.ConstructFast((uint) this._value.Length);
-            for (uint i = 0; i < _value.Length; ++i)
+            var array = engine.Array.ConstructFast((uint) _value.Length);
+            for (int i = 0; i < _value.Length; ++i)
             {
-                array.SetIndexValue(i, this._value[0], updateLength: false);
+                array.SetIndexValue((uint) i, _value[i], updateLength: false);
             }
 
             return array;

+ 29 - 6
Jint/Native/JsValue.cs

@@ -42,6 +42,13 @@ namespace Jint.Native
             return _type == Types.Undefined;
         }
 
+        [Pure]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal bool IsNullOrUndefined()
+        {
+            return _type < Types.Boolean;
+        }
+
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsArray()
@@ -144,25 +151,41 @@ namespace Jint.Native
             }
             return this as ArrayInstance;
         }
-        
+
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal IIterator GetIterator(Engine engine)
+        {
+            if (!TryGetIterator(engine, out var iterator))
+            {
+                return ExceptionHelper.ThrowTypeError<IIterator>(engine, "The value is not iterable");
+            }
+
+            return iterator;
+        }
+        
+        [Pure]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal bool TryGetIterator(Engine engine, out IIterator iterator)
         {
             if (!(this is ObjectInstance oi)
                 || !oi.TryGetValue(GlobalSymbolRegistry.Iterator._value, out var value)
                 || !(value is ICallable callable))
             {
-                ExceptionHelper.ThrowTypeError(engine, "The value is not iterable");
-                return null;
+                iterator = null;
+                return false;
             }
 
             var obj = (ObjectInstance) callable.Call(this, Arguments.Empty);
-            if (obj is IIterator iterator)
+            if (obj is IIterator i)
+            {
+                iterator = i;
+            }
+            else
             {
-                return iterator;
+                iterator = new IteratorInstance.ObjectWrapper(obj);
             }
-            return new IteratorInstance.ObjectWrapper(obj);
+            return true;
         }
 
         [Pure]

+ 43 - 71
Jint/Native/Map/MapConstructor.cs

@@ -1,4 +1,5 @@
 using Jint.Native.Function;
+using Jint.Native.Iterator;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
@@ -10,7 +11,7 @@ namespace Jint.Native.Map
 {
     public sealed class MapConstructor : FunctionInstance, IConstructor
     {
-        private MapConstructor(Engine engine, string name) :  base(engine, name, null, null, false)
+        private MapConstructor(Engine engine, string name) : base(engine, name, null, null, false)
         {
         }
 
@@ -72,81 +73,52 @@ namespace Jint.Native.Map
                 Extensible = true
             };
 
-            if (arguments.Length > 0
-                && !arguments[0].IsUndefined()
-                && !arguments[0].IsNull())
+            if (arguments.Length > 0 && !arguments[0].IsNullOrUndefined())
             {
                 var iterator = arguments.At(0).GetIterator(_engine);
-                if (iterator != null)
+                var mapProtocol = new MapProtocol(_engine, instance, iterator);
+                mapProtocol.Execute();
+            }
+
+            return instance;
+        }
+
+        private sealed class MapProtocol : IteratorProtocol
+        {
+            private readonly MapInstance _instance;
+            private readonly ICallable _setter;
+
+            public MapProtocol(
+                Engine engine,
+                MapInstance instance,
+                IIterator iterator) : base(engine, iterator, 2)
+            {
+                _instance = instance;
+                var setterProperty = instance.GetProperty("set");
+
+                if (setterProperty is null
+                    || !setterProperty.TryGetValue(instance, out var setterValue)
+                    || (_setter = setterValue as ICallable) is null)
                 {
-                    var setterProperty = instance.GetProperty("set");
-
-                    ICallable setter = null;
-                    if (setterProperty == null
-                        || !setterProperty.TryGetValue(instance, out var setterValue)
-                        || (setter = setterValue as ICallable) == null)
-                    {
-                        ExceptionHelper.ThrowTypeError(_engine, "set must be callable");
-                        return null;
-                    }
-                    
-                    var args = _engine._jsValueArrayPool.RentArray(2);
-                    try
-                    {
-                        do
-                        {
-                            var item = iterator.Next();
-                            if (item.TryGetValue("done", out var done) && done.AsBoolean())
-                            {
-                                break;
-                            }
-
-                            if (!item.TryGetValue("value", out var currentValue))
-                            {
-                                break;
-                            }
-
-                            if (!(currentValue is ObjectInstance oi))
-                            {
-                                ExceptionHelper.ThrowTypeError(_engine, "iterator's value must be an object");
-                                break;
-                            }
-
-                            JsValue key = Undefined;
-                            JsValue value = Undefined;
-                            if (oi.TryGetValue("0", out var arrayIndex)
-                                && oi.TryGetValue("1", out var source))
-                            {
-                                if (source is ObjectInstance oi2)
-                                {
-                                    key = oi2.Get("0");
-                                    value = oi2.Get("1");
-                                }
-                                else
-                                {
-                                    ExceptionHelper.ThrowTypeError(_engine, "iterator's value must be an object");
-                                    break;
-                                }
-                            }
-
-                            args[0] = key;
-                            args[1] = value;
-                            setter.Call(instance, args);
-                        } while (true);
-                    }
-                    catch
-                    {
-                        iterator.Return();
-                        throw;
-                    }
-                    finally
-                    {
-                        _engine._jsValueArrayPool.ReturnArray(args);
-                    }
+                    ExceptionHelper.ThrowTypeError(_engine, "set must be callable");
                 }
             }
 
-            return instance;
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                if (!(currentValue is ObjectInstance oi))
+                {
+                    ExceptionHelper.ThrowTypeError(_engine, "iterator's value must be an object");
+                    return;
+                }
+
+                oi.TryGetValue("0", out var key);
+                oi.TryGetValue("1", out var value);
+
+                args[0] = key;
+                args[1] = value;
+                _setter.Call(_instance, args);
+            }
         }
     }
-}
+}

+ 0 - 2
Jint/Native/Map/MapPrototype.cs

@@ -127,7 +127,5 @@ namespace Jint.Native.Map
         {
             return ((MapInstance) thisObj).Values();
         }
-
-
     }
 }

+ 8 - 4
Jint/Native/Number/NumberPrototype.cs

@@ -82,13 +82,17 @@ namespace Jint.Native.Number
 
         private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
         {
-            var number = thisObj.TryCast<NumberInstance>();
-            if (ReferenceEquals(number, null))
+            if (thisObj is NumberInstance ni)
             {
-                ExceptionHelper.ThrowTypeError(Engine);
+                return ni.NumberData;
+            }
+
+            if (thisObj is JsNumber)
+            {
+                return thisObj;
             }
 
-            return number.NumberData;
+            return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
         }
 
         private const double Ten21 = 1e21;

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

@@ -64,7 +64,7 @@ namespace Jint.Native.Object
                 return Construct(arguments);
             }
 
-            if(arguments[0].IsNull() || arguments[0].IsUndefined())
+            if(arguments[0].IsNullOrUndefined())
             {
                 return Construct(arguments);
             }

+ 41 - 10
Jint/Native/Object/ObjectInstance.cs

@@ -10,6 +10,7 @@ using Jint.Native.Function;
 using Jint.Native.Number;
 using Jint.Native.RegExp;
 using Jint.Native.String;
+using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors.Specialized;
@@ -826,25 +827,35 @@ namespace Jint.Native.Object
         internal virtual bool FindWithCallback(
             JsValue[] arguments,
             out uint index,
-            out JsValue value)
+            out JsValue value,
+            bool visitUnassigned)
         {
-            uint GetLength()
+            long GetLength()
             {
                 var desc = GetProperty("length");
                 var descValue = desc.Value;
+                double len;
                 if (desc.IsDataDescriptor() && !ReferenceEquals(descValue, null))
                 {
-                    return TypeConverter.ToUint32(descValue);
+                    len = TypeConverter.ToNumber(descValue);
                 }
-
-                var getter = desc.Get ?? Undefined;
-                if (getter.IsUndefined())
+                else
                 {
-                    return 0;
+                    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));
+                    }
                 }
 
-                // if getter is not undefined it must be ICallable
-                return TypeConverter.ToUint32(((ICallable) getter).Call(this, Arguments.Empty));
+                return (long) System.Math.Max(
+                    0, 
+                    System.Math.Min(len, ArrayPrototype.ArrayOperations.MaxArrayLikeLength));
             }
 
             bool TryGetValue(uint idx, out JsValue jsValue)
@@ -871,7 +882,7 @@ namespace Jint.Native.Object
             var length = GetLength();
             for (uint k = 0; k < length; k++)
             {
-                if (TryGetValue(k, out var kvalue))
+                if (TryGetValue(k, out var kvalue) || visitUnassigned)
                 {
                     args[0] = kvalue;
                     args[1] = k;
@@ -903,6 +914,26 @@ namespace Jint.Native.Object
             return null;
         }
 
+        internal virtual bool IsConcatSpreadable => TryGetIsConcatSpreadable(out var isConcatSpreadable) && isConcatSpreadable;
+
+        internal virtual bool IsArrayLike => TryGetValue("length", out var lengthValue)
+                                             && lengthValue.IsNumber()
+                                             && ((JsNumber) lengthValue)._value >= 0;
+
+        protected bool TryGetIsConcatSpreadable(out bool isConcatSpreadable)
+        {
+            isConcatSpreadable = false;
+            if (TryGetValue(GlobalSymbolRegistry.IsConcatSpreadable._value, out var isConcatSpreadableValue)
+                && !ReferenceEquals(isConcatSpreadableValue, null)
+                && !isConcatSpreadableValue.IsUndefined())
+            {
+                isConcatSpreadable = TypeConverter.ToBoolean(isConcatSpreadableValue);
+                return true;
+            }
+
+            return false;
+        }
+
         public override bool Equals(JsValue obj)
         {
             if (ReferenceEquals(null, obj))

+ 27 - 59
Jint/Native/Set/SetConstructor.cs

@@ -1,5 +1,5 @@
-using Jint.Native.Array;
-using Jint.Native.Function;
+using Jint.Native.Function;
+using Jint.Native.Iterator;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
@@ -11,7 +11,7 @@ namespace Jint.Native.Set
 {
     public sealed class SetConstructor : FunctionInstance, IConstructor
     {
-        private SetConstructor(Engine engine, string name) :  base(engine, name, null, null, false)
+        private SetConstructor(Engine engine, string name) : base(engine, name, null, null, false)
         {
         }
 
@@ -72,74 +72,42 @@ namespace Jint.Native.Set
                 Prototype = PrototypeObject,
                 Extensible = true
             };
-            if (arguments.Length > 0
-                && !arguments[0].IsUndefined()
-                && !arguments[0].IsNull())
+            if (arguments.Length > 0 && !arguments[0].IsNullOrUndefined())
             {
                 var iterator = arguments.At(0).GetIterator(_engine);
-                if (iterator != null)
-                {
-                    var setterProperty = instance.GetProperty("add");
-
-                    ICallable adder;
-                    if (setterProperty == null
-                        || !setterProperty.TryGetValue(instance, out var setterValue)
-                        || (adder = setterValue as ICallable) == null)
-                    {
-                        ExceptionHelper.ThrowTypeError(_engine, "add must be callable");
-                        return null;
-                    }
-
-                    var args = _engine._jsValueArrayPool.RentArray(1);
-                    try
-                    {
-                        do
-                        {
-                            var item = iterator.Next();
-                            if (item.TryGetValue("done", out var done) && done.AsBoolean())
-                            {
-                                break;
-                            }
-
-                            if (!item.TryGetValue("value", out var currentValue))
-                            {
-                                break;
-                            }
-
-                            args[0] = ExtractValueFromIteratorInstance(currentValue);
-
-                            adder.Call(instance, args);
-                        } while (true);
-                    }
-                    catch
-                    {
-                        iterator.Return();
-                        throw;
-                    }
-                    finally
-                    {
-                        _engine._jsValueArrayPool.ReturnArray(args);
-                    }
-                }
+                var protocol = new SetProtocol(_engine, instance, iterator);
+                protocol.Execute();
             }
 
             return instance;
         }
 
-        private static JsValue ExtractValueFromIteratorInstance(JsValue jsValue)
+        private sealed class SetProtocol : IteratorProtocol
         {
-            if (jsValue is ArrayInstance ai)
+            private readonly SetInstance _instance;
+            private readonly ICallable _adder;
+
+            public SetProtocol(
+                Engine engine,
+                SetInstance instance,
+                IIterator iterator) : base(engine, iterator, 1)
             {
-                uint index = 0;
-                if (ai.GetLength() > 1)
+                _instance = instance;
+                var setterProperty = instance.GetProperty("add");
+
+                if (setterProperty is null
+                    || !setterProperty.TryGetValue(instance, out var setterValue)
+                    || (_adder = setterValue as ICallable) is null)
                 {
-                    index = 1;
+                    ExceptionHelper.ThrowTypeError(_engine, "add must be callable");
                 }
-                ai.TryGetValue(index, out var value);
-                return value;
             }
 
-            return jsValue;
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                args[0] = ExtractValueFromIteratorInstance(currentValue);
+                _adder.Call(_instance, args);
+            }
         }
     }
-}
+}

+ 8 - 4
Jint/Native/String/StringPrototype.cs

@@ -815,13 +815,17 @@ namespace Jint.Native.String
 
         private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
         {
-            var s = thisObj.TryCast<StringInstance>();
-            if (ReferenceEquals(s, null))
+            if (thisObj is StringInstance si)
             {
-                ExceptionHelper.ThrowTypeError(Engine);
+                return si.PrimitiveValue;
             }
 
-            return s.PrimitiveValue;
+            if (thisObj is JsString)
+            {
+                return thisObj;
+            }
+
+            return ExceptionHelper.ThrowTypeError<JsValue>(Engine);
         }
 
         /// <summary>

+ 11 - 11
Jint/Native/Symbol/GlobalSymbolRegistry.cs

@@ -4,16 +4,16 @@ namespace Jint.Native.Symbol
 {
     public class GlobalSymbolRegistry : Dictionary<string, JsSymbol>
     {
-        public static JsSymbol HasInstance { get; } = new JsSymbol("Symbol.hasInstance");
-        public static JsSymbol IsConcatSpreadable { get; } = new JsSymbol("Symbol.isConcatSpreadable");
-        public static JsSymbol Iterator { get; } = new JsSymbol("Symbol.iterator");
-        public static JsSymbol Match { get; } = new JsSymbol("Symbol.match");
-        public static JsSymbol Replace { get; } = new JsSymbol("Symbol.replace");
-        public static JsSymbol Search { get; } = new JsSymbol("Symbol.search");
-        public static JsSymbol Species { get; } = new JsSymbol("Symbol.species");
-        public static JsSymbol Split { get; } = new JsSymbol("Symbol.split");
-        public static JsSymbol ToPrimitive { get; } = new JsSymbol("Symbol.toPrimitive");
-        public static JsSymbol ToStringTag { get; } = new JsSymbol("Symbol.toStringTag");
-        public static JsSymbol Unscopables { get; } = new JsSymbol("Symbol.unscopables");
+        public static readonly JsSymbol HasInstance = new JsSymbol("Symbol.hasInstance");
+        public static readonly JsSymbol IsConcatSpreadable = new JsSymbol("Symbol.isConcatSpreadable");
+        public static readonly JsSymbol Iterator = new JsSymbol("Symbol.iterator");
+        public static readonly JsSymbol Match = new JsSymbol("Symbol.match");
+        public static readonly JsSymbol Replace = new JsSymbol("Symbol.replace");
+        public static readonly JsSymbol Search = new JsSymbol("Symbol.search");
+        public static readonly JsSymbol Species = new JsSymbol("Symbol.species");
+        public static readonly JsSymbol Split = new JsSymbol("Symbol.split");
+        public static readonly JsSymbol ToPrimitive = new JsSymbol("Symbol.toPrimitive");
+        public static readonly JsSymbol ToStringTag = new JsSymbol("Symbol.toStringTag");
+        public static readonly JsSymbol Unscopables = new JsSymbol("Symbol.unscopables");
     }
 }

+ 26 - 7
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -22,6 +22,9 @@ namespace Jint.Runtime.Environments
         private const string BindingNameArguments = "arguments";
         private Binding _argumentsBinding;
 
+        // false = not accessed, true = accessed, null = values copied
+        private bool? _argumentsBindingWasAccessed = false;
+
         public DeclarativeEnvironmentRecord(Engine engine) : base(engine)
         {
         }
@@ -57,6 +60,7 @@ namespace Jint.Runtime.Environments
 
             if (key.Length == 9 && key == BindingNameArguments)
             {
+                _argumentsBindingWasAccessed = true;
                 return ref _argumentsBinding;
             }
 
@@ -95,7 +99,6 @@ namespace Jint.Runtime.Environments
             {
                 _dictionary?.Remove(key);
             }
-
         }
 
         private bool TryGetValue(string key, out Binding value)
@@ -209,12 +212,6 @@ namespace Jint.Runtime.Environments
             return keys;
         }
 
-        internal void ReleaseArguments()
-        {
-            _engine._argumentsInstancePool.Return(_argumentsBinding.Value as ArgumentsInstance);
-            _argumentsBinding = default;
-        }
-
         /// <summary>
         /// Optimized version for function calls.
         /// </summary>
@@ -299,5 +296,27 @@ namespace Jint.Runtime.Environments
                 }
             }
         }
+        
+        internal override void FunctionWasCalled()
+        {
+            // we can safely release arguments only if it doesn't have possibility to escape the scope
+            // so check if someone ever accessed it
+            if (!(_argumentsBinding.Value is ArgumentsInstance argumentsInstance))
+            {
+                return;
+            }
+            
+            if (!argumentsInstance._initialized && _argumentsBindingWasAccessed == false)
+            {
+                _engine._argumentsInstancePool.Return(argumentsInstance);
+                _argumentsBinding = default;
+            }
+            else if (_argumentsBindingWasAccessed != null && argumentsInstance._args.Length > 0)
+            {
+                // we need to ensure we hold on to arguments given
+                argumentsInstance.PersistArguments();
+                _argumentsBindingWasAccessed = null;
+            }
+        }
     }
 }

+ 5 - 0
Jint/Runtime/Environments/EnvironmentRecord.cs

@@ -77,6 +77,11 @@ namespace Jint.Runtime.Environments
             return false;
         }
 
+        /// <summary>
+        /// Informs whether arguments instance was accessed and maybe thus stored,
+        /// which makes it unsuitable for pooling and reuse.
+        /// </summary>
+        internal abstract void FunctionWasCalled();
     }
 }
 

+ 4 - 0
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -78,5 +78,9 @@ namespace Jint.Runtime.Environments
 
             return ArrayExt.Empty<string>();
         }
+
+        internal override void FunctionWasCalled()
+        {
+        }
     }
 }

+ 16 - 0
Jint/Runtime/ExceptionHelper.cs

@@ -5,6 +5,12 @@ namespace Jint.Runtime
 {
     internal static class ExceptionHelper
     {
+        public static T ThrowSyntaxError<T>(Engine engine, string message = null)
+        {
+            ThrowSyntaxError(engine, message);
+            return default;
+        }
+
         public static void ThrowSyntaxError(Engine engine, string message = null)
         {
             throw new JavaScriptException(engine.SyntaxError, message);
@@ -31,6 +37,11 @@ namespace Jint.Runtime
             throw new JavaScriptException(engine.ReferenceError, message);
         }
 
+        public static T ThrowTypeErrorNoEngine<T>(string message = null, Exception exception = null)
+        {
+            throw new TypeErrorException(message);
+        }
+
         public static T ThrowTypeError<T>(Engine engine, string message = null, Exception exception = null)
         {
             ThrowTypeError(engine, message, exception);
@@ -42,6 +53,11 @@ namespace Jint.Runtime
             throw new JavaScriptException(engine.TypeError, message, exception);
         }
 
+        public static T ThrowRangeError<T>(Engine engine, string message = null)
+        {
+            throw new JavaScriptException(engine.RangeError, message);
+        }
+
         public static void ThrowRangeError(Engine engine, string message = null)
         {
             throw new JavaScriptException(engine.RangeError, message);

+ 9 - 7
Jint/Runtime/ExpressionIntepreter.cs

@@ -346,6 +346,9 @@ namespace Jint.Runtime
                 case BinaryOperator.UnsignedRightShift:
                     return (uint)TypeConverter.ToInt32(left) >> (int)(TypeConverter.ToUint32(right) & 0x1F);
 
+                case BinaryOperator.Exponentiation:
+                    return Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right));
+
                 case BinaryOperator.InstanceOf:
                     var f = right.TryCast<FunctionInstance>();
                     if (ReferenceEquals(f, null))
@@ -657,7 +660,7 @@ namespace Jint.Runtime
 
                     if (function == null)
                     {
-                        ExceptionHelper.ThrowSyntaxError(_engine);
+                        return ExceptionHelper.ThrowSyntaxError<JsValue>(_engine);
                     }
 
                     ScriptFunctionInstance functionInstance;
@@ -765,8 +768,7 @@ namespace Jint.Runtime
                 _engine,
                 functionExpression,
                 funcEnv,
-                functionExpression.Strict
-                );
+                functionExpression.Strict);
 
             if (!string.IsNullOrEmpty(functionExpression.Id?.Name))
             {
@@ -846,10 +848,9 @@ namespace Jint.Runtime
                 }
             }
 
-            var callable = func as ICallable;
-            if (callable == null)
+            if (!(func is ICallable callable))
             {
-                ExceptionHelper.ThrowTypeError(_engine);
+                return ExceptionHelper.ThrowTypeError<JsValue>(_engine);
             }
 
             var thisObject = Undefined.Instance;
@@ -1057,7 +1058,8 @@ namespace Jint.Runtime
                     {
                         return "object";
                     }
-                    switch (v.Type)
+
+                    switch (v._type)
                     {
                         case Types.Boolean: return "boolean";
                         case Types.Number: return "number";

+ 16 - 1
Jint/Runtime/StatementInterpreter.cs

@@ -213,7 +213,7 @@ namespace Jint.Runtime
 
             var varRef = _engine.EvaluateExpression(identifier) as Reference;
             var experValue = _engine.GetValue(_engine.EvaluateExpression(forInStatement.Right), true);
-            if (experValue.IsUndefined() || experValue.IsNull())
+            if (experValue.IsNullOrUndefined())
             {
                 return new Completion(CompletionType.Normal, null, null);
             }
@@ -343,6 +343,11 @@ namespace Jint.Runtime
             {
                 c = new Completion(CompletionType.Throw, e.Error, null, withStatement.Location);
             }
+            catch (TypeErrorException e)
+            {
+                var error = _engine.TypeError.Construct(new JsValue[] {e.Message});
+                c = new Completion(CompletionType.Throw, error, null, withStatement.Location);
+            }
             finally
             {
                 _engine.UpdateLexicalEnvironment(oldEnv);
@@ -456,6 +461,11 @@ namespace Jint.Runtime
                 var completion = new Completion(CompletionType.Throw, v.Error, null, v.Location ?? s?.Location);
                 return completion;
             }
+            catch (TypeErrorException e)
+            {
+                var error = _engine.TypeError.Construct(new JsValue[] {e.Message});
+                c = new Completion(CompletionType.Throw, error, null, s?.Location);
+            }
 
             return new Completion(c.Type, c.GetValueOrDefault(), c.Identifier);
         }
@@ -482,6 +492,11 @@ namespace Jint.Runtime
             {
                 return new Completion(CompletionType.Throw, v.Error, null, v.Location ?? s?.Location);
             }
+            catch (TypeErrorException e)
+            {
+                var error = _engine.TypeError.Construct(new JsValue[] {e.Message});
+                return new Completion(CompletionType.Throw, error, null, s?.Location);
+            }
         }
 
         /// <summary>

+ 3 - 0
Jint/Runtime/TypeConverter.cs

@@ -104,6 +104,9 @@ namespace Jint.Runtime
                     return ((JsBoolean) o)._value ? 1 : 0;
                 case Types.String:
                     return ToNumber(o.AsStringWithoutTypeCheck());
+                case Types.Symbol:
+                    // TODO proper TypeError would require Engine instance and a lot of API changes
+                    return ExceptionHelper.ThrowTypeErrorNoEngine<double>("Cannot convert a Symbol value to a number");
                 default:
                     return ToNumber(ToPrimitive(o, Types.Number));
             }

+ 14 - 0
Jint/Runtime/TypeErrorException.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Jint.Runtime
+{
+    /// <summary>
+    /// Workaround for situation where engine is not easily accessible.
+    /// </summary>
+    internal sealed class TypeErrorException : Exception
+    {
+        public TypeErrorException(string message) : base(message)
+        {
+        }
+    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов