Skip to content

fix: reject NaN results from arithmetic operations#915

Open
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/nan-handling-clean
Open

fix: reject NaN results from arithmetic operations#915
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/nan-handling-clean

Conversation

@He-Pin

@He-Pin He-Pin commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

fix: guard modulo-by-zero across all evaluation paths

Motivation:
sjsonnet silently produced NaN for modulo-by-zero (42 % 0) instead of throwing "division by zero" like go-jsonnet. The modulo operator lacked zero-divisor guards in 4 out of 6 evaluation paths, and the static optimizer constant-folded x % 0 into Val.Num(NaN).

Modification:

  • Evaluator: add rd == 0 checks to evalBinaryOpNumNum, visitBinaryOpAsDouble, visitBinaryOp, and tryInlineArith for OP_%
  • Evaluator: add isNaN defense check to OP_/ in visitBinaryOp (catches NaN from unguarded stdlib math functions like std.sqrt(-1))
  • StaticOptimizer: add r == 0 guard to tryFoldAsDouble and main-transform constant-folding for OP_% (consistent with OP_/)
  • Fix percent_mod_int5.jsonnet.golden to expect "division by zero" (verified against go-jsonnet binary)
  • Add error.modulo_by_zero and error.modulo_by_zero_float regression tests

Result:
All modulo evaluation paths now reject division by zero, matching go-jsonnet behavior. NaN can no longer silently leak through any path.

References:

  • go-jsonnet builtins.go:108-135: builtinDiv/builtinModulo check y.value == 0 → "Division by zero."
  • go-jsonnet builtins.go:1108-1116: makeDoubleCheck guards NaN/Infinity on all arithmetic results

@He-Pin He-Pin marked this pull request as ready for review June 10, 2026 09:00
@He-Pin He-Pin marked this pull request as draft June 10, 2026 09:12
Motivation:
sjsonnet silently produced NaN for modulo-by-zero (e.g. `42 % 0`)
instead of throwing "division by zero" like go-jsonnet. The modulo
operator lacked zero-divisor guards in 4 out of 6 evaluation paths,
and the static optimizer constant-folded `x % 0` into Val.Num(NaN).

Modification:
- Evaluator: add rd==0 checks to evalBinaryOpNumNum,
  visitBinaryOpAsDouble, visitBinaryOp, and tryInlineArith for OP_%
- Evaluator: add isNaN defense check to OP_/ in visitBinaryOp
  (catches NaN from stdlib math functions like std.sqrt(-1))
- StaticOptimizer: add r==0 guard to tryFoldAsDouble and main
  transform constant-folding for OP_% (consistent with OP_/)
- Fix percent_mod_int5.jsonnet.golden to expect "division by zero"
  (verified against go-jsonnet binary)
- Add regression tests for modulo-by-zero error cases

Result:
All modulo evaluation paths now reject division by zero, matching
go-jsonnet behavior. NaN can no longer silently leak through.
@He-Pin He-Pin force-pushed the fix/nan-handling-clean branch from be1f38a to 1c47422 Compare June 10, 2026 12:05
@He-Pin He-Pin marked this pull request as ready for review June 10, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant