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
17 changes: 12 additions & 5 deletions sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ class Evaluator(
if (rd == 0) null
else { val r = ld / rd; if (r.isInfinite) null else Val.cachedNum(pos, r) }
case Expr.BinaryOp.OP_% =>
Val.cachedNum(pos, ld % rd)
if (rd == 0) null else Val.cachedNum(pos, ld % rd)
case Expr.BinaryOp.OP_+ =>
val r = ld + rd; if (r.isInfinite) null else Val.cachedNum(pos, r)
case Expr.BinaryOp.OP_- =>
Expand Down Expand Up @@ -719,7 +719,9 @@ class Evaluator(
val r = ld / rd
if (r.isInfinite) Error.fail("overflow", pos)
Val.cachedNum(pos, r)
case Expr.BinaryOp.OP_% => Val.cachedNum(pos, ld % rd)
case Expr.BinaryOp.OP_% =>
if (rd == 0) Error.fail("Division by zero.", pos)
Val.cachedNum(pos, ld % rd)
// 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 @@ -862,7 +864,10 @@ class Evaluator(
val result = l / r
if (result.isInfinite) Error.fail("overflow", pos); result
case Expr.BinaryOp.OP_% =>
visitExprAsDouble(e.lhs) % visitExprAsDouble(e.rhs)
val l = visitExprAsDouble(e.lhs)
val r = visitExprAsDouble(e.rhs)
if (r == 0) Error.fail("Division by zero.", pos)
l % r
case Expr.BinaryOp.OP_+ =>
val r = visitExprAsDouble(e.lhs) + visitExprAsDouble(e.rhs)
if (r.isInfinite) Error.fail("overflow", pos); r
Expand Down Expand Up @@ -1342,8 +1347,10 @@ class Evaluator(
l match {
case Val.Num(_, ld) =>
r match {
case Val.Num(_, rd) => Val.cachedNum(pos, ld % rd)
case _ => failBinOp(l, e.op, r, pos)
case Val.Num(_, rd) =>
if (rd == 0) Error.fail("Division by zero.", pos)
Val.cachedNum(pos, ld % rd)
case _ => failBinOp(l, e.op, r, pos)
}
case ls: Val.Str => Format.format(ls.str, r, pos)
case _ => failBinOp(l, e.op, r, pos)
Expand Down
6 changes: 3 additions & 3 deletions sjsonnet/src/sjsonnet/StaticOptimizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class StaticOptimizer(
case BinaryOp.OP_/ =>
if (r == 0) return Double.NaN
val res = l / r; if (res.isInfinite) return Double.NaN; res
case BinaryOp.OP_% => l % r
case BinaryOp.OP_% => if (r == 0) return Double.NaN; l % r
case BinaryOp.OP_<< =>
val ll = l.toSafeLong(pos)(ev); val rr = r.toSafeLong(pos)(ev)
if (rr < 0) return Double.NaN
Expand Down Expand Up @@ -480,8 +480,8 @@ class StaticOptimizer(
}
case BinaryOp.OP_% =>
(lhs, rhs) match {
case (Val.Num(_, l), Val.Num(_, r)) => Val.Num(pos, l % r)
case _ => fallback
case (Val.Num(_, l), Val.Num(_, r)) if r != 0 => Val.Num(pos, l % r)
case _ => fallback
}
case BinaryOp.OP_< =>
tryFoldComparison(pos, lhs, BinaryOp.OP_<, rhs, fallback)
Expand Down
7 changes: 5 additions & 2 deletions sjsonnet/src/sjsonnet/stdlib/MathModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,10 @@ object MathModule extends AbstractFunctionModule {
new Val.Builtin2("mod", "a", "b") {
def evalRhs(a: Eval, b: Eval, ev: EvalScope, pos: Position): Val = {
(a.value, b.value) match {
case (x: Val.Num, y: Val.Num) => Val.cachedNum(pos, x.asDouble % y.asDouble)
case _ => Format.format(a.value.asString, b.value, pos)(ev)
case (x: Val.Num, y: Val.Num) =>
if (y.asDouble == 0) Error.fail("Division by zero.", pos)(ev)
Val.cachedNum(pos, x.asDouble % y.asDouble)
case _ => Format.format(a.value.asString, b.value, pos)(ev)
}
}
}
Expand All @@ -268,6 +270,7 @@ object MathModule extends AbstractFunctionModule {
* Performs modulo arithmetic for numeric values.
*/
builtin("modulo", "a", "b") { (pos, ev, a: Double, b: Double) =>
if (b == 0) Error.fail("Division by zero.", pos)(ev)
a % b
},
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
sjsonnet.Error: not a number
sjsonnet.Error: Division by zero.
at [<root>].(percent_mod_int5.jsonnet:1:4)

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Modulo by zero must report "Division by zero." matching go-jsonnet behavior.
1 % 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sjsonnet.Error: Division by zero.
at [<root>].(error.modulo_by_zero.jsonnet:2:3)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Runtime modulo by zero (non-constant operands) must also report "Division by zero."
local x = 10;
local y = 0;
x % y
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sjsonnet.Error: Division by zero.
Loading