Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
[5895] added code to raise error in sleep if invalid value
  • Loading branch information
DominicPrince2003 authored Jul 6, 2025
commit 435159e7ced029447ccdaecb0ea635cab0d8e429
10 changes: 10 additions & 0 deletions Lib/test/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,17 @@ def test_conversions(self):
def test_sleep(self):
self.assertRaises(ValueError, time.sleep, -2)
self.assertRaises(ValueError, time.sleep, -1)
self.assertRaises(ValueError, time.sleep, float('nan'))
self.assertRaises(ValueError, time.sleep, float('inf'))
self.assertRaises(ValueError, time.sleep, -float('inf'))
self.assertRaises(ValueError, time.sleep, 1e100)
time.sleep(0)
time.sleep(0.000001)
time.sleep(1e-9)
time.sleep(0.5)
time.sleep(1.2)
time.sleep(2)
Comment on lines +158 to +167
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Lib/test/test_time.py is originated from CPython, we usually don't directly edit this file.

We usually add our original test under extra_tests like extra_tests/snippets/stdlib_time.py.

If you also want to contribute to CPython, you can submit a similar patch to CPyhton project: https://github.com/python/cpython



# TODO: RUSTPYTHON
@unittest.expectedFailure
Expand Down
35 changes: 30 additions & 5 deletions vm/src/stdlib/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,22 @@ mod decl {

#[cfg(not(unix))]
#[pyfunction]
fn sleep(dur: Duration) {
std::thread::sleep(dur);
fn sleep(secs: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Try to get as float first, if that fails try as integer
let secs = if let Ok(float_val) = f64::try_from_object(vm, secs.clone()) {
float_val
} else if let Ok(int_val) = i64::try_from_object(vm, secs) {
int_val as f64
} else {
return Err(vm.new_type_error("sleep() argument must be a number"));
};
if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
Comment thread
DominicPrince2003 marked this conversation as resolved.
Outdated
Comment on lines +94 to +104
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This validation logic is duplicated in the unix implementation of sleep as well (lines 710-720). To improve maintainability and avoid code duplication, consider extracting this logic into a shared helper function. This function could be defined at the module level and be called by both sleep implementations.

For example:

fn get_sleep_secs(secs_obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<f64> {
    // Try to get as float first, if that fails try as integer
    let secs = if let Ok(float_val) = f64::try_from_object(vm, secs_obj.clone()) {
        float_val
    } else if let Ok(int_val) = i64::try_from_object(vm, secs_obj) {
        int_val as f64
    } else {
        return Err(vm.new_type_error("sleep() argument must be a number"));
    };

    if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
    }
    Ok(secs)
}


let duration = Duration::from_secs_f64(secs);
std::thread::sleep(duration);
Ok(())
}
Comment on lines +93 to 109
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential precision issue with u64::MAX conversion.

The validation logic has a potential issue with floating-point precision. When u64::MAX is converted to f64, it loses precision due to floating-point representation limits, which could allow some values larger than u64::MAX to pass validation.

Consider this alternative approach that handles the precision issue:

-    if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
-        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
-    }
+    if !secs.is_finite() {
+        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
+    }
+    if secs < 0.0 {
+        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
+    }
+    // Check for values that would overflow when converted to Duration
+    if secs > Duration::MAX.as_secs_f64() {
+        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
+    }

This approach uses Duration::MAX.as_secs_f64() which provides a more accurate upper bound for the sleep duration.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn sleep(secs: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Try to get as float first, if that fails try as integer
let secs = if let Ok(float_val) = f64::try_from_object(vm, secs.clone()) {
float_val
} else if let Ok(int_val) = i64::try_from_object(vm, secs) {
int_val as f64
} else {
return Err(vm.new_type_error("sleep() argument must be a number"));
};
if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
let duration = Duration::from_secs_f64(secs);
std::thread::sleep(duration);
Ok(())
}
fn sleep(secs: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Try to get as float first, if that fails try as integer
let secs = if let Ok(float_val) = f64::try_from_object(vm, secs.clone()) {
float_val
} else if let Ok(int_val) = i64::try_from_object(vm, secs) {
int_val as f64
} else {
return Err(vm.new_type_error("sleep() argument must be a number"));
};
// Validate the float before converting to Duration
if !secs.is_finite() {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
if secs < 0.0 {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
// Check for values that would overflow when converted to Duration
if secs > Duration::MAX.as_secs_f64() {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
let duration = Duration::from_secs_f64(secs);
std::thread::sleep(duration);
Ok(())
}
🤖 Prompt for AI Agents
In vm/src/stdlib/time.rs around lines 93 to 109, the current validation compares
the sleep duration against u64::MAX converted to f64, which can cause precision
loss and allow invalid values. To fix this, replace the upper bound check with
Duration::MAX.as_secs_f64() to accurately represent the maximum allowable
duration. Update the condition to ensure secs is less than or equal to this
value, preventing precision-related validation errors.

💡 Verification agent

🧩 Analysis chain

Verify error message consistency with CPython.

Ensure that the error messages match CPython's behavior for better compatibility. The current generic message might not align with Python's standard library.

Also applies to: 708-721


🏁 Script executed:

#!/bin/bash
# Check CPython's error messages for time.sleep() with invalid arguments
python3 -c "
import time
try:
    time.sleep(-1)
except ValueError as e:
    print('Negative value error:', e)

try:
    time.sleep('invalid')
except TypeError as e:
    print('Type error:', e)

try:
    time.sleep(float('inf'))
except ValueError as e:
    print('Infinity error:', e)

try:
    time.sleep(float('nan'))
except ValueError as e:
    print('NaN error:', e)
"

Length of output: 669


🏁 Script executed:

#!/bin/bash
# Enhanced check of CPython’s error messages for time.sleep() with various invalid arguments
python3 - << 'PYCODE'
import time

tests = [
    (-1,       "negative"),
    ("invalid","string"),
    (float('inf'), "infinite"),
    (float('nan'), "nan")
]

for val, label in tests:
    try:
        time.sleep(val)
    except Exception as e:
        print(f"{label} -> {type(e).__name__}: {e}")
PYCODE

Length of output: 264


Match CPython’s time.sleep error messages

The generic checks in sleep() should be split to mirror CPython’s behavior exactly:

  • On non-number inputs, raise TypeError like
    ‘<type>’ object cannot be interpreted as an integer
    instead of "sleep() argument must be a number".
  • For negative values, use ValueError
    "sleep length must be non-negative".
  • For NaN, use ValueError
    "Invalid value NaN (not a number)".
  • For infinities or overflow, use OverflowError
    "timestamp too large to convert to C _PyTime_t".

Apply the same adjustments in the secondary implementation at lines 708–721.

Locations to update:

  • vm/src/stdlib/time.rs: lines 93–109
  • vm/src/stdlib/time.rs: lines 708–721
🤖 Prompt for AI Agents
In vm/src/stdlib/time.rs at lines 93 to 109, update the error handling in the
sleep() function to match CPython's exact error messages and types: raise a
TypeError with the message "'<type>' object cannot be interpreted as an integer"
for non-number inputs, raise a ValueError with "sleep length must be
non-negative" for negative values, raise a ValueError with "Invalid value NaN
(not a number)" for NaN inputs, and raise an OverflowError with "timestamp too
large to convert to C _PyTime_t" for infinities or values exceeding the maximum
allowed. Apply the same changes to the secondary implementation at lines 708 to
721 in the same file.


#[cfg(not(target_os = "wasi"))]
Expand Down Expand Up @@ -527,7 +541,7 @@ mod platform {
use crate::{
PyObject, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine,
builtins::{PyNamespace, PyStrRef},
convert::IntoPyException,
convert::{IntoPyException,TryFromObject},
};
use nix::{sys::time::TimeSpec, time::ClockId};
use std::time::Duration;
Expand Down Expand Up @@ -691,9 +705,20 @@ mod platform {
}

#[pyfunction]
fn sleep(dur: Duration, vm: &VirtualMachine) -> PyResult<()> {
fn sleep(secs: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// this is basically std::thread::sleep, but that catches interrupts and we don't want to;

// Try to get as float first, if that fails try as integer
let secs = if let Ok(float_val) = f64::try_from_object(vm, secs.clone()) {
float_val
} else if let Ok(int_val) = i64::try_from_object(vm, secs) {
int_val as f64
} else {
return Err(vm.new_type_error("sleep() argument must be a number"));
};
if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
Comment thread
DominicPrince2003 marked this conversation as resolved.
Outdated
let dur = Duration::from_secs_f64(secs);
Comment on lines +708 to +721
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential precision issue with u64::MAX conversion (Unix implementation).

Same precision issue exists in the Unix implementation as in the non-Unix version.

Apply the same fix as suggested for the non-Unix implementation:

-    if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
-        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
-    }
+    if !secs.is_finite() {
+        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
+    }
+    if secs < 0.0 {
+        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
+    }
+    // Check for values that would overflow when converted to Duration
+    if secs > Duration::MAX.as_secs_f64() {
+        return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn sleep(secs: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// this is basically std::thread::sleep, but that catches interrupts and we don't want to;
// Try to get as float first, if that fails try as integer
let secs = if let Ok(float_val) = f64::try_from_object(vm, secs.clone()) {
float_val
} else if let Ok(int_val) = i64::try_from_object(vm, secs) {
int_val as f64
} else {
return Err(vm.new_type_error("sleep() argument must be a number"));
};
if !secs.is_finite() || secs < 0.0 || secs > u64::MAX as f64 {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
let dur = Duration::from_secs_f64(secs);
let secs = if let Ok(float_val) = f64::try_from_object(vm, secs.clone()) {
float_val
} else if let Ok(int_val) = i64::try_from_object(vm, secs) {
int_val as f64
} else {
return Err(vm.new_type_error("sleep() argument must be a number"));
};
if !secs.is_finite() {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
if secs < 0.0 {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
// Check for values that would overflow when converted to Duration
if secs > Duration::MAX.as_secs_f64() {
return Err(vm.new_value_error("sleep length must be a non-negative finite number"));
}
let dur = Duration::from_secs_f64(secs);
🤖 Prompt for AI Agents
In vm/src/stdlib/time.rs around lines 708 to 721, the code compares secs to
u64::MAX as f64, which can cause precision issues. To fix this, replace the
comparison with a check against u64::MAX as u128 converted to f64 or use a safer
method to ensure the comparison is accurate without precision loss, similar to
the fix applied in the non-Unix implementation.

let ts = TimeSpec::from(dur);
let res = unsafe { libc::nanosleep(ts.as_ref(), std::ptr::null_mut()) };
let interrupted = res == -1 && nix::Error::last_raw() == libc::EINTR;
Expand Down