From fd73a9c38bc05d10aec8b5c33546fae6adf27f91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 00:56:51 +0000 Subject: [PATCH 01/11] Initial plan From da24a782e24c8b3b6bd3b5f2baa04a9922bb6a5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:06:59 +0000 Subject: [PATCH 02/11] Fix duration conversion rounding for os.utime Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 33 +++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 4f921e9c5de..eb1f5ad9d58 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -126,10 +126,7 @@ impl TryFromObject for std::time::Duration { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { if let Some(float) = obj.downcast_ref::() { let f = float.to_f64(); - if f < 0.0 { - return Err(vm.new_value_error("negative duration")); - } - Ok(Self::from_secs_f64(f)) + duration_from_f64_floor(f, vm) } else if let Some(int) = obj.try_index_opt(vm) { let int = int?; let bigint = int.as_bigint(); @@ -149,3 +146,31 @@ impl TryFromObject for std::time::Duration { } } } + +fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult { + const NANOS_PER_SEC: f64 = 1_000_000_000.0; + const NANOS_PER_SEC_U128: u128 = 1_000_000_000; + const MAX_TOTAL_NANOS: u128 = + ((u64::MAX as u128) * NANOS_PER_SEC_U128) + (NANOS_PER_SEC_U128 - 1); + + if !f.is_finite() { + return Err(vm.new_value_error("value out of range")); + } + if f < 0.0 { + return Err(vm.new_value_error("negative duration")); + } + + let total_nanos = (f * NANOS_PER_SEC).floor(); + if total_nanos.is_sign_negative() { + return Err(vm.new_value_error("negative duration")); + } + if total_nanos > MAX_TOTAL_NANOS as f64 { + return Err(vm.new_value_error("value out of range")); + } + + let total_nanos = total_nanos as u128; + let secs = (total_nanos / NANOS_PER_SEC_U128) as u64; + let nanos = (total_nanos % NANOS_PER_SEC_U128) as u32; + + Ok(std::time::Duration::new(secs, nanos)) +} From b911e6fc409e1c6b919ac7da6d850c2061645edf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:08:16 +0000 Subject: [PATCH 03/11] Address review feedback for duration rounding Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index eb1f5ad9d58..8631c0418f1 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -150,6 +150,7 @@ impl TryFromObject for std::time::Duration { fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult { const NANOS_PER_SEC: f64 = 1_000_000_000.0; const NANOS_PER_SEC_U128: u128 = 1_000_000_000; + // Maximum duration representable by (secs, nanos) with u64 seconds and <1e9 nanoseconds. const MAX_TOTAL_NANOS: u128 = ((u64::MAX as u128) * NANOS_PER_SEC_U128) + (NANOS_PER_SEC_U128 - 1); @@ -161,16 +162,13 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult MAX_TOTAL_NANOS as f64 { return Err(vm.new_value_error("value out of range")); } - let total_nanos = total_nanos as u128; - let secs = (total_nanos / NANOS_PER_SEC_U128) as u64; - let nanos = (total_nanos % NANOS_PER_SEC_U128) as u32; + let total_nanos_u128 = total_nanos as u128; + let secs = (total_nanos_u128 / NANOS_PER_SEC_U128) as u64; + let nanos = (total_nanos_u128 % NANOS_PER_SEC_U128) as u32; Ok(std::time::Duration::new(secs, nanos)) } From 7578fd9f6bd49aff245be96986e9fa23ae74a3bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:09:16 +0000 Subject: [PATCH 04/11] Refine duration conversion constants and checks Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 8631c0418f1..881d51501f5 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -148,8 +148,8 @@ impl TryFromObject for std::time::Duration { } fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult { - const NANOS_PER_SEC: f64 = 1_000_000_000.0; const NANOS_PER_SEC_U128: u128 = 1_000_000_000; + const NANOS_PER_SEC: f64 = NANOS_PER_SEC_U128 as f64; // Maximum duration representable by (secs, nanos) with u64 seconds and <1e9 nanoseconds. const MAX_TOTAL_NANOS: u128 = ((u64::MAX as u128) * NANOS_PER_SEC_U128) + (NANOS_PER_SEC_U128 - 1); @@ -162,6 +162,9 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult MAX_TOTAL_NANOS as f64 { return Err(vm.new_value_error("value out of range")); } From 31765b77b32d2dd9c3368601789d38408b8538f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:10:24 +0000 Subject: [PATCH 05/11] Clarify float nanosecond conversion cast Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 881d51501f5..f209b18309f 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,13 +162,11 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult MAX_TOTAL_NANOS as f64 { return Err(vm.new_value_error("value out of range")); } + // safe: total_nanos is finite, non-negative, and below MAX_TOTAL_NANOS let total_nanos_u128 = total_nanos as u128; let secs = (total_nanos_u128 / NANOS_PER_SEC_U128) as u64; let nanos = (total_nanos_u128 % NANOS_PER_SEC_U128) as u32; From 56acf0ac7cf0897ddc0e2f0b2bea6ee84af702a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:11:35 +0000 Subject: [PATCH 06/11] Use checked conversion for float nanoseconds Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index f209b18309f..4f6477709d2 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,12 +162,13 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult MAX_TOTAL_NANOS as f64 { + let total_nanos_u128 = total_nanos + .to_u128() + .ok_or_else(|| vm.new_value_error("value out of range"))?; + if total_nanos_u128 > MAX_TOTAL_NANOS { return Err(vm.new_value_error("value out of range")); } - // safe: total_nanos is finite, non-negative, and below MAX_TOTAL_NANOS - let total_nanos_u128 = total_nanos as u128; let secs = (total_nanos_u128 / NANOS_PER_SEC_U128) as u64; let nanos = (total_nanos_u128 % NANOS_PER_SEC_U128) as u32; From 514b7164b00e5a15d2ffa7783df367ece8364b16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:12:58 +0000 Subject: [PATCH 07/11] Add explicit float bounds before nanosecond cast Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 4f6477709d2..8a043e6defe 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,9 +162,11 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult u128::MAX as f64 { + return Err(vm.new_value_error("value out of range")); + } + + let total_nanos_u128 = total_nanos as u128; if total_nanos_u128 > MAX_TOTAL_NANOS { return Err(vm.new_value_error("value out of range")); } From 4c3e73ff062f99029dd6b34048ac1b495ac0d33b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:13:57 +0000 Subject: [PATCH 08/11] Streamline duration bounds validation Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 8a043e6defe..65aad1f849c 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,10 +162,6 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult u128::MAX as f64 { - return Err(vm.new_value_error("value out of range")); - } - let total_nanos_u128 = total_nanos as u128; if total_nanos_u128 > MAX_TOTAL_NANOS { return Err(vm.new_value_error("value out of range")); From 73aa6938fb9d9aa1d098ca709ce01dad6ecee944 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:15:14 +0000 Subject: [PATCH 09/11] Convert float nanoseconds with checked filtering Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 65aad1f849c..ad732bd0d9a 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,10 +162,10 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult MAX_TOTAL_NANOS { - return Err(vm.new_value_error("value out of range")); - } + let total_nanos_u128 = total_nanos + .to_u128() + .filter(|value| *value <= MAX_TOTAL_NANOS) + .ok_or_else(|| vm.new_value_error("value out of range"))?; let secs = (total_nanos_u128 / NANOS_PER_SEC_U128) as u64; let nanos = (total_nanos_u128 % NANOS_PER_SEC_U128) as u32; From efea81d7b2932f16a4da4fb68736e272555600de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:16:18 +0000 Subject: [PATCH 10/11] Use explicit bounds check before casting nanoseconds Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index ad732bd0d9a..b033c374798 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,10 +162,11 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult Date: Thu, 25 Dec 2025 01:17:41 +0000 Subject: [PATCH 11/11] Check nanosecond sign before duration bounds Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/convert/try_from.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index b033c374798..e950ec3e19e 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -162,11 +162,13 @@ fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult MAX_TOTAL_NANOS { + return Err(vm.new_value_error("value out of range")); + } let secs = (total_nanos_u128 / NANOS_PER_SEC_U128) as u64; let nanos = (total_nanos_u128 % NANOS_PER_SEC_U128) as u32;