diff --git a/Jint.Tests/Runtime/ExecutionConstraintTests.cs b/Jint.Tests/Runtime/ExecutionConstraintTests.cs index c8dd513c86..fc0ca04f4e 100644 --- a/Jint.Tests/Runtime/ExecutionConstraintTests.cs +++ b/Jint.Tests/Runtime/ExecutionConstraintTests.cs @@ -321,6 +321,41 @@ public void ShouldLimitArraySizeForJoin() Assert.Throws(() => engine.Evaluate("new Array(2147483647).join('*')")); } + [Fact] + public void ShouldLimitTypedArraySizeForFill() + { + var engine = new Engine(o => o.MaxStatements(1_000).LimitMemory(4_000_000)); + Assert.Throws(() => engine.Evaluate("var arr = new Uint8Array(100000000); arr.fill(255);")); + } + + [Fact] + public void ShouldLimitTypedArraySizeForCopyWithin() + { + var engine = new Engine(o => o.MaxStatements(1_000).LimitMemory(4_000_000)); + Assert.Throws(() => engine.Evaluate("var arr = new Uint8Array(100000000); arr[0] = 1; arr.copyWithin(1, 0);")); + } + + [Fact] + public void ShouldLimitTypedArraySizeForReverse() + { + var engine = new Engine(o => o.MaxStatements(1_000).LimitMemory(4_000_000)); + Assert.Throws(() => engine.Evaluate("var arr = new Uint8Array(100000000); arr.reverse();")); + } + + [Fact] + public void ShouldLimitTypedArraySizeForToReversed() + { + var engine = new Engine(o => o.MaxStatements(1_000).LimitMemory(4_000_000)); + Assert.Throws(() => engine.Evaluate("var arr = new Uint8Array(100000000); arr.toReversed();")); + } + + [Fact] + public void ShouldLimitTypedArraySizeForWith() + { + var engine = new Engine(o => o.MaxStatements(1_000).LimitMemory(4_000_000)); + Assert.Throws(() => engine.Evaluate("var arr = new Uint8Array(100000000); arr.with(0, 1);")); + } + [Fact] public void ShouldConsiderConstraintsWhenCallingInvoke() { diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index a53600f2b6..7055712260 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -19,6 +19,8 @@ namespace Jint.Native.Array; /// public sealed class ArrayPrototype : ArrayInstance { + private const int ConstraintCheckInterval = 10_000; + private readonly Realm _realm; private readonly ArrayConstructor _constructor; private readonly ObjectTraverseStack _joinStack; @@ -169,6 +171,12 @@ private ObjectInstance With(JsValue thisObject, JsCallArguments arguments) { a[k] = k == (ulong) actualIndex ? value : o.Get(k); k++; + + // Check constraints periodically to prevent long-running operations + if (k > 0 && k % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return new JsArray(_engine, a); } @@ -229,9 +237,15 @@ private JsValue Fill(JsValue thisObject, JsCallArguments arguments) final = (ulong) System.Math.Min(relativeEnd, length); } + // Check constraints periodically to prevent memory exhaustion in large fills for (var i = k; i < final; ++i) { operations.Set(i, value, throwOnError: false); + + if (i > k && (i - k) % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return o; @@ -290,6 +304,7 @@ private JsValue CopyWithin(JsValue thisObject, JsCallArguments arguments) } var count = (long) System.Math.Min(final - from, len - to); + var initialCount = count; long direction = 1; @@ -315,6 +330,12 @@ private JsValue CopyWithin(JsValue thisObject, JsCallArguments arguments) from += direction; to += direction; count--; + + // Check constraints periodically to prevent long-running operations + if ((initialCount - count) % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return o; @@ -1255,6 +1276,12 @@ private JsValue Reverse(JsValue thisObject, JsCallArguments arguments) } lower++; + + // Check constraints periodically to prevent long-running operations + if (lower > 0 && lower % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return o.Target; @@ -1441,6 +1468,12 @@ private JsValue ToReversed(JsValue thisObject, JsCallArguments arguments) { var from = len - k - 1; a[k++] = o.Get(from); + + // Check constraints periodically to prevent long-running operations + if (k > 0 && k % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return new JsArray(_engine, a); } diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs index 83c58485dd..c621e65677 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -20,6 +20,8 @@ namespace Jint.Native.TypedArray; /// internal sealed class IntrinsicTypedArrayPrototype : Prototype { + private const int ConstraintCheckInterval = 10_000; + private readonly IntrinsicTypedArrayConstructor _constructor; private ClrFunction? _originalIteratorFunction; @@ -366,6 +368,7 @@ private JsValue CopyWithin(JsValue thisObject, JsCallArguments arguments) direction = 1; } + var initialCountBytes = countBytes; while (countBytes > 0) { if (fromByteIndex < bufferByteLimit && toByteIndex < bufferByteLimit) @@ -375,6 +378,12 @@ private JsValue CopyWithin(JsValue thisObject, JsCallArguments arguments) fromByteIndex += direction; toByteIndex += direction; countBytes--; + + // Check constraints periodically to prevent long-running operations + if ((initialCountBytes - countBytes) % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } else { @@ -497,6 +506,12 @@ private JsValue Fill(JsValue thisObject, JsCallArguments arguments) for (var i = k; i < endIndex; ++i) { o[i] = value; + + // Check constraints periodically to prevent memory exhaustion in large fills + if (i > k && (i - k) % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return thisObject; @@ -996,6 +1011,12 @@ private ObjectInstance Reverse(JsValue thisObject, JsCallArguments arguments) o[upper] = lowerValue; lower++; + + // Check constraints periodically to prevent long-running operations + if (lower > 0 && lower % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return o; @@ -1506,6 +1527,12 @@ private JsValue ToReversed(JsValue thisObject, JsCallArguments arguments) { var from = len - k - 1; a[k++] = o.Get(from); + + // Check constraints periodically to prevent long-running operations + if (k > 0 && k % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return a; @@ -1568,6 +1595,12 @@ private ObjectInstance With(JsValue thisObject, JsCallArguments arguments) { a[k] = k == (int) actualIndex ? value : o.Get(k); k++; + + // Check constraints periodically to prevent long-running operations + if (k > 0 && k % ConstraintCheckInterval == 0) + { + _engine.Constraints.Check(); + } } return a;