diff --git a/core/vm.cpp b/core/vm.cpp index 27629fd6..371602ff 100644 --- a/core/vm.cpp +++ b/core/vm.cpp @@ -2559,8 +2559,9 @@ class Interpreter { case BOP_SHIFT_L: { if (rhs.v.d < 0) throw makeError(ast.location, "shift by negative exponent."); - int64_t long_l = lhs.v.d; - int64_t long_r = rhs.v.d; + + int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); + int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); long_r = long_r % 64; scratch = makeNumber(long_l << long_r); } break; @@ -2568,27 +2569,28 @@ class Interpreter { case BOP_SHIFT_R: { if (rhs.v.d < 0) throw makeError(ast.location, "shift by negative exponent."); - int64_t long_l = lhs.v.d; - int64_t long_r = rhs.v.d; + + int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); + int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); long_r = long_r % 64; scratch = makeNumber(long_l >> long_r); } break; case BOP_BITWISE_AND: { - int64_t long_l = lhs.v.d; - int64_t long_r = rhs.v.d; + int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); + int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); scratch = makeNumber(long_l & long_r); } break; case BOP_BITWISE_XOR: { - int64_t long_l = lhs.v.d; - int64_t long_r = rhs.v.d; + int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); + int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); scratch = makeNumber(long_l ^ long_r); } break; case BOP_BITWISE_OR: { - int64_t long_l = lhs.v.d; - int64_t long_r = rhs.v.d; + int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); + int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); scratch = makeNumber(long_l | long_r); } break; @@ -3406,4 +3408,21 @@ std::vector jsonnet_vm_execute_stream(Allocator *alloc, const AST * return vm.manifestStream(string_output); } +inline int64_t safeDoubleToInt64(double value, const internal::LocationRange& loc) { + if (std::isnan(value) || std::isinf(value)) { + throw internal::StaticError(loc, "numeric value is not finite"); + } + + // Constants for safe double-to-int conversion + // IEEE 754 doubles can only precisely represent integers up to 2^53-1 + constexpr int64_t DOUBLE_MAX_SAFE_INTEGER = (1LL << 53) - 1; + constexpr int64_t DOUBLE_MIN_SAFE_INTEGER = -((1LL << 53) - 1); + + // Check if the value is within the safe integer range + if (value < DOUBLE_MIN_SAFE_INTEGER || value > DOUBLE_MAX_SAFE_INTEGER) { + throw internal::StaticError(loc, "numeric value outside safe integer range for bitwise operation"); + } + return static_cast(value); +} + } // namespace jsonnet::internal diff --git a/core/vm.h b/core/vm.h index 830972eb..471dcad5 100644 --- a/core/vm.h +++ b/core/vm.h @@ -129,6 +129,25 @@ std::vector jsonnet_vm_execute_stream( double gc_min_objects, double gc_growth_trigger, const VmNativeCallbackMap &natives, JsonnetImportCallback *import_callback, void *import_callback_ctx, bool string_output); +/** Safely converts a double to an int64_t, with range and validity checks. + * + * This function is used primarily for bitwise operations which require integer operands. + * It performs two safety checks: + * 1. Verifies the value is finite (not NaN or Infinity) + * 2. Ensures the value is within the safe integer range (±(2^53-1)) + * + * The safe integer range limitation is necessary because IEEE 754 double precision + * floating point numbers can only precisely represent integers up to 2^53-1. + * Beyond this range, precision is lost, which would lead to unpredictable results + * in bitwise operations that depend on exact bit patterns. + * + * \param value The double value to convert + * \param loc The location in source code (for error reporting) + * \throws StaticError if value is not finite or outside the safe integer range + * \returns The value converted to int64_t + */ +int64_t safeDoubleToInt64(double value, const LocationRange& loc); + } // namespace jsonnet::internal #endif