Browse Source

Support writing length to IList under interop (#2135)

Marko Lahma 1 month ago
parent
commit
45e466a59f

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

@@ -3868,4 +3868,25 @@ try {
         }
     }
 
+    [Fact]
+    public void ShouldBeAbleToWriteLengthOfListLike()
+    {
+        var list = new List<string> { "a", "b", "c" };
+        _engine.SetValue("list", list);
+
+        _engine.Evaluate("list.length = 2;");
+        list.Should().HaveCount(2);
+        list[0].Should().Be("a");
+        list[1].Should().Be("b");
+
+        _engine.Evaluate("list.length = 0;");
+        list.Should().BeEmpty();
+
+        var act = () => _engine.Evaluate("list.length = -1;");
+        act.Should().Throw<JavaScriptException>().WithMessage("Invalid array length");
+
+        _engine.Evaluate("list.length = 1;");
+        list.Should().HaveCount(1);
+        list[0].Should().Be(null);
+    }
 }

+ 51 - 5
Jint/Runtime/Interop/ObjectWrapper.Specialized.cs

@@ -26,7 +26,7 @@ internal abstract class ArrayLikeWrapper : ObjectWrapper
 
     public abstract int Length { get; }
 
-    public override JsValue Get(JsValue property, JsValue receiver)
+    public sealed override JsValue Get(JsValue property, JsValue receiver)
     {
         if (property.IsInteger())
         {
@@ -36,7 +36,7 @@ internal abstract class ArrayLikeWrapper : ObjectWrapper
         return base.Get(property, receiver);
     }
 
-    public override bool HasProperty(JsValue property)
+    public sealed override bool HasProperty(JsValue property)
     {
         if (property.IsNumber())
         {
@@ -54,7 +54,7 @@ internal abstract class ArrayLikeWrapper : ObjectWrapper
         return base.HasProperty(property);
     }
 
-    public override bool Delete(JsValue property)
+    public sealed override bool Delete(JsValue property)
     {
         if (!_engine.Options.Interop.AllowWrite)
         {
@@ -84,9 +84,53 @@ internal abstract class ArrayLikeWrapper : ObjectWrapper
         return base.Delete(property);
     }
 
-
     public abstract object? GetAt(int index);
 
+    public sealed override bool Set(JsValue property, JsValue value, JsValue receiver)
+    {
+        if (ReferenceEquals(receiver, this) && CommonProperties.Length.Equals(property))
+        {
+            if (!CanWrite)
+            {
+                return false;
+            }
+
+            if (value.IsInteger())
+            {
+                var length = value.AsInteger();
+                if (length < 0)
+                {
+                    ExceptionHelper.ThrowRangeError(_engine.Realm, "Invalid array length");
+                }
+
+                if (length == Length)
+                {
+                    return true;
+                }
+
+                if (length > Length)
+                {
+                    EnsureCapacity(length);
+                }
+                else
+                {
+                    // decrease the length, remove items
+                    for (var i = Length - 1; i >= length; i--)
+                    {
+                        RemoveAt(i);
+                    }
+                }
+                return true;
+            }
+
+            ExceptionHelper.ThrowTypeError(_engine.Realm, "Invalid array length");
+        }
+
+        return base.Set(property, value, receiver);
+    }
+
+    protected virtual bool CanWrite => _engine.Options.Interop.AllowWrite;
+
     public void SetAt(int index, JsValue value)
     {
         if (_engine.Options.Interop.AllowWrite)
@@ -133,7 +177,7 @@ internal abstract class ArrayLikeWrapper : ObjectWrapper
     }
 }
 
-internal class ListWrapper : ArrayLikeWrapper
+internal sealed class ListWrapper : ArrayLikeWrapper
 {
     private readonly IList? _list;
 
@@ -226,6 +270,8 @@ internal sealed class ReadOnlyListWrapper<[DynamicallyAccessedMembers(Dynamicall
         return null;
     }
 
+    protected override bool CanWrite => false;
+
     public override void AddDefault() => ExceptionHelper.ThrowNotSupportedException();
 
     protected override void DoSetAt(int index, object? value) => ExceptionHelper.ThrowNotSupportedException();