From 5b245e756c5bbf1e29f69f6b009ad88c4280992b Mon Sep 17 00:00:00 2001 From: He-Pin Date: Thu, 18 Jun 2026 17:42:42 +0800 Subject: [PATCH] fix: << overflow check for large shift amounts (multiples of 64) Motivation: The << operator's overflow check `math.abs(ll) >= (1L << (63 - rr))` broke when rr >= 64 and rr % 64 == 0, because Java's << masks the shift count to 6 bits, producing negative thresholds (e.g., 1L << 63 = Long.MIN_VALUE). This caused false errors for valid expressions like `1 << 64` and `0 << 128` that go-jsonnet and jrsonnet handle correctly via masking semantics. Modification: Mask the shift amount to `rr % 64` before applying the overflow check and the actual shift operation, matching Java/Go bit-shift semantics. Applied to both tryInlineArith (fast path) and visitBinaryOp (normal path). Result: `1 << 64` now returns 1 (was false error), `0 << 1000` returns 0, and `3 << 126` still correctly errors on overflow. Matches go-jsonnet and jrsonnet behavior. References: KNOWN_SEMANTIC_BUGS.md Bug #3 --- sjsonnet/src/sjsonnet/Evaluator.scala | 12 ++++++++---- .../error.leftshift_large_amount_overflow.jsonnet | 1 + ...or.leftshift_large_amount_overflow.jsonnet.golden | 2 ++ .../fix_leftshift_large_amount.jsonnet | 7 +++++++ .../fix_leftshift_large_amount.jsonnet.golden | 1 + 5 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet.golden create mode 100644 sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet.golden diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index e41deda5d..237940247 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -256,8 +256,11 @@ class Evaluator( val ll = ld.toLong; val rl = rd.toLong if (ll.toDouble != ld || rl.toDouble != rd) null // not safe integers else if (rl < 0) null - else if (rl >= 1 && math.abs(ll) >= (1L << (63 - rl))) null - else Val.cachedNum(pos, (ll << rl).toDouble) + else { + val masked = (rl % 64).toInt + if (masked >= 1 && math.abs(ll) >= (1L << (63 - masked))) null + else Val.cachedNum(pos, (ll << masked).toDouble) + } case Expr.BinaryOp.OP_>> => val ll = ld.toLong; val rl = rd.toLong if (ll.toDouble != ld || rl.toDouble != rd) null @@ -1411,10 +1414,11 @@ 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) else - Val.cachedNum(pos, (ll << rr).toDouble) + Val.cachedNum(pos, (ll << masked).toDouble) case Expr.BinaryOp.OP_>> => val ll = visitExprAsDouble(e.lhs).toSafeLong(pos) diff --git a/sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet b/sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet new file mode 100644 index 000000000..e02da1493 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet @@ -0,0 +1 @@ +3 << 126 diff --git a/sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet.golden new file mode 100644 index 000000000..9d38e3ad6 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.leftshift_large_amount_overflow.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: numeric value outside safe integer range for bitwise operation + at [].(error.leftshift_large_amount_overflow.jsonnet:1:3) diff --git a/sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet b/sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet new file mode 100644 index 000000000..3e70af8b0 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet @@ -0,0 +1,7 @@ +std.assertEqual(1 << 64, 1) && +std.assertEqual(0 << 64, 0) && +std.assertEqual(0 << 1000, 0) && +std.assertEqual(1 << 128, 1) && +std.assertEqual(1 << 192, 1) && +std.assertEqual(4.5 << 66, 16) && +true diff --git a/sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet.golden new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/fix_leftshift_large_amount.jsonnet.golden @@ -0,0 +1 @@ +true