Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Jint.Tests/Runtime/BigIntTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Numerics;
using Jint.Native;
using Jint.Runtime;

namespace Jint.Tests.Runtime;

Expand Down Expand Up @@ -67,4 +68,60 @@ public void ObjectIsReturnsFalseDifferentComparisonsWithBigInt(string statement)
// Assert
Assert.False(result.AsBoolean());
}

[Theory]
[InlineData("11n ** 711111111n")]
[InlineData("2n ** 10000000n")]
[InlineData("100n ** 1000000n")]
public void ExponentiationShouldThrowForExcessiveSize(string expression)
{
var engine = new Engine(options =>
{
options.TimeoutInterval(TimeSpan.FromSeconds(5));
options.LimitMemory(16_000_000);
});

var ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate(expression));
Assert.Contains("Maximum BigInt size exceeded", ex.Message);
}

[Theory]
[InlineData("11n ** 711111111n")]
[InlineData("2n ** 10000000n")]
public void ExponentiationAssignmentShouldThrowForExcessiveSize(string expression)
{
var engine = new Engine(options =>
{
options.TimeoutInterval(TimeSpan.FromSeconds(5));
options.LimitMemory(16_000_000);
});

// Convert "a ** b" to "x = a; x **= b" to test assignment path
var parts = expression.Split(new[] { " ** " }, StringSplitOptions.None);
var script = $"var x = {parts[0]}; x **= {parts[1]}; x";

var ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate(script));
Assert.Contains("Maximum BigInt size exceeded", ex.Message);
}

[Theory]
[InlineData("2n ** 100n", "1267650600228229401496703205376")]
[InlineData("3n ** 50n", "717897987691852588770249")]
[InlineData("0n ** 1000000n", "0")]
[InlineData("1n ** 1000000n", "1")]
[InlineData("(-1n) ** 1000001n", "-1")]
public void ExponentiationShouldWorkForReasonableSizes(string expression, string expected)
{
var engine = new Engine();
var result = engine.Evaluate(expression);
Assert.Equal(expected, result.ToString());
}

[Fact]
public void NegativeExponentShouldThrowRangeError()
{
var engine = new Engine();
var ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate("2n ** -1n"));
Assert.Contains("Exponent must be positive", ex.Message);
}
}
15 changes: 12 additions & 3 deletions Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,11 +355,20 @@ protected override object EvaluateInternal(EvaluationContext context)
else
{
var exponent = TypeConverter.ToBigInt(rval);
if (exponent > int.MaxValue || exponent < int.MinValue)
if (exponent < 0)
{
Throw.TypeError(context.Engine.Realm, "Cannot do exponentiation with exponent not fitting int32");
Throw.RangeError(context.Engine.Realm, "Exponent must be positive");
}
newLeftValue = JsBigInt.Create(BigInteger.Pow(TypeConverter.ToBigInt(originalLeftValue), (int) exponent));

if (exponent > int.MaxValue)
{
Throw.RangeError(context.Engine.Realm, "Maximum BigInt size exceeded");
}

var intExponent = (int) exponent;
var baseValue = TypeConverter.ToBigInt(originalLeftValue);
JintBinaryExpression.ValidateBigIntPowSize(context.Engine.Realm, baseValue, intExponent);
newLeftValue = JsBigInt.Create(BigInteger.Pow(baseValue, intExponent));
}

break;
Expand Down
27 changes: 24 additions & 3 deletions Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,23 @@ internal static void AssertValidBigIntArithmeticOperands(JsValue left, JsValue r
}
}

/// <summary>
/// Validates that BigInteger.Pow(base, exponent) won't produce an excessively large result.
/// Limits result to ~1 million bits (~125 KB) to prevent memory exhaustion.
/// </summary>
internal static void ValidateBigIntPowSize(Realm realm, BigInteger baseValue, int exponent)
{
if (exponent > 0)
{
var absBase = BigInteger.Abs(baseValue);
if (absBase > BigInteger.One
&& (double) exponent * BigInteger.Log(absBase, 2.0) > 1_000_000)
{
Throw.RangeError(realm, "Maximum BigInt size exceeded");
}
}
}

private sealed class StrictlyEqualBinaryExpression : JintBinaryExpression
{
public StrictlyEqualBinaryExpression(NonLogicalBinaryExpression expression) : base(expression)
Expand Down Expand Up @@ -881,11 +898,15 @@ protected override object EvaluateInternal(EvaluationContext context)
Throw.RangeError(context.Engine.Realm, "Exponent must be positive");
}

if (exponent > int.MaxValue || exponent < int.MinValue)
if (exponent > int.MaxValue)
{
Throw.TypeError(context.Engine.Realm, "Exponent does not fit 32bit range");
Throw.RangeError(context.Engine.Realm, "Maximum BigInt size exceeded");
}
result = JsBigInt.Create(BigInteger.Pow(left.AsBigInt(), (int) exponent));

var intExponent = (int) exponent;
var baseValue = left.AsBigInt();
ValidateBigIntPowSize(context.Engine.Realm, baseValue, intExponent);
result = JsBigInt.Create(BigInteger.Pow(baseValue, intExponent));
}

return result;
Expand Down