浏览代码

Fix Promise.all behavior under interop (#911)

* add test case
* create array directly bypassing conversions
* update some call sites for optimal performance
* ensure wrapped .NET dictionaries are not array-like
Marko Lahma 4 年之前
父节点
当前提交
f180d16e7c

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

@@ -2556,5 +2556,12 @@ namespace Jint.Tests.Runtime
             Assert.Equal(1, _engine.Evaluate("arr.indexOf(b)").AsNumber());
             Assert.Equal(1, _engine.Evaluate("arr.indexOf(b)").AsNumber());
             Assert.True(_engine.Evaluate("arr.includes(b)").AsBoolean());
             Assert.True(_engine.Evaluate("arr.includes(b)").AsBoolean());
         }
         }
+
+        [Fact]
+        public void ObjectWrapperWrappingDictionaryShouldNotBeArrayLike()
+        {
+            var wrapper = new ObjectWrapper(_engine, new Dictionary<string, object>());
+            Assert.False(wrapper.IsArrayLike);
+        }
     }
     }
 }
 }

+ 29 - 0
Jint.Tests/Runtime/PromiseTests.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Collections.Generic;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Promise;
 using Jint.Native.Promise;
 using Jint.Runtime;
 using Jint.Runtime;
@@ -496,5 +497,33 @@ namespace Jint.Tests.Runtime
         }
         }
 
 
         #endregion
         #endregion
+
+        #region Regression
+
+        [Fact(Timeout = 5000)]
+        public void PromiseRegression_SingleElementArrayWithClrDictionaryInPromiseAll()
+        {
+            var engine = new Engine();
+            var dictionary = new Dictionary<string, object>
+            {
+                { "Value 1", 1 },
+                { "Value 2", "a string" }
+            };
+            engine.SetValue("clrDictionary", dictionary);
+
+            var resultAsObject = engine
+                .Evaluate(@"
+const promiseArray = [clrDictionary];
+return Promise.all(promiseArray);") // Returning and array through Promise.any()
+                .UnwrapIfPromise()
+                .ToObject();
+
+            var result = (object[]) resultAsObject;
+
+            Assert.Single(result);
+            Assert.IsType<Dictionary<string, object>>(result[0]);
+        }
+
+        #endregion
     }
     }
 }
 }

+ 20 - 0
Jint/Native/Array/ArrayConstructor.cs

@@ -381,6 +381,26 @@ namespace Jint.Native.Array
             return jsArray;
             return jsArray;
         }
         }
 
 
+        public ArrayInstance ConstructFast(JsValue[] contents)
+        {
+            var instance = ConstructFast((ulong) contents.Length);
+            for (var i = 0; i < contents.Length; i++)
+            {
+                instance.SetIndexValue((uint) i, contents[i], updateLength: false);
+            }
+            return instance;
+        }
+
+        internal ArrayInstance ConstructFast(List<JsValue> contents)
+        {
+            var instance = ConstructFast((ulong) contents.Count);
+            for (var i = 0; i < contents.Count; i++)
+            {
+                instance.SetIndexValue((uint) i, contents[i], updateLength: false);
+            }
+            return instance;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal ArrayInstance ConstructFast(ulong length)
         internal ArrayInstance ConstructFast(ulong length)
         {
         {

+ 1 - 10
Jint/Native/Json/JsonParser.cs

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
-using System.Linq;
 using Esprima;
 using Esprima;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native.Object;
 using Jint.Native.Object;
@@ -600,14 +599,6 @@ namespace Jint.Native.Json
             return node;
             return node;
         }
         }
 
 
-        public ObjectInstance CreateArrayInstance(IEnumerable<JsValue> values)
-        {
-            var jsValues = values.ToArray();
-            var jsArray = _engine.Array.Construct(jsValues.Length);
-            _engine.Array.PrototypeObject.Push(jsArray, jsValues);
-            return jsArray;
-        }
-
         // Throw an exception
         // Throw an exception
 
 
         private void ThrowError(Token token, string messageFormat, params object[] arguments)
         private void ThrowError(Token token, string messageFormat, params object[] arguments)
@@ -693,7 +684,7 @@ namespace Jint.Native.Json
 
 
             Expect("]");
             Expect("]");
 
 
-            return CreateArrayInstance(elements);
+            return _engine.Array.ConstructFast(elements);
         }
         }
 
 
         public ObjectInstance ParseJsonObject()
         public ObjectInstance ParseJsonObject()

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

@@ -254,14 +254,14 @@ namespace Jint.Native.Object
         {
         {
             var o = TypeConverter.ToObject(_engine, arguments.At(0));
             var o = TypeConverter.ToObject(_engine, arguments.At(0));
             var names = o.GetOwnPropertyKeys(Types.String);
             var names = o.GetOwnPropertyKeys(Types.String);
-            return _engine.Array.Construct(names.ToArray());
+            return _engine.Array.ConstructFast(names);
         }
         }
 
 
         private JsValue GetOwnPropertySymbols(JsValue thisObject, JsValue[] arguments)
         private JsValue GetOwnPropertySymbols(JsValue thisObject, JsValue[] arguments)
         {
         {
             var o = TypeConverter.ToObject(_engine, arguments.At(0));
             var o = TypeConverter.ToObject(_engine, arguments.At(0));
             var keys = o.GetOwnPropertyKeys(Types.Symbol);
             var keys = o.GetOwnPropertyKeys(Types.Symbol);
-            return _engine.Array.Construct(keys.ToArray());
+            return _engine.Array.ConstructFast(keys);
         }
         }
 
 
         private JsValue Create(JsValue thisObject, JsValue[] arguments)
         private JsValue Create(JsValue thisObject, JsValue[] arguments)

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

