| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
If bytearray.resize() or bytearray.take_bytes() fails, leave the bytearray unchanged. If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error.
|
It would be nice if the "guaranteed no global" case was also used for the PyBytes_FromStringAndSize(NULL, size); call. That the first parameter there must be NULL to avoid a global is unintuitive. |
Sorry, something went wrong.
There was a problem hiding this comment.
No need to add a new parameter, it saves nothing. _PyBytes_Resize can be implemented via _PyBytes_ResizeKeepOnError.
I wonder if we can simply change the behavior of _PyBytes_Resize.
Sorry, something went wrong.
Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
For the in-place resize code path, no longer call _Py_ForgetReference() and _PyReftracerTrack() before PyObject_Realloc().
|
Please review the updated PR. I addressed reviews. @maurycy found a fix for the memmove() code path which worried me. I applied his suggestion and added a test.
Thanks for the advice. I reworked _PyBytes_Resize(): for the in-place resize code path, no longer call _Py_ForgetReference() and _PyReftracerTrack() before PyObject_Realloc(). Only call them on success. With this change, I was able to easy implement _PyBytes_Resize() with _PyBytes_ResizeKeepOnError(). |
Sorry, something went wrong.
Maybe PyBytes_FromStringAndSize() documentation should be elaborated to explain that PyBytes_FromStringAndSize(NULL, size) can be mutated, whereas PyBytes_FromStringAndSize(str, size) must not be mutated? Note: PR gh-156996 does fix PyBytes_FromStringAndSize() usage in bytearray. I don't try to replace this fix. |
Sorry, something went wrong.
|
bytearray.resize() and bytearray.take_bytes() have been fixed to no longer use a singleton: I merged main in my PR to get the PR gh-156996 fix. |
Sorry, something went wrong.
|
@maurycy: I modified resize() and take_bytes() to avoid memmove() usage if we would be unable to revert the bytesarray to its previous state on MemoryError. Does it look correct to you? I also added more tests injecting MemoryError. |
Sorry, something went wrong.
|
@vstinner: Thank you. I will take a look more carefully later today. |
Sorry, something went wrong.
|
@vstinner Two more findings. That's such a nice PR! Good news: the PR fixes this regression: 2026-09-10T17:44:57.890346000+0200 maurycy@gimel /Users/maurycy/work/cpython (main 9bd670c?) % ./python.exe
Python 3.16.0a0 (heads/main:9bd670cba21, Sep 10 2026, 17:37:43) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123")
... try:
... _testcapi.set_nomemory(0)
... del ba[1:-1]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
>>> len(ba)
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
len(ba)
~~~^^^^
SystemError: <built-in function len> returned NULL without setting an exception
>>> bytes(ba)
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
bytes(ba)
~~~~~^^^^
ValueError: size must be >= 0
>>>
2026-09-10T17:45:16.126365000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr157243 (pr157243 f51cba5?) % ./python.exe
Python 3.16.0a0 (heads/pr157243:f51cba57d3e, Sep 10 2026, 14:31:04) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123")
... try:
... _testcapi.set_nomemory(0)
... del ba[1:-1]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
>>> len(ba)
2
>>> bytes(ba)
b'a3'
>>> Perhaps we cover this with test, too? Bad news: 2026-09-10T17:47:19.394053000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr157243 (pr157243 f51cba5?) % ./python.exe
Python 3.16.0a0 (heads/pr157243:f51cba57d3e, Sep 10 2026, 14:31:04) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123456")
... ba += b"7"
...
... try:
... _testcapi.set_nomemory(0)
... del ba[::2]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
... ba
...
bytearray(b'b135734567')
>>>
2026-09-10T17:48:34.225959000+0200 maurycy@gimel /Users/maurycy/work/cpython (main 9bd670c?) % ./python.exe
Python 3.16.0a0 (heads/main:9bd670cba21, Sep 10 2026, 17:37:43) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123456")
... ba += b"7"
...
... try:
... _testcapi.set_nomemory(0)
... del ba[::2]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
... ba
...
bytearray(b'')Unfortunately, I don't have bandwitch to investigate what's the exact fix here |
Sorry, something went wrong.
alloc is just the ob_bytes_object size.
|
@maurycy @cmaloney: Oh, you made multiple comments. Let me push a first batch of fixes:
I checked tracemalloc "ref tracer" callback: it does read the object memory, so the pointer must not be a dangling pointer or Python will crash. I reverted my change: _PyReftracerTrack(v, PyRefTracer_DESTROY) is now called before PyObject_Realloc() again (as done currently). |
Sorry, something went wrong.
|
@vstinner Thanks, I appreciate it! Could you re-request a review once you've pushed the remaining fixes? |
Sorry, something went wrong.
|
@maurycy @cmaloney: I finished to update the PR, you can now review it. I pushed a fix to write the null byte on bytearray_realign_data_lock_held() error path. I'm not sure that test_bytes_resize_tracer() is needed: it's a functional test on PyRefTracer_DESTROY and PyRefTracer_CREATE events. I wrote it to make sure that we don't mess up with these events if the code is modified later. I also wrote a large change locally to check bytearray consistency in all methods modifying bytearray, but I prefer to propose a separated PR later for this change. For example, my check makes sure that the bytearray has trailing null byte. See also PR gh-156943 which uses a "canary byte" to detect buffer overflow in the PyBytesWriter C API. |
Sorry, something went wrong.
| static void | ||
| bytearray_reinit_from_bytes(PyByteArrayObject *self, Py_ssize_t size, | ||
| Py_ssize_t alloc) | ||
| bytearray_reinit_from_bytes(PyByteArrayObject *self, Py_ssize_t size) |
There was a problem hiding this comment.
Removing the alloc parameter is not strictly needed by this PR, but I removed it to help me checking that the bytearray remains consistent with my changes.
Sorry, something went wrong.
I'm not sure that Python 3.14 and older leaves the bytearray unchanged on MemoryError. For example, bytearray_setslice_linear() contains the following comment in Python 3.14 on bytearray_resize_lock_held() error path: /* memmove() removed bytes, the bytearray object cannot be
restored in its previous state. */ |
Sorry, something went wrong.
Always use _PyBytes_ResizeKeepOnError(), but move remaining bytes on error.
Rename bytearray_realign_data_lock_held() to bytearray_resize_storage(). On error, only move data if ob_start was different than ob_bytes. Add one more test: resize() shrinks without logical offset.
|
@vstinner You're right. Both del ba[1:-1] and del ba[::2] behave the same on 3.14. The del ba[1:-1] is since 732224e: `git bisect log` (`v3.14.0..main`, `Objects/bytearrayobject.c`)2026-09-11T22:58:48.213303000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (main 392ad7e?) % cat repro.py
import _testcapi
ba = bytearray(b"abc123")
try:
_testcapi.set_nomemory(0); del ba[1:-1]
except MemoryError: pass
finally: _testcapi.remove_mem_hooks()
assert len(ba) == 2, ba
2026-09-11T22:58:48.905587000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (main 392ad7e?) % git bisect start upstream-ro/main v3.14.0 -- Objects/bytearrayobject.c
Bisecting: a merge base must be tested
[b092705907c758d4f9742028652c9802f9f03dd3] Python 3.14.0b1
maurycy@gimel cpython (git)-[tags/v3.14.0b1|bisect]?- % git bisect run sh -c 'make -s -j8 >/dev/null 2>&1 || exit 125; ./python.exe repro.py; test $? -eq 0'
running 'sh' '-c' 'make -s -j8 >/dev/null 2>&1 || exit 125; ./python.exe repro.py; test $? -eq 0'
Bisecting: 9 revisions left to test after this (roughly 3 steps)
[9976c2b6349a079ae39931d960b8c147e21c6c3f] gh-143195: fix UAF in `{bytearray,memoryview}.hex(sep)` via re-entrant `sep.__len__` (#143209)
running 'sh' '-c' 'make -s -j8 >/dev/null 2>&1 || exit 125; ./python.exe repro.py; test $? -eq 0'
Traceback (most recent call last):
File "/Users/maurycy/src/github.com/maurycy/cpython/repro.py", line 7, in <module>
assert len(ba) == 2, ba
~~~^^^^
SystemError: <built-in function len> returned NULL without setting an exception
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[6416e6ebe5b88087ada6f4a56972053edb9c2e01] gh-129559: Remove extra dot in bytearray.resize AC (#140134)
running 'sh' '-c' 'make -s -j8 >/dev/null 2>&1 || exit 125; ./python.exe repro.py; test $? -eq 0'
Bisecting: 2 revisions left to test after this (roughly 1 step)
[e265ce8a563ba7f91c5ada0592de8cb85622b433] gh-139871: Optimize small takes in bytearray.take_bytes (GH-141741)
running 'sh' '-c' 'make -s -j8 >/dev/null 2>&1 || exit 125; ./python.exe repro.py; test $? -eq 0'
Traceback (most recent call last):
File "/Users/maurycy/src/github.com/maurycy/cpython/repro.py", line 7, in <module>
assert len(ba) == 2, ba
~~~^^^^
SystemError: <built-in function len> returned NULL without setting an exception
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[732224e1139f7ed4fe0259a2dad900f84910949e] gh-139871: Add `bytearray.take_bytes([n])` to efficiently extract `bytes` (GH-140128)
running 'sh' '-c' 'make -s -j8 >/dev/null 2>&1 || exit 125; ./python.exe repro.py; test $? -eq 0'
Traceback (most recent call last):
File "/Users/maurycy/src/github.com/maurycy/cpython/repro.py", line 7, in <module>
assert len(ba) == 2, ba
~~~^^^^
SystemError: <built-in function len> returned NULL without setting an exception
732224e1139f7ed4fe0259a2dad900f84910949e is the first 'bad' commit
commit 732224e1139f7ed4fe0259a2dad900f84910949e
Author: Cody Maloney <cmaloney@users.noreply.github.com>
Date: 2025-11-13 05:19:44 -0800
gh-139871: Add `bytearray.take_bytes([n])` to efficiently extract `bytes` (GH-140128)
Update `bytearray` to contain a `bytes` and provide a zero-copy path to
"extract" the `bytes`. This allows making several code paths more efficient.
This does not move any codepaths to make use of this new API. The documentation
changes include common code patterns which can be made more efficient with
this API.
---
When just changing `bytearray` to contain `bytes` I ran pyperformance on a
`--with-lto --enable-optimizations --with-static-libpython` build and don't see
any major speedups or slowdowns with this; all seems to be in the noise of
my machine (Generally changes under 5% or benchmarks that don't touch
bytes/bytearray).
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Maurycy Pawłowski-Wieroński <5383+maurycy@users.noreply.github.com>
Doc/library/stdtypes.rst | 24 +++
Doc/whatsnew/3.15.rst | 80 +++++++
Include/cpython/bytearrayobject.h | 16 +-
Include/internal/pycore_bytesobject.h | 8 +
Lib/test/test_bytes.py | 81 +++++++
Lib/test/test_capi/test_bytearray.py | 5 +-
Lib/test/test_sys.py | 2 +-
.../2025-10-14-18-24-16.gh-issue-139871.SWtuUz.rst | 2 +
Objects/bytearrayobject.c | 238 ++++++++++++++-------
Objects/bytesobject.c | 8 +-
Objects/clinic/bytearrayobject.c.h | 39 +++-
11 files changed, 407 insertions(+), 96 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-14-18-24-16.gh-issue-139871.SWtuUz.rst
bisect found first 'bad' commit
maurycy@gimel cpython (git)-[bisect/bad|bisect]?- % I cannot approve but it LGreatTM. |
Sorry, something went wrong.
|
The PR became quite big, so I modified it to focus strictly on _PyBytes_ResizeKeepOnError(). Once this PR will be merged, I will open a second issue to fix bytearray methods. |
Sorry, something went wrong.
|
Ok, I merged the first part. I created the follow-up PR for bytearray: PR gh-157340. |
Sorry, something went wrong.
⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️Hi! The buildbot AMD64 Arch Linux TraceRefs 3.x (tierless) has failed when building commit f5dd52d. What do you need to do:
You can take a look at the buildbot page here: https://buildbot.python.org/#/builders/484/builds/7361 Failed tests:
Summary of the results of the build (if available): == Click to see traceback logsNote: switching to 'f5dd52df16e1b3f3f8cc2762f1de783a41d69f4f'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at f5dd52df16e gh-157242: Fix PyBytesWriter_Resize() on MemoryError (#157243)
Switched to and reset branch 'main'
make: *** [Makefile:2481: buildbottest] Error 2 |
Sorry, something went wrong.
Ah, Py_TRACE_REFS builds are special, they cannot test set_nomemory(). I wrote PR gh-157350 to fix test_capi on Py_TRACE_REFS build. |
Sorry, something went wrong.
…7243) If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
…7243) If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
| Back | FazBrowse Home | New Git URL |
If PyBytesWriter_Resize() fails, leave the writer unchanged.
Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error.