Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 12 additions & 0 deletions crates/monty-js/__test__/limits.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ len(result)
t.true(error.message.includes('MemoryError'))
})

test('memory limit accepts values below u32 max', (t) => {
const m = new Monty('1 + 1')
const limits: ResourceLimits = { maxMemory: 2 ** 31 }
t.is(m.run({ limits }), 2)
})

test('memory limit accepts values above u32 max', (t) => {
const m = new Monty('1 + 1')
const limits: ResourceLimits = { maxMemory: 2 ** 33 }
t.is(m.run({ limits }), 2)
})

// =============================================================================
// Limits with inputs tests
// =============================================================================
Expand Down
29 changes: 26 additions & 3 deletions crates/monty-js/src/limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pub struct JsResourceLimits {
pub max_allocations: Option<u32>,
/// Maximum execution time in seconds.
pub max_duration_secs: Option<f64>,
/// Maximum heap memory in bytes.
pub max_memory: Option<u32>,
/// Maximum heap memory in bytes. Stored as `f64` to match JS `number`.
pub max_memory: Option<f64>,
/// Run garbage collection every N allocations.
pub gc_interval: Option<u32>,
/// Maximum function call stack depth (default: 1000).
Expand All @@ -42,7 +42,7 @@ impl From<JsResourceLimits> for ResourceLimits {
limits = limits.max_duration(Duration::from_secs_f64(secs));
}
if let Some(max) = js_limits.max_memory {
limits = limits.max_memory(max as usize);
limits = limits.max_memory(js_number_to_usize(max, "maxMemory"));
Comment thread
davidhewitt marked this conversation as resolved.
Outdated
}
if let Some(interval) = js_limits.gc_interval {
limits = limits.gc_interval(interval as usize);
Expand All @@ -51,3 +51,26 @@ impl From<JsResourceLimits> for ResourceLimits {
limits
}
}

/// Converts a JavaScript `number` used for a size/count limit into `usize`.
///
/// JavaScript numbers are IEEE-754 doubles, so integers above `2^53 - 1`
/// cannot be represented exactly. Rejecting values outside the safe integer
/// range avoids silently rounding resource limits at the napi boundary.
fn js_number_to_usize(value: f64, name: &str) -> usize {
const JS_MAX_SAFE_INTEGER: u64 = (1_u64 << 53) - 1;

match value {
v if !v.is_finite() => panic!("{name} must be a finite number"),
v if v < 0.0 => panic!("{name} must be non-negative"),
v if v.fract() != 0.0 => panic!("{name} must be an integer"),
v if v > JS_MAX_SAFE_INTEGER as f64 => {
panic!("{name} must be a safe integer (<= {JS_MAX_SAFE_INTEGER})")
}
v => {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let value = v as u64;
usize::try_from(value).unwrap_or_else(|_| panic!("{name} must fit in Rust usize on this platform"))
}
}
}
Comment thread
davidhewitt marked this conversation as resolved.
Loading