Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6afa8ad
mmap: guard move() against dest/src past the mapping size
youknowone Aug 13, 2026
d0cc9b2
exceptions: keep ImportError name/path/name_from out of an empty inst…
youknowone Aug 13, 2026
3cdca38
asyncio: stop unwrapping the _current_tasks downcast in current_task()
youknowone Aug 13, 2026
8525e5a
asyncio: raise TypeError when FutureIter.throw()'s exception class re…
youknowone Aug 13, 2026
4f09fc9
sequence: use a fallible reservation for sequence repetition
youknowone Aug 13, 2026
4555824
collections: reject an oversized deque repetition with MemoryError
youknowone Aug 13, 2026
5fc6713
itertools: raise OverflowError for an out-of-range r argument
youknowone Aug 13, 2026
7ca0170
math: stream the generic sumprod path instead of collecting both iter…
youknowone Aug 13, 2026
e10a9dc
_imp: raise TypeError for a second positional argument to find_frozen
youknowone Aug 13, 2026
a625bb7
_typing: check _idfunc arity before indexing args
youknowone Aug 13, 2026
3a5a6bf
exceptions: require a sequence for the ExceptionGroup excs argument
youknowone Aug 13, 2026
8074cd2
mmap: treat an inverted find/rfind range as empty
youknowone Aug 13, 2026
4bfb4e5
collections: give deque and defaultdict a GC traverse
youknowone Aug 13, 2026
b84a311
itertools: opt the iterator types into GC traverse
youknowone Aug 13, 2026
126a1f7
_ctypes: mask an out-of-range int instead of panicking
youknowone Aug 13, 2026
18be6c8
hashlib: import _hashlib when a hash module is loaded
youknowone Aug 13, 2026
12c1e5b
_csv: fall back to the built-in dialect defaults when none is registered
youknowone Aug 13, 2026
737ded1
builtins: reject surrogates in compile()/eval() source instead of pan…
youknowone Aug 13, 2026
12186b0
_suggestions: require a list for _generate_suggestions candidates
youknowone Aug 13, 2026
06229d8
lzma: size the filter chain before consuming it
youknowone Aug 13, 2026
56d1054
classmethod: opt into GC traverse
youknowone Aug 13, 2026
131c89c
posix: reject unbounded iterables in posix_spawn and setgroups
youknowone Aug 13, 2026
f8c3167
_ctypes: size Array slice assignment and _argtypes_ before collecting
youknowone Aug 13, 2026
cb3c217
sys: propagate the breakpointhook warning failure
youknowone Aug 13, 2026
2ab38ec
structseq: require the sequence argument when constructing a struct s…
youknowone Aug 13, 2026
98a62d2
Guard the hash slot dispatch against unbounded recursion
youknowone Aug 13, 2026
2e1173b
genericalias: guard the __parameters__ walk against unbounded recursion
youknowone Aug 13, 2026
661b1af
Fix the type confusion in PyAtomicRef's Debug impl
youknowone Aug 13, 2026
eefb38b
_sre: disallow instantiating Match
youknowone Aug 13, 2026
0ca5a81
utils: return the empty repr instead of asserting a non-empty collection
youknowone Aug 13, 2026
3d8b930
itertools: advance cycle's index atomically
youknowone Aug 13, 2026
bce0970
_ctypes: reject a float argument to a foreign call without argtypes
youknowone Aug 13, 2026
95ef3f7
Report the iterator itself from PyIter's traverse
youknowone Aug 13, 2026
ead3b16
Remove the obsolete expectedFailure on test_code_module.test_unicode_…
youknowone Aug 13, 2026
c6b4df3
itertools: reserve the combination indices fallibly
youknowone Aug 13, 2026
cc6a434
Apply the struct sequence constructor's dict argument
youknowone Aug 13, 2026
f6808a2
_imp: report the argument count in find_frozen's arity error
youknowone Aug 13, 2026
1340654
Add regression tests for the reproduced crashers
youknowone Aug 13, 2026
bbe08af
Tolerate a changed-size RuntimeError in the set repr stress test
youknowone Aug 13, 2026
6545c58
Move the crash regression tests into per-module snippets
youknowone Aug 13, 2026
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
1 change: 0 additions & 1 deletion Lib/test/test_code_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ def test_indentation_error(self):
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 1\n\nnow exiti [truncated]... doesn't start with 'UnicodeEncodeError: '
def test_unicode_error(self):
self.infunc.side_effect = ["'\ud800'", EOFError('Finished')]
self.console.interact()
Expand Down
6 changes: 0 additions & 6 deletions Lib/test/test_structseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ def test_fields(self):
self.assertEqual(t.n_unnamed_fields, 0)
self.assertEqual(t.n_fields, time._STRUCT_TM_ITEMS)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument dict
def test_constructor(self):
t = time.struct_time

