Skip to content

Commit 3544891

Browse files
committed
fix: improve safety checks for left shift operations
Add safety checks to prevent undefined behavior in bitwise left shift operations. These checks detect potential overflow conditions before they occur and throw appropriate error messages to help users identify problematic code. The error messages are consistent between runtime and static error handling. Added tests. Signed-off-by: Ville Vesilehto <[email protected]>
1 parent 42e89a4 commit 3544891

10 files changed

+52
-3
lines changed

core/vm.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -2563,6 +2563,13 @@ class Interpreter {
25632563
int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location);
25642564
int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location);
25652565
long_r = long_r % 64;
2566+
2567+
// Additional safety check for left shifts to prevent undefined behavior
2568+
if (long_r >= 1 && long_l >= (1LL << (63 - long_r))) {
2569+
throw makeError(ast.location,
2570+
"numeric value outside safe integer range for bitwise operation.");
2571+
}
2572+
25662573
scratch = makeNumber(long_l << long_r);
25672574
} break;
25682575

@@ -3414,13 +3421,13 @@ inline int64_t safeDoubleToInt64(double value, const internal::LocationRange& lo
34143421
}
34153422

34163423
// Constants for safe double-to-int conversion
3417-
// IEEE 754 doubles can only precisely represent integers up to 2^53-1
3424+
// IEEE 754 doubles precisely represent integers up to 2^53, beyond which precision is lost
34183425
constexpr int64_t DOUBLE_MAX_SAFE_INTEGER = (1LL << 53) - 1;
34193426
constexpr int64_t DOUBLE_MIN_SAFE_INTEGER = -((1LL << 53) - 1);
34203427

34213428
// Check if the value is within the safe integer range
34223429
if (value < DOUBLE_MIN_SAFE_INTEGER || value > DOUBLE_MAX_SAFE_INTEGER) {
3423-
throw internal::StaticError(loc, "numeric value outside safe integer range for bitwise operation");
3430+
throw internal::StaticError(loc, "numeric value outside safe integer range for bitwise operation.");
34243431
}
34253432
return static_cast<int64_t>(value);
34263433
}

core/vm.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ std::vector<std::string> jsonnet_vm_execute_stream(
137137
* 2. Ensures the value is within the safe integer range (±(2^53-1))
138138
*
139139
* The safe integer range limitation is necessary because IEEE 754 double precision
140-
* floating point numbers can only precisely represent integers up to 2^53-1.
140+
* floating point numbers can only precisely represent integers up to 2^53.
141141
* Beyond this range, precision is lost, which would lead to unpredictable results
142142
* in bitwise operations that depend on exact bit patterns.
143143
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Value just beyond MAX_SAFE_INTEGER (2^53)
2+
local beyond_max = 9007199254740992; // 2^53
3+
beyond_max << 1 // Should throw error "numeric value outside safe integer range for bitwise operation"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
STATIC ERROR: error.integer_conversion.jsonnet:3:1-16: numeric value outside safe integer range for bitwise operation.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Test that left-shifting a value that would exceed int64_t range throws an error
2+
local large_value = 1 << 62; // 2^62
3+
large_value << 2 // Would be 2^64, exceeding signed 64-bit integer range
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
STATIC ERROR: error.integer_left_shift.jsonnet:3:1-17: numeric value outside safe integer range for bitwise operation.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
local large_value = 4503599627370496;
2+
large_value << 11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
RUNTIME ERROR: numeric value outside safe integer range for bitwise operation.
2+
error.integer_left_shift_runtime.jsonnet:2:1-18
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Test values at boundary of safe integer range
2+
std.assertEqual(~9007199254740991, -9007199254740992) && // ~MAX_SAFE_INTEGER
3+
std.assertEqual(~(-9007199254740991), 9007199254740990) && // ~MIN_SAFE_INTEGER
4+
5+
// Test basic values
6+
std.assertEqual(~0, -1) &&
7+
std.assertEqual(~1, -2) &&
8+
std.assertEqual(~(-1), 0) &&
9+
10+
// Test shift operations with large values at safe boundary
11+
// MAX_SAFE_INTEGER (2^53-1) right shift by 4 bits
12+
std.assertEqual(9007199254740991 >> 4, 562949953421311) &&
13+
// MAX_SAFE_INTEGER (2^53-1) left shift by 1 bit (result is still precisely representable)
14+
std.assertEqual(9007199254740991 << 1, 18014398509481982) &&
15+
// (2^52-1) left shift by 1 bit (result is MAX_SAFE_INTEGER-1)
16+
std.assertEqual(4503599627370495 << 1, 9007199254740990) &&
17+
18+
// Test larger values within safe range
19+
std.assertEqual(~123456789, -123456790) &&
20+
std.assertEqual(~(-987654321), 987654320) &&
21+
22+
// Test other bitwise operations
23+
std.assertEqual(123 & 456, 72) &&
24+
std.assertEqual(123 | 456, 507) &&
25+
std.assertEqual(123 ^ 456, 435) &&
26+
std.assertEqual(123 << 2, 492) &&
27+
std.assertEqual(123 >> 2, 30) &&
28+
29+
true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
true

0 commit comments

Comments
 (0)