diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index bb7632df..cc54e6ad 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -240,7 +240,7 @@ class Evaluator( @inline private def tryInlineArith(op: Int, ld: Double, rd: Double, pos: Position): Val = (op: @switch) match { case Expr.BinaryOp.OP_* => - val r = ld * rd; if (r.isInfinite) null else Val.cachedNum(pos, r) + val r = ld * rd; if (r.isNaN || r.isInfinite) null else Val.cachedNum(pos, r) case Expr.BinaryOp.OP_/ => if (rd == 0) null else { val r = ld / rd; if (r.isNaN || r.isInfinite) null else Val.cachedNum(pos, r) } @@ -248,9 +248,9 @@ class Evaluator( if (rd == 0) null else { val r = ld % rd; if (r.isNaN) null else Val.cachedNum(pos, r) } case Expr.BinaryOp.OP_+ => - val r = ld + rd; if (r.isInfinite) null else Val.cachedNum(pos, r) + val r = ld + rd; if (r.isNaN || r.isInfinite) null else Val.cachedNum(pos, r) case Expr.BinaryOp.OP_- => - val r = ld - rd; if (r.isInfinite) null else Val.cachedNum(pos, r) + val r = ld - rd; if (r.isNaN || r.isInfinite) null else Val.cachedNum(pos, r) case Expr.BinaryOp.OP_<< => val ll = ld.toLong; val rl = rd.toLong if (ll.toDouble != ld || rl.toDouble != rd) null // not safe integers @@ -706,23 +706,32 @@ class Evaluator( val ld = ln.asDouble val rd = rn.asDouble (op: @switch) match { - case Expr.BinaryOp.OP_+ => Val.cachedNum(pos, ld + rd) + case Expr.BinaryOp.OP_+ => + val r = ld + rd + if (r.isNaN) Error.fail("not a number", pos) + if (r.isInfinite) Error.fail("overflow", pos) + Val.cachedNum(pos, r) case Expr.BinaryOp.OP_- => val r = ld - rd + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos) Val.cachedNum(pos, r) case Expr.BinaryOp.OP_* => val r = ld * rd + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos) Val.cachedNum(pos, r) case Expr.BinaryOp.OP_/ => if (rd == 0) Error.fail("Division by zero.", pos) val r = ld / rd + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos) Val.cachedNum(pos, r) case Expr.BinaryOp.OP_% => if (rd == 0) Error.fail("Division by zero.", pos) - Val.cachedNum(pos, ld % rd) + val r = ld % rd + if (r.isNaN) Error.fail("not a number", pos) + Val.cachedNum(pos, r) // Use position-free static singletons for boolean results — this method is only called // from comprehension fast paths where position info on boolean results is unnecessary. // Avoids 1 object allocation per comparison in inner loops (significant for 1M+ iterations). @@ -857,23 +866,28 @@ class Evaluator( (e.op: @switch) match { case Expr.BinaryOp.OP_* => val r = visitExprAsDouble(e.lhs) * visitExprAsDouble(e.rhs) + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos); r case Expr.BinaryOp.OP_/ => val l = visitExprAsDouble(e.lhs) val r = visitExprAsDouble(e.rhs) if (r == 0) Error.fail("Division by zero.", pos) val result = l / r + if (result.isNaN) Error.fail("not a number", pos) if (result.isInfinite) Error.fail("overflow", pos); result case Expr.BinaryOp.OP_% => val l = visitExprAsDouble(e.lhs) val r = visitExprAsDouble(e.rhs) if (r == 0) Error.fail("Division by zero.", pos) - l % r + val result = l % r + if (result.isNaN) Error.fail("not a number", pos); result case Expr.BinaryOp.OP_+ => val r = visitExprAsDouble(e.lhs) + visitExprAsDouble(e.rhs) + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos); r case Expr.BinaryOp.OP_- => val r = visitExprAsDouble(e.lhs) - visitExprAsDouble(e.rhs) + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos); r case Expr.BinaryOp.OP_<< => val ll = visitExprAsDouble(e.lhs).toSafeLong(pos) @@ -1334,10 +1348,12 @@ class Evaluator( // Pure numeric fast path: avoid intermediate Val.Num allocation case Expr.BinaryOp.OP_* => val r = visitExprAsDouble(e.lhs) * visitExprAsDouble(e.rhs) + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos) Val.cachedNum(pos, r) case Expr.BinaryOp.OP_- => val r = visitExprAsDouble(e.lhs) - visitExprAsDouble(e.rhs) + if (r.isNaN) Error.fail("not a number", pos) if (r.isInfinite) Error.fail("overflow", pos) Val.cachedNum(pos, r) case Expr.BinaryOp.OP_/ => @@ -1370,9 +1386,13 @@ class Evaluator( val l = visitExpr(e.lhs) val r = visitExpr(e.rhs) (l, r) match { - case (Val.Num(_, l), Val.Num(_, r)) => Val.cachedNum(pos, l + r) - case (l: Val.Str, r: Val.Str) => Val.Str.concat(pos, l, r) - case (n: Val.Num, r: Val.Str) => + case (Val.Num(_, l), Val.Num(_, r)) => + val result = l + r + if (result.isNaN) Error.fail("not a number", pos) + if (result.isInfinite) Error.fail("overflow", pos) + Val.cachedNum(pos, result) + case (l: Val.Str, r: Val.Str) => Val.Str.concat(pos, l, r) + case (n: Val.Num, r: Val.Str) => Val.Str.concat(pos, Val.Str(pos, RenderUtils.renderDouble(n.asDouble)), r) case (l: Val.Str, n: Val.Num) => Val.Str.concat(pos, l, Val.Str(pos, RenderUtils.renderDouble(n.asDouble))) diff --git a/sjsonnet/test/resources/new_test_suite/arithmetic_normal_operations.jsonnet b/sjsonnet/test/resources/new_test_suite/arithmetic_normal_operations.jsonnet new file mode 100644 index 00000000..bf389610 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/arithmetic_normal_operations.jsonnet @@ -0,0 +1,5 @@ +// Test that normal arithmetic operations still work correctly. +assert 1 + 2 == 3; +assert 3 * 4 == 12; +assert 10 - 3 == 7; +true diff --git a/sjsonnet/test/resources/new_test_suite/arithmetic_normal_operations.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/arithmetic_normal_operations.jsonnet.golden new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/arithmetic_normal_operations.jsonnet.golden @@ -0,0 +1 @@ +true diff --git a/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_addition.jsonnet b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_addition.jsonnet new file mode 100644 index 00000000..87214820 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_addition.jsonnet @@ -0,0 +1,2 @@ +// Test that addition overflow (Infinity) errors instead of silently propagating. +1e308 + 1e308 diff --git a/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_addition.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_addition.jsonnet.golden new file mode 100644 index 00000000..d6cfd626 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_addition.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: overflow + at [].(error.arithmetic_overflow_addition.jsonnet:2:7) diff --git a/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_multiplication.jsonnet b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_multiplication.jsonnet new file mode 100644 index 00000000..0ee10de9 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_multiplication.jsonnet @@ -0,0 +1,2 @@ +// Test that multiplication overflow (Infinity) errors instead of silently propagating. +1e308 * 1e308 diff --git a/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_multiplication.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_multiplication.jsonnet.golden new file mode 100644 index 00000000..47400074 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_multiplication.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: overflow + at [].(error.arithmetic_overflow_multiplication.jsonnet:2:7) diff --git a/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_subtraction.jsonnet b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_subtraction.jsonnet new file mode 100644 index 00000000..55906eda --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_subtraction.jsonnet @@ -0,0 +1,2 @@ +// Test that subtraction overflow (Infinity) errors instead of silently propagating. +-1e308 - 1e308 diff --git a/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_subtraction.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_subtraction.jsonnet.golden new file mode 100644 index 00000000..43eeae58 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.arithmetic_overflow_subtraction.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: overflow + at [].(error.arithmetic_overflow_subtraction.jsonnet:2:8)