Expand All @@ -111,7 +110,6 @@ def test_constructor(self):
s = "123456789"
self.assertEqual("".join(t(s)), s)

@unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message
def test_constructor_with_duplicate_fields(self):
t = time.struct_time

Expand All @@ -125,7 +123,6 @@ def test_constructor_with_duplicate_fields(self):
with self.assertRaisesRegex(TypeError, error_message):
t("1234567890", dict={"error": 0, "tm_zone": "some zone", "tm_mon": 1})

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: expected at most 1 arguments, got 2
def test_constructor_with_duplicate_unnamed_fields(self):
assert os.stat_result.n_unnamed_fields > 0
n_visible_fields = os.stat_result.n_sequence_fields
Expand All @@ -142,7 +139,6 @@ def test_constructor_with_duplicate_unnamed_fields(self):
re.escape("got duplicate or unexpected field name(s)")):
os.stat_result((*range(n_visible_fields), -1.0), {'st_atime': -1.0})

@unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message
def test_constructor_with_unknown_fields(self):
t = time.struct_time

Expand Down Expand Up @@ -185,7 +181,6 @@ def test_pickling(self):
self.assertEqual(t2.tm_year, t.tm_year)
self.assertEqual(t2.tm_zone, t.tm_zone)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: expected at most 1 arguments, got 2
def test_pickling_with_unnamed_fields(self):
assert os.stat_result.n_unnamed_fields > 0

Expand Down Expand Up @@ -220,7 +215,6 @@ def test_copying(self):
self.assertIsNot(t3[0], t[0])
self.assertIsNot(t3.tm_year, t.tm_year)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: expected at most 1 arguments, got 2
def test_copying_with_unnamed_fields(self):
assert os.stat_result.n_unnamed_fields > 0

Expand Down
2 changes: 1 addition & 1 deletion crates/capi/src/genericaliasobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ pub unsafe extern "C" fn Py_GenericAlias(
with_vm(|vm| {
let origin = unsafe { &*origin }.to_owned();
let args = unsafe { &*args }.to_owned();
PyGenericAlias::from_args(origin, args, vm).into_pyobject(vm)
PyGenericAlias::from_args(origin, args, vm).map(|alias| alias.into_pyobject(vm))
})
}
54 changes: 35 additions & 19 deletions crates/stdlib/src/_asyncio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ pub(crate) mod _asyncio {
vm::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{
PyBaseException, PyBaseExceptionRef, PyDict, PyDictRef, PyGenericAlias, PyList,
PyListRef, PyModule, PySet, PyTuple, PyType, PyTypeRef,
PyBaseException, PyBaseExceptionRef, PyDict, PyGenericAlias, PyList, PyListRef,
PyModule, PySet, PyTuple, PyType, PyTypeRef,
},
extend_module,
function::{FuncArgs, KwArgs, OptionalArg, OptionalOption, PySetterValue},
Expand Down Expand Up @@ -779,7 +779,7 @@ pub(crate) mod _asyncio {
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}
}
Expand Down Expand Up @@ -1036,7 +1036,7 @@ pub(crate) mod _asyncio {
)));
}

