Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add safe double-to-int64 conversion for bitwise operations #1217

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions core/vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2559,36 +2559,38 @@ 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;

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;

Expand Down Expand Up @@ -3406,4 +3408,21 @@ std::vector<std::string> 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<int64_t>(value);
}

} // namespace jsonnet::internal
19 changes: 19 additions & 0 deletions core/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,25 @@ std::vector<std::string> 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