From 7a02afe0baa84f83ff9968c2df44e4a0c5b247a3 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 2 Jul 2025 10:16:40 +0300 Subject: [PATCH] Support writing length to array like under interop --- Jint.Tests/Runtime/InteropTests.cs | 21 +++++++ .../Interop/ObjectWrapper.Specialized.cs | 56 +++++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 3a401280a9..f1fe981f70 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3868,4 +3868,25 @@ public void Tick() } } + [Fact] + public void ShouldBeAbleToWriteLengthOfListLike() + { + var list = new List { "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().WithMessage("Invalid array length"); + + _engine.Evaluate("list.length = 1;"); + list.Should().HaveCount(1); + list[0].Should().Be(null); + } } diff --git a/Jint/Runtime/Interop/ObjectWrapper.Specialized.cs b/Jint/Runtime/Interop/ObjectWrapper.Specialized.cs index b6decdc0cc..3ea030f82d 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.Specialized.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.Specialized.cs @@ -26,7 +26,7 @@ protected ArrayLikeWrapper( 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 @@ public override JsValue Get(JsValue property, JsValue receiver) 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 @@ public override bool HasProperty(JsValue property) 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 @@ public override bool Delete(JsValue property) 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 @@ public virtual void EnsureCapacity(int capacity) } } -internal class ListWrapper : ArrayLikeWrapper +internal sealed class ListWrapper : ArrayLikeWrapper { private readonly IList? _list; @@ -226,6 +270,8 @@ public ReadOnlyListWrapper(Engine engine, IReadOnlyList target, Type type) : return null; } + protected override bool CanWrite => false; + public override void AddDefault() => ExceptionHelper.ThrowNotSupportedException(); protected override void DoSetAt(int index, object? value) => ExceptionHelper.ThrowNotSupportedException();