mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Validate mmap assignments against current storage (#9071)
* Preserve ParamSpec bounds as in Python 3.14.8 Extract the backported bound-validation removal and align the CPython reference pin to 3.14.8 so both snippet interpreters exercise the same contract. Assisted-by: OpenAI Codex:model identifier unavailable * Align TextIOWrapper chunk-size deletion with Python 3.14.8 The 3.14.8 reference pin exposes the Argument Clinic deletion guard moved before initialization and attachment checks by CPython 90a1f02b7da0c356d10a23f1faee7dc063f887ed. Preserve assignment checks and align the exact deletion error, including subclass type names. Assisted-by: OpenAI Codex:model identifier unavailable * Validate mmap assignments against current storage Use the existing mapping guard for bounds and writes after argument conversion, including empty assignments. Match the Python 3.14.8 ordering while preserving export guards and ownership APIs. Assisted-by: OpenAI Codex:model identifier unavailable * Align mmap boundary regression with Python 3.14.8 Include the matching upstream GH-158027 test-data correction: use an integer RHS so the unchanged IndexError assertion checks index bounds after conversion. Preserve the runtime assignment and storage guards. Assisted-by: OpenAI Codex:model identifier unavailable --------- Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
This commit is contained in:
1 parent
8d27253fa2
commit
4dc50cc472
8 files changed
+112
-62
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
3.14.7
|
||||
3.14.8
|
||||
Vendored
+1
-1
@@ -73,7 +73,7 @@ class MmapTests(unittest.TestCase):
|
||||
|
||||
# Shouldn't crash on boundary (Issue #5292)
|
||||
self.assertRaises(IndexError, m.__getitem__, len(m))
|
||||
self.assertRaises(IndexError, m.__setitem__, len(m), b'\0')
|
||||
self.assertRaises(IndexError, m.__setitem__, len(m), 0)
|
||||
|
||||
# Modify the file's content
|
||||
m[0] = b'3'[0]
|
||||
|
||||
+25
-33
@@ -1455,17 +1455,15 @@ mod mmap {
|
||||
value: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let i: usize = i
|
||||
.wrapped_at(self.__len__())
|
||||
.ok_or_else(|| vm.new_index_error("mmap index out of range"))?;
|
||||
|
||||
let b = value_from_object(vm, value)?;
|
||||
|
||||
self.try_writable(vm, |mmap| {
|
||||
let i = i
|
||||
.wrapped_at(mmap.len())
|
||||
.ok_or_else(|| vm.new_index_error("mmap index out of range"))?;
|
||||
mmap[i] = b;
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
||||
fn setitem_by_slice(
|
||||
@@ -1474,43 +1472,37 @@ mod mmap {
|
||||
value: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let (range, step, slice_len) = slice.adjust_indices(self.__len__());
|
||||
|
||||
let bytes = bytes_from_object(vm, value)?;
|
||||
|
||||
if bytes.len() != slice_len {
|
||||
return Err(vm.new_index_error("mmap slice assignment is wrong size"));
|
||||
}
|
||||
self.try_writable(vm, |mmap| {
|
||||
let (range, step, slice_len) = slice.adjust_indices(mmap.len());
|
||||
|
||||
if slice_len == 0 {
|
||||
// do nothing
|
||||
Ok(())
|
||||
} else if step == 1 {
|
||||
self.try_writable(vm, |mmap| {
|
||||
if bytes.len() != slice_len {
|
||||
return Err(vm.new_index_error("mmap slice assignment is wrong size"));
|
||||
}
|
||||
|
||||
if slice_len == 0 {
|
||||
// do nothing
|
||||
} else if step == 1 {
|
||||
(&mut mmap[range])
|
||||
.write(&bytes)
|
||||
.map_err(|err| err.to_pyexception(vm))?;
|
||||
Ok(())
|
||||
})?
|
||||
} else {
|
||||
let mut bi = 0; // bytes index
|
||||
if step.is_negative() {
|
||||
for i in range.rev().step_by(step.unsigned_abs()) {
|
||||
self.try_writable(vm, |mmap| {
|
||||
mmap[i] = bytes[bi];
|
||||
})?;
|
||||
bi += 1;
|
||||
}
|
||||
} else {
|
||||
for i in range.step_by(step.unsigned_abs()) {
|
||||
self.try_writable(vm, |mmap| {
|
||||
let mut bi = 0; // bytes index
|
||||
if step.is_negative() {
|
||||
for i in range.rev().step_by(step.unsigned_abs()) {
|
||||
mmap[i] = bytes[bi];
|
||||
})?;
|
||||
bi += 1;
|
||||
bi += 1;
|
||||
}
|
||||
} else {
|
||||
for i in range.step_by(step.unsigned_abs()) {
|
||||
mmap[i] = bytes[bi];
|
||||
bi += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-19
@@ -3568,6 +3568,17 @@ mod _io {
|
||||
|
||||
#[pygetset(setter, name = "_CHUNK_SIZE")]
|
||||
fn set_chunksize(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let PySetterValue::Assign(object_value) = value else {
|
||||
let type_name = self.class().slot_name();
|
||||
let mut end = type_name.len().min(100);
|
||||
while !type_name.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
return Err(vm.new_attribute_error(format!(
|
||||
"attribute '_CHUNK_SIZE' of '{}' objects cannot be deleted",
|
||||
&type_name[..end]
|
||||
)));
|
||||
};
|
||||
{
|
||||
let textio = self.lock(vm)?;
|
||||
if self.buffer.deref().is_none() {
|
||||
@@ -3576,27 +3587,20 @@ mod _io {
|
||||
drop(textio);
|
||||
}
|
||||
|
||||
let chunk_size: isize = match value {
|
||||
PySetterValue::Assign(object_value) => {
|
||||
let integer = object_value.try_index(vm)?;
|
||||
let integer = object_value.try_index(vm)?;
|
||||
|
||||
integer.try_to_primitive::<isize>(vm).map_err(|_| {
|
||||
let class = object_value.class();
|
||||
let type_name = class.name();
|
||||
let mut end = type_name.len().min(200);
|
||||
while !type_name.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
vm.new_value_error(format!(
|
||||
"cannot fit '{}' into an index-sized integer",
|
||||
&type_name[..end]
|
||||
))
|
||||
})?
|
||||
let chunk_size = integer.try_to_primitive::<isize>(vm).map_err(|_| {
|
||||
let class = object_value.class();
|
||||
let type_name = class.name();
|
||||
let mut end = type_name.len().min(200);
|
||||
while !type_name.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
PySetterValue::Delete => {
|
||||
return Err(vm.new_attribute_error("cannot delete attribute"));
|
||||
}
|
||||
};
|
||||
vm.new_value_error(format!(
|
||||
"cannot fit '{}' into an index-sized integer",
|
||||
&type_name[..end]
|
||||
))
|
||||
})?;
|
||||
|
||||
if chunk_size <= 0 {
|
||||
return Err(vm.new_value_error("a strictly positive integer is required"));
|
||||
|
||||
@@ -583,10 +583,7 @@ pub(crate) mod typevar {
|
||||
return Err(vm.new_type_error("ParamSpec() takes at most 1 positional argument"));
|
||||
};
|
||||
|
||||
let bound = kwargs
|
||||
.swap_remove("bound")
|
||||
.map(|b| type_check(b, "Bound must be a type.", vm))
|
||||
.transpose()?;
|
||||
let bound = kwargs.swap_remove("bound");
|
||||
let covariant = kwargs
|
||||
.swap_remove("covariant")
|
||||
.map(|v| v.try_to_bool(vm))
|
||||
|
||||
@@ -166,10 +166,24 @@ expect_value_error(
|
||||
"underlying buffer has been detached",
|
||||
lambda: setattr(detached_textio, "_CHUNK_SIZE", 16),
|
||||
)
|
||||
expect_value_error(
|
||||
"underlying buffer has been detached",
|
||||
lambda: delattr(detached_textio, "_CHUNK_SIZE"),
|
||||
)
|
||||
for wrapper in (textio, uninitialized_textio, detached_textio):
|
||||
with assert_raises(AttributeError) as error:
|
||||
del wrapper._CHUNK_SIZE
|
||||
assert str(error.exception) == (
|
||||
"attribute '_CHUNK_SIZE' of '_io.TextIOWrapper' objects cannot be deleted"
|
||||
)
|
||||
|
||||
|
||||
for wrapper_name in ("ChunkWrapper", "€" * 34):
|
||||
wrapper_type = type(wrapper_name, (TextIOWrapper,), {})
|
||||
wrapper = wrapper_type(BytesIO())
|
||||
with assert_raises(AttributeError) as error:
|
||||
del wrapper._CHUNK_SIZE
|
||||
displayed_name = wrapper_name.encode("utf-8")[:100].decode("utf-8", "ignore")
|
||||
assert str(error.exception) == (
|
||||
f"attribute '_CHUNK_SIZE' of '{displayed_name}' objects cannot be deleted"
|
||||
)
|
||||
wrapper.close()
|
||||
|
||||
|
||||
long_type_name = "X" * 250
|
||||
|
||||
@@ -77,3 +77,41 @@ if sys.platform == "win32":
|
||||
with assert_raises(ValueError):
|
||||
mapped.resize(4)
|
||||
assert mapped.closed
|
||||
|
||||
|
||||
# Assignment validates current storage after argument conversion, even if empty.
|
||||
with mmap.mmap(-1, 4) as mapped:
|
||||
with assert_raises(ValueError):
|
||||
mapped[4] = 256
|
||||
mapped[0:0] = b""
|
||||
with assert_raises(ValueError):
|
||||
mapped[0:0] = b""
|
||||
|
||||
with mmap.mmap(-1, 4, access=mmap.ACCESS_READ) as mapped:
|
||||
with assert_raises(TypeError):
|
||||
mapped[0:0] = b""
|
||||
|
||||
|
||||
class CloseOnIndex:
|
||||
def __init__(self, mapped):
|
||||
self.mapped = mapped
|
||||
|
||||
def __index__(self):
|
||||
self.mapped.close()
|
||||
return 0
|
||||
|
||||
|
||||
for close_from in ("key", "value", "slice"):
|
||||
mapped = mmap.mmap(-1, 4)
|
||||
closer = CloseOnIndex(mapped)
|
||||
try:
|
||||
with assert_raises(ValueError):
|
||||
if close_from == "key":
|
||||
mapped[closer] = 0
|
||||
elif close_from == "value":
|
||||
mapped[0] = closer
|
||||
else:
|
||||
mapped[closer:0] = b""
|
||||
assert mapped.closed
|
||||
finally:
|
||||
mapped.close()
|
||||
@@ -65,6 +65,11 @@ except RecursionError:
|
||||
pass
|
||||
|
||||
|
||||
# Python 3.14.8 retains ParamSpec bounds without converting or validating them.
|
||||
assert ParamSpec("Unbounded").__bound__ is None
|
||||
for bound in (None, "int", "not valid Python!", (int, str), object()):
|
||||
assert ParamSpec("BoundSpec", bound=bound).__bound__ is bound
|
||||
|
||||
# Eager aliases without a caller module never inspect their aliased value.
|
||||
alias_module_lookups = []
|
||||
|
||||
|
||||
Reference in new issue
Block a user