From 3912a4dc4e18f74c213a9559efa744a54b35cb94 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:42:42 +0000 Subject: [PATCH 1/4] Refactor: Utilize standard helpers in random module This patch updates `src/stdlib/random.rs` to use the standard helper functions (`check_arg_count`, `expect_number`, `expect_list`) from `src/stdlib/helpers.rs`, significantly reducing duplicated validation logic across all random number generation functions. Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- src/stdlib/random.rs | 145 ++++++++----------------------------------- 1 file changed, 26 insertions(+), 119 deletions(-) diff --git a/src/stdlib/random.rs b/src/stdlib/random.rs index 9a017162..e0b57b1c 100644 --- a/src/stdlib/random.rs +++ b/src/stdlib/random.rs @@ -1,3 +1,4 @@ +use super::helpers::{check_arg_count, expect_list, expect_number}; use crate::interpreter::environment::Environment; use crate::interpreter::error::RuntimeError; use crate::interpreter::value::Value; @@ -18,13 +19,7 @@ thread_local! { /// Generate a cryptographically secure random number between 0 and 1 pub fn native_random(args: Vec) -> Result { - if !args.is_empty() { - return Err(RuntimeError::new( - format!("random expects 0 arguments, got {}", args.len()), - 0, - 0, - )); - } + check_arg_count("random", &args, 0)?; RNG.with(|rng| { let mut rng = rng.borrow_mut(); @@ -35,41 +30,10 @@ pub fn native_random(args: Vec) -> Result { /// Generate a random number between min and max (inclusive) pub fn native_random_between(args: Vec) -> Result { - if args.len() != 2 { - return Err(RuntimeError::new( - format!("random_between expects 2 arguments, got {}", args.len()), - 0, - 0, - )); - } + check_arg_count("random_between", &args, 2)?; - let min = match &args[0] { - Value::Number(n) => *n, - _ => { - return Err(RuntimeError::new( - format!( - "random_between expects numbers, got {}", - args[0].type_name() - ), - 0, - 0, - )); - } - }; - - let max = match &args[1] { - Value::Number(n) => *n, - _ => { - return Err(RuntimeError::new( - format!( - "random_between expects numbers, got {}", - args[1].type_name() - ), - 0, - 0, - )); - } - }; + let min = expect_number(&args[0])?; + let max = expect_number(&args[1])?; if min > max { return Err(RuntimeError::new( @@ -91,35 +55,10 @@ pub fn native_random_between(args: Vec) -> Result { /// Generate a random integer between min and max (inclusive) pub fn native_random_int(args: Vec) -> Result { - if args.len() != 2 { - return Err(RuntimeError::new( - format!("random_int expects 2 arguments, got {}", args.len()), - 0, - 0, - )); - } + check_arg_count("random_int", &args, 2)?; - let min = match &args[0] { - Value::Number(n) => *n as i64, - _ => { - return Err(RuntimeError::new( - format!("random_int expects numbers, got {}", args[0].type_name()), - 0, - 0, - )); - } - }; - - let max = match &args[1] { - Value::Number(n) => *n as i64, - _ => { - return Err(RuntimeError::new( - format!("random_int expects numbers, got {}", args[1].type_name()), - 0, - 0, - )); - } - }; + let min = expect_number(&args[0])? as i64; + let max = expect_number(&args[1])? as i64; if min > max { return Err(RuntimeError::new( @@ -141,13 +80,7 @@ pub fn native_random_int(args: Vec) -> Result { /// Generate a random boolean value pub fn native_random_boolean(args: Vec) -> Result { - if !args.is_empty() { - return Err(RuntimeError::new( - format!("random_boolean expects 0 arguments, got {}", args.len()), - 0, - 0, - )); - } + check_arg_count("random_boolean", &args, 0)?; RNG.with(|rng| { let mut rng = rng.borrow_mut(); @@ -158,59 +91,31 @@ pub fn native_random_boolean(args: Vec) -> Result { /// Select a random element from a list pub fn native_random_from(args: Vec) -> Result { - if args.len() != 1 { + check_arg_count("random_from", &args, 1)?; + + let list_ref = expect_list(&args[0])?; + let list = list_ref.borrow(); + + if list.is_empty() { return Err(RuntimeError::new( - format!("random_from expects 1 argument, got {}", args.len()), + "random_from: cannot select from empty list".to_string(), 0, 0, )); } - match &args[0] { - Value::List(list_ref) => { - let list = list_ref.borrow(); - if list.is_empty() { - return Err(RuntimeError::new( - "random_from: cannot select from empty list".to_string(), - 0, - 0, - )); - } - - RNG.with(|rng| { - let mut rng = rng.borrow_mut(); - let index = rng.random_range(0..list.len()); - Ok(list[index].clone()) - }) - } - _ => Err(RuntimeError::new( - format!("random_from expects a list, got {}", args[0].type_name()), - 0, - 0, - )), - } + RNG.with(|rng| { + let mut rng = rng.borrow_mut(); + let index = rng.random_range(0..list.len()); + Ok(list[index].clone()) + }) } /// Set the random seed for reproducible results pub fn native_random_seed(args: Vec) -> Result { - if args.len() != 1 { - return Err(RuntimeError::new( - format!("random_seed expects 1 argument, got {}", args.len()), - 0, - 0, - )); - } + check_arg_count("random_seed", &args, 1)?; - let seed = match &args[0] { - Value::Number(n) => *n as u64, - _ => { - return Err(RuntimeError::new( - format!("random_seed expects a number, got {}", args[0].type_name()), - 0, - 0, - )); - } - }; + let seed = expect_number(&args[0])? as u64; RNG.with(|rng| { // Replace the RNG with a seeded one @@ -221,7 +126,9 @@ pub fn native_random_seed(args: Vec) -> Result { /// Generate a UUID v4 (random UUID) /// Usage: generate_uuid() -> "550e8400-e29b-41d4-a716-446655440000" -pub fn native_generate_uuid(_args: Vec) -> Result { +pub fn native_generate_uuid(args: Vec) -> Result { + check_arg_count("generate_uuid", &args, 0)?; + use uuid::Uuid; let uuid = Uuid::new_v4(); From 5bfe6681bc3d77114f380a1befe1e9b5e8a24b61 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:55:32 +0000 Subject: [PATCH 2/4] test: add tests for native_generate_uuid argument rejection and output validation Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- src/stdlib/random.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/stdlib/random.rs b/src/stdlib/random.rs index e0b57b1c..4a092ad1 100644 --- a/src/stdlib/random.rs +++ b/src/stdlib/random.rs @@ -180,6 +180,27 @@ mod tests { } } + #[test] + fn test_generate_uuid_validates_args() { + let result = native_generate_uuid(vec![Value::Number(1.0)]); + assert!(result.is_err()); + assert!(result.unwrap_err().message.contains("generate_uuid expects 0 arguments")); + } + + #[test] + fn test_generate_uuid_format() { + let result = native_generate_uuid(vec![]); + assert!(result.is_ok()); + + if let Ok(Value::Text(uuid_str)) = result { + // Very basic UUID format check: 36 chars, 4 hyphens + assert_eq!(uuid_str.len(), 36); + assert_eq!(uuid_str.chars().filter(|&c| c == '-').count(), 4); + } else { + panic!("Expected text from generate_uuid"); + } + } + #[test] fn test_random_between_validates_range() { let result = native_random_between(vec![Value::Number(5.0), Value::Number(10.0)]); From 44d868c095723bca871017e1085e0d1e81fd2bdc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:54:02 +0000 Subject: [PATCH 3/4] fix: address formatting failure in random.rs Ran `cargo fmt` to resolve the CI check formatting failure in `src/stdlib/random.rs` after the previous commit. Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- src/stdlib/random.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/stdlib/random.rs b/src/stdlib/random.rs index 4a092ad1..99dfbc42 100644 --- a/src/stdlib/random.rs +++ b/src/stdlib/random.rs @@ -184,7 +184,12 @@ mod tests { fn test_generate_uuid_validates_args() { let result = native_generate_uuid(vec![Value::Number(1.0)]); assert!(result.is_err()); - assert!(result.unwrap_err().message.contains("generate_uuid expects 0 arguments")); + assert!( + result + .unwrap_err() + .message + .contains("generate_uuid expects 0 arguments") + ); } #[test] From 3ace111635b0e612b1eb42fc248af0881f941c6b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:55:37 +0000 Subject: [PATCH 4/4] fix: validate bounds for random_between and avoid panics Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- src/stdlib/random.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/stdlib/random.rs b/src/stdlib/random.rs index 99dfbc42..95c0ed66 100644 --- a/src/stdlib/random.rs +++ b/src/stdlib/random.rs @@ -35,6 +35,17 @@ pub fn native_random_between(args: Vec) -> Result { let min = expect_number(&args[0])?; let max = expect_number(&args[1])?; + if !min.is_finite() || !max.is_finite() { + return Err(RuntimeError::new( + format!( + "random_between: bounds must be finite numbers, got min: {}, max: {}", + min, max + ), + 0, + 0, + )); + } + if min > max { return Err(RuntimeError::new( format!( @@ -218,6 +229,20 @@ mod tests { } } + #[test] + fn test_random_between_rejects_non_finite_bounds() { + let nan = f64::NAN; + let inf = f64::INFINITY; + + let result_nan = native_random_between(vec![Value::Number(nan), Value::Number(10.0)]); + assert!(result_nan.is_err()); + assert!(result_nan.unwrap_err().message.contains("finite numbers")); + + let result_inf = native_random_between(vec![Value::Number(0.0), Value::Number(inf)]); + assert!(result_inf.is_err()); + assert!(result_inf.unwrap_err().message.contains("finite numbers")); + } + #[test] fn test_random_int_produces_integers() { let result = native_random_int(vec![Value::Number(1.0), Value::Number(10.0)]);