mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Support Python 3.15 zlib checksum combination and version deprecation (#9414)
* Support Python 3.15 zlib checksum combination and version deprecation Forward checksum combination to the existing zlib-rs backend, retaining CPython checksum masking, conversion warnings, and signed length bounds. Deprecate __version__ through module __getattr__. Sync the canonical test_zlib with scripts/update_lib. Implementation and source review assisted by OpenAI Codex; focused compilation and Clippy passed. Canonical runtime tests remain pending. Assisted-by: OpenAI Codex: model version unavailable * Fix zlib combine ABI conversions for cross-platform Clippy Mark checksum results must-use and make the C unsigned-long and length conversions explicit per ABI. Preserve Windows GNU's 32-bit z_off64_t and large-file length handling without lint suppression. OpenAI Codex assisted with implementation and source review. Standalone common-crate Clippy passed; Windows validation remains with CI. Assisted-by: OpenAI Codex: model version unavailable --------- Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
1 parent
a20c0b8954
commit
167a999fcb
3 files changed
+234
-5
No files matched your search
Vendored
+119
-1
@@ -119,6 +119,114 @@ class ChecksumTestCase(unittest.TestCase):
|
||||
self.assertEqual(binascii.crc32(b'spam'), zlib.crc32(b'spam'))
|
||||
|
||||
|
||||
class ChecksumCombineMixin:
|
||||
"""Mixin class for testing checksum combination."""
|
||||
|
||||
N = 1000
|
||||
default_iv: int
|
||||
|
||||
def parse_iv(self, iv):
|
||||
"""Parse an IV value.
|
||||
|
||||
- The default IV is returned if *iv* is None.
|
||||
- A random IV is returned if *iv* is -1.
|
||||
- Otherwise, *iv* is returned as is.
|
||||
"""
|
||||
if iv is None:
|
||||
return self.default_iv
|
||||
if iv == -1:
|
||||
return random.randint(1, 0x80000000)
|
||||
return iv
|
||||
|
||||
def checksum(self, data, init=None):
|
||||
"""Compute the checksum of data with a given initial value.
|
||||
|
||||
The *init* value is parsed by ``parse_iv``.
|
||||
"""
|
||||
iv = self.parse_iv(init)
|
||||
return self._checksum(data, iv)
|
||||
|
||||
def _checksum(self, data, init):
|
||||
raise NotImplementedError
|
||||
|
||||
def combine(self, a, b, blen):
|
||||
"""Combine two checksums together."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_random_data(self, data_len, *, iv=None):
|
||||
"""Get a triplet (data, iv, checksum)."""
|
||||
data = random.randbytes(data_len)
|
||||
init = self.parse_iv(iv)
|
||||
checksum = self.checksum(data, init)
|
||||
return data, init, checksum
|
||||
|
||||
def test_combine_empty(self):
|
||||
for _ in range(self.N):
|
||||
a, iv, checksum = self.get_random_data(32, iv=-1)
|
||||
res = self.combine(iv, self.checksum(a), len(a))
|
||||
self.assertEqual(res, checksum)
|
||||
|
||||
def test_combine_no_iv(self):
|
||||
for _ in range(self.N):
|
||||
a, _, chk_a = self.get_random_data(32)
|
||||
b, _, chk_b = self.get_random_data(64)
|
||||
res = self.combine(chk_a, chk_b, len(b))
|
||||
self.assertEqual(res, self.checksum(a + b))
|
||||
|
||||
def test_combine_no_iv_invalid_length(self):
|
||||
a, _, chk_a = self.get_random_data(32)
|
||||
b, _, chk_b = self.get_random_data(64)
|
||||
checksum = self.checksum(a + b)
|
||||
for invalid_len in [1, len(a), 48, len(b) + 1, 191]:
|
||||
invalid_res = self.combine(chk_a, chk_b, invalid_len)
|
||||
self.assertNotEqual(invalid_res, checksum)
|
||||
|
||||
self.assertRaises(TypeError, self.combine, 0, 0, "len")
|
||||
|
||||
def test_combine_with_iv(self):
|
||||
for _ in range(self.N):
|
||||
a, iv_a, chk_a_with_iv = self.get_random_data(32, iv=-1)
|
||||
chk_a_no_iv = self.checksum(a)
|
||||
b, iv_b, chk_b_with_iv = self.get_random_data(64, iv=-1)
|
||||
chk_b_no_iv = self.checksum(b)
|
||||
|
||||
# We can represent c = COMBINE(CHK(a, iv_a), CHK(b, iv_b)) as:
|
||||
#
|
||||
# c = CHK(CHK(b'', iv_a) + CHK(a) + CHK(b'', iv_b) + CHK(b))
|
||||
# = COMBINE(
|
||||
# COMBINE(CHK(b'', iv_a), CHK(a)),
|
||||
# COMBINE(CHK(b'', iv_b), CHK(b)),
|
||||
# )
|
||||
# = COMBINE(COMBINE(iv_a, CHK(a)), COMBINE(iv_b, CHK(b)))
|
||||
tmp0 = self.combine(iv_a, chk_a_no_iv, len(a))
|
||||
tmp1 = self.combine(iv_b, chk_b_no_iv, len(b))
|
||||
expected = self.combine(tmp0, tmp1, len(b))
|
||||
checksum = self.combine(chk_a_with_iv, chk_b_with_iv, len(b))
|
||||
self.assertEqual(checksum, expected)
|
||||
|
||||
|
||||
class CRC32CombineTestCase(ChecksumCombineMixin, unittest.TestCase):
|
||||
|
||||
default_iv = 0
|
||||
|
||||
def _checksum(self, data, init):
|
||||
return zlib.crc32(data, init)
|
||||
|
||||
def combine(self, a, b, blen):
|
||||
return zlib.crc32_combine(a, b, blen)
|
||||
|
||||
|
||||
class Adler32CombineTestCase(ChecksumCombineMixin, unittest.TestCase):
|
||||
|
||||
default_iv = 1
|
||||
|
||||
def _checksum(self, data, init):
|
||||
return zlib.adler32(data, init)
|
||||
|
||||
def combine(self, a, b, blen):
|
||||
return zlib.adler32_combine(a, b, blen)
|
||||
|
||||
|
||||
# Issue #10276 - check that inputs >=4 GiB are handled correctly.
|
||||
class ChecksumBigBufferTestCase(unittest.TestCase):
|
||||
|
||||
@@ -982,7 +1090,7 @@ class ZlibDecompressorTest(unittest.TestCase):
|
||||
self.assertRaises(EOFError, zlibd.decompress, b"")
|
||||
|
||||
@support.skip_if_pgo_task
|
||||
@bigmemtest(size=_4G + 100, memuse=3.3)
|
||||
@bigmemtest(size=_4G + 100, memuse=4.5)
|
||||
def testDecompress4G(self, size):
|
||||
# "Test zlib._ZlibDecompressor.decompress() with >4GiB input"
|
||||
blocksize = min(10 * 1024 * 1024, size)
|
||||
@@ -1114,5 +1222,15 @@ class CustomInt:
|
||||
return 100
|
||||
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
def test_deprecated__version__(self):
|
||||
with self.assertWarnsRegex(
|
||||
DeprecationWarning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20",
|
||||
) as cm:
|
||||
getattr(zlib, "__version__")
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -28,6 +28,34 @@ pub const MAX_WBITS: i32 = 15;
|
||||
pub const DEF_BUF_SIZE: usize = 16 * 1024;
|
||||
pub const DEF_MEM_LEVEL: u8 = 8;
|
||||
|
||||
// CPython uses a large-file off_t except on Windows (always 32-bit) and
|
||||
// GNU/Hurd, where its configure script disables large-file support.
|
||||
#[cfg(windows)]
|
||||
pub type ChecksumLength = i32;
|
||||
#[cfg(not(any(windows, target_os = "hurd")))]
|
||||
pub type ChecksumLength = i64;
|
||||
#[cfg(target_os = "hurd")]
|
||||
pub type ChecksumLength = libc::off_t;
|
||||
|
||||
#[must_use]
|
||||
pub fn adler32_combine(adler1: u32, adler2: u32, len2: ChecksumLength) -> u32 {
|
||||
z_checksum_to_u32(libz_rs_sys::adler32_combine64(
|
||||
z_checksum_from_u32(adler1),
|
||||
z_checksum_from_u32(adler2),
|
||||
z_checksum_length(len2),
|
||||
))
|
||||
}
|
||||
|
||||
// As in zlib, CRC combination requires a nonnegative length.
|
||||
#[must_use]
|
||||
pub fn crc32_combine(crc1: u32, crc2: u32, len2: ChecksumLength) -> u32 {
|
||||
z_checksum_to_u32(libz_rs_sys::crc32_combine64(
|
||||
z_checksum_from_u32(crc1),
|
||||
z_checksum_from_u32(crc2),
|
||||
z_checksum_length(len2),
|
||||
))
|
||||
}
|
||||
|
||||
const USE_AFTER_FINISH_ERR: &str = "Error -2: inconsistent stream state";
|
||||
|
||||
fn valid_level(level: i32) -> bool {
|
||||
@@ -58,6 +86,34 @@ const fn z_size_to_u64(value: core::ffi::c_ulong) -> u64 {
|
||||
}
|
||||
}
|
||||
|
||||
const fn z_checksum_length(value: ChecksumLength) -> libz_rs_sys::z_off64_t {
|
||||
#[cfg(any(
|
||||
all(windows, not(target_env = "gnu")),
|
||||
all(target_os = "hurd", target_pointer_width = "32")
|
||||
))]
|
||||
{
|
||||
value as i64
|
||||
}
|
||||
#[cfg(not(any(
|
||||
all(windows, not(target_env = "gnu")),
|
||||
all(target_os = "hurd", target_pointer_width = "32")
|
||||
)))]
|
||||
{
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
const fn z_checksum_from_u32(value: u32) -> core::ffi::c_ulong {
|
||||
#[cfg(all(target_pointer_width = "64", not(windows)))]
|
||||
{
|
||||
value as u64
|
||||
}
|
||||
#[cfg(any(target_pointer_width = "32", windows))]
|
||||
{
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
const fn z_checksum_to_u32(value: core::ffi::c_ulong) -> u32 {
|
||||
#[cfg(all(target_pointer_width = "64", not(windows)))]
|
||||
{
|
||||
|
||||
@@ -7,9 +7,9 @@ mod zlib {
|
||||
use crate::compression::{DecompressArgs, DecompressorArgs};
|
||||
use crate::vm::{
|
||||
Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
|
||||
builtins::{PyBaseExceptionRef, PyBytes, PyBytesRef, PyType, PyTypeRef},
|
||||
builtins::{PyBaseExceptionRef, PyBytes, PyBytesRef, PyIntRef, PyStr, PyType, PyTypeRef},
|
||||
common::lock::PyMutex,
|
||||
convert::TryFromBorrowedObject,
|
||||
convert::{TryFromBorrowedObject, TryFromObject},
|
||||
function::{ArgBytesLike, ArgIndex, OptionalArg, PySize, PySsize},
|
||||
object::PyAtomicRef,
|
||||
types::Constructor,
|
||||
@@ -17,6 +17,7 @@ mod zlib {
|
||||
use adler32::RollingAdler32 as Adler32;
|
||||
use alloc::fmt;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use num_traits::ToPrimitive;
|
||||
use rustpython_common::compression::zlib as backend;
|
||||
|
||||
#[pyattr]
|
||||
@@ -26,8 +27,19 @@ mod zlib {
|
||||
Z_NO_COMPRESSION, Z_NO_FLUSH, Z_PARTIAL_FLUSH, Z_RLE, Z_SYNC_FLUSH, Z_TREES,
|
||||
};
|
||||
|
||||
#[pyattr(name = "__version__")]
|
||||
const __VERSION__: &str = "1.0";
|
||||
#[pyfunction]
|
||||
fn __getattr__(name: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
if name.downcast_ref::<PyStr>().and_then(|name| name.to_str()) == Some("__version__") {
|
||||
crate::vm::stdlib::_warnings::warn(
|
||||
vm.ctx.exceptions.deprecation_warning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20".to_owned(),
|
||||
1,
|
||||
vm,
|
||||
)?;
|
||||
return Ok(vm.ctx.new_str("1.0").into());
|
||||
}
|
||||
Err(vm.new_attribute_error(format!("module 'zlib' has no attribute {}", name.repr(vm)?)))
|
||||
}
|
||||
|
||||
// We statically link libz-rs, so the compile-time and runtime versions
|
||||
// always match.
|
||||
@@ -84,6 +96,49 @@ mod zlib {
|
||||
crate::binascii::crc32(data, value)
|
||||
}
|
||||
|
||||
struct ChecksumValue(u32);
|
||||
|
||||
impl TryFromObject for ChecksumValue {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
let value = obj.try_index(vm)?;
|
||||
if !value
|
||||
.as_bigint()
|
||||
.to_i64()
|
||||
.is_some_and(|value| (i64::from(i32::MIN)..=i64::from(u32::MAX)).contains(&value))
|
||||
{
|
||||
crate::vm::stdlib::_warnings::warn(
|
||||
vm.ctx.exceptions.deprecation_warning,
|
||||
"integer value out of range".to_owned(),
|
||||
1,
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
Ok(Self(value.as_u32_mask()))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn adler32_combine(
|
||||
adler1: ChecksumValue,
|
||||
adler2: ChecksumValue,
|
||||
len2: PyIntRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<u32> {
|
||||
let len2 = len2.try_to_primitive::<backend::ChecksumLength>(vm)?;
|
||||
Ok(backend::adler32_combine(adler1.0, adler2.0, len2))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn crc32_combine(
|
||||
crc1: ChecksumValue,
|
||||
crc2: ChecksumValue,
|
||||
len2: PyIntRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<u32> {
|
||||
let len2 = len2.try_to_primitive::<backend::ChecksumLength>(vm)?;
|
||||
Ok(backend::crc32_combine(crc1.0, crc2.0, len2))
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct PyFuncCompressArgs {
|
||||
#[pyarg(positional)]
|
||||
|
||||
Reference in new issue
Block a user