mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Fix 33 reproduced fuzzing and static-review defects (#8514)
* mmap: guard move() against dest/src past the mapping size
An offset larger than the mapping made `size - dest` underflow, producing
an out-of-range slice index panic. Check the high side first, as write() does.
Assisted-by: Claude
* exceptions: keep ImportError name/path/name_from out of an empty instance dict
ImportError.__reduce__ unwrapped get_arg(0), which is None when the exception
was constructed with no positional argument, aborting on pickle.dumps(ImportError()).
Fall back to the full args tuple there, and expose name/path/name_from as class
attributes defaulting to None so a bare ImportError() reduces to (cls, ()).
Assisted-by: Claude
* asyncio: stop unwrapping the _current_tasks downcast in current_task()
_asyncio._current_tasks is a reassignable module attribute; the three sibling
functions already degrade gracefully when it is not a dict, current_task() did not.
Assisted-by: Claude
* asyncio: raise TypeError when FutureIter.throw()'s exception class returns a non-exception
exc_type.call() goes through a Python-controlled __new__, so its result can be
any object; the downcast was unwrapped. Report it as a TypeError instead.
Assisted-by: Claude
* sequence: use a fallible reservation for sequence repetition
The guard only rejects a repeat whose per-element size crosses MAX_MEMORY_SIZE,
so (1,) * (10**12) still reached Vec::with_capacity and aborted in the allocator.
Reserve fallibly and surface a MemoryError.
Assisted-by: Claude
* collections: reject an oversized deque repetition with MemoryError
deque * sys.maxsize reached the allocator and aborted with a capacity overflow.
Apply the MAX_MEMORY_SIZE guard the other sequences already carry.
Assisted-by: Claude
* itertools: raise OverflowError for an out-of-range r argument
combinations/combinations_with_replacement/permutations narrowed r with
to_usize().unwrap(), so r=2**64 panicked instead of raising.
Assisted-by: Claude
* math: stream the generic sumprod path instead of collecting both iterables
The big-int path collected each argument into a Vec before multiplying, so an
unbounded iterable exhausted memory. Advance both iterators in lockstep and
accumulate, reporting a length mismatch when only one is exhausted.
Assisted-by: Claude
* _imp: raise TypeError for a second positional argument to find_frozen
withdata is keyword-only and unimplemented; passing it positionally hit an
unimplemented!() and aborted.
Assisted-by: Claude
* _typing: check _idfunc arity before indexing args
_typing._idfunc() with no argument indexed args[0] out of bounds.
Assisted-by: Claude
* exceptions: require a sequence for the ExceptionGroup excs argument
The argument was collected before being validated, so an unbounded iterable
such as itertools.count() exhausted memory. Reject a non-sequence up front.
Assisted-by: Claude
* mmap: treat an inverted find/rfind range as empty
find(b"x", 5, 2) built a slice whose start exceeded its end and panicked.
Assisted-by: Claude
* collections: give deque and defaultdict a GC traverse
Neither type opted into traversal, so a reference cycle through a deque or a
defaultdict was never collected.
Assisted-by: Claude
* itertools: opt the iterator types into GC traverse
cycle and its siblings hold Python references but declared no traverse, so a
cycle built through one of them leaked.
Assisted-by: Claude
* _ctypes: mask an out-of-range int instead of panicking
c_char_p(2**64) and pointer item assignment called .expect() on the narrowing
conversion. Wrap the value to the target width, which is what the C
implementation stores.
Assisted-by: Claude
* hashlib: import _hashlib when a hash module is loaded
The hash object type is a static type owned by _hashlib; calling _md5.md5()
without _hashlib imported hit an uninitialized static type and panicked.
Assisted-by: Claude
* _csv: fall back to the built-in dialect defaults when none is registered
The dialect table is empty until csv.py registers 'excel', so _csv.reader([])
unwrapped a missing entry and panicked.
Assisted-by: Claude
* builtins: reject surrogates in compile()/eval() source instead of panicking
expect_str() panics on a str containing surrogates; convert with try_as_utf8
so eval(chr(0xd800)) raises.
Assisted-by: Claude
* _suggestions: require a list for _generate_suggestions candidates
The argument was collected before validation, so an unbounded iterable
exhausted memory.
Assisted-by: Claude
* lzma: size the filter chain before consuming it
filters= was collected into a Vec before the length check, so an unbounded
iterable exhausted memory. Take the length through the sequence protocol first.
Assisted-by: Claude
* classmethod: opt into GC traverse
staticmethod already declares traverse; classmethod did not, so a cycle through
the wrapped callable leaked.
Assisted-by: Claude
* posix: reject unbounded iterables in posix_spawn and setgroups
argv, setsigdef, setsigmask and setgroups bound an ArgIterable and collected it
before validating, so an infinite generator exhausted memory. Require a
list/tuple for argv, validate signals while streaming, and take setgroups
through the sequence protocol.
Assisted-by: Claude
* _ctypes: size Array slice assignment and _argtypes_ before collecting
Both eagerly collected their argument, so an unbounded iterable exhausted
memory before the length check ran.
Assisted-by: Claude
* sys: propagate the breakpointhook warning failure
warn() was unwrapped, so an unimportable $PYTHONBREAKPOINT under
-W error panicked instead of raising.
Assisted-by: Claude
* structseq: require the sequence argument when constructing a struct sequence
A no-argument construction produced an empty backing tuple, and reading any
named field then indexed out of bounds.
Assisted-by: Claude
* Guard the hash slot dispatch against unbounded recursion
`PyObject::hash` invoked the type's hash slot directly, so an element-wise
`__hash__` following a deeply nested object graph recursed one native frame
per level and overflowed the stack. Wrap the dispatch in `with_recursion`,
matching the repr and rich-compare dispatches in the same file, so
`hash(x)` on a nested tuple/GenericAlias/slice raises `RecursionError`.
Assisted-by: Claude
* genericalias: guard the __parameters__ walk against unbounded recursion
`make_parameters_from_slice` recursed into every list/tuple argument with
nothing counting the frames, so `list[L]` for a self-referential or deeply
nested `L` overflowed the native stack at subscript time. Wrap the descent
in `with_recursion`, which makes the walk fallible; `PyGenericAlias::new`,
`from_args` and `make_parameters` now return `PyResult` and every caller
propagates.
Assisted-by: Claude
* Fix the type confusion in PyAtomicRef's Debug impl
`PyAtomicRef<T>` stores a pointer to a `Py<T>`, as `Deref`, `load_raw`,
`swap` and `Drop` all read it, but `Debug` cast it to a bare `T` and
formatted the object header as payload bytes. For `PyFunction`, whose
`code: PyAtomicRef<PyCode>` has a pointer-chasing `Debug`, that
dereferenced header words and segfaulted.
Cast to `PyObject` instead, which is what `Drop` already does and which
also covers the `PyAtomicRef<PyObject>` and `PyAtomicRef<Option<T>>`
instantiations that have no `Py<T>`.
`_asyncio._enter_task` reached this through `{:?}` in its "Cannot enter
into task" message; format the two tasks with their Python repr, which is
what the message is meant to show.
Assisted-by: Claude
* _sre: disallow instantiating Match
`Match` inherited `object.__new__`, so `M.__new__(M)` produced an instance
whose `regs`/`string`/`pattern` were never filled in by a match run; the
mapping subscript path read them and dereferenced garbage. The type has no
public constructor, so mark it `DISALLOW_INSTANTIATION`, which makes
`M.__new__(M)` raise `TypeError: cannot create 're.Match' instances`.
Assisted-by: Claude
* utils: return the empty repr instead of asserting a non-empty collection
`collection_repr` took the first element with an `.expect()` justified by the
caller's preceding non-empty check. Another thread clearing the collection
between that check and the iteration made the iterator yield nothing and
panicked the worker. Fall back to the caller-supplied empty form, which is
the text those callers already produce for an empty collection.
Assisted-by: Claude
* itertools: advance cycle's index atomically
`cycle.__next__` did `fetch_add(1)` and then reset the index to 0 in a
separate store, so two threads replaying the saved items could both read an
index past the end of `saved` and panic on the slice access. Do the advance
and the wrap in one `fetch_update`.
Assisted-by: Claude
* _ctypes: reject a float argument to a foreign call without argtypes
`conv_param`, the conversion used when `argtypes` is not set, converted its
argument with `try_int`, which goes through `__int__` and so accepts a
float. `libc.strlen(1.5)` therefore passed 1 where the callee expects a
`char *` and the callee dereferenced it. Match `ConvParam`, which does a
`PyLong_Check` and converts nothing: take the branch only for an `int` (or
a subclass, so `True` still converts), and let a float fall through to
"Don't know how to convert parameter".
The branch below it converted a float to a C double, but `try_int` claimed
every float before it could run, so it was dead; `CArgValue::Double`
existed only for that branch and both go. Typed doubles are unaffected —
they travel as `CArgValue::Typed` with code 'd'.
Assisted-by: Claude
* Report the iterator itself from PyIter's traverse
`Traverse for PyIter<O>` delegated to the inherent `PyObject::traverse` of
the object it wraps, so it reported that iterator's referents instead of
the iterator. The iterator's own reference to those referents was then
never subtracted during the collector's reference-subtraction pass, the
referents kept a non-zero gc_refs, and every object reachable from them
was classified as a root.
Any cycle running through a type with a `PyIter` field therefore survived
collection: `map`, `filter`, `zip`, `enumerate`, `reversed` and the
`itertools` iterators all leaked, while the same cycle through a `list`,
`tuple` or `list_iterator` collected. Report the wrapped object, as the
`PyObjectRef`, `PyRef<T>` and `PyStackRef` impls do.
`itertools.tee` still leaks: its shared buffer is a `PyRc<PyItertoolsTeeData>`
rather than a Python object, so the collector cannot see through it.
Assisted-by: Claude
* Remove the obsolete expectedFailure on test_code_module.test_unicode_error
Compiling a source string containing a lone surrogate now raises
UnicodeEncodeError, so the test passes.
Assisted-by: Claude
* itertools: reserve the combination indices fallibly
`combinations` and `combinations_with_replacement` built their index vector
with an infallible allocation, so an `r` that passes the ssize_t check but
does not fit in memory aborted the process instead of raising MemoryError.
Assisted-by: Claude
* Apply the struct sequence constructor's dict argument
`structseq(sequence, dict)` discarded its second argument, so the hidden
fields past `n_sequence_fields` — `tm_zone`, `st_atime` and friends — were
always None when constructed directly or restored from a `(sequence, dict)`
pickle, and a non-dict second argument was accepted silently.
Take the dict, require it to be a dict, and fill the hidden slots the
sequence did not cover from it. A key that names a field the sequence
already supplied, or no field at all, is now a
"got duplicate or unexpected field name(s)" TypeError instead of being
dropped. Both arguments are bindable by name, as `sequence` and `dict`.
`os.stat_result` and `os.statvfs_result` did not accept a second argument
at all; they and `time.struct_time` now share the parsing.
Assisted-by: Claude
* _imp: report the argument count in find_frozen's arity error
Assisted-by: Claude
* Add regression tests for the reproduced crashers
One case per catalog entry, each asserting the behavior the fix produces:
recursion guards, the memory-unsafety sites, the overflow and unbounded
allocation guards, the eager-collection rejections, the unwrap sites, and
the cycles the collector now breaks. Every expected value was checked
against CPython 3.14.
Assisted-by: Claude
* Tolerate a changed-size RuntimeError in the set repr stress test
The mutator and reader threads race on purpose; a "changed size during
iteration" RuntimeError is a valid outcome of that race and should not fail
the test. The panic it guards against is not.
Assisted-by: Claude
* Move the crash regression tests into per-module snippets
crash_regressions.py collected every reproduced crasher in one file. Split it
into the snippet for the module each case exercises, and add stdlib_gc.py,
stdlib_asyncio.py, stdlib_lzma.py, stdlib_threading_set_repr.py and
stdlib_threading_itertools_cycle.py for the cases with no existing home.
The suite runs every snippet under the host CPython too, so the checks only
RustPython raises are guarded by sys.implementation.name: the hash and
__parameters__ recursion depth, and the deque repeat overflow. The float
ctypes argument accepts either TypeError or ctypes.ArgumentError.
Assisted-by: Claude
This commit is contained in:
1 parent
c9a62449f0
commit
2ed082a51e
93 files changed
+1157
-342
No files matched your search
Vendored
-1
@@ -128,7 +128,6 @@ class TestInteractiveConsole(unittest.TestCase, MockSys):
|
||||
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()
|
||||
|
||||
Vendored
-6
@@ -87,7 +87,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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
|
||||
|
||||
@@ -111,7 +110,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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
|
||||
|
||||
@@ -125,7 +123,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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
|
||||
@@ -142,7 +139,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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
|
||||
|
||||
@@ -185,7 +181,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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
|
||||
|
||||
@@ -220,7 +215,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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
|
||||
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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},
|
||||
@@ -779,7 +779,7 @@ pub(crate) mod _asyncio {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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!(
|
||||
@@ -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]
|
||||
@@ -1840,7 +1852,7 @@ pub(crate) mod _asyncio {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
@@ -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());
|
||||
|
||||
@@ -282,7 +282,7 @@ mod _queue {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1234,7 +1234,7 @@ pub mod array {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -462,7 +462,7 @@ mod _contextvars {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -562,7 +562,7 @@ mod _contextvars {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-17
@@ -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))?;
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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 =
|
||||
@@ -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)
|
||||
@@ -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)?;
|
||||
@@ -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 {
|
||||
|
||||
+11
-16
@@ -727,25 +727,20 @@ mod math {
|
||||
}
|
||||
|
||||
// Generic Python path
|
||||
let (p_i, q_i) = (p_i.unwrap(), q_i.unwrap());
|
||||
|
||||
// Collect current + remaining elements
|
||||
let p_remaining: Result<Vec<PyObjectRef>, _> =
|
||||
core::iter::once(Ok(p_i)).chain(p_iter).collect();
|
||||
let q_remaining: Result<Vec<PyObjectRef>, _> =
|
||||
core::iter::once(Ok(q_i)).chain(q_iter).collect();
|
||||
let (p_vec, q_vec) = (p_remaining?, q_remaining?);
|
||||
|
||||
if p_vec.len() != q_vec.len() {
|
||||
return Err(vm.new_value_error("Inputs are not the same length"));
|
||||
}
|
||||
|
||||
let (mut p_i, mut q_i) = (p_i.unwrap(), q_i.unwrap());
|
||||
let mut total = obj_total.unwrap_or_else(|| vm.ctx.new_int(0).into());
|
||||
for (p_item, q_item) in p_vec.into_iter().zip(q_vec) {
|
||||
let prod = vm._mul(&p_item, &q_item)?;
|
||||
loop {
|
||||
let prod = vm._mul(&p_i, &q_i)?;
|
||||
total = vm._add(&total, &prod)?;
|
||||
|
||||
let next_p = p_iter.next().transpose()?;
|
||||
let next_q = q_iter.next().transpose()?;
|
||||
match (next_p, next_q) {
|
||||
(Some(next_p), Some(next_q)) => (p_i, q_i) = (next_p, next_q),
|
||||
(None, None) => return Ok(total),
|
||||
_ => return Err(vm.new_value_error("Inputs are not the same length")),
|
||||
}
|
||||
}
|
||||
return Ok(total);
|
||||
}
|
||||
|
||||
Ok(obj_total.unwrap_or_else(|| vm.ctx.new_int(0).into()))
|
||||
|
||||
@@ -3,10 +3,17 @@ pub(crate) use _md5::module_def;
|
||||
#[pymodule]
|
||||
mod _md5 {
|
||||
use crate::hashlib::_hashlib::{HashArgs, local_md5};
|
||||
use crate::vm::{PyPayload, PyResult, VirtualMachine};
|
||||
use crate::vm::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule};
|
||||
|
||||
#[pyfunction]
|
||||
fn md5(args: HashArgs, vm: &VirtualMachine) -> PyResult {
|
||||
Ok(local_md5(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(())
|
||||
}
|
||||
}
|
||||
@@ -777,7 +777,10 @@ mod mmap {
|
||||
let start = options
|
||||
.start
|
||||
.map_or_else(|| self.pos(), |start| start.saturated_at(size));
|
||||
let end = options.end.map_or(size, |end| end.saturated_at(size));
|
||||
let end = options
|
||||
.end
|
||||
.map_or(size, |end| end.saturated_at(size))
|
||||
.max(start);
|
||||
(start, end)
|
||||
}
|
||||
|
||||
@@ -886,7 +889,7 @@ mod mmap {
|
||||
let dest = dest.try_to_primitive(vm).ok()?;
|
||||
let src = src.try_to_primitive(vm).ok()?;
|
||||
let cnt = cnt.try_to_primitive(vm).ok()?;
|
||||
if size - dest < cnt || size - src < cnt {
|
||||
if dest > size || src > size || size - dest < cnt || size - src < cnt {
|
||||
return None;
|
||||
}
|
||||
Some((dest, src, cnt))
|
||||
|
||||
@@ -3,10 +3,17 @@ pub(crate) use _sha1::module_def;
|
||||
#[pymodule]
|
||||
mod _sha1 {
|
||||
use crate::hashlib::_hashlib::{HashArgs, local_sha1};
|
||||
use crate::vm::{PyPayload, PyResult, VirtualMachine};
|
||||
use crate::vm::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule};
|
||||
|
||||
#[pyfunction]
|
||||
fn sha1(args: HashArgs, vm: &VirtualMachine) -> PyResult {
|
||||
Ok(local_sha1(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(())
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ mod _sha3 {
|
||||
HashArgs, local_sha3_224, local_sha3_256, local_sha3_384, local_sha3_512, local_shake_128,
|
||||
local_shake_256,
|
||||
};
|
||||
use crate::vm::{PyPayload, PyResult, VirtualMachine};
|
||||
use crate::vm::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule};
|
||||
|
||||
#[pyfunction]
|
||||
fn sha3_224(args: HashArgs, vm: &VirtualMachine) -> PyResult {
|
||||
@@ -37,4 +37,11 @@ mod _sha3 {
|
||||
fn shake_256(args: HashArgs, vm: &VirtualMachine) -> PyResult {
|
||||
Ok(local_shake_256(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(())
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,25 @@ pub(crate) use _suggestions::module_def;
|
||||
|
||||
#[pymodule]
|
||||
mod _suggestions {
|
||||
use rustpython_vm::VirtualMachine;
|
||||
use rustpython_vm::{PyResult, VirtualMachine, builtins::PyList};
|
||||
|
||||
use crate::vm::PyObjectRef;
|
||||
|
||||
#[pyfunction]
|
||||
fn _generate_suggestions(
|
||||
candidates: Vec<PyObjectRef>,
|
||||
candidates: PyObjectRef,
|
||||
name: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyObjectRef {
|
||||
match crate::vm::suggestion::calculate_suggestions(candidates.iter(), &name) {
|
||||
Some(suggestion) => suggestion.into(),
|
||||
None => vm.ctx.none(),
|
||||
}
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let candidates = candidates
|
||||
.downcast::<PyList>()
|
||||
.map_err(|_| vm.new_type_error("candidates must be a list"))?;
|
||||
let candidates = candidates.borrow_vec();
|
||||
Ok(
|
||||
match crate::vm::suggestion::calculate_suggestions(candidates.iter(), &name) {
|
||||
Some(suggestion) => suggestion.into(),
|
||||
None => vm.ctx.none(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,11 @@ impl PyAsyncGen {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,7 +554,11 @@ impl PyByteArray {
|
||||
|
||||
// TODO: Uncomment when Python adds __class_getitem__ to bytearray
|
||||
// #[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,7 +544,11 @@ impl PyBytes {
|
||||
|
||||
// TODO: Uncomment when Python adds __class_getitem__ to bytes
|
||||
// #[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::{
|
||||
///
|
||||
/// Class methods are different than C++ or Java static methods.
|
||||
/// If you want those, see the staticmethod builtin.
|
||||
#[pyclass(module = false, name = "classmethod")]
|
||||
#[pyclass(module = false, name = "classmethod", traverse)]
|
||||
#[derive(Debug)]
|
||||
pub struct PyClassMethod {
|
||||
callable: PyMutex<PyObjectRef>,
|
||||
@@ -187,7 +187,11 @@ impl PyClassMethod {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,11 @@ impl PyCoroutine {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +535,11 @@ impl PyDict {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,11 @@ impl Constructor for PyEnumerate {
|
||||
#[pyclass(with(Py, IterNext, Iterable, Constructor), flags(BASETYPE))]
|
||||
impl PyEnumerate {
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,11 @@ impl PyGenerator {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl Constructor for PyGenericAlias {
|
||||
} else {
|
||||
PyTuple::new_ref(vec![arguments], &vm.ctx)
|
||||
};
|
||||
Ok(Self::new(origin, args, false, vm))
|
||||
Self::new(origin, args, false, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,14 +92,14 @@ impl PyGenericAlias {
|
||||
args: PyTupleRef,
|
||||
starred: bool,
|
||||
vm: &VirtualMachine,
|
||||
) -> Self {
|
||||
let parameters = make_parameters(&args, vm);
|
||||
Self {
|
||||
) -> PyResult<Self> {
|
||||
let parameters = make_parameters(&args, vm)?;
|
||||
Ok(Self {
|
||||
origin: origin.into(),
|
||||
args,
|
||||
parameters,
|
||||
starred,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a GenericAlias from an origin and PyObjectRef arguments (helper for compatibility)
|
||||
@@ -107,7 +107,7 @@ impl PyGenericAlias {
|
||||
origin: impl Into<PyObjectRef>,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> Self {
|
||||
) -> PyResult<Self> {
|
||||
let args = if let Ok(tuple) = args.try_to_ref::<PyTuple>(vm) {
|
||||
tuple.to_owned()
|
||||
} else {
|
||||
@@ -228,7 +228,7 @@ impl PyGenericAlias {
|
||||
vm,
|
||||
)?;
|
||||
|
||||
Ok(Self::new(zelf.origin.clone(), new_args, false, vm).into_pyobject(vm))
|
||||
Ok(Self::new(zelf.origin.clone(), new_args, false, vm)?.into_pyobject(vm))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
@@ -247,7 +247,7 @@ impl PyGenericAlias {
|
||||
if zelf.starred {
|
||||
// (next, (iter(GenericAlias(origin, args)),))
|
||||
let next_fn = vm.builtins.get_attr("next", vm)?;
|
||||
let non_starred = Self::new(zelf.origin.clone(), zelf.args.clone(), false, vm);
|
||||
let non_starred = Self::new(zelf.origin.clone(), zelf.args.clone(), false, vm)?;
|
||||
let iter_obj = PyGenericAliasIterator {
|
||||
obj: crate::common::lock::PyMutex::new(Some(non_starred.into_pyobject(vm))),
|
||||
}
|
||||
@@ -292,11 +292,11 @@ impl PyGenericAlias {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
|
||||
pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
|
||||
make_parameters_from_slice(args.as_slice(), vm)
|
||||
}
|
||||
|
||||
fn make_parameters_from_slice(args: &[PyObjectRef], vm: &VirtualMachine) -> PyTupleRef {
|
||||
fn make_parameters_from_slice(args: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<PyTupleRef> {
|
||||
let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
@@ -326,7 +326,9 @@ fn make_parameters_from_slice(args: &[PyObjectRef], vm: &VirtualMachine) -> PyTu
|
||||
let list = arg.downcast_ref::<PyList>().unwrap();
|
||||
list.borrow_vec().to_vec()
|
||||
};
|
||||
let sub = make_parameters_from_slice(&items, vm);
|
||||
let sub = vm.with_recursion("while computing __parameters__", || {
|
||||
make_parameters_from_slice(&items, vm)
|
||||
})?;
|
||||
for sub_param in sub.iter() {
|
||||
if tuple_index(¶meters, sub_param).is_none() {
|
||||
parameters.push(sub_param.clone());
|
||||
@@ -335,7 +337,7 @@ fn make_parameters_from_slice(args: &[PyObjectRef], vm: &VirtualMachine) -> PyTu
|
||||
}
|
||||
}
|
||||
|
||||
PyTuple::new_ref(parameters, &vm.ctx)
|
||||
Ok(PyTuple::new_ref(parameters, &vm.ctx))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -716,7 +718,7 @@ impl crate::types::IterNext for PyGenericAliasIterator {
|
||||
let alias = obj
|
||||
.downcast_ref::<PyGenericAlias>()
|
||||
.ok_or_else(|| vm.new_type_error("generic_alias_iterator expected GenericAlias"))?;
|
||||
let starred = PyGenericAlias::new(alias.origin.clone(), alias.args.clone(), true, vm);
|
||||
let starred = PyGenericAlias::new(alias.origin.clone(), alias.args.clone(), true, vm)?;
|
||||
Ok(PyIterReturn::Return(starred.into_pyobject(vm)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,11 @@ impl PyInterpolation {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
|
||||
@@ -421,7 +421,11 @@ impl PyList {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,11 @@ impl PyMappingProxy {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
|
||||
@@ -554,7 +554,11 @@ impl Py<PyMemoryView> {
|
||||
)]
|
||||
impl PyMemoryView {
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
|
||||
@@ -364,7 +364,11 @@ impl PyRange {
|
||||
|
||||
// TODO: Uncomment when Python adds __class_getitem__ to range
|
||||
// #[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +386,8 @@ impl PySetInner {
|
||||
}
|
||||
|
||||
fn repr(&self, class_name: Option<&str>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
|
||||
collection_repr(class_name, "{", "}", self.elements().iter(), vm)
|
||||
let empty = format!("{}()", class_name.unwrap_or("set"));
|
||||
collection_repr(class_name, "{", "}", &empty, self.elements().iter(), vm)
|
||||
}
|
||||
|
||||
fn add(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
@@ -881,7 +882,11 @@ impl PySet {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -1286,7 +1291,11 @@ impl PyFrozenSet {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,11 @@ impl PySlice {
|
||||
|
||||
// TODO: Uncomment when Python adds __class_getitem__ to slice
|
||||
// #[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,11 @@ impl PyStaticMethod {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,11 @@ impl PyTemplate {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
|
||||
@@ -504,7 +504,11 @@ impl PyTuple {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -609,7 +613,7 @@ impl Representable for PyTuple {
|
||||
let s = if zelf.len() == 1 {
|
||||
wtf8_concat!("(", zelf.elements[0].repr(vm)?.as_wtf8(), ",)")
|
||||
} else {
|
||||
collection_repr(None, "(", ")", zelf.elements.iter(), vm)?
|
||||
collection_repr(None, "(", ")", "()", zelf.elements.iter(), vm)?
|
||||
};
|
||||
vm.ctx.new_str(s)
|
||||
} else {
|
||||
|
||||
@@ -234,7 +234,7 @@ pub(crate) fn or_op(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine)
|
||||
}
|
||||
|
||||
fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
|
||||
let parameters = genericalias::make_parameters(args, vm);
|
||||
let parameters = genericalias::make_parameters(args, vm)?;
|
||||
let result = dedup_and_flatten_args(¶meters, vm)?;
|
||||
Ok(result.args)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,11 @@ impl PyWeak {
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(super) mod types {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
@@ -255,20 +255,11 @@ pub(super) mod types {
|
||||
)));
|
||||
}
|
||||
|
||||
// Validate exceptions is a sequence (not set or None)
|
||||
// Validate exceptions is a sequence
|
||||
let exceptions_arg = &args[1];
|
||||
|
||||
// Check for set/frozenset (not a sequence - unordered)
|
||||
if exceptions_arg.fast_isinstance(vm.ctx.types.set_type)
|
||||
|| exceptions_arg.fast_isinstance(vm.ctx.types.frozenset_type)
|
||||
{
|
||||
return Err(vm.new_type_error("second argument (exceptions) must be a sequence"));
|
||||
}
|
||||
|
||||
// Check for None
|
||||
if exceptions_arg.is(&vm.ctx.none) {
|
||||
return Err(vm.new_type_error("second argument (exceptions) must be a sequence"));
|
||||
}
|
||||
exceptions_arg.try_sequence(vm).map_err(|_| {
|
||||
vm.new_type_error("second argument (exceptions) must be a sequence")
|
||||
})?;
|
||||
|
||||
let exceptions: Vec<PyObjectRef> = exceptions_arg.try_to_value(vm).map_err(|_| {
|
||||
vm.new_type_error("second argument (exceptions) must be a sequence")
|
||||
|
||||
@@ -988,6 +988,9 @@ impl ExceptionZoo {
|
||||
|
||||
extend_exception!(PyImportError, ctx, excs.import_error, {
|
||||
"msg" => ctx.new_readonly_getset("msg", excs.import_error, make_arg_getter(0)),
|
||||
"name" => ctx.none(),
|
||||
"path" => ctx.none(),
|
||||
"name_from" => ctx.none(),
|
||||
});
|
||||
extend_exception!(PyModuleNotFoundError, ctx, excs.module_not_found_error);
|
||||
|
||||
@@ -1908,10 +1911,11 @@ pub(super) mod types {
|
||||
#[pymethod]
|
||||
fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef {
|
||||
let obj = exc.as_object().to_owned();
|
||||
let mut result: Vec<PyObjectRef> = vec![
|
||||
obj.class().to_owned().into(),
|
||||
vm.new_tuple((exc.get_arg(0).unwrap(),)).into(),
|
||||
];
|
||||
let args: PyObjectRef = match exc.get_arg(0) {
|
||||
Some(arg) => vm.new_tuple((arg,)).into(),
|
||||
None => exc.args().into(),
|
||||
};
|
||||
let mut result: Vec<PyObjectRef> = vec![obj.class().to_owned().into(), args];
|
||||
|
||||
if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) {
|
||||
result.push(dict.into());
|
||||
@@ -1938,10 +1942,21 @@ pub(super) mod types {
|
||||
)));
|
||||
}
|
||||
|
||||
let dict = crate::builtins::object::object_get_dict(zelf.clone(), vm)?;
|
||||
dict.set_item("name", vm.unwrap_or_none(name), vm)?;
|
||||
dict.set_item("path", vm.unwrap_or_none(path), vm)?;
|
||||
dict.set_item("name_from", vm.unwrap_or_none(name_from), vm)?;
|
||||
if let Some(name) = name {
|
||||
zelf.set_attr("name", name, vm)?;
|
||||
} else if let Some(dict) = zelf.dict() {
|
||||
dict.del_item("name", vm).ok();
|
||||
}
|
||||
if let Some(path) = path {
|
||||
zelf.set_attr("path", path, vm)?;
|
||||
} else if let Some(dict) = zelf.dict() {
|
||||
dict.del_item("path", vm).ok();
|
||||
}
|
||||
if let Some(name_from) = name_from {
|
||||
zelf.set_attr("name_from", name_from, vm)?;
|
||||
} else if let Some(dict) = zelf.dict() {
|
||||
dict.del_item("name_from", vm).ok();
|
||||
}
|
||||
PyBaseException::slot_init(zelf, args, vm)
|
||||
}
|
||||
|
||||
|
||||
@@ -269,13 +269,16 @@ cfg_select! {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for PyAtomicRef<T> {
|
||||
impl<T> fmt::Debug for PyAtomicRef<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "PyAtomicRef(")?;
|
||||
// The stored pointer is a `Py<T>` — the full object, header included —
|
||||
// as `Deref`, `load_raw` and `swap` all read it. Formatting it as a
|
||||
// bare payload would skip the header and print misaligned bytes.
|
||||
unsafe {
|
||||
self.inner
|
||||
.load(Ordering::Relaxed)
|
||||
.cast::<T>()
|
||||
.cast::<PyObject>()
|
||||
.as_ref()
|
||||
.fmt(f)
|
||||
}?;
|
||||
|
||||
@@ -16,7 +16,11 @@ where
|
||||
|
||||
unsafe impl<O: Borrow<PyObject>> Traverse for PyIter<O> {
|
||||
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
|
||||
self.0.borrow().traverse(tracer_fn);
|
||||
// Report the iterator itself, not its referents: an owner holding a
|
||||
// `PyIter` owns the iterator object, and reporting what the iterator
|
||||
// points at instead leaves the iterator's own reference unaccounted
|
||||
// for, so a cycle running through it is never collected.
|
||||
tracer_fn(self.0.borrow());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
PyType, PyTypeRef, PyUtf8Str, int::check_int_to_str_digits, pystr::AsPyStr,
|
||||
},
|
||||
common::{hash::PyHash, str::to_ascii},
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
convert::ToPyObject,
|
||||
dict_inner::DictKey,
|
||||
function::{Either, FuncArgs, PyArithmeticValue, PySetterValue},
|
||||
object::PyPayload,
|
||||
@@ -694,7 +694,7 @@ impl PyObject {
|
||||
|
||||
pub fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> {
|
||||
if let Some(hash) = self.class().slots.hash.load() {
|
||||
return hash(self, vm);
|
||||
return vm.with_recursion("while hashing", || hash(self, vm));
|
||||
}
|
||||
|
||||
Err(vm.new_type_error(format!("unhashable type: '{}'", self.class().name())))
|
||||
@@ -741,8 +741,8 @@ impl PyObject {
|
||||
} else {
|
||||
if self.class().fast_issubclass(vm.ctx.types.type_type) {
|
||||
if self.is(vm.ctx.types.type_type) {
|
||||
return PyGenericAlias::from_args(self.class().to_owned(), needle, vm)
|
||||
.to_pyresult(vm);
|
||||
let alias = PyGenericAlias::from_args(self.class().to_owned(), needle, vm)?;
|
||||
return Ok(alias.to_pyobject(vm));
|
||||
}
|
||||
|
||||
if let Some(class_getitem) =
|
||||
|
||||
@@ -104,7 +104,12 @@ where
|
||||
return Err(vm.new_memory_error(""));
|
||||
}
|
||||
|
||||
let mut v = Vec::with_capacity(n * self.as_ref().len());
|
||||
let total = n
|
||||
.checked_mul(self.as_ref().len())
|
||||
.ok_or_else(|| vm.new_memory_error(""))?;
|
||||
let mut v = Vec::new();
|
||||
v.try_reserve_exact(total)
|
||||
.map_err(|_| vm.new_memory_error(""))?;
|
||||
for _ in 0..n {
|
||||
v.extend_from_slice(self.as_ref());
|
||||
}
|
||||
|
||||
@@ -1718,12 +1718,16 @@ fn populate_field_types(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
FieldType::ListOf(name) => {
|
||||
let elem = resolve_node(name);
|
||||
let args = PyTuple::new_ref(vec![elem], &vm.ctx);
|
||||
PyGenericAlias::new(list_type.clone(), args, false, vm).to_pyobject(vm)
|
||||
PyGenericAlias::new(list_type.clone(), args, false, vm)
|
||||
.expect("static field types are not nested, so no recursion is possible")
|
||||
.to_pyobject(vm)
|
||||
}
|
||||
FieldType::ListOfBuiltin(name) => {
|
||||
let elem = resolve_builtin(name);
|
||||
let args = PyTuple::new_ref(vec![elem], &vm.ctx);
|
||||
PyGenericAlias::new(list_type.clone(), args, false, vm).to_pyobject(vm)
|
||||
PyGenericAlias::new(list_type.clone(), args, false, vm)
|
||||
.expect("static field types are not nested, so no recursion is possible")
|
||||
.to_pyobject(vm)
|
||||
}
|
||||
FieldType::Optional(name) => {
|
||||
let base = resolve_node(name);
|
||||
|
||||
@@ -13,6 +13,7 @@ mod _collections {
|
||||
convert::ToPyObject,
|
||||
function::{FuncArgs, KwArgs, OptionalArg, PyComparisonValue},
|
||||
iter::PyExactSizeIterator,
|
||||
object::{Traverse, TraverseFn},
|
||||
protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
|
||||
recursion::ReprGuard,
|
||||
sequence::{MutObjectSequenceOp, OptionalRangeArgs},
|
||||
@@ -22,13 +23,19 @@ mod _collections {
|
||||
Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter,
|
||||
},
|
||||
utils::collection_repr,
|
||||
vm::MAX_MEMORY_SIZE,
|
||||
};
|
||||
use alloc::collections::VecDeque;
|
||||
use core::cmp::max;
|
||||
use core::{cmp::max, mem::size_of};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(module = "collections", name = "deque", unhashable = true)]
|
||||
#[pyclass(
|
||||
module = "collections",
|
||||
name = "deque",
|
||||
unhashable = true,
|
||||
traverse = "manual"
|
||||
)]
|
||||
#[derive(Debug, Default, PyPayload)]
|
||||
struct PyDeque {
|
||||
deque: PyRwLock<VecDeque<PyObjectRef>>,
|
||||
@@ -36,6 +43,21 @@ mod _collections {
|
||||
state: AtomicCell<usize>, // incremented whenever the indices move
|
||||
}
|
||||
|
||||
// SAFETY: Traverse visits each owned Python reference at most once.
|
||||
unsafe impl Traverse for PyDeque {
|
||||
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
|
||||
if let Some(deque) = self.deque.try_read_recursive() {
|
||||
for obj in deque.iter() {
|
||||
obj.traverse(tracer_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
|
||||
out.extend(self.deque.get_mut().drain(..));
|
||||
}
|
||||
}
|
||||
|
||||
type PyDequeRef = PyRef<PyDeque>;
|
||||
|
||||
#[derive(FromArgs)]
|
||||
@@ -318,6 +340,10 @@ mod _collections {
|
||||
let deque = self.borrow_deque();
|
||||
let n = vm.check_repeat_or_overflow_error(deque.len(), n)?;
|
||||
let mul_len = n * deque.len();
|
||||
let result_len = self.maxlen.map_or(mul_len, |maxlen| mul_len.min(maxlen));
|
||||
if n > 1 && result_len.saturating_mul(size_of::<PyObjectRef>()) >= MAX_MEMORY_SIZE {
|
||||
return Err(vm.new_memory_error(""));
|
||||
}
|
||||
let iter = deque.iter().cycle().take(mul_len);
|
||||
let skipped = self
|
||||
.maxlen
|
||||
@@ -400,7 +426,7 @@ mod _collections {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -576,9 +602,10 @@ mod _collections {
|
||||
let closing_part = zelf
|
||||
.maxlen
|
||||
.map_or_else(|| "]".to_owned(), |maxlen| format!("], maxlen={maxlen}"));
|
||||
let empty = format!("{class_name}([{closing_part})");
|
||||
|
||||
if zelf.__len__() == 0 {
|
||||
return Ok(vm.ctx.new_str(format!("{class_name}([{closing_part})")));
|
||||
return Ok(vm.ctx.new_str(empty));
|
||||
}
|
||||
|
||||
if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
|
||||
@@ -586,6 +613,7 @@ mod _collections {
|
||||
Some(&class_name),
|
||||
"[",
|
||||
&closing_part,
|
||||
&empty,
|
||||
deque.iter(),
|
||||
vm,
|
||||
)?))
|
||||
@@ -753,7 +781,8 @@ mod _collections {
|
||||
module = "collections",
|
||||
name = "defaultdict",
|
||||
base = PyDict,
|
||||
unhashable = true
|
||||
unhashable = true,
|
||||
traverse = "manual"
|
||||
)]
|
||||
#[derive(Debug, Default)]
|
||||
struct PyDefaultDict {
|
||||
@@ -761,6 +790,21 @@ mod _collections {
|
||||
default_factory: PyRwLock<Option<PyObjectRef>>,
|
||||
}
|
||||
|
||||
// SAFETY: Traverse visits each owned Python reference at most once.
|
||||
unsafe impl Traverse for PyDefaultDict {
|
||||
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
|
||||
self.dict.traverse(tracer_fn);
|
||||
self.default_factory.traverse(tracer_fn);
|
||||
}
|
||||
|
||||
fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
|
||||
Traverse::clear(&mut self.dict, out);
|
||||
if let Some(factory) = self.default_factory.get_mut().take() {
|
||||
out.push(factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
with(AsMapping, AsNumber, Constructor, Initializer, Representable),
|
||||
flags(BASETYPE, MAPPING, HAS_DICT)
|
||||
|
||||
@@ -141,7 +141,6 @@ pub(crate) mod _ctypes {
|
||||
ffi_value_from_type_code(code.encode_utf8(&mut buf), bytes)
|
||||
}
|
||||
super::CArgValue::Int(v) => FfiValue::I32(*v),
|
||||
super::CArgValue::Double(v) => FfiValue::F64(*v),
|
||||
super::CArgValue::Pointer(v) => FfiValue::Pointer(*v),
|
||||
// 'V' aggregates format via the object-address default arm below.
|
||||
super::CArgValue::Aggregate { .. } => FfiValue::Pointer(0),
|
||||
|
||||
@@ -511,7 +511,11 @@ impl AsMapping for PyCArray {
|
||||
)]
|
||||
impl PyCArray {
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
fn __class_getitem__(
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
@@ -990,13 +994,19 @@ impl PyCArray {
|
||||
let (range, step, slice_len) = sat_slice.adjust_indices(length);
|
||||
|
||||
// other_len = PySequence_Length(value);
|
||||
let items: Vec<PyObjectRef> = vm.extract_elements_with(&value, Ok)?;
|
||||
let other_len = items.len();
|
||||
// Size the operand before consuming it so an unbounded iterable is
|
||||
// rejected without being materialized.
|
||||
let other_len = value
|
||||
.sequence_unchecked()
|
||||
.length(vm)
|
||||
.map_err(|_| vm.new_value_error("Can only assign sequence of same size"))?;
|
||||
|
||||
if other_len != slice_len {
|
||||
return Err(vm.new_value_error("Can only assign sequence of same size"));
|
||||
}
|
||||
|
||||
let items: Vec<PyObjectRef> = vm.extract_elements_with(&value, Ok)?;
|
||||
|
||||
// Use SaturatedSliceIter for correct index iteration (handles negative step)
|
||||
let iter = SaturatedSliceIter::from_adjust_indices(range, step, slice_len);
|
||||
|
||||
|
||||
@@ -1939,7 +1939,7 @@ fn struct_union_paramfunc(obj: &PyObject, stg_info: &StgInfo, _vm: &VirtualMachi
|
||||
|
||||
/// A foreign-call argument in a form the unified `call` entry point accepts: a
|
||||
/// simple-typed scalar (its ctypes code plus a native-endian bytes snapshot),
|
||||
/// an untyped int/float, or an address. Any object whose memory an address
|
||||
/// an untyped int, or an address. Any object whose memory an address
|
||||
/// refers to is kept alive by the enclosing `Argument`/`CArgObject`, not here.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CArgValue {
|
||||
@@ -1947,8 +1947,6 @@ pub enum CArgValue {
|
||||
Typed { code: char, bytes: Vec<u8> },
|
||||
/// Untyped Python int (ConvParam default: C int).
|
||||
Int(i32),
|
||||
/// Untyped Python float (ConvParam default: C double).
|
||||
Double(f64),
|
||||
/// Address-valued argument (pointer decay, byref, buffer copies, NULL = 0).
|
||||
Pointer(usize),
|
||||
/// By-value aggregate: its call layout plus a snapshot of its bytes.
|
||||
@@ -1985,7 +1983,6 @@ impl CArgValue {
|
||||
buffer: bytes,
|
||||
},
|
||||
Self::Int(value) => CallArg::Int(*value),
|
||||
Self::Double(value) => CallArg::Double(*value),
|
||||
Self::Pointer(value) => CallArg::Pointer(*value),
|
||||
Self::Aggregate { layout, bytes } => CallArg::Aggregate {
|
||||
layout,
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyBytes, PyDict, PyStr, PyTuple, PyType, PyTypeRef},
|
||||
builtins::{PyBytes, PyDict, PyInt, PyStr, PyTuple, PyType, PyTypeRef},
|
||||
class::StaticType,
|
||||
function::FuncArgs,
|
||||
protocol::{BufferDescriptor, PyBuffer, PyNumberMethods},
|
||||
@@ -171,7 +171,10 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
}
|
||||
|
||||
// 10. Python int -> i32 (default integer type)
|
||||
if let Ok(int_val) = value.try_int(vm) {
|
||||
// PyLong_Check: only an int (or a subclass) converts. Going through
|
||||
// `__int__` would accept a float and pass its truncated value where the
|
||||
// callee expects a pointer.
|
||||
if let Some(int_val) = value.downcast_ref::<PyInt>() {
|
||||
let val = int_val.as_bigint().to_i32().unwrap_or(0);
|
||||
return Ok(Argument {
|
||||
keep: None,
|
||||
@@ -179,15 +182,7 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
});
|
||||
}
|
||||
|
||||
// 11. Python float -> f64
|
||||
if let Ok(float_val) = value.try_float(vm) {
|
||||
return Ok(Argument {
|
||||
keep: None,
|
||||
value: CArgValue::Double(float_val.to_f64()),
|
||||
});
|
||||
}
|
||||
|
||||
// 12. Check _as_parameter_ attribute
|
||||
// 11. Check _as_parameter_ attribute
|
||||
if let Ok(as_param) = value.get_attr("_as_parameter_", vm) {
|
||||
return conv_param(&as_param, vm);
|
||||
}
|
||||
@@ -939,6 +934,23 @@ struct CallInfo {
|
||||
ret: RetSpec,
|
||||
}
|
||||
|
||||
fn extract_arg_types(argtypes: &PyObject, vm: &VirtualMachine) -> PyResult<Vec<PyTypeRef>> {
|
||||
let error = || vm.new_type_error("_argtypes_ must be a sequence of types");
|
||||
let sequence = argtypes.try_sequence(vm).map_err(|_| error())?;
|
||||
let length = sequence.length(vm).map_err(|_| error())?;
|
||||
let mut types = Vec::new();
|
||||
types
|
||||
.try_reserve(length)
|
||||
.map_err(|_| vm.new_memory_error(""))?;
|
||||
|
||||
for index in 0..length {
|
||||
let item = sequence.get_item(index as isize, vm).map_err(|_| error())?;
|
||||
types.push(item.downcast::<PyType>().map_err(|_| error())?);
|
||||
}
|
||||
|
||||
Ok(types)
|
||||
}
|
||||
|
||||
/// Determine how to retrieve the return value from restype, reproducing the
|
||||
/// prior `ffi_return_type` + `is_pointer_return` dispatch.
|
||||
fn compute_ret_spec(
|
||||
@@ -1007,13 +1019,7 @@ fn extract_call_info(zelf: &Py<PyCFuncPtr>, vm: &VirtualMachine) -> PyResult<Cal
|
||||
let explicit_arg_types: Option<Vec<PyTypeRef>> =
|
||||
if let Some(argtypes_obj) = zelf.argtypes.read().as_ref() {
|
||||
if !vm.is_none(argtypes_obj) {
|
||||
Some(
|
||||
argtypes_obj
|
||||
.try_to_value::<Vec<PyObjectRef>>(vm)?
|
||||
.into_iter()
|
||||
.filter_map(|obj| obj.downcast::<PyType>().ok())
|
||||
.collect(),
|
||||
)
|
||||
Some(extract_arg_types(argtypes_obj, vm)?)
|
||||
} else {
|
||||
None // argtypes is None -> use ConvParam
|
||||
}
|
||||
@@ -1023,13 +1029,7 @@ fn extract_call_info(zelf: &Py<PyCFuncPtr>, vm: &VirtualMachine) -> PyResult<Cal
|
||||
.get_attr(vm.ctx.intern_str("_argtypes_"))
|
||||
&& !vm.is_none(&class_argtypes)
|
||||
{
|
||||
Some(
|
||||
class_argtypes
|
||||
.try_to_value::<Vec<PyObjectRef>>(vm)?
|
||||
.into_iter()
|
||||
.filter_map(|obj| obj.downcast::<PyType>().ok())
|
||||
.collect(),
|
||||
)
|
||||
Some(extract_arg_types(&class_argtypes, vm)?)
|
||||
} else {
|
||||
None // No argtypes -> use ConvParam
|
||||
};
|
||||
@@ -1944,14 +1944,7 @@ impl PyCThunk {
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Self> {
|
||||
let arg_type_vec: Vec<PyTypeRef> = match arg_types {
|
||||
Some(args) if !vm.is_none(&args) => args
|
||||
.try_to_value::<Vec<PyObjectRef>>(vm)?
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
item.downcast::<PyType>()
|
||||
.map_err(|_| vm.new_type_error("_argtypes_ must be a sequence of types"))
|
||||
})
|
||||
.collect::<PyResult<Vec<_>>>()?,
|
||||
Some(args) if !vm.is_none(&args) => extract_arg_types(&args, vm)?,
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
|
||||
@@ -668,7 +668,7 @@ impl PyCPointer {
|
||||
let ptr_val = if vm.is_none(value) {
|
||||
0usize
|
||||
} else if let Ok(int_val) = value.try_index(vm) {
|
||||
int_val.as_bigint().to_usize().unwrap_or(0)
|
||||
super::simple::bigint_to_i128_wrapping(int_val.as_bigint()) as usize
|
||||
} else {
|
||||
return Err(vm.new_type_error("bytes/string or integer address expected"));
|
||||
};
|
||||
@@ -684,12 +684,13 @@ impl PyCPointer {
|
||||
// Use write_unaligned for safety on strict-alignment architectures
|
||||
if let Ok(int_val) = value.try_int(vm) {
|
||||
let i = int_val.as_bigint();
|
||||
let wrapped = super::simple::bigint_to_i128_wrapping(i);
|
||||
let bytes;
|
||||
let write_value = match size {
|
||||
1 => AddressWriteValue::U8(i.to_u8().expect("int too large")),
|
||||
2 => AddressWriteValue::I16(i.to_i16().expect("int too large")),
|
||||
4 => AddressWriteValue::I32(i.to_i32().expect("int too large")),
|
||||
8 => AddressWriteValue::I64(i.to_i64().expect("int too large")),
|
||||
1 => AddressWriteValue::U8(wrapped as u8),
|
||||
2 => AddressWriteValue::I16(wrapped as i16),
|
||||
4 => AddressWriteValue::I32(wrapped as i32),
|
||||
8 => AddressWriteValue::I64(wrapped as i64),
|
||||
_ => {
|
||||
bytes = i.to_signed_bytes_le();
|
||||
AddressWriteValue::Bytes(&bytes)
|
||||
|
||||
@@ -72,6 +72,17 @@ fn new_simple_type(
|
||||
Ok(PyCSimple(PyCData::from_bytes(zeroed_bytes(size), None)))
|
||||
}
|
||||
|
||||
pub(super) fn bigint_to_i128_wrapping(value: &malachite_bigint::BigInt) -> i128 {
|
||||
let bytes = value.to_signed_bytes_le();
|
||||
let fill = bytes
|
||||
.last()
|
||||
.map_or(0, |byte| if *byte & 0x80 == 0 { 0 } else { u8::MAX });
|
||||
let mut wrapped = [fill; 16];
|
||||
let len = bytes.len().min(wrapped.len());
|
||||
wrapped[..len].copy_from_slice(&bytes[..len]);
|
||||
i128::from_le_bytes(wrapped)
|
||||
}
|
||||
|
||||
fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResult {
|
||||
match _type_ {
|
||||
"c" => {
|
||||
@@ -756,7 +767,7 @@ fn value_to_bytes_endian(
|
||||
"b" => {
|
||||
// c_byte - signed char (1 byte)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -764,7 +775,7 @@ fn value_to_bytes_endian(
|
||||
"B" => {
|
||||
// c_ubyte - unsigned char (1 byte)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -772,7 +783,7 @@ fn value_to_bytes_endian(
|
||||
"h" => {
|
||||
// c_short (2 bytes)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -780,7 +791,7 @@ fn value_to_bytes_endian(
|
||||
"H" => {
|
||||
// c_ushort (2 bytes)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -788,7 +799,7 @@ fn value_to_bytes_endian(
|
||||
"i" => {
|
||||
// c_int (4 bytes)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -796,7 +807,7 @@ fn value_to_bytes_endian(
|
||||
"I" => {
|
||||
// c_uint (4 bytes)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -804,7 +815,7 @@ fn value_to_bytes_endian(
|
||||
"l" => {
|
||||
// c_long (platform dependent)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -812,7 +823,7 @@ fn value_to_bytes_endian(
|
||||
"L" => {
|
||||
// c_ulong (platform dependent)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -820,7 +831,7 @@ fn value_to_bytes_endian(
|
||||
"q" => {
|
||||
// c_longlong (8 bytes)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -828,7 +839,7 @@ fn value_to_bytes_endian(
|
||||
"Q" => {
|
||||
// c_ulonglong (8 bytes)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large"))
|
||||
SimpleStorageValue::Signed(bigint_to_i128_wrapping(int_val.as_bigint()))
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
}
|
||||
@@ -889,10 +900,7 @@ fn value_to_bytes_endian(
|
||||
"P" => {
|
||||
// c_void_p - pointer type (platform pointer size)
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
let v = int_val
|
||||
.as_bigint()
|
||||
.to_usize()
|
||||
.expect("int too large for pointer");
|
||||
let v = bigint_to_i128_wrapping(int_val.as_bigint()) as usize;
|
||||
SimpleStorageValue::Pointer(v)
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
@@ -902,10 +910,7 @@ fn value_to_bytes_endian(
|
||||
// c_char_p - pointer to char (stores pointer value from int)
|
||||
// PyBytes case is handled in slot_new/set_value with make_z_buffer()
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
let v = int_val
|
||||
.as_bigint()
|
||||
.to_usize()
|
||||
.expect("int too large for pointer");
|
||||
let v = bigint_to_i128_wrapping(int_val.as_bigint()) as usize;
|
||||
SimpleStorageValue::Pointer(v)
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
@@ -915,10 +920,7 @@ fn value_to_bytes_endian(
|
||||
// c_wchar_p - pointer to wchar_t (stores pointer value from int)
|
||||
// PyStr case is handled in slot_new/set_value with make_wchar_buffer()
|
||||
if let Ok(int_val) = value.try_index(vm) {
|
||||
let v = int_val
|
||||
.as_bigint()
|
||||
.to_usize()
|
||||
.expect("int too large for pointer");
|
||||
let v = bigint_to_i128_wrapping(int_val.as_bigint()) as usize;
|
||||
SimpleStorageValue::Pointer(v)
|
||||
} else {
|
||||
SimpleStorageValue::Zero
|
||||
|
||||
@@ -302,7 +302,7 @@ mod _functools {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ mod _imp {
|
||||
PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyBytesRef, PyCode, PyMemoryView, PyModule, PyStrRef, PyUtf8StrRef},
|
||||
convert::TryFromBorrowedObject,
|
||||
function::OptionalArg,
|
||||
function::{FuncArgs, OptionalArg},
|
||||
import, version,
|
||||
};
|
||||
|
||||
@@ -320,14 +320,16 @@ mod _imp {
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[pyfunction]
|
||||
fn find_frozen(
|
||||
name: PyUtf8StrRef,
|
||||
withdata: OptionalArg<bool>,
|
||||
args: FuncArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<(Option<PyRef<PyMemoryView>>, bool, Option<PyStrRef>)>> {
|
||||
if withdata.into_option().is_some() {
|
||||
// this is keyword-only argument in CPython
|
||||
unimplemented!();
|
||||
if args.args.len() > 1 {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"find_frozen() takes exactly 1 positional argument ({} given)",
|
||||
args.args.len()
|
||||
)));
|
||||
}
|
||||
let (name,): (PyUtf8StrRef,) = args.bind(vm)?;
|
||||
|
||||
let name_str = name.as_str();
|
||||
let info = match super::find_frozen(name_str, vm) {
|
||||
|
||||
@@ -498,7 +498,7 @@ mod _sre {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -597,7 +597,7 @@ mod _sre {
|
||||
regs: Vec<(isize, isize)>,
|
||||
}
|
||||
|
||||
#[pyclass(with(AsMapping, Representable))]
|
||||
#[pyclass(with(AsMapping, Representable), flags(DISALLOW_INSTANTIATION))]
|
||||
impl Match {
|
||||
pub(crate) fn new(state: &mut State, pattern: PyRef<Pattern>, string: PyObjectRef) -> Self {
|
||||
let string_position = state.cursor.position;
|
||||
@@ -844,7 +844,7 @@ mod _sre {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,17 @@ pub(crate) mod decl {
|
||||
};
|
||||
|
||||
#[pyfunction]
|
||||
pub(crate) fn _idfunc(args: FuncArgs, _vm: &VirtualMachine) -> PyObjectRef {
|
||||
args.args[0].clone()
|
||||
pub(crate) fn _idfunc(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
if !args.kwargs.is_empty() {
|
||||
return Err(vm.new_type_error("_typing._idfunc() takes no keyword arguments"));
|
||||
}
|
||||
if args.args.len() != 1 {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"_typing._idfunc() takes exactly one argument ({} given)",
|
||||
args.args.len()
|
||||
)));
|
||||
}
|
||||
Ok(args.args[0].clone())
|
||||
}
|
||||
|
||||
#[pyfunction(name = "override")]
|
||||
@@ -288,7 +297,7 @@ pub(crate) mod decl {
|
||||
PyTuple::new_ref(vec![args], &vm.ctx)
|
||||
};
|
||||
let origin: PyObjectRef = zelf.as_object().to_owned();
|
||||
Ok(PyGenericAlias::new(origin, args_tuple, false, vm).into_pyobject(vm))
|
||||
Ok(PyGenericAlias::new(origin, args_tuple, false, vm)?.into_pyobject(vm))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
|
||||
@@ -341,6 +341,7 @@ mod builtins {
|
||||
};
|
||||
match &source {
|
||||
ArgStrOrBytesLike::Str(source) => {
|
||||
let source = source.try_as_utf8(vm)?.as_str();
|
||||
if source.as_bytes().contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
@@ -548,13 +549,14 @@ mod builtins {
|
||||
Either::A(either) => {
|
||||
let source = match &either {
|
||||
ArgStrOrBytesLike::Str(source) => {
|
||||
let source = source.try_as_utf8(vm)?.as_str();
|
||||
if source.as_bytes().contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code string cannot contain null bytes".into(),
|
||||
));
|
||||
}
|
||||
let source = source.expect_str().trim_start_matches([' ', '\t']);
|
||||
let source = source.trim_start_matches([' ', '\t']);
|
||||
audit_compile_source(vm, source.as_bytes(), "<string>")?;
|
||||
source.to_owned()
|
||||
}
|
||||
@@ -597,6 +599,7 @@ mod builtins {
|
||||
}
|
||||
let source = match &either {
|
||||
ArgStrOrBytesLike::Str(source) => {
|
||||
let source = source.try_as_utf8(vm)?.as_str();
|
||||
if source.as_bytes().contains(&0) {
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
@@ -604,7 +607,7 @@ mod builtins {
|
||||
));
|
||||
}
|
||||
audit_compile_source(vm, source.as_bytes(), "<string>")?;
|
||||
source.expect_str().to_owned()
|
||||
source.to_owned()
|
||||
}
|
||||
ArgStrOrBytesLike::Buf(source) => {
|
||||
let source: &[u8] = &source.borrow_buf();
|
||||
|
||||
@@ -26,7 +26,7 @@ mod decl {
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "chain")]
|
||||
#[pyclass(name = "chain", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsChain {
|
||||
source: PyRwLock<Option<PyIter>>,
|
||||
@@ -64,7 +64,7 @@ mod decl {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "compress")]
|
||||
#[pyclass(name = "compress", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsCompress {
|
||||
data: PyIter,
|
||||
@@ -166,7 +166,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "count")]
|
||||
#[pyclass(name = "count", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsCount {
|
||||
cur: PyRwLock<PyObjectRef>,
|
||||
@@ -237,11 +237,12 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "cycle")]
|
||||
#[pyclass(name = "cycle", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsCycle {
|
||||
iter: PyIter,
|
||||
saved: PyRwLock<Vec<PyObjectRef>>,
|
||||
#[pytraverse(skip)]
|
||||
index: AtomicCell<usize>,
|
||||
}
|
||||
|
||||
@@ -273,11 +274,15 @@ mod decl {
|
||||
return Ok(PyIterReturn::StopIteration(None));
|
||||
}
|
||||
|
||||
let last_index = zelf.index.fetch_add(1);
|
||||
|
||||
if last_index >= saved.len() - 1 {
|
||||
zelf.index.store(0);
|
||||
}
|
||||
// Advance and wrap in a single atomic step. A separate
|
||||
// fetch_add followed by a reset lets a second thread observe
|
||||
// an index past the end of `saved`.
|
||||
let last_index = match zelf.index.fetch_update(|index| {
|
||||
let next = index + 1;
|
||||
Some(if next < saved.len() { next } else { 0 })
|
||||
}) {
|
||||
Ok(index) | Err(index) => index,
|
||||
};
|
||||
|
||||
saved[last_index].clone()
|
||||
};
|
||||
@@ -287,10 +292,11 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "repeat")]
|
||||
#[pyclass(name = "repeat", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsRepeat {
|
||||
object: PyObjectRef,
|
||||
#[pytraverse(skip)]
|
||||
times: Option<PyRwLock<usize>>,
|
||||
}
|
||||
|
||||
@@ -365,7 +371,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "starmap")]
|
||||
#[pyclass(name = "starmap", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsStarmap {
|
||||
function: PyObjectRef,
|
||||
@@ -412,11 +418,12 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "takewhile")]
|
||||
#[pyclass(name = "takewhile", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsTakewhile {
|
||||
predicate: PyObjectRef,
|
||||
iterable: PyIter,
|
||||
#[pytraverse(skip)]
|
||||
stop_flag: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
@@ -474,11 +481,12 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "dropwhile")]
|
||||
#[pyclass(name = "dropwhile", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsDropwhile {
|
||||
predicate: PyObjectRef,
|
||||
iterable: PyIter,
|
||||
#[pytraverse(skip)]
|
||||
start_flag: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
@@ -533,11 +541,13 @@ mod decl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Traverse)]
|
||||
struct GroupByState {
|
||||
current_value: Option<PyObjectRef>,
|
||||
current_key: Option<PyObjectRef>,
|
||||
#[pytraverse(skip)]
|
||||
next_group: bool,
|
||||
#[pytraverse(skip)]
|
||||
grouper: Option<PyWeakRef<PyItertoolsGrouper>>,
|
||||
}
|
||||
|
||||
@@ -561,7 +571,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "groupby")]
|
||||
#[pyclass(name = "groupby", traverse)]
|
||||
#[derive(PyPayload)]
|
||||
struct PyItertoolsGroupBy {
|
||||
iterable: PyIter,
|
||||
@@ -661,7 +671,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "_grouper")]
|
||||
#[pyclass(name = "_grouper", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsGrouper {
|
||||
groupby: PyRef<PyItertoolsGroupBy>,
|
||||
@@ -703,13 +713,17 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "islice")]
|
||||
#[pyclass(name = "islice", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsIslice {
|
||||
iterable: PyIter,
|
||||
#[pytraverse(skip)]
|
||||
cur: AtomicCell<usize>,
|
||||
#[pytraverse(skip)]
|
||||
next: AtomicCell<usize>,
|
||||
#[pytraverse(skip)]
|
||||
stop: Option<usize>,
|
||||
#[pytraverse(skip)]
|
||||
step: usize,
|
||||
}
|
||||
|
||||
@@ -828,7 +842,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "filterfalse")]
|
||||
#[pyclass(name = "filterfalse", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsFilterFalse {
|
||||
predicate: PyObjectRef,
|
||||
@@ -887,7 +901,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "accumulate")]
|
||||
#[pyclass(name = "accumulate", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsAccumulate {
|
||||
iterable: PyIter,
|
||||
@@ -1053,7 +1067,7 @@ mod decl {
|
||||
#[pymethod]
|
||||
fn __copy__(&self) -> Self {
|
||||
Self {
|
||||
tee_data: PyRc::clone(&self.tee_data),
|
||||
tee_data: self.tee_data.clone(),
|
||||
index: AtomicCell::new(self.index.load()),
|
||||
}
|
||||
}
|
||||
@@ -1068,12 +1082,15 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "product")]
|
||||
#[pyclass(name = "product", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsProduct {
|
||||
pools: Vec<Vec<PyObjectRef>>,
|
||||
#[pytraverse(skip)]
|
||||
idxs: PyRwLock<Vec<usize>>,
|
||||
#[pytraverse(skip)]
|
||||
cur: AtomicCell<usize>,
|
||||
#[pytraverse(skip)]
|
||||
stop: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
@@ -1169,13 +1186,16 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "combinations")]
|
||||
#[pyclass(name = "combinations", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsCombinations {
|
||||
pool: Vec<PyObjectRef>,
|
||||
#[pytraverse(skip)]
|
||||
indices: PyRwLock<Vec<usize>>,
|
||||
result: PyRwLock<Option<Vec<PyObjectRef>>>,
|
||||
#[pytraverse(skip)]
|
||||
r: AtomicCell<usize>,
|
||||
#[pytraverse(skip)]
|
||||
exhausted: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
@@ -1201,13 +1221,21 @@ mod decl {
|
||||
if r.is_negative() {
|
||||
return Err(vm.new_value_error("r must be non-negative"));
|
||||
}
|
||||
let r = r.to_usize().unwrap();
|
||||
let r = r.to_isize().ok_or_else(|| {
|
||||
vm.new_overflow_error("Python int too large to convert to C ssize_t")
|
||||
})? as usize;
|
||||
|
||||
let n = pool.len();
|
||||
|
||||
let mut indices = Vec::new();
|
||||
indices
|
||||
.try_reserve_exact(r)
|
||||
.map_err(|_| vm.new_memory_error(""))?;
|
||||
indices.extend(0..r);
|
||||
|
||||
Ok(Self {
|
||||
pool,
|
||||
indices: PyRwLock::new((0..r).collect()),
|
||||
indices: PyRwLock::new(indices),
|
||||
result: PyRwLock::new(None),
|
||||
r: AtomicCell::new(r),
|
||||
exhausted: AtomicCell::new(r > n),
|
||||
@@ -1280,12 +1308,15 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "combinations_with_replacement")]
|
||||
#[pyclass(name = "combinations_with_replacement", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsCombinationsWithReplacement {
|
||||
pool: Vec<PyObjectRef>,
|
||||
#[pytraverse(skip)]
|
||||
indices: PyRwLock<Vec<usize>>,
|
||||
#[pytraverse(skip)]
|
||||
r: AtomicCell<usize>,
|
||||
#[pytraverse(skip)]
|
||||
exhausted: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
@@ -1302,13 +1333,21 @@ mod decl {
|
||||
if r.is_negative() {
|
||||
return Err(vm.new_value_error("r must be non-negative"));
|
||||
}
|
||||
let r = r.to_usize().unwrap();
|
||||
let r = r.to_isize().ok_or_else(|| {
|
||||
vm.new_overflow_error("Python int too large to convert to C ssize_t")
|
||||
})? as usize;
|
||||
|
||||
let n = pool.len();
|
||||
|
||||
let mut indices = Vec::new();
|
||||
indices
|
||||
.try_reserve_exact(r)
|
||||
.map_err(|_| vm.new_memory_error(""))?;
|
||||
indices.resize(r, 0);
|
||||
|
||||
Ok(Self {
|
||||
pool,
|
||||
indices: PyRwLock::new(vec![0; r]),
|
||||
indices: PyRwLock::new(indices),
|
||||
r: AtomicCell::new(r),
|
||||
exhausted: AtomicCell::new(n == 0 && r > 0),
|
||||
})
|
||||
@@ -1366,15 +1405,20 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "permutations")]
|
||||
#[pyclass(name = "permutations", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsPermutations {
|
||||
pool: Vec<PyObjectRef>, // Collected input iterable
|
||||
indices: PyRwLock<Vec<usize>>, // One index per element in pool
|
||||
cycles: PyRwLock<Vec<usize>>, // One rollover counter per element in the result
|
||||
pool: Vec<PyObjectRef>, // Collected input iterable
|
||||
#[pytraverse(skip)]
|
||||
indices: PyRwLock<Vec<usize>>, // One index per element in pool
|
||||
#[pytraverse(skip)]
|
||||
cycles: PyRwLock<Vec<usize>>, // One rollover counter per element in the result
|
||||
#[pytraverse(skip)]
|
||||
result: PyRwLock<Option<Vec<usize>>>, // Indexes of the most recently returned result
|
||||
r: AtomicCell<usize>, // Size of result tuple
|
||||
exhausted: AtomicCell<bool>, // Set when the iterator is exhausted
|
||||
#[pytraverse(skip)]
|
||||
r: AtomicCell<usize>, // Size of result tuple
|
||||
#[pytraverse(skip)]
|
||||
exhausted: AtomicCell<bool>, // Set when the iterator is exhausted
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
@@ -1408,7 +1452,9 @@ mod decl {
|
||||
if val.is_negative() {
|
||||
return Err(vm.new_value_error("r must be non-negative"));
|
||||
}
|
||||
val.to_usize().unwrap()
|
||||
val.to_isize().ok_or_else(|| {
|
||||
vm.new_overflow_error("Python int too large to convert to C ssize_t")
|
||||
})? as usize
|
||||
}
|
||||
None => n,
|
||||
};
|
||||
@@ -1524,7 +1570,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "zip_longest")]
|
||||
#[pyclass(name = "zip_longest", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsZipLongest {
|
||||
iterators: Vec<PyIter>,
|
||||
@@ -1562,7 +1608,7 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "pairwise")]
|
||||
#[pyclass(name = "pairwise", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsPairwise {
|
||||
iterator: PyIter,
|
||||
@@ -1611,12 +1657,15 @@ mod decl {
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "batched")]
|
||||
#[pyclass(name = "batched", traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PyItertoolsBatched {
|
||||
#[pytraverse(skip)]
|
||||
exhausted: AtomicCell<bool>,
|
||||
iterable: PyIter,
|
||||
#[pytraverse(skip)]
|
||||
n: AtomicCell<usize>,
|
||||
#[pytraverse(skip)]
|
||||
strict: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,10 @@ pub(super) mod _os {
|
||||
ospath::{OsPath, OsPathOrFd, OutputMode, PathConverter},
|
||||
protocol::PyIterReturn,
|
||||
recursion::ReprGuard,
|
||||
types::{Destructor, IterNext, Iterable, PyStructSequence, Representable, SelfIter},
|
||||
types::{
|
||||
Destructor, IterNext, Iterable, PyStructSequence, PyStructSequenceData, Representable,
|
||||
SelfIter,
|
||||
},
|
||||
vm::VirtualMachine,
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
@@ -883,7 +886,7 @@ pub(super) mod _os {
|
||||
cls: PyTypeRef,
|
||||
args: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyGenericAlias {
|
||||
) -> PyResult<PyGenericAlias> {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
}
|
||||
|
||||
@@ -1314,8 +1317,12 @@ pub(super) mod _os {
|
||||
impl PyStatResult {
|
||||
#[pyslot]
|
||||
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let seq: PyObjectRef = args.bind(vm)?;
|
||||
let result = crate::types::struct_sequence_new(cls.clone(), seq, vm)?;
|
||||
let result = crate::types::struct_sequence_new(
|
||||
cls.clone(),
|
||||
args.bind(vm)?,
|
||||
StatResultData::OPTIONAL_FIELD_NAMES,
|
||||
vm,
|
||||
)?;
|
||||
let tuple = result.downcast_ref::<PyTuple>().unwrap();
|
||||
let mut items: Vec<PyObjectRef> = tuple.to_vec();
|
||||
|
||||
@@ -1964,8 +1971,12 @@ pub(super) mod _os {
|
||||
impl PyStatvfsResult {
|
||||
#[pyslot]
|
||||
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let seq: PyObjectRef = args.bind(vm)?;
|
||||
crate::types::struct_sequence_new(cls, seq, vm)
|
||||
crate::types::struct_sequence_new(
|
||||
cls,
|
||||
args.bind(vm)?,
|
||||
StatvfsResultData::OPTIONAL_FIELD_NAMES,
|
||||
vm,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1333,14 +1333,13 @@ pub mod module {
|
||||
// cfg from nix
|
||||
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
|
||||
#[pyfunction]
|
||||
fn setgroups(
|
||||
group_ids: crate::function::ArgIterable<RawGid>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let gids = group_ids
|
||||
.iter(vm)?
|
||||
.map(|gid| gid.map(|gid| gid.0))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
fn setgroups(group_ids: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
group_ids
|
||||
.try_sequence(vm)
|
||||
.map_err(|_| vm.new_type_error("setgroups argument must be a sequence"))?;
|
||||
let gids = vm.extract_elements_with(&group_ids, |gid| {
|
||||
RawGid::try_from_object(vm, gid).map(|gid| gid.0)
|
||||
})?;
|
||||
rustpython_host_env::posix::setgroups_raw(&gids).map_err(|err| err.into_pyexception(vm))
|
||||
}
|
||||
|
||||
@@ -1400,7 +1399,7 @@ pub mod module {
|
||||
#[pyarg(positional)]
|
||||
path: OsPath,
|
||||
#[pyarg(positional)]
|
||||
args: crate::function::ArgIterable<OsPath>,
|
||||
args: PyObjectRef,
|
||||
#[pyarg(positional)]
|
||||
env: Option<crate::function::ArgMapping>,
|
||||
#[pyarg(named, default)]
|
||||
@@ -1439,6 +1438,19 @@ pub mod module {
|
||||
.into_cstring(vm)
|
||||
.map_err(|_| vm.new_value_error("path should not have nul bytes"))?;
|
||||
|
||||
let function_name = if spawnp {
|
||||
"posix_spawnp"
|
||||
} else {
|
||||
"posix_spawn"
|
||||
};
|
||||
if !self.args.fast_isinstance(vm.ctx.types.list_type)
|
||||
&& !self.args.fast_isinstance(vm.ctx.types.tuple_type)
|
||||
{
|
||||
return Err(
|
||||
vm.new_type_error(format!("{function_name}: argv must be a tuple or list"))
|
||||
);
|
||||
}
|
||||
|
||||
let mut file_actions = Vec::new();
|
||||
if let Some(it) = self.file_actions {
|
||||
for action in it.iter(vm)? {
|
||||
@@ -1478,20 +1490,21 @@ pub mod module {
|
||||
}
|
||||
}
|
||||
|
||||
let setsigdef = self
|
||||
.setsigdef
|
||||
.map(|sigs| {
|
||||
let sigs = sigs.iter(vm)?.collect::<PyResult<Vec<_>>>()?;
|
||||
for &sig in &sigs {
|
||||
if !rustpython_host_env::posix::validate_posix_spawn_signal(sig) {
|
||||
return Err(
|
||||
vm.new_value_error(format!("signal number {sig} out of range"))
|
||||
);
|
||||
}
|
||||
let collect_signals = |sigs: crate::function::ArgIterable<i32>| {
|
||||
let mut collected = Vec::new();
|
||||
for sig in sigs.iter(vm)? {
|
||||
let sig = sig?;
|
||||
if !rustpython_host_env::posix::validate_posix_spawn_signal(sig) {
|
||||
return Err(vm.new_value_error(format!("signal number {sig} out of range")));
|
||||
}
|
||||
Ok(sigs)
|
||||
})
|
||||
.transpose()?;
|
||||
if !collected.contains(&sig) {
|
||||
collected.push(sig);
|
||||
}
|
||||
}
|
||||
Ok(collected)
|
||||
};
|
||||
|
||||
let setsigdef = self.setsigdef.map(&collect_signals).transpose()?;
|
||||
|
||||
if let Some(_scheduler) = self.scheduler {
|
||||
// TODO: Implement scheduler parameter handling
|
||||
@@ -1507,29 +1520,12 @@ pub mod module {
|
||||
));
|
||||
}
|
||||
|
||||
let setsigmask = self
|
||||
.setsigmask
|
||||
.map(|sigs| {
|
||||
let sigs = sigs.iter(vm)?.collect::<PyResult<Vec<_>>>()?;
|
||||
for &sig in &sigs {
|
||||
if !rustpython_host_env::posix::validate_posix_spawn_signal(sig) {
|
||||
return Err(
|
||||
vm.new_value_error(format!("signal number {sig} out of range"))
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(sigs)
|
||||
})
|
||||
.transpose()?;
|
||||
let setsigmask = self.setsigmask.map(collect_signals).transpose()?;
|
||||
|
||||
let args: Vec<CString> = self
|
||||
.args
|
||||
.iter(vm)?
|
||||
.map(|res| {
|
||||
CString::new(res?.into_bytes())
|
||||
.map_err(|_| vm.new_value_error("path should not have nul bytes"))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
let args = vm.extract_elements_with(&self.args, |arg| {
|
||||
CString::new(OsPath::try_from_object(vm, arg)?.into_bytes())
|
||||
.map_err(|_| vm.new_value_error("path should not have nul bytes"))
|
||||
})?;
|
||||
let env = if let Some(env_dict) = self.env {
|
||||
envp_from_dict(env_dict, vm)?
|
||||
} else {
|
||||
|
||||
@@ -888,8 +888,7 @@ pub mod sys {
|
||||
format!("Ignoring unimportable $PYTHONBREAKPOINT: \"{env_var}\"",),
|
||||
0,
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
Ok(vm.ctx.none())
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ mod decl {
|
||||
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyStrRef, PyTypeRef},
|
||||
function::{Either, FuncArgs, OptionalArg},
|
||||
types::{PyStructSequence, struct_sequence_new},
|
||||
types::{PyStructSequence, PyStructSequenceData, struct_sequence_new},
|
||||
};
|
||||
#[cfg(any(unix, windows))]
|
||||
use crate::{
|
||||
@@ -811,8 +811,12 @@ mod decl {
|
||||
impl PyStructTime {
|
||||
#[pyslot]
|
||||
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let (seq, _dict): (PyObjectRef, OptionalArg<PyObjectRef>) = args.bind(vm)?;
|
||||
struct_sequence_new(cls, seq, vm)
|
||||
struct_sequence_new(
|
||||
cls,
|
||||
args.bind(vm)?,
|
||||
StructTimeData::OPTIONAL_FIELD_NAMES,
|
||||
vm,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,5 +5,7 @@ mod zoo;
|
||||
|
||||
pub use slot::*;
|
||||
pub use slot_defs::{SLOT_DEFS, SlotAccessor, SlotDef};
|
||||
pub use structseq::{PyStructSequence, PyStructSequenceData, struct_sequence_new};
|
||||
pub use structseq::{
|
||||
PyStructSequence, PyStructSequenceData, StructSequenceNewArgs, struct_sequence_new,
|
||||
};
|
||||
pub(crate) use zoo::TypeZoo;
|
||||
@@ -2,9 +2,11 @@ use crate::common::lock::LazyLock;
|
||||
use crate::common::wtf8::Wtf8;
|
||||
use crate::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func,
|
||||
builtins::{PyBaseExceptionRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef},
|
||||
builtins::{
|
||||
PyBaseExceptionRef, PyDict, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef,
|
||||
},
|
||||
class::{PyClassImpl, StaticType},
|
||||
function::{Either, FuncArgs, PyComparisonValue, PyMethodDef, PyMethodFlags},
|
||||
function::{Either, FuncArgs, OptionalArg, PyComparisonValue, PyMethodDef, PyMethodFlags},
|
||||
iter::PyExactSizeIterator,
|
||||
protocol::{PyMappingMethods, PySequenceMethods},
|
||||
sliceable::{SequenceIndex, SliceableSequenceOp},
|
||||
@@ -21,12 +23,35 @@ const DEFAULT_STRUCTSEQ_REDUCE: PyMethodDef = PyMethodDef::new_const(
|
||||
None,
|
||||
);
|
||||
|
||||
/// The arguments every struct sequence constructor takes.
|
||||
#[derive(FromArgs)]
|
||||
pub struct StructSequenceNewArgs {
|
||||
#[pyarg(any)]
|
||||
pub sequence: PyObjectRef,
|
||||
#[pyarg(any, optional)]
|
||||
pub dict: OptionalArg<PyObjectRef>,
|
||||
}
|
||||
|
||||
/// Create a new struct sequence instance from a sequence.
|
||||
///
|
||||
/// `dict` supplies the hidden fields — the ones past `n_sequence_fields`, named
|
||||
/// by `hidden_field_names` in order — that the sequence itself did not cover. It
|
||||
/// may not name a field the sequence already supplied, nor one that does not
|
||||
/// exist.
|
||||
///
|
||||
/// The class must have `n_sequence_fields` and `n_fields` attributes set
|
||||
/// (done automatically by `PyStructSequence::extend_pyclass`).
|
||||
pub fn struct_sequence_new(cls: PyTypeRef, seq: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
pub fn struct_sequence_new(
|
||||
cls: PyTypeRef,
|
||||
args: StructSequenceNewArgs,
|
||||
hidden_field_names: &[&str],
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
// = structseq_new
|
||||
let StructSequenceNewArgs {
|
||||
sequence: seq,
|
||||
dict,
|
||||
} = args;
|
||||
|
||||
#[cold]
|
||||
fn length_error(
|
||||
@@ -60,6 +85,16 @@ pub fn struct_sequence_new(cls: PyTypeRef, seq: PyObjectRef, vm: &VirtualMachine
|
||||
.ok_or_else(|| vm.new_type_error("missing n_fields attribute"))?
|
||||
.try_into_value(vm)?;
|
||||
|
||||
let dict = match dict {
|
||||
OptionalArg::Missing => None,
|
||||
OptionalArg::Present(dict) => Some(dict.downcast::<PyDict>().map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"{}() takes a dict as second arg, if any",
|
||||
cls.slot_name()
|
||||
))
|
||||
})?),
|
||||
};
|
||||
|
||||
let seq: Vec<PyObjectRef> = seq.try_into_value(vm)?;
|
||||
let len = seq.len();
|
||||
|
||||
@@ -67,10 +102,30 @@ pub fn struct_sequence_new(cls: PyTypeRef, seq: PyObjectRef, vm: &VirtualMachine
|
||||
return Err(length_error(&cls.slot_name(), min_len, max_len, len, vm));
|
||||
}
|
||||
|
||||
// Copy items and pad with None
|
||||
// Copy items and pad the hidden fields the sequence did not cover with None.
|
||||
let mut items = seq;
|
||||
items.resize_with(max_len, || vm.ctx.none());
|
||||
|
||||
// Fill those padded slots from `dict`. Every key has to land in one of them:
|
||||
// a key naming a field the sequence already supplied, or no field at all,
|
||||
// would otherwise be silently dropped.
|
||||
if let Some(dict) = dict.filter(|dict| !dict.is_empty()) {
|
||||
let mut found = 0;
|
||||
let names = hidden_field_names.get(len - min_len..).unwrap_or(&[]);
|
||||
for (item, name) in items[len..].iter_mut().zip(names) {
|
||||
if let Some(value) = dict.get_item_opt(*name, vm)? {
|
||||
*item = value;
|
||||
found += 1;
|
||||
}
|
||||
}
|
||||
if found != dict.__len__() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}() got duplicate or unexpected field name(s)",
|
||||
cls.slot_name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
.into_ref_with_type(vm, cls)
|
||||
.map(Into::into)
|
||||
@@ -193,6 +248,11 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
/// The Data struct that provides field definitions.
|
||||
type Data: PyStructSequenceData;
|
||||
|
||||
#[pyslot]
|
||||
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
struct_sequence_new(cls, args.bind(vm)?, Self::Data::OPTIONAL_FIELD_NAMES, vm)
|
||||
}
|
||||
|
||||
/// Convert a Data struct into a PyStructSequence instance.
|
||||
fn from_data(data: Self::Data, vm: &VirtualMachine) -> PyTupleRef {
|
||||
let tuple =
|
||||
|
||||
@@ -33,6 +33,7 @@ pub(crate) fn collection_repr<'a, I>(
|
||||
class_name: Option<&str>,
|
||||
prefix: &str,
|
||||
suffix: &str,
|
||||
empty: &str,
|
||||
iter: I,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Wtf8Buf>
|
||||
@@ -47,10 +48,9 @@ where
|
||||
repr.push_str(prefix);
|
||||
{
|
||||
let mut parts_iter = iter.map(|o| o.repr(vm));
|
||||
let first = parts_iter
|
||||
.next()
|
||||
.transpose()?
|
||||
.expect("this is not called for empty collection");
|
||||
let Some(first) = parts_iter.next().transpose()? else {
|
||||
return Ok(Wtf8Buf::from(empty));
|
||||
};
|
||||
repr.push_wtf8(first.as_wtf8());
|
||||
for part in parts_iter {
|
||||
repr.push_str(", ");
|
||||
|
||||
@@ -145,3 +145,8 @@ except _IncompleteInputError as exc:
|
||||
assert exc.args[0] == "incomplete input", repr(exc)
|
||||
else:
|
||||
raise AssertionError("expected _IncompleteInputError")
|
||||
|
||||
# The source is encoded before it is parsed, so a lone surrogate has to be
|
||||
# reported rather than assumed away.
|
||||
with assert_raises(UnicodeEncodeError):
|
||||
compile(chr(0xD800), "<test>", "eval")
|
||||
@@ -1,3 +1,5 @@
|
||||
from testutils import assert_raises
|
||||
|
||||
assert 3 == eval("1+2")
|
||||
|
||||
code = compile("5+3", "x.py", "eval")
|
||||
@@ -75,3 +77,8 @@ try:
|
||||
assert False, "eval with code containing free variables should fail"
|
||||
except NameError as e:
|
||||
pass
|
||||
|
||||
# The source is encoded before it is parsed, so a lone surrogate has to be
|
||||
# reported rather than assumed away.
|
||||
with assert_raises(UnicodeEncodeError):
|
||||
eval(chr(0xD800))
|
||||
@@ -1,4 +1,5 @@
|
||||
import builtins
|
||||
import itertools
|
||||
import pickle
|
||||
import platform
|
||||
import sys
|
||||
@@ -393,3 +394,19 @@ except* ValueError as err:
|
||||
assert err.exceptions[0].args == ("x",)
|
||||
else:
|
||||
assert False, "except* handler did not run"
|
||||
|
||||
# The exceptions argument is a sequence, so an arbitrary iterable must be
|
||||
# rejected rather than drained.
|
||||
try:
|
||||
ExceptionGroup("m", itertools.count())
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
assert False, "ExceptionGroup accepted an unbounded iterable"
|
||||
|
||||
# ImportError.__reduce__ has to cope with the exception carrying no args.
|
||||
assert pickle.loads(pickle.dumps(ImportError())).args == ()
|
||||
restored = pickle.loads(pickle.dumps(ImportError("m", name="n", path="p")))
|
||||
assert restored.args == ("m",)
|
||||
assert restored.name == "n"
|
||||
assert restored.path == "p"
|
||||
@@ -1,3 +1,5 @@
|
||||
from testutils import assert_raises
|
||||
|
||||
exec("def square(x):\n return x * x\n")
|
||||
assert 16 == square(4) # noqa: F821
|
||||
|
||||
@@ -71,3 +73,8 @@ def f():
|
||||
|
||||
|
||||
f()
|
||||
|
||||
# The source is encoded before it is parsed, so a lone surrogate has to be
|
||||
# reported rather than assumed away.
|
||||
with assert_raises(UnicodeEncodeError):
|
||||
exec(chr(0xD800))
|
||||
@@ -1,3 +1,5 @@
|
||||
import sys
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
|
||||
@@ -28,3 +30,19 @@ with assert_raises(TypeError):
|
||||
|
||||
with assert_raises(TypeError):
|
||||
hash([])
|
||||
|
||||
# Hashing a deeply nested tuple must not run off the native stack: the hash
|
||||
# slot dispatch is what recurses, so that is where the depth is checked.
|
||||
|
||||
if sys.implementation.name == "rustpython":
|
||||
# CPython, which also runs this snippet, survives this depth unguarded.
|
||||
deep_tuple = ()
|
||||
for _ in range(sys.getrecursionlimit() * 2):
|
||||
deep_tuple = (deep_tuple,)
|
||||
with assert_raises(RecursionError):
|
||||
hash(deep_tuple)
|
||||
# a dict key and a set member are hashed on insertion, same dispatch
|
||||
with assert_raises(RecursionError):
|
||||
{deep_tuple: 1}
|
||||
with assert_raises(RecursionError):
|
||||
{deep_tuple}
|
||||
@@ -1,3 +1,5 @@
|
||||
import sys
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
x = [1, 2, 3]
|
||||
@@ -923,3 +925,8 @@ class poc:
|
||||
list1 = rewrite_list_eq([poc()])
|
||||
list1.remove(list1)
|
||||
assert list1 == []
|
||||
|
||||
# The repeat count is multiplied by the element size; a count that overflows
|
||||
# that product must raise instead of wrapping into a short allocation.
|
||||
with assert_raises(MemoryError):
|
||||
[1] * sys.maxsize
|
||||
@@ -1,3 +1,5 @@
|
||||
import sys
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
assert (1, 2) == (1, 2)
|
||||
@@ -93,3 +95,8 @@ assert (float("inf"), float("inf")) <= (float("inf"), float("inf"))
|
||||
assert (float("inf"), float("inf")) >= (float("inf"), float("inf"))
|
||||
assert not (float("inf"), float("inf")) < (float("inf"), float("inf"))
|
||||
assert not (float("inf"), float("inf")) > (float("inf"), float("inf"))
|
||||
|
||||
# The repeat count is multiplied by the element size; a count that overflows
|
||||
# that product must raise instead of wrapping into a short allocation.
|
||||
with assert_raises(MemoryError):
|
||||
(1,) * sys.maxsize
|
||||
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
from types import (
|
||||
AsyncGeneratorType,
|
||||
BuiltinFunctionType,
|
||||
@@ -62,3 +63,9 @@ for typ in reviter_types:
|
||||
for typ in internal_types:
|
||||
with assert_raises(TypeError):
|
||||
typ()
|
||||
|
||||
# a match object carries state that only the matcher can fill in
|
||||
with assert_raises(TypeError):
|
||||
re.Match()
|
||||
with assert_raises(TypeError):
|
||||
re.Match.__new__(re.Match)
|
||||
@@ -0,0 +1,52 @@
|
||||
"""The private _asyncio accessors, reached directly instead of through a loop.
|
||||
|
||||
CPython's _asyncio rejects every call below with "loop ... is not the running
|
||||
loop" before it gets anywhere, and does not expose _current_tasks at all, so
|
||||
these only run where they are reachable.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
if sys.implementation.name != "rustpython":
|
||||
sys.exit(0)
|
||||
|
||||
import _asyncio
|
||||
|
||||
|
||||
def _task():
|
||||
pass
|
||||
|
||||
|
||||
# The "already entered" message formats both tasks; a plain function used to be
|
||||
# formatted as the wrong type there.
|
||||
_asyncio._enter_task(0, _task)
|
||||
with assert_raises(RuntimeError) as cm:
|
||||
_asyncio._enter_task(0, _task)
|
||||
assert "Cannot enter into task" in str(cm.exception), cm.exception
|
||||
assert "<function _task at " in str(cm.exception), cm.exception
|
||||
_asyncio._leave_task(0, _task)
|
||||
|
||||
# The module-level task registry is writable from Python, so current_task() has
|
||||
# to cope with it holding something that is not a mapping.
|
||||
saved_current_tasks = _asyncio._current_tasks
|
||||
try:
|
||||
_asyncio._current_tasks = 42
|
||||
assert _asyncio.current_task(loop=object()) is None
|
||||
finally:
|
||||
_asyncio._current_tasks = saved_current_tasks
|
||||
|
||||
|
||||
class BadException(Exception):
|
||||
def __new__(cls, *args):
|
||||
return 42
|
||||
|
||||
|
||||
# throw() has to check what __new__ handed back before treating it as an
|
||||
# exception.
|
||||
future = _asyncio.Future(loop=object())
|
||||
with assert_raises(TypeError):
|
||||
future.__await__().throw(BadException)
|
||||
|
||||
print("ok")
|
||||
@@ -1,3 +1,4 @@
|
||||
import sys
|
||||
from collections import deque
|
||||
from typing import Deque
|
||||
|
||||
@@ -100,3 +101,10 @@ assert repr(D([1, 2, 3])) == "D([1, 2, 3])"
|
||||
|
||||
|
||||
assert_raises(ValueError, lambda: deque().index(10, 0, 10000000000000000000000000))
|
||||
|
||||
if sys.implementation.name == "rustpython":
|
||||
# The repeat count is multiplied by the length; a count that overflows that
|
||||
# product must be rejected up front. CPython instead appends block by block
|
||||
# until the allocator gives up, so it is left out of this check.
|
||||
with assert_raises(MemoryError):
|
||||
deque([0]) * sys.maxsize
|
||||
@@ -1,3 +1,4 @@
|
||||
import _csv
|
||||
import csv
|
||||
import io
|
||||
import sys
|
||||
@@ -344,3 +345,8 @@ def test_reader_skipinitialspace_preserves_quoted_spaces():
|
||||
|
||||
|
||||
test_reader_skipinitialspace_preserves_quoted_spaces()
|
||||
|
||||
# The reader is built with the default dialect, which it has to look up itself.
|
||||
|
||||
with assert_raises(StopIteration):
|
||||
next(_csv.reader([]))
|
||||
@@ -1,3 +1,5 @@
|
||||
import ctypes
|
||||
import itertools
|
||||
import os as _os
|
||||
import sys as _sys
|
||||
import types as _types
|
||||
@@ -425,4 +427,25 @@ else:
|
||||
|
||||
# print(get_win_folder_via_ctypes("CSIDL_DOWNLOADS"))
|
||||
|
||||
# A value wider than the C type is masked down to it instead of failing an
|
||||
# unchecked conversion.
|
||||
assert ctypes.c_char_p(2**64).value is None
|
||||
assert ctypes.c_int(2**64 + 7).value == 7
|
||||
buf = (ctypes.c_int * 1)()
|
||||
int_ptr = ctypes.cast(buf, ctypes.POINTER(ctypes.c_int))
|
||||
int_ptr[0] = 2**64 + 5
|
||||
assert int_ptr[0] == 5
|
||||
|
||||
# A slice assignment is length-checked against the slice, so the right-hand
|
||||
# side must not be drained first.
|
||||
array3 = (ctypes.c_int * 3)()
|
||||
try:
|
||||
array3[0:3] = itertools.count()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
assert False, "slice assignment accepted an unbounded iterable"
|
||||
array3[0:3] = [7, 8, 9]
|
||||
assert list(array3) == [7, 8, 9]
|
||||
|
||||
print("done")
|
||||
@@ -1,6 +1,7 @@
|
||||
# Exercises the migrated _ctypes foreign-call path (routed through the unified
|
||||
# host_env `call` entry point): scalar int/double arguments and returns,
|
||||
# pointer (c_char_p / c_void_p) returns, and a use_errno round-trip.
|
||||
# pointer (c_char_p / c_void_p) returns, a use_errno round-trip, and the
|
||||
# argument conversion an untyped call performs.
|
||||
#
|
||||
# Prints "OK" and exits 0; any failed assertion aborts. Output is identical
|
||||
# under CPython and RustPython on the same platform.
|
||||
@@ -61,4 +62,17 @@ set_errno(0)
|
||||
libc.strtol(b"9" * 40, None, 10)
|
||||
assert get_errno() == errno.ERANGE, (get_errno(), errno.ERANGE)
|
||||
|
||||
# 7. A float has no implicit conversion to an integer argument: converting it
|
||||
# would pass a truncated value where the callee expects an int or a pointer.
|
||||
libc.abs.argtypes = None
|
||||
for bad in (1.5, 0.0, 1e300):
|
||||
try:
|
||||
libc.abs(bad)
|
||||
except (TypeError, ctypes.ArgumentError):
|
||||
pass
|
||||
else:
|
||||
assert False, f"{bad!r} was accepted as an integer argument"
|
||||
assert libc.abs(-3) == 3
|
||||
assert libc.abs(True) == 1
|
||||
|
||||
print("OK")
|
||||
@@ -0,0 +1,66 @@
|
||||
"""The cycle collector has to walk the internal fields of containers and
|
||||
iterators.
|
||||
|
||||
Every type below is built into the cycle
|
||||
|
||||
node -> node.__dict__ -> wrapper -> container -> node
|
||||
|
||||
so the only path back to `node` runs through a field of the wrapper. A type
|
||||
that reports nothing while being traversed, or reports the objects it iterates
|
||||
instead of the iterator it holds, leaves its own reference unaccounted for: the
|
||||
cycle is then classified as reachable and `node` is never freed.
|
||||
"""
|
||||
|
||||
import gc
|
||||
import itertools
|
||||
import weakref
|
||||
from collections import defaultdict, deque
|
||||
|
||||
|
||||
class Node:
|
||||
pass
|
||||
|
||||
|
||||
def collects(wrap):
|
||||
"""Report whether the collector breaks the cycle built around wrap()."""
|
||||
|
||||
def build():
|
||||
container = []
|
||||
node = Node()
|
||||
container.append(node)
|
||||
node.held = wrap(container)
|
||||
return weakref.ref(node)
|
||||
|
||||
gc.collect()
|
||||
ref = build()
|
||||
gc.collect()
|
||||
return ref() is None
|
||||
|
||||
|
||||
# containers keeping their items in a field of their own
|
||||
assert collects(deque)
|
||||
assert collects(lambda c: defaultdict(int, {"k": c}))
|
||||
assert collects(lambda c: classmethod(lambda cls: c))
|
||||
|
||||
# iterators: the wrapper holds an iterator, and that iterator holds the
|
||||
# container
|
||||
assert collects(iter)
|
||||
assert collects(lambda c: map(str, c))
|
||||
assert collects(lambda c: filter(None, c))
|
||||
assert collects(lambda c: zip(c))
|
||||
assert collects(enumerate)
|
||||
assert collects(reversed)
|
||||
assert collects(itertools.chain)
|
||||
assert collects(itertools.cycle)
|
||||
assert collects(lambda c: itertools.islice(c, 5))
|
||||
assert collects(itertools.groupby)
|
||||
assert collects(itertools.accumulate)
|
||||
assert collects(lambda c: itertools.starmap(str, c))
|
||||
assert collects(lambda c: itertools.takewhile(bool, c))
|
||||
assert collects(lambda c: itertools.dropwhile(bool, c))
|
||||
assert collects(lambda c: itertools.filterfalse(None, c))
|
||||
assert collects(lambda c: itertools.compress(c, [1]))
|
||||
assert collects(lambda c: itertools.product(c))
|
||||
assert collects(lambda c: itertools.combinations(c, 1))
|
||||
|
||||
print("ok")
|
||||
@@ -1,3 +1,5 @@
|
||||
import _md5
|
||||
import _sha1
|
||||
import hashlib
|
||||
|
||||
# print(hashlib.md5)
|
||||
@@ -48,3 +50,9 @@ print(h.hexdigest())
|
||||
assert (
|
||||
h.hexdigest() == "25738bfe4cc104131e1b45bece4dfd4e7e1d6f0dffda1211e996e9d5d3b66e81"
|
||||
)
|
||||
|
||||
# The single-algorithm modules set up their own types rather than relying on
|
||||
# hashlib having done it.
|
||||
|
||||
assert _md5.md5(b"").hexdigest() == "d41d8cd98f00b204e9800998ecf8427e"
|
||||
assert _sha1.sha1(b"").hexdigest() == "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
||||
@@ -1,6 +1,8 @@
|
||||
import _imp
|
||||
import time as import_time
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
assert _imp.is_builtin("time") == True
|
||||
assert _imp.is_builtin("os") == False
|
||||
assert _imp.is_builtin("not existing module") == False
|
||||
@@ -29,3 +31,8 @@ _imp.get_frozen_object("__hello__")
|
||||
|
||||
hello = _imp.init_frozen("__hello__")
|
||||
assert hello.initialized == True
|
||||
|
||||
# withdata is keyword-only
|
||||
with assert_raises(TypeError):
|
||||
_imp.find_frozen("x", True)
|
||||
assert _imp.find_frozen("_this_module_does_not_exist_") is None
|
||||
@@ -524,3 +524,19 @@ assert next(it) == (1, 3)
|
||||
assert next(it) == (2, None)
|
||||
with assert_raises(StopIteration):
|
||||
next(it)
|
||||
|
||||
# r is an arbitrary Python int: one too large for an index must raise
|
||||
# OverflowError, and a representable one that cannot be allocated must raise
|
||||
# MemoryError.
|
||||
for factory in (
|
||||
itertools.combinations,
|
||||
itertools.combinations_with_replacement,
|
||||
itertools.permutations,
|
||||
):
|
||||
with assert_raises(OverflowError):
|
||||
factory(range(5), 2**64)
|
||||
|
||||
with assert_raises(MemoryError):
|
||||
itertools.combinations(range(5), 2**44)
|
||||
with assert_raises(MemoryError):
|
||||
itertools.combinations_with_replacement(range(5), 2**44)
|
||||
@@ -0,0 +1,22 @@
|
||||
import itertools
|
||||
import lzma
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
# A raw-format compressor needs the filter chain's length before it can build
|
||||
# it, so a filter argument that is not a sequence has to be rejected instead of
|
||||
# being drained.
|
||||
with assert_raises(TypeError):
|
||||
lzma.LZMACompressor(
|
||||
format=lzma.FORMAT_RAW,
|
||||
filters=({"id": lzma.FILTER_LZMA2} for _ in itertools.count()),
|
||||
)
|
||||
|
||||
compressor = lzma.LZMACompressor(
|
||||
format=lzma.FORMAT_RAW, filters=[{"id": lzma.FILTER_LZMA2}]
|
||||
)
|
||||
compressed = compressor.compress(b"data") + compressor.flush()
|
||||
decompressor = lzma.LZMADecompressor(
|
||||
format=lzma.FORMAT_RAW, filters=[{"id": lzma.FILTER_LZMA2}]
|
||||
)
|
||||
assert decompressor.decompress(compressed) == b"data"
|
||||
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import math
|
||||
|
||||
from testutils import assert_raises, skip_if_unsupported
|
||||
@@ -311,3 +312,9 @@ assert math.fmod(0.0, 3.0) == 0.0
|
||||
assert math.fmod(0.0, NINF) == 0.0
|
||||
|
||||
assert math.gamma(1) == 1.0
|
||||
|
||||
# sumprod compares the two lengths as it goes; it must not drain either
|
||||
# argument first.
|
||||
assert_raises(ValueError, lambda: math.sumprod(itertools.count(), [1, 2, 3]))
|
||||
assert_raises(ValueError, lambda: math.sumprod([1, 2, 3], itertools.count()))
|
||||
assert math.sumprod(iter([1, 2, 3]), iter([4, 5, 6])) == 32
|
||||
@@ -1,6 +1,19 @@
|
||||
import mmap
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
mapped = mmap.mmap(-1, 1)
|
||||
assert mapped.seekable()
|
||||
mapped.close()
|
||||
assert mapped.seekable()
|
||||
|
||||
mapped = mmap.mmap(-1, 10)
|
||||
# an inverted range finds nothing rather than being subtracted into a huge one
|
||||
assert mapped.find(b"x", 5, 2) == -1
|
||||
assert mapped.rfind(b"x", 5, 2) == -1
|
||||
# both offsets are bounds-checked before anything is copied
|
||||
with assert_raises(ValueError):
|
||||
mapped.move(20, 0, 1)
|
||||
with assert_raises(ValueError):
|
||||
mapped.move(0, 20, 1)
|
||||
mapped.close()
|
||||
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
@@ -528,3 +529,19 @@ assert os.access("nonexistent_file_12345", os.F_OK) is False
|
||||
assert os.access("nonexistent_file_12345", os.W_OK) is False
|
||||
assert os.access("README.md", os.F_OK) is True
|
||||
assert os.access("README.md", os.R_OK) is True
|
||||
|
||||
# argv and the group list are sequences; an arbitrary iterable must be rejected
|
||||
# rather than drained.
|
||||
if hasattr(os, "posix_spawn"):
|
||||
with assert_raises(TypeError):
|
||||
os.posix_spawn("/bin/true", map(str, itertools.count()), os.environ)
|
||||
if hasattr(os, "setgroups"):
|
||||
with assert_raises(TypeError):
|
||||
os.setgroups(itertools.count())
|
||||
|
||||
# The optional second argument fills the fields past the visible ones, and the
|
||||
# getters must not index past what __new__ stored.
|
||||
assert os.stat_result(tuple(range(10))).st_atime == 7
|
||||
assert os.stat_result(tuple(range(10)), {"st_atime": 1.5}).st_atime == 1.5
|
||||
with assert_raises(TypeError):
|
||||
os.stat_result(tuple(range(10)), ["st_atime"])
|
||||
@@ -12,3 +12,7 @@ with assert_raises(KeyError):
|
||||
fake_name = "fake_user"
|
||||
while pwd.getpwnam(fake_name):
|
||||
fake_name += "1"
|
||||
|
||||
# The field getters must not index a struct sequence that __new__ never filled.
|
||||
with assert_raises(TypeError):
|
||||
pwd.struct_passwd()
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
@@ -158,3 +159,18 @@ def test_getframemodulename():
|
||||
test_getframemodulename.__module__ = "awesome_module"
|
||||
|
||||
assert test_getframemodulename() == "awesome_module"
|
||||
|
||||
# An unimportable $PYTHONBREAKPOINT warns, and the hook has to survive that
|
||||
# warning being turned into an exception.
|
||||
saved_breakpoint_env = os.environ.get("PYTHONBREAKPOINT")
|
||||
os.environ["PYTHONBREAKPOINT"] = "nonexistent_xyz.foo"
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
with assert_raises(RuntimeWarning):
|
||||
sys.breakpointhook()
|
||||
finally:
|
||||
if saved_breakpoint_env is None:
|
||||
del os.environ["PYTHONBREAKPOINT"]
|
||||
else:
|
||||
os.environ["PYTHONBREAKPOINT"] = saved_breakpoint_env
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Stress itertools.cycle from several threads at once.
|
||||
|
||||
cycle() advances its index and wraps it back to zero when it reaches the end of
|
||||
the saved items. Doing that in two separate steps lets another thread observe
|
||||
the index past the end and read out of bounds, so the update has to be a single
|
||||
atomic step.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import threading
|
||||
|
||||
shared_cycle = itertools.cycle([1, 2, 3])
|
||||
|
||||
|
||||
def spin():
|
||||
for _ in range(20000):
|
||||
next(shared_cycle)
|
||||
|
||||
|
||||
threads = [threading.Thread(target=spin) for _ in range(4)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
print("ok")
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Stress set repr against concurrent mutation.
|
||||
|
||||
repr() checks that the set is non-empty and then reads its first element. The
|
||||
two steps are separate, so another thread can empty the set in between; the
|
||||
read has to cope with that rather than trusting the earlier check.
|
||||
|
||||
Threads that observe a mutation mid-iteration raise RuntimeError, which is a
|
||||
legitimate outcome here; a regression shows up as a crash instead.
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
shared_set = {1, 2, 3, 4, 5}
|
||||
stop = False
|
||||
|
||||
|
||||
def mutate():
|
||||
while not stop:
|
||||
try:
|
||||
shared_set.clear()
|
||||
shared_set.update({1, 2, 3})
|
||||
except RuntimeError: # changed size during iteration
|
||||
pass
|
||||
|
||||
|
||||
def read():
|
||||
for _ in range(20000):
|
||||
try:
|
||||
repr(shared_set)
|
||||
except RuntimeError: # changed size during iteration
|
||||
pass
|
||||
|
||||
|
||||
mutators = [threading.Thread(target=mutate) for _ in range(2)]
|
||||
readers = [threading.Thread(target=read) for _ in range(2)]
|
||||
for t in mutators + readers:
|
||||
t.start()
|
||||
for t in readers:
|
||||
t.join()
|
||||
stop = True
|
||||
for t in mutators:
|
||||
t.join()
|
||||
|
||||
print("ok")
|
||||
@@ -82,3 +82,16 @@ perf_elapsed = time.perf_counter() - perf_start
|
||||
|
||||
assert monotonic_elapsed >= 0.01
|
||||
assert perf_elapsed >= 0.01
|
||||
|
||||
# The optional second argument fills the fields that are not part of the
|
||||
# sequence.
|
||||
fields = (2024, 1, 2, 3, 4, 5, 6, 7, 0)
|
||||
assert time.struct_time(fields).tm_zone is None
|
||||
assert time.struct_time(fields, {"tm_zone": "UTC"}).tm_zone == "UTC"
|
||||
assert time.struct_time(fields, {"tm_gmtoff": 60}).tm_gmtoff == 60
|
||||
try:
|
||||
time.struct_time(fields, ["tm_zone", "UTC"])
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
assert False, "struct_time accepted a non-dict second argument"
|
||||
@@ -1,5 +1,9 @@
|
||||
import itertools
|
||||
import traceback
|
||||
|
||||
import _suggestions
|
||||
from testutils import assert_raises
|
||||
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError as ex:
|
||||
@@ -25,3 +29,10 @@ try:
|
||||
except ZeroDivisionError as ex2:
|
||||
tb = traceback.extract_tb(ex2.__traceback__)
|
||||
assert len(tb) == 1
|
||||
|
||||
# The candidate list backing "Did you mean" suggestions is a list; an arbitrary
|
||||
# iterable must be rejected rather than drained.
|
||||
|
||||
with assert_raises(TypeError):
|
||||
_suggestions._generate_suggestions(itertools.count(), "x")
|
||||
assert _suggestions._generate_suggestions(["value"], "valu") == "value"
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ast
|
||||
import platform
|
||||
import sys
|
||||
import types
|
||||
|
||||
from testutils import assert_raises
|
||||
@@ -34,3 +35,26 @@ def _run_missing_type_params_regression():
|
||||
|
||||
|
||||
_run_missing_type_params_regression()
|
||||
|
||||
if sys.implementation.name == "rustpython":
|
||||
# __parameters__ is computed when the alias is built, and the walk descends
|
||||
# into every list and tuple argument, so a self-referential or deeply
|
||||
# nested argument must be caught. CPython, which also runs this snippet,
|
||||
# does not walk into plain lists at all.
|
||||
self_referential = []
|
||||
self_referential.append(self_referential)
|
||||
with assert_raises(RecursionError):
|
||||
list[self_referential]
|
||||
|
||||
nested = [0]
|
||||
for _ in range(sys.getrecursionlimit() * 2):
|
||||
nested = [nested]
|
||||
with assert_raises(RecursionError):
|
||||
list[nested]
|
||||
|
||||
# hashing an alias walks the same shape
|
||||
deep_alias = int
|
||||
for _ in range(sys.getrecursionlimit() * 2):
|
||||
deep_alias = list[deep_alias]
|
||||
with assert_raises(RecursionError):
|
||||
hash(deep_alias)
|
||||
@@ -1,6 +1,9 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import TypeVar
|
||||
|
||||
import _typing
|
||||
from testutils import assert_raises
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@@ -35,3 +38,10 @@ class ClassWithUnionParams:
|
||||
|
||||
def method(self, value: Union[int, float]) -> Union[str, bytes]:
|
||||
return str(value)
|
||||
|
||||
|
||||
# _idfunc takes exactly one argument, checked before the argument is read.
|
||||
|
||||
assert _typing._idfunc(1) == 1
|
||||
with assert_raises(TypeError):
|
||||
_typing._idfunc()
|
||||
Reference in new issue
Block a user