diff --git a/Jint.Tests/Runtime/ArrayTests.cs b/Jint.Tests/Runtime/ArrayTests.cs index 549de4c25..62e06c859 100644 --- a/Jint.Tests/Runtime/ArrayTests.cs +++ b/Jint.Tests/Runtime/ArrayTests.cs @@ -39,6 +39,22 @@ public void ArrayPrototypeToStringWithObject() Assert.Equal("[object Object]", result); } + [Fact] + public void ArrayPrototypeJoinWithCircularReference() + { + var result = _engine.Evaluate("Array.prototype.join.call((c = [1, 2, 3, 4], b = [1, 2, 3, 4], b[1] = c, c[1] = b, c))").AsString(); + + Assert.Equal("1,1,,3,4,3,4", result); + } + + [Fact] + public void ArrayPrototypeToLocaleStringWithCircularReference() + { + var result = _engine.Evaluate("Array.prototype.toLocaleString.call((c = [1, 2, 3, 4], b = [1, 2, 3, 4], b[1] = c, c[1] = b, c))").AsString(); + + Assert.Equal("1,1,,3,4,3,4", result); + } + [Fact] public void EmptyStringKey() { diff --git a/Jint/Collections/ObjectTraverseStack.cs b/Jint/Collections/ObjectTraverseStack.cs index 41cedbdd0..c41d3bbce 100644 --- a/Jint/Collections/ObjectTraverseStack.cs +++ b/Jint/Collections/ObjectTraverseStack.cs @@ -16,7 +16,7 @@ public ObjectTraverseStack(Engine engine) _engine = engine; } - public void Enter(JsValue value) + public bool TryEnter(JsValue value) { if (value is null) { @@ -25,10 +25,20 @@ public void Enter(JsValue value) if (_stack.Contains(value)) { - ExceptionHelper.ThrowTypeError(_engine.Realm, "Cyclic reference detected."); + return false; } _stack.Push(value); + + return true; + } + + public void Enter(JsValue value) + { + if (!TryEnter(value)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, "Cyclic reference detected."); + } } public void Exit() diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 0765b9ac9..b33bd282f 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -21,6 +21,7 @@ public sealed class ArrayPrototype : ArrayInstance { private readonly Realm _realm; private readonly ArrayConstructor _constructor; + private readonly ObjectTraverseStack _joinStack; internal ClrFunction? _originalIteratorFunction; internal ArrayPrototype( @@ -33,6 +34,7 @@ internal ArrayPrototype( _length = new PropertyDescriptor(JsNumber.PositiveZero, PropertyFlag.Writable); _realm = realm; _constructor = arrayConstructor; + _joinStack = new(engine); } protected override void Initialize() @@ -1273,6 +1275,11 @@ private JsValue Join(JsValue thisObject, JsValue[] arguments) return JsString.Empty; } + if (!_joinStack.TryEnter(thisObject)) + { + return JsString.Empty; + } + static string StringFromJsValue(JsValue value) { return value.IsNullOrUndefined() @@ -1283,6 +1290,7 @@ static string StringFromJsValue(JsValue value) var s = StringFromJsValue(o.Get(0)); if (len == 1) { + _joinStack.Exit(); return s; } @@ -1296,6 +1304,7 @@ static string StringFromJsValue(JsValue value) } sb.Append(StringFromJsValue(o.Get(k))); } + _joinStack.Exit(); return sb.ToString(); } @@ -1314,6 +1323,11 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) return JsString.Empty; } + if (!_joinStack.TryEnter(thisObject)) + { + return JsString.Empty; + } + using var r = new ValueStringBuilder(); for (uint k = 0; k < len; k++) { @@ -1327,6 +1341,7 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) r.Append(s); } } + _joinStack.Exit(); return r.ToString(); }