@@ -1260,11 +1260,10 @@ namespace Jint.Native.Object
                         }
                         }
                         else
                         else
                         {
                         {
-                            array.SetIndexValue(index, _engine.Array.Construct(new[]
-                            {
-                                property,
-                                value
-                            }), updateLength: false);
+                            var objectInstance = _engine.Array.ConstructFast(2);
+                            objectInstance.SetIndexValue(0,  property, updateLength: false);
+                            objectInstance.SetIndexValue(1, value, updateLength: false);
+                            array.SetIndexValue(index, objectInstance, updateLength: false);
                         }
                         }
                     }
                     }
 
 

+ 2 - 3
Jint/Native/Promise/PromiseConstructor.cs

@@ -17,7 +17,6 @@ namespace Jint.Native.Promise
         JsValue RejectObj
         JsValue RejectObj
     );
     );
 
 
-
     public sealed class PromiseConstructor : FunctionInstance, IConstructor
     public sealed class PromiseConstructor : FunctionInstance, IConstructor
     {
     {
         private static readonly JsString _functionName = new JsString("Promise");
         private static readonly JsString _functionName = new JsString("Promise");
@@ -230,8 +229,8 @@ namespace Jint.Native.Promise
                 // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
                 // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
                 if (results.TrueForAll(static x => x != null) && doneIterating)
                 if (results.TrueForAll(static x => x != null) && doneIterating)
                 {
                 {
-                    resolve.Call(Undefined,
-                        new JsValue[] {Engine.Array.Construct(results.ToArray())});
+                    var array = _engine.Array.ConstructFast(results);
+                    resolve.Call(Undefined, new JsValue[] { array });
                 }
                 }
             }
             }
 
 

+ 1 - 1
Jint/Native/Proxy/ProxyInstance.cs

@@ -40,7 +40,7 @@ namespace Jint.Native.Proxy
 
 
         public JsValue Call(JsValue thisObject, JsValue[] arguments)
         public JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
         {
-            var jsValues = new[] { _target, thisObject, _engine.Array.Construct(arguments) };
+            var jsValues = new[] { _target, thisObject, _engine.Array.ConstructFast(arguments) };
             if (TryCallHandler(TrapApply, jsValues, out var result))
             if (TryCallHandler(TrapApply, jsValues, out var result))
             {
             {
                 return result;
                 return result;

+ 2 - 6
Jint/Native/String/StringPrototype.cs

@@ -3,7 +3,6 @@ using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
 using Jint.Collections;
 using Jint.Collections;
-using Jint.Native.Array;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
 using Jint.Native.RegExp;
 using Jint.Native.Symbol;
 using Jint.Native.Symbol;
@@ -335,7 +334,6 @@ namespace Jint.Native.String
 
 
             // Coerce into a number, true will become 1
             // Coerce into a number, true will become 1
             var lim = limit.IsUndefined() ? uint.MaxValue : TypeConverter.ToUint32(limit);
             var lim = limit.IsUndefined() ? uint.MaxValue : TypeConverter.ToUint32(limit);
-            var len = s.Length;
 
 
             if (lim == 0)
             if (lim == 0)
             {
             {
@@ -348,10 +346,8 @@ namespace Jint.Native.String
             }
             }
             else if (separator.IsUndefined())
             else if (separator.IsUndefined())
             {
             {
-                var jsValues = _engine._jsValueArrayPool.RentArray(1);
-                jsValues[0] = s;
-                var arrayInstance = (ArrayInstance)Engine.Array.Construct(jsValues);
-                _engine._jsValueArrayPool.ReturnArray(jsValues);
+                var arrayInstance = Engine.Array.ConstructFast(1);
+                arrayInstance.SetIndexValue(0, s, updateLength: false);
                 return arrayInstance;
                 return arrayInstance;
             }
             }
             else
             else

+ 6 - 0
Jint/Runtime/Interop/TypeDescriptor.cs

@@ -26,6 +26,12 @@ namespace Jint.Runtime.Interop
         
         
         private static bool DetermineIfObjectIsArrayLikeClrCollection(Type type)
         private static bool DetermineIfObjectIsArrayLikeClrCollection(Type type)
         {
         {
+            if (typeof(IDictionary).IsAssignableFrom(type))
+            {
+                // dictionaries are considered normal-object-like
+                return false;
+            }
+
             if (typeof(ICollection).IsAssignableFrom(type))
             if (typeof(ICollection).IsAssignableFrom(type))
             {
             {
                 return true;
                 return true;