use crate::runtime::repl::{Repl, Value};
use crate::runtime::transaction::{SavePoint, TransactionMetadata};
use anyhow::{anyhow, Result};
use std::time::{Duration, Instant};
impl Repl {
pub fn eval_bounded(
&mut self,
input: &str,
memory_limit: Option<usize>,
time_limit: Option<Duration>,
) -> Result<String> {
let metadata = TransactionMetadata {
description: format!("eval: {}", input.chars().take(50).collect::<String>()),
memory_limit,
time_limit,
speculative: false,
};
let tx_id = self.tx_state.begin_transaction(metadata)?;
let start = Instant::now();
let deadline = time_limit.map(|d| start + d);
let result = self.eval_with_transaction(input, tx_id, deadline);
match result {
Ok(output) => {
self.tx_state.commit_transaction(tx_id)?;
Ok(output)
}
Err(e) => {
self.tx_state.rollback_transaction(tx_id)?;
Err(e)
}
}
}
pub fn try_eval(&mut self, input: &str) -> Result<String> {
let savepoint = self.tx_state.savepoint()?;
match self.eval(input) {
Ok(result) => {
savepoint.commit()?;
Ok(result)
}
Err(e) => {
drop(savepoint);
Err(e)
}
}
}
pub fn eval_atomic(&mut self, inputs: &[&str]) -> Result<Vec<String>> {
let savepoint = self.tx_state.savepoint()?;
let mut results = Vec::new();
for input in inputs {
match self.eval(input) {
Ok(result) => results.push(result),
Err(e) => {
drop(savepoint);
return Err(e);
}
}
}
savepoint.commit()?;
Ok(results)
}
fn eval_with_transaction(
&mut self,
input: &str,
tx_id: crate::runtime::transaction::TransactionId,
deadline: Option<Instant>,
) -> Result<String> {
if let Some(deadline) = deadline {
if Instant::now() >= deadline {
return Err(anyhow!("Evaluation timeout"));
}
}
self.tx_state.check_transaction_limits(tx_id)?;
self.eval(input)
}
pub fn memory_usage(&self) -> usize {
self.tx_state.memory_used()
}
pub fn checkpoint(&mut self) -> Result<CheckpointHandle> {
let savepoint = self.tx_state.savepoint()?;
Ok(CheckpointHandle {
savepoint: Some(savepoint),
})
}
}
pub struct CheckpointHandle {
savepoint: Option<SavePoint>,
}
impl CheckpointHandle {
pub fn commit(mut self) -> Result<()> {
if let Some(sp) = self.savepoint.take() {
sp.commit()?;
}
Ok(())
}
pub fn rollback(mut self) -> Result<()> {
if let Some(sp) = self.savepoint.take() {
sp.rollback()?;
}
Ok(())
}
}
impl Drop for CheckpointHandle {
fn drop(&mut self) {
if let Some(sp) = self.savepoint.take() {
let _ = sp.rollback();
}
}
}
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub max_memory: usize,
pub max_time: Duration,
pub max_depth: usize,
pub max_allocations: usize,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory: 100 * 1024 * 1024, max_time: Duration::from_secs(5),
max_depth: 1000,
max_allocations: 1_000_000,
}
}
}
impl ResourceLimits {
pub fn untrusted() -> Self {
Self {
max_memory: 10 * 1024 * 1024, max_time: Duration::from_secs(1),
max_depth: 100,
max_allocations: 10_000,
}
}
pub fn testing() -> Self {
Self {
max_memory: 1024 * 1024, max_time: Duration::from_millis(100),
max_depth: 50,
max_allocations: 1_000,
}
}
}
pub struct Sandbox {
repl: Repl,
limits: ResourceLimits,
}
impl Sandbox {
pub fn new(limits: ResourceLimits) -> Result<Self> {
let mut config = crate::runtime::repl::ReplConfig::default();
config.max_memory = limits.max_memory;
config.max_depth = limits.max_depth;
Ok(Self {
repl: Repl::with_config(config)?,
limits,
})
}
pub fn eval(&mut self, input: &str) -> Result<String> {
self.repl.eval_bounded(
input,
Some(self.limits.max_memory),
Some(self.limits.max_time),
)
}
pub fn reset(&mut self) -> Result<()> {
self.repl.tx_state.clear();
Ok(())
}
}
#[cfg(test)]
#[ignore = "Resource evaluation tests not fully implemented"]
mod tests {
use super::*;
#[cfg(test)]
#[ignore = "Property tests not fully implemented"]
use proptest::prelude::*;
#[test]
fn test_bounded_evaluation() {
let mut repl = Repl::new().expect("operation should succeed in test");
let result = repl.eval_bounded("1 + 1", Some(1024), Some(Duration::from_secs(1)));
assert!(result.is_ok());
assert_eq!(result.expect("operation should succeed in test"), "2");
}
#[test]
fn test_atomic_evaluation() {
let mut repl = Repl::new().expect("operation should succeed in test");
let results = repl
.eval_atomic(&["let x = 1", "let y = 2", "x + y"])
.expect("operation should succeed in test");
assert_eq!(results.len(), 3);
assert_eq!(results[2], "3");
let result = repl.eval_atomic(&["let z = 10", "invalid syntax here", "z + 1"]);
assert!(result.is_err());
assert!(repl.eval("z").is_err());
}
#[test]
fn test_checkpoint() {
let mut repl = Repl::new().expect("operation should succeed in test");
repl.eval("let x = 1").expect("operation should succeed in test");
let checkpoint = repl.checkpoint().expect("operation should succeed in test");
repl.eval("let x = 2").expect("operation should succeed in test");
repl.eval("let y = 3").expect("operation should succeed in test");
checkpoint.rollback().expect("operation should succeed in test");
assert_eq!(repl.eval("x").expect("operation should succeed in test"), "1");
assert!(repl.eval("y").is_err());
}
#[test]
fn test_sandbox() {
let limits = ResourceLimits::testing();
let mut sandbox = Sandbox::new(limits).expect("operation should succeed in test");
let result = sandbox.eval("2 * 3").expect("operation should succeed in test");
assert_eq!(result, "6");
sandbox.eval("let a = 42").expect("operation should succeed in test");
sandbox.reset().expect("operation should succeed in test");
assert!(sandbox.eval("a").is_err());
}
}
#[cfg(test)]
#[ignore = "Property tests for resource evaluation not implemented"]
mod property_tests_resource_eval {
use super::*;
use proptest::prelude::*;
use proptest::proptest;
proptest! {
#[test]
fn test_eval_bounded_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let _ = std::panic::catch_unwind(|| {
});
}
}
}
#[cfg(test)]
mod resource_limits_tests {
use super::*;
#[test]
fn test_resource_limits_default_r162() {
let limits = ResourceLimits::default();
assert_eq!(limits.max_memory, 100 * 1024 * 1024);
assert_eq!(limits.max_time, Duration::from_secs(5));
assert_eq!(limits.max_depth, 1000);
assert_eq!(limits.max_allocations, 1_000_000);
}
#[test]
fn test_resource_limits_untrusted_r162() {
let limits = ResourceLimits::untrusted();
assert_eq!(limits.max_memory, 10 * 1024 * 1024);
assert_eq!(limits.max_time, Duration::from_secs(1));
assert_eq!(limits.max_depth, 100);
assert_eq!(limits.max_allocations, 10_000);
}
#[test]
fn test_resource_limits_testing_r162() {
let limits = ResourceLimits::testing();
assert_eq!(limits.max_memory, 1024 * 1024);
assert_eq!(limits.max_time, Duration::from_millis(100));
assert_eq!(limits.max_depth, 50);
assert_eq!(limits.max_allocations, 1_000);
}
#[test]
fn test_resource_limits_clone_r162() {
let limits = ResourceLimits::default();
let cloned = limits.clone();
assert_eq!(limits.max_memory, cloned.max_memory);
assert_eq!(limits.max_time, cloned.max_time);
assert_eq!(limits.max_depth, cloned.max_depth);
assert_eq!(limits.max_allocations, cloned.max_allocations);
}
#[test]
fn test_resource_limits_debug_r162() {
let limits = ResourceLimits::default();
let debug_str = format!("{:?}", limits);
assert!(debug_str.contains("ResourceLimits"));
assert!(debug_str.contains("max_memory"));
assert!(debug_str.contains("max_time"));
assert!(debug_str.contains("max_depth"));
assert!(debug_str.contains("max_allocations"));
}
#[test]
fn test_resource_limits_untrusted_more_restrictive_than_default_r162() {
let default = ResourceLimits::default();
let untrusted = ResourceLimits::untrusted();
assert!(untrusted.max_memory < default.max_memory);
assert!(untrusted.max_time < default.max_time);
assert!(untrusted.max_depth < default.max_depth);
assert!(untrusted.max_allocations < default.max_allocations);
}
#[test]
fn test_resource_limits_testing_most_restrictive_r162() {
let untrusted = ResourceLimits::untrusted();
let testing = ResourceLimits::testing();
assert!(testing.max_memory < untrusted.max_memory);
assert!(testing.max_time < untrusted.max_time);
assert!(testing.max_depth < untrusted.max_depth);
assert!(testing.max_allocations < untrusted.max_allocations);
}
#[test]
fn test_resource_limits_custom_values_r162() {
let limits = ResourceLimits {
max_memory: 512,
max_time: Duration::from_millis(50),
max_depth: 10,
max_allocations: 100,
};
assert_eq!(limits.max_memory, 512);
assert_eq!(limits.max_time, Duration::from_millis(50));
assert_eq!(limits.max_depth, 10);
assert_eq!(limits.max_allocations, 100);
}
#[test]
fn test_resource_limits_zero_values_r162() {
let limits = ResourceLimits {
max_memory: 0,
max_time: Duration::ZERO,
max_depth: 0,
max_allocations: 0,
};
assert_eq!(limits.max_memory, 0);
assert_eq!(limits.max_time, Duration::ZERO);
assert_eq!(limits.max_depth, 0);
assert_eq!(limits.max_allocations, 0);
}
#[test]
fn test_resource_limits_max_values_r162() {
let limits = ResourceLimits {
max_memory: usize::MAX,
max_time: Duration::MAX,
max_depth: usize::MAX,
max_allocations: usize::MAX,
};
assert_eq!(limits.max_memory, usize::MAX);
assert_eq!(limits.max_time, Duration::MAX);
assert_eq!(limits.max_depth, usize::MAX);
assert_eq!(limits.max_allocations, usize::MAX);
}
#[test]
fn test_resource_limits_memory_1mb_r162() {
let limits = ResourceLimits {
max_memory: 1024 * 1024,
..ResourceLimits::default()
};
assert_eq!(limits.max_memory, 1024 * 1024);
assert_eq!(limits.max_time, Duration::from_secs(5));
}
#[test]
fn test_resource_limits_time_1s_r162() {
let limits = ResourceLimits {
max_time: Duration::from_secs(1),
..ResourceLimits::default()
};
assert_eq!(limits.max_time, Duration::from_secs(1));
assert_eq!(limits.max_memory, 100 * 1024 * 1024);
}
#[test]
fn test_resource_limits_depth_50_r162() {
let limits = ResourceLimits {
max_depth: 50,
..ResourceLimits::default()
};
assert_eq!(limits.max_depth, 50);
assert_eq!(limits.max_allocations, 1_000_000);
}
#[test]
fn test_resource_limits_allocations_1000_r162() {
let limits = ResourceLimits {
max_allocations: 1000,
..ResourceLimits::default()
};
assert_eq!(limits.max_allocations, 1000);
assert_eq!(limits.max_depth, 1000);
}
#[test]
fn test_resource_limits_time_subsecond_r162() {
let limits = ResourceLimits {
max_time: Duration::from_millis(250),
..ResourceLimits::default()
};
assert_eq!(limits.max_time.as_millis(), 250);
}
#[test]
fn test_resource_limits_time_microseconds_r162() {
let limits = ResourceLimits {
max_time: Duration::from_micros(500),
..ResourceLimits::default()
};
assert_eq!(limits.max_time.as_micros(), 500);
}
#[test]
fn test_resource_limits_time_nanoseconds_r162() {
let limits = ResourceLimits {
max_time: Duration::from_nanos(1000),
..ResourceLimits::default()
};
assert_eq!(limits.max_time.as_nanos(), 1000);
}
#[test]
fn test_resource_limits_untrusted_is_10mb_r162() {
let limits = ResourceLimits::untrusted();
assert_eq!(limits.max_memory, 10 * 1024 * 1024);
assert_eq!(limits.max_memory, 10_485_760);
}
#[test]
fn test_resource_limits_testing_is_1mb_r162() {
let limits = ResourceLimits::testing();
assert_eq!(limits.max_memory, 1024 * 1024);
assert_eq!(limits.max_memory, 1_048_576);
}
#[test]
fn test_resource_limits_default_is_100mb_r162() {
let limits = ResourceLimits::default();
assert_eq!(limits.max_memory, 100 * 1024 * 1024);
assert_eq!(limits.max_memory, 104_857_600);
}
#[test]
fn test_resource_limits_multiple_clones_r162() {
let original = ResourceLimits::testing();
let clone1 = original.clone();
let clone2 = clone1.clone();
let clone3 = clone2.clone();
assert_eq!(original.max_memory, clone3.max_memory);
assert_eq!(original.max_time, clone3.max_time);
assert_eq!(original.max_depth, clone3.max_depth);
assert_eq!(original.max_allocations, clone3.max_allocations);
}
#[test]
fn test_resource_limits_struct_size_r162() {
let size = std::mem::size_of::<ResourceLimits>();
assert!(size <= 64, "ResourceLimits size is {size} bytes");
}
#[test]
fn test_resource_limits_time_comparison_r163() {
let default = ResourceLimits::default();
let untrusted = ResourceLimits::untrusted();
let testing = ResourceLimits::testing();
assert!(default.max_time > untrusted.max_time);
assert!(untrusted.max_time > testing.max_time);
}
#[test]
fn test_resource_limits_memory_comparison_r163() {
let default = ResourceLimits::default();
let untrusted = ResourceLimits::untrusted();
let testing = ResourceLimits::testing();
assert!(default.max_memory > untrusted.max_memory);
assert!(untrusted.max_memory > testing.max_memory);
}
#[test]
fn test_resource_limits_depth_comparison_r163() {
let default = ResourceLimits::default();
let untrusted = ResourceLimits::untrusted();
let testing = ResourceLimits::testing();
assert!(default.max_depth > untrusted.max_depth);
assert!(untrusted.max_depth > testing.max_depth);
}
#[test]
fn test_resource_limits_allocations_comparison_r163() {
let default = ResourceLimits::default();
let untrusted = ResourceLimits::untrusted();
let testing = ResourceLimits::testing();
assert!(default.max_allocations > untrusted.max_allocations);
assert!(untrusted.max_allocations > testing.max_allocations);
}
#[test]
fn test_resource_limits_nonzero_default_values_r163() {
let limits = ResourceLimits::default();
assert!(limits.max_memory > 0);
assert!(limits.max_time > Duration::ZERO);
assert!(limits.max_depth > 0);
assert!(limits.max_allocations > 0);
}
#[test]
fn test_resource_limits_nonzero_untrusted_values_r163() {
let limits = ResourceLimits::untrusted();
assert!(limits.max_memory > 0);
assert!(limits.max_time > Duration::ZERO);
assert!(limits.max_depth > 0);
assert!(limits.max_allocations > 0);
}
#[test]
fn test_resource_limits_nonzero_testing_values_r163() {
let limits = ResourceLimits::testing();
assert!(limits.max_memory > 0);
assert!(limits.max_time > Duration::ZERO);
assert!(limits.max_depth > 0);
assert!(limits.max_allocations > 0);
}
#[test]
fn test_resource_limits_time_in_seconds_r163() {
let limits = ResourceLimits::default();
assert_eq!(limits.max_time.as_secs(), 5);
let untrusted = ResourceLimits::untrusted();
assert_eq!(untrusted.max_time.as_secs(), 1);
}
#[test]
fn test_resource_limits_time_in_millis_r163() {
let limits = ResourceLimits::testing();
assert_eq!(limits.max_time.as_millis(), 100);
}
#[test]
fn test_resource_limits_partial_update_r163() {
let mut limits = ResourceLimits::default();
limits.max_memory = 50 * 1024 * 1024; limits.max_time = Duration::from_secs(10);
assert_eq!(limits.max_memory, 50 * 1024 * 1024);
assert_eq!(limits.max_time, Duration::from_secs(10));
assert_eq!(limits.max_depth, 1000);
assert_eq!(limits.max_allocations, 1_000_000);
}
}