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
38 changes: 29 additions & 9 deletions sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -240,17 +240,17 @@ 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) }
case Expr.BinaryOp.OP_% =>
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
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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_/ =>
Expand Down Expand Up @@ -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)))
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Test that addition overflow (Infinity) errors instead of silently propagating.
1e308 + 1e308
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sjsonnet.Error: overflow
at [<root>].(error.arithmetic_overflow_addition.jsonnet:2:7)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Test that multiplication overflow (Infinity) errors instead of silently propagating.
1e308 * 1e308
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sjsonnet.Error: overflow
at [<root>].(error.arithmetic_overflow_multiplication.jsonnet:2:7)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Test that subtraction overflow (Infinity) errors instead of silently propagating.
-1e308 - 1e308
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sjsonnet.Error: overflow
at [<root>].(error.arithmetic_overflow_subtraction.jsonnet:2:8)
Loading