From bf404e0b7c204d727c497bb0c58e0c76886b3c63 Mon Sep 17 00:00:00 2001 From: Luan Taraschi <130802253+luantaraschi@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:06:16 -0300 Subject: [PATCH] Keep a bytearray exported while it formats with % (#9147) bytearray.__mod__ copied the format and ran the arguments' __repr__, __str__, __bytes__ and __index__ without holding an export, so a callback could clear or extend the format and formatting went on with the old copy: fmt = bytearray(b"%a end") class S: def __repr__(self): fmt.clear() return "E" fmt % S() # bytearray(b'E end'), fmt left empty CPython holds a buffer on the format for the whole call, so the resize fails with BufferError and the format is left as it was. Taking an export for the duration of __mod__ does the same here, the way bytearray.join already does for its separator. This lets test_mod_concurrent_mutation in test_bytes run again. Assisted-by: Claude Code:claude-opus-5-5 --- Lib/test/test_bytes.py | 1 - crates/vm/src/builtins/bytearray.rs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index ffc59dab9..2ae5ca33d 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1393,7 +1393,6 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): except OSError: pass - @unittest.skip("TODO: RUSTPYTHON; segfault") def test_mod_concurrent_mutation(self): # Prevent crash in __mod__ when formatting mutates the bytearray. # Regression test for https://github.com/python/cpython/issues/142557. diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index ac7e071f5..8ad76aefc 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -454,7 +454,9 @@ impl PyByteArray { fn __mod__(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { // Formatting calls the values' conversion methods, which can reach - // this bytearray, so the format is taken by value. + // this bytearray, so the format is taken by value. It also stays + // exported until formatting ends, so resizing it raises BufferError. + let _export = self.begin_export(); let format = self.inner().clone(); let formatted = format.cformat(values, vm)?; Ok(formatted.into())