Skip to content

fix: harden NaN defense in arithmetic fast paths + add float modulo-by-zero test#915

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

fix: harden NaN defense in arithmetic fast paths + add float modulo-by-zero test#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: harden NaN defense in arithmetic fast paths + add float modulo-by-zero test

Motivation:

After #910 landed the modulo-by-zero guards across all evaluator paths and StaticOptimizer, there remained a gap: tryInlineArith (the eager-eval fast path in visitAsLazy) and the outer visitBinaryOp did not check whether the result of / or % is NaN. This meant that if a NaN ever reached these paths (e.g. from stdlib math functions like std.sqrt(-1) being folded into a lazy thunk), it could leak as a Val.Num(NaN) instead of producing an error — diverging from go-jsonnet's makeDoubleCheck behavior.

Additionally, the error.modulo_by_zero golden file had a trailing blank line that git diff --check flagged, and arithmetic_nan_handling.jsonnet had a misleading comment ("Test NaN handling") while only testing normal arithmetic.

Modification:

  • Evaluator.tryInlineArith: add r.isNaN checks for OP_/ and OP_% results (fallback to LazyExpr on NaN, matching existing isInfinite fallback pattern)
  • Evaluator.visitBinaryOp: add result.isNaNError.fail("not a number", pos) for OP_/ and OP_% (consistent with go-jsonnet's makeDoubleCheck)
  • Fix error.modulo_by_zero.jsonnet.golden trailing blank line
  • Clarify arithmetic_nan_handling.jsonnet comment to accurately describe its purpose (regression tests ensuring NaN guards don't break normal arithmetic)
  • Add error.modulo_by_zero_float.jsonnet + golden: 0.0 % 0.0"Division by zero."

Note: The modulo-by-zero guards in evalBinaryOpNumNum, visitBinaryOpAsDouble, and StaticOptimizer.tryFoldBinaryOp / tryFoldAsDouble are already in master via #910. This PR only adds the result-level NaN defense that #910 didn't cover.

Result:

NaN can no longer leak through tryInlineArith or visitBinaryOp for division/modulo results. All modulo-by-zero cases (integer and float) now correctly produce "Division by zero." or "division by zero" errors, matching go-jsonnet behavior. Golden file passes git diff --check.

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
  • fix: report "Division by zero." for modulo by zero #910: modulo-by-zero guards across evalBinaryOpNumNum, visitBinaryOpAsDouble, StaticOptimizer

@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
@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
@stephenamar-db

Copy link
Copy Markdown
Collaborator

please rebase

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 1c47422 to d262c37 Compare June 12, 2026 04:36
@He-Pin He-Pin changed the title fix: reject NaN results from arithmetic operations fix: harden NaN defense in arithmetic fast paths + add float modulo-by-zero test Jun 12, 2026
@He-Pin He-Pin force-pushed the fix/nan-handling-clean branch from ef3b0b4 to d262c37 Compare June 12, 2026 06:17
@He-Pin

He-Pin commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@stephenamar-db rebased now.

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.

2 participants