From 34e2b632f317cc8343e5aa1dd63f243b8ecbb833 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Wed, 29 Jul 2026 15:25:38 +0800 Subject: [PATCH 1/2] fix: mask left shifts in numeric fast path Motivation: Jsonnet interprets shift counts modulo 64, but the numeric fast path validated left shifts using the unmasked count. Expressions routed through outer arithmetic could therefore fail even when the generic evaluator succeeded. Modification: Normalize non-negative left-shift counts modulo 64 before both overflow validation and execution, and add regressions for counts 64, 65, and 128. Result: Numeric fast-path behavior now matches the generic evaluator, go-jsonnet, jrsonnet, and the Jsonnet specification without changing valid count-65 behavior. References: - https://github.com/databricks/sjsonnet/pull/1096 - https://jsonnet.org/ref/spec.html --- sjsonnet/src/sjsonnet/Evaluator.scala | 5 +++-- .../new_test_suite/leftshift_fast_path_masking.jsonnet | 6 ++++++ .../leftshift_fast_path_masking.jsonnet.golden | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet.golden diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index 5f99b43bf..5dd31baf3 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -894,9 +894,10 @@ class Evaluator( val ll = visitExprAsDouble(e.lhs).toSafeLong(pos) val rr = visitExprAsDouble(e.rhs).toSafeLong(pos) if (rr < 0) Error.fail("Shift by negative exponent", pos) - if (rr >= 1 && math.abs(ll) >= (1L << (63 - rr))) + val masked = (rr % 64).toInt + if (masked >= 1 && math.abs(ll) >= (1L << (63 - masked))) Error.fail("Numeric value outside safe integer range for bitwise operation", pos) - (ll << rr).toDouble + (ll << masked).toDouble case Expr.BinaryOp.OP_>> => val ll = visitExprAsDouble(e.lhs).toSafeLong(pos) val rr = visitExprAsDouble(e.rhs).toSafeLong(pos) diff --git a/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet b/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet new file mode 100644 index 000000000..85c26f1ff --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet @@ -0,0 +1,6 @@ +std.assertEqual((1 << 64) * 1, 1) && +std.assertEqual(((1 << 64) + 0) * 1, 1) && +std.assertEqual((1 << 65) * 1, 2) && +std.assertEqual((1 << 128) * 1, 1) && +std.assertEqual((0 << 128) * 1, 0) && +true diff --git a/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet.golden new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet.golden @@ -0,0 +1 @@ +true From 41f7ec3bce855bca3d909135ab518d7fdb2b97e7 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Wed, 29 Jul 2026 17:46:09 +0800 Subject: [PATCH 2/2] test: add negative operand cases for left shift fast path Motivation: The original tests only covered zero and positive left operands. The overflow check uses math.abs(ll), so negative values exercise a distinct code path that should be regression-protected. Modification: Add assertions for (-1)<<64, (-2)<<65, (-3)<<128 through the numeric fast path (via * 1). Result: Negative operand behavior is now locked in. --- .../new_test_suite/leftshift_fast_path_masking.jsonnet | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet b/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet index 85c26f1ff..7ffe5f75d 100644 --- a/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet +++ b/sjsonnet/test/resources/new_test_suite/leftshift_fast_path_masking.jsonnet @@ -3,4 +3,7 @@ std.assertEqual(((1 << 64) + 0) * 1, 1) && std.assertEqual((1 << 65) * 1, 2) && std.assertEqual((1 << 128) * 1, 1) && std.assertEqual((0 << 128) * 1, 0) && +std.assertEqual(((-1) << 64) * 1, -1) && +std.assertEqual(((-2) << 65) * 1, -4) && +std.assertEqual(((-3) << 128) * 1, -3) && true