mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Report bytes out-of-range elements as CPython does (#8679)
* Report `bytes` out-of-range elements as CPython does `collect_bytes` is shared by the `bytes` and `bytearray` entry points and hardcoded one element error, so `bytes([256])` said "byte must be in range(0, 256)" where CPython says "bytes". Every `bytearray` path does say "byte", so only the one caller was wrong. The element error is now a parameter, alongside the type error this function already varies per caller. Verified against CPython 3.14 for all seven paths. Assisted-by: Claude Code:claude-opus-5 * Keep the bytearray wording in PyByteArray_FromObject It called `bytes_from_object`, so the previous commit made it report "bytes must be in range(0, 256)" where CPython's `PyByteArray_FromObject` says "byte". It is the C-level bytearray constructor, so it now uses `bytearray_from_object`, which carries that wording and the constructor's unsized iteration. Assisted-by: Claude Code:claude-opus-5
This commit is contained in:
1 parent
521b75ec4a
commit
1345a82443
3 files changed
+44
-9
No files matched your search
@@ -3,7 +3,7 @@ use crate::object::define_py_check;
|
||||
use crate::pystate::with_vm;
|
||||
use core::ffi::c_char;
|
||||
use rustpython_vm::builtins::PyByteArray;
|
||||
use rustpython_vm::byte::bytes_from_object;
|
||||
use rustpython_vm::byte::bytearray_from_object;
|
||||
|
||||
define_py_check!(fn PyByteArray_Check, types.bytearray_type);
|
||||
|
||||
@@ -41,7 +41,7 @@ pub unsafe extern "C" fn PyByteArray_FromStringAndSize(
|
||||
pub unsafe extern "C" fn PyByteArray_FromObject(obj: *mut PyObject) -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
let obj = unsafe { &*obj };
|
||||
let data = bytes_from_object(vm, obj)?;
|
||||
let data = bytearray_from_object(vm, obj)?;
|
||||
Ok(vm.ctx.new_bytearray(data))
|
||||
})
|
||||
}
|
||||
|
||||
+23
-7
@@ -7,9 +7,15 @@ use crate::{
|
||||
protocol::{BufferFlags, PyBuffer},
|
||||
};
|
||||
|
||||
/// The element error `bytes` reports, which is the one entry point here that does
|
||||
/// not name a single byte: `bytes([256])` says "bytes must be in range(0, 256)"
|
||||
/// where every `bytearray` path says "byte".
|
||||
const BYTES_ELEMENT_ERROR: &str = "bytes must be in range(0, 256)";
|
||||
const BYTE_ELEMENT_ERROR: &str = "byte must be in range(0, 256)";
|
||||
|
||||
// PyBytes_FromObject
|
||||
pub fn bytes_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Vec<u8>> {
|
||||
collect_bytes(vm, obj, true, |name| {
|
||||
collect_bytes(vm, obj, true, BYTES_ELEMENT_ERROR, |name| {
|
||||
format!("cannot convert '{name}' object to bytes")
|
||||
})
|
||||
}
|
||||
@@ -18,7 +24,7 @@ pub fn bytes_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Vec<u8
|
||||
/// slice of one, which run the iterator without asking the object they were
|
||||
/// handed how long it is.
|
||||
pub fn bytearray_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Vec<u8>> {
|
||||
collect_bytes(vm, obj, false, |name| {
|
||||
collect_bytes(vm, obj, false, BYTE_ELEMENT_ERROR, |name| {
|
||||
format!("cannot convert '{name}' object to bytearray")
|
||||
})
|
||||
}
|
||||
@@ -26,17 +32,19 @@ pub fn bytearray_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Ve
|
||||
/// [`bytes_from_object`] for `bytearray_extend()`, which names what it was
|
||||
/// doing rather than what it was converting to.
|
||||
pub fn bytearray_extend_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Vec<u8>> {
|
||||
collect_bytes(vm, obj, true, |name| {
|
||||
collect_bytes(vm, obj, true, BYTE_ELEMENT_ERROR, |name| {
|
||||
format!("can't extend bytearray with {name}")
|
||||
})
|
||||
}
|
||||
|
||||
/// `measured` is whether the object is asked how long it is; `unusable` names,
|
||||
/// from the class name, what could not be done with one that is not iterable.
|
||||
/// `measured` is whether the object is asked how long it is; `element` is the error
|
||||
/// for a value outside `range(0, 256)`; `unusable` names, from the class name, what
|
||||
/// could not be done with one that is not iterable.
|
||||
fn collect_bytes(
|
||||
vm: &VirtualMachine,
|
||||
obj: &PyObject,
|
||||
measured: bool,
|
||||
element: &'static str,
|
||||
unusable: impl FnOnce(&str) -> String,
|
||||
) -> PyResult<Vec<u8>> {
|
||||
if obj.check_buffer() {
|
||||
@@ -52,7 +60,7 @@ fn collect_bytes(
|
||||
if cls.slots.iter.load().is_none() && !cls.has_attr(identifier!(vm, __getitem__)) {
|
||||
return Err(vm.new_type_error(unusable(&cls.name())));
|
||||
}
|
||||
let value = |x: PyObjectRef| value_from_object(vm, &x);
|
||||
let value = |x: PyObjectRef| value_from_object_with(vm, &x, element);
|
||||
let elements = if measured {
|
||||
vm.map_iterable_object_sized(obj, value)
|
||||
} else {
|
||||
@@ -67,8 +75,16 @@ fn collect_bytes(
|
||||
}
|
||||
|
||||
pub fn value_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<u8> {
|
||||
value_from_object_with(vm, obj, BYTE_ELEMENT_ERROR)
|
||||
}
|
||||
|
||||
fn value_from_object_with(
|
||||
vm: &VirtualMachine,
|
||||
obj: &PyObject,
|
||||
element: &'static str,
|
||||
) -> PyResult<u8> {
|
||||
obj.try_index(vm)?
|
||||
.as_bigint()
|
||||
.to_u8()
|
||||
.ok_or_else(|| vm.new_value_error("byte must be in range(0, 256)"))
|
||||
.ok_or_else(|| vm.new_value_error(element))
|
||||
}
|
||||
@@ -813,3 +813,22 @@ cannot(
|
||||
lambda: bytearray(b"ab").__setitem__(slice(0, 2), "ab"),
|
||||
"can assign only bytes, buffers, or iterables of ints in range(0, 256)",
|
||||
)
|
||||
|
||||
|
||||
def out_of_range(fn, message):
|
||||
try:
|
||||
fn()
|
||||
except ValueError as e:
|
||||
assert str(e) == message, e
|
||||
else:
|
||||
raise AssertionError(f"expected ValueError: {message}")
|
||||
|
||||
|
||||
# `bytes` is the one entry point that does not name a single byte, and both the
|
||||
# sized and unsized iterator paths report it that way.
|
||||
out_of_range(lambda: bytes([256]), "bytes must be in range(0, 256)")
|
||||
out_of_range(lambda: bytes(iter([256])), "bytes must be in range(0, 256)")
|
||||
out_of_range(lambda: bytes([-1]), "bytes must be in range(0, 256)")
|
||||
out_of_range(lambda: bytearray([256]), "byte must be in range(0, 256)")
|
||||
out_of_range(lambda: bytearray(iter([256])), "byte must be in range(0, 256)")
|
||||
out_of_range(lambda: bytearray().extend([256]), "byte must be in range(0, 256)")
|
||||
Reference in new issue
Block a user