let exc = if exc_type.fast_isinstance(vm.ctx.types.type_type) {
let exc: PyBaseExceptionRef = if exc_type.fast_isinstance(vm.ctx.types.type_type) {
// exc_type is a class
let exc_class: PyTypeRef = exc_type.clone().downcast().unwrap();
// Must be a subclass of BaseException
Expand All @@ -1047,12 +1047,23 @@ pub(crate) mod _asyncio {
}

let val = exc_val.unwrap_or_none(vm);
if vm.is_none(&val) {
let exc = if vm.is_none(&val) {
exc_type.call((), vm)?
} else if val.fast_isinstance(&exc_class) {
val
} else {
exc_type.call((val,), vm)?
};
match exc.downcast() {
Ok(exc) => exc,
Err(obj) => {
let exc_class_repr = exc_class.as_object().repr(vm)?;
vm.new_type_error(format!(
"calling {} should have returned an instance of BaseException, not {}",
exc_class_repr.as_wtf8(),
obj.class()
))
}
}
} else if exc_type.fast_isinstance(vm.ctx.exceptions.base_exception_type) {
// exc_type is an exception instance
Expand All @@ -1063,7 +1074,7 @@ pub(crate) mod _asyncio {
vm.new_type_error("instance exception may not have a separate value")
);
}
exc_type
exc_type.downcast().unwrap()
} else {
// exc_type is neither a class nor an exception instance
return Err(vm.new_type_error(format!(
Expand All @@ -1075,10 +1086,11 @@ pub(crate) mod _asyncio {
if let OptionalArg::Present(tb) = exc_tb
&& !vm.is_none(&tb)
{
exc.set_attr(vm.ctx.intern_str("__traceback__"), tb, vm)?;
exc.as_object()
.set_attr(vm.ctx.intern_str("__traceback__"), tb, vm)?;
}

Err(exc.downcast().unwrap())
Err(exc)
}

#[pymethod]
Expand Down Expand Up @@ -1840,7 +1852,7 @@ pub(crate) mod _asyncio {
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}
}
Expand Down Expand Up @@ -2405,7 +2417,9 @@ pub(crate) mod _asyncio {

// Slow path: look up in the module-level dict for cross-thread queries
let current_tasks = get_current_tasks_dict(vm)?;
let dict: PyDictRef = current_tasks.downcast().unwrap();
let Ok(dict) = current_tasks.downcast::<PyDict>() else {
return Ok(vm.ctx.none());
};

match dict.get_item(&*loop_obj, vm) {
Ok(task) => Ok(task),
Expand Down Expand Up @@ -2485,15 +2499,17 @@ pub(crate) mod _asyncio {
#[pyfunction]
fn _enter_task(loop_: PyObjectRef, task: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
// Per-thread check, matching CPython's ts->asyncio_running_task
{
let running_task = vm.asyncio_running_task.borrow();
if running_task.is_some() {
return Err(vm.new_runtime_error(format!(
"Cannot enter into task {:?} while another task {:?} is being executed.",
task,
running_task.as_ref().unwrap()
)));
}
let running_task = vm.asyncio_running_task.borrow().clone();
if let Some(running_task) = running_task {
let task_repr = task.repr(vm)?;
let running_task_repr = running_task.repr(vm)?;
return Err(vm.new_runtime_error(wtf8_concat!(
"Cannot enter into task ",
task_repr.as_wtf8(),
" while another task ",
running_task_repr.as_wtf8(),
" is being executed."
)));
}

*vm.asyncio_running_task.borrow_mut() = Some(task.clone());
Expand Down
2 changes: 1 addition & 1 deletion crates/stdlib/src/_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ mod _queue {
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/stdlib/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1234,7 +1234,7 @@ pub mod array {
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}
}
Expand Down
9 changes: 8 additions & 1 deletion crates/stdlib/src/blake2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub(crate) use _blake2::module_def;
#[pymodule]
mod _blake2 {
use crate::hashlib::_hashlib::{BlakeHashArgs, local_blake2b, local_blake2s};
use crate::vm::{PyPayload, PyResult, VirtualMachine};
use crate::vm::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule};

#[pyattr(name = "_GIL_MINSIZE")]
const GIL_MINSIZE: u16 = 2048;
Expand Down Expand Up @@ -43,4 +43,11 @@ mod _blake2 {
fn blake2s(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult {
Ok(local_blake2s(args, vm)?.into_pyobject(vm))
}

#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
let _ = vm.import("_hashlib", 0);
__module_exec(vm, module);
Ok(())
}
}
4 changes: 2 additions & 2 deletions crates/stdlib/src/contextvars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ mod _contextvars {
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}
}
Expand Down Expand Up @@ -562,7 +562,7 @@ mod _contextvars {
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
) -> PyResult<PyGenericAlias> {
PyGenericAlias::from_args(cls, args, vm)
}

Expand Down
15 changes: 10 additions & 5 deletions crates/stdlib/src/csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,11 +779,16 @@ mod _csv {
// TODO: Maybe need to update the obj from HashMap
}
DialectItem::Obj(o) => Ok(self.update_py_dialect(o.clone())),
DialectItem::None => {
let g = GLOBAL_HASHMAP.lock();
let res = g.get("excel").unwrap().clone();
Ok(self.update_py_dialect(res))
}
DialectItem::None => Ok(self.update_py_dialect(PyDialect {
delimiter: b',',
quotechar: Some(b'"'),
escapechar: None,
doublequote: true,
skipinitialspace: false,
lineterminator: "\r\n".to_owned(),
quoting: QuoteStyle::Minimal,
strict: false,
})),
}
}

Expand Down
35 changes: 18 additions & 17 deletions crates/stdlib/src/lzma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,40 +337,43 @@ mod _lzma {
}

fn parse_filter_chain_spec(
filter_specs: Vec<PyObjectRef>,
filter_specs: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<Filters> {
const LZMA_FILTERS_MAX: usize = 4;
if filter_specs.len() > LZMA_FILTERS_MAX {
let filter_specs_len = filter_specs.length(vm)?;
if filter_specs_len > LZMA_FILTERS_MAX {
return Err(new_lzma_error(
format!("Too many filters - liblzma supports a maximum of {LZMA_FILTERS_MAX}"),
vm,
));
}

let filter_specs = filter_specs.try_sequence(vm)?;
let mut filters = Filters::new();
for spec in &filter_specs {
let filter_id = get_dict_opt_u64(spec, "id", vm)?
for i in 0..filter_specs_len {
let spec = filter_specs.get_item(i as isize, vm)?;
let filter_id = get_dict_opt_u64(&spec, "id", vm)?
.ok_or_else(|| vm.new_value_error("Filter specifier must have an \"id\" entry"))?;

match filter_id {
FILTER_LZMA1 => {
let opts = parse_filter_spec_lzma(spec, vm)?;
let opts = parse_filter_spec_lzma(&spec, vm)?;
filters.lzma1(&opts);
}
FILTER_LZMA2 => {
let opts = parse_filter_spec_lzma(spec, vm)?;
let opts = parse_filter_spec_lzma(&spec, vm)?;
filters.lzma2(&opts);
}
FILTER_DELTA => {
let dist = parse_filter_spec_delta(spec, vm)?;
let dist = parse_filter_spec_delta(&spec, vm)?;
filters
.delta_properties(&[(dist - 1) as u8])
.map_err(|e| catch_lzma_error(e, vm))?;
}
FILTER_X86 | FILTER_POWERPC | FILTER_IA64 | FILTER_ARM | FILTER_ARMTHUMB
| FILTER_SPARC => {
let start_offset = parse_filter_spec_bcj(spec, vm)?;
let start_offset = parse_filter_spec_bcj(&spec, vm)?;
add_bcj_filter(&mut filters, filter_id, start_offset)
.map_err(|e| catch_lzma_error(e, vm))?;
}
Expand Down Expand Up @@ -570,7 +573,7 @@ mod _lzma {
#[pyarg(any, optional)]
memlimit: Option<u64>,
#[pyarg(any, optional)]
filters: Option<Vec<PyObjectRef>>,
filters: Option<PyObjectRef>,
}

impl Constructor for LZMADecompressor {
Expand Down Expand Up @@ -735,7 +738,7 @@ mod _lzma {
fn init_xz(
check: i32,
preset: u32,
filters: Option<Vec<PyObjectRef>>,
filters: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<Stream> {
let real_check =
Expand All @@ -751,10 +754,11 @@ mod _lzma {

fn init_alone(
preset: u32,
filter_specs: Option<Vec<PyObjectRef>>,
filter_specs: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<Stream> {
if let Some(_filter_specs) = filter_specs {
if let Some(filter_specs) = filter_specs {
filter_specs.length(vm)?;
// TODO: validate single LZMA1 filter and use its options
let options = LzmaOptions::new_preset(preset).map_err(|_| {
new_lzma_error(format!("Invalid compression preset: {preset}"), vm)
Expand All @@ -768,10 +772,7 @@ mod _lzma {
}
}

fn init_raw(
filter_specs: Option<Vec<PyObjectRef>>,
vm: &VirtualMachine,
) -> PyResult<Stream> {
fn init_raw(filter_specs: Option<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Stream> {
let filter_specs = filter_specs
.ok_or_else(|| vm.new_value_error("Must specify filters for FORMAT_RAW"))?;
let filters = parse_filter_chain_spec(filter_specs, vm)?;
Expand All @@ -788,7 +789,7 @@ mod _lzma {
#[pyarg(any, optional)]
preset: Option<PyObjectRef>,
#[pyarg(any, optional)]
filters: Option<Vec<PyObjectRef>>,
filters: Option<PyObjectRef>,
}

impl Constructor for LZMACompressor {
Expand Down
Loading
Loading