diff --git a/Lib/pickle.py b/Lib/pickle.py index 3e7cf25cb05337..95836afdc2b43e 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -920,17 +920,11 @@ def save_picklebuffer(self, obj): # Write data in-band # XXX The C implementation avoids a copy here buf = m.tobytes() - in_memo = id(buf) in self.memo if m.readonly: - if in_memo: - self._save_bytes_no_memo(buf) - else: - self.save_bytes(buf) + self._save_bytes_no_memo(buf) else: - if in_memo: - self._save_bytearray_no_memo(buf) - else: - self.save_bytearray(buf) + self._save_bytearray_no_memo(buf) + self.memoize(obj) else: # Write data out-of-band self.write(NEXT_BUFFER) diff --git a/Lib/profiling/sampling/sample.py b/Lib/profiling/sampling/sample.py index 6a76bbeeb24ee3..9195f5ee6dd390 100644 --- a/Lib/profiling/sampling/sample.py +++ b/Lib/profiling/sampling/sample.py @@ -58,6 +58,10 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD try: self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads) except RuntimeError as err: + if os.name == "nt" and sys.executable.endswith("python.exe"): + raise SystemExit( + "Running profiling.sampling from virtualenv on Windows platform is not supported" + ) from err raise SystemExit(err) from err # Track sample intervals and total sample count self.sample_intervals = deque(maxlen=100) diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index c2018c9785b9b3..9ba498ce8f575d 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -3100,6 +3100,51 @@ def test_bytearray_memoization(self): self.assertIsNot(b2a, b2b) self.assert_is_copy(b2a, b2b) + def test_picklebuffer_memoization(self): + if self.py_version < (3, 8): + self.skipTest('not supported in Python < 3.8') + array_types = [bytes, bytearray] + for proto in range(5, pickle.HIGHEST_PROTOCOL + 1): + for array_type in array_types: + for s in b'', b'xyz', b'xyz'*100: + with self.subTest(proto=proto, array_type=array_type, s=s, independent=False): + b = pickle.PickleBuffer(array_type(s)) + p = self.dumps((b, b), proto) + b1, b2 = self.loads(p) + self.assertIs(b1, b2) + + with self.subTest(proto=proto, array_type=array_type, s=s, independent=True): + b = array_type(s) + b1a = pickle.PickleBuffer(b) + b2a = pickle.PickleBuffer(b) + p = self.dumps((b1a, b2a), proto) + b1b, b2b = self.loads(p) + if array_type is not bytes: + self.assertIsNot(b1b, b2b) + self.assert_is_copy(b1b, b) + self.assert_is_copy(b2b, b) + + def test_empty_picklebuffer_memoization(self): + # gh-148914: Empty writable PickleBuffer memoized an empty bytearray + # with the id of b'' (a singleton in CPython). + if self.py_version < (3, 8): + self.skipTest('not supported in Python < 3.8') + for proto in range(5, pickle.HIGHEST_PROTOCOL + 1): + for readonly in False, True: + with self.subTest(proto=proto, readonly=readonly): + b = b'' + ba = bytearray() + buf = pickle.PickleBuffer(b if readonly else ba) + p = self.dumps((buf, b, ba), proto) + buf, b, ba = self.loads(p) + array_type = bytes if readonly else bytearray + self.assertIsInstance(buf, array_type) + self.assertIsInstance(b, bytes) + self.assertIsInstance(ba, bytearray) + self.assertEqual(buf, b'') + self.assertEqual(b, b'') + self.assertEqual(ba, b'') + def test_ints(self): for proto in protocols: n = sys.maxsize diff --git a/Misc/NEWS.d/next/Library/2026-04-27-17-12-11.gh-issue-148914.i5C3kW.rst b/Misc/NEWS.d/next/Library/2026-04-27-17-12-11.gh-issue-148914.i5C3kW.rst new file mode 100644 index 00000000000000..8348aad0d892c3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-27-17-12-11.gh-issue-148914.i5C3kW.rst @@ -0,0 +1,6 @@ +Fix memoization of in-band :class:`~pickle.PickleBuffer` in the Python +implementation of :mod:`pickle`. Previously, identical +:class:`!PickleBuffer`\ s did not preserve identity, and empty writable +:class:`!PickleBuffer` memoized an empty bytearray object in place of +``b''``, so the following references to ``b''`` were unpickled as an empty +bytearray object. diff --git a/Misc/NEWS.d/next/Security/2026-05-02-15-38-03.gh-issue-149254.0HOL0j.rst b/Misc/NEWS.d/next/Security/2026-05-02-15-38-03.gh-issue-149254.0HOL0j.rst new file mode 100644 index 00000000000000..f3cf924db25b4c --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-05-02-15-38-03.gh-issue-149254.0HOL0j.rst @@ -0,0 +1 @@ +Update Android and iOS installer to use OpenSSL 3.5.6. diff --git a/Platforms/Android/__main__.py b/Platforms/Android/__main__.py index 315632ea12c07d..d2546cf76c206b 100755 --- a/Platforms/Android/__main__.py +++ b/Platforms/Android/__main__.py @@ -216,8 +216,14 @@ def make_build_python(context): def unpack_deps(host, prefix_dir, cache_dir): os.chdir(prefix_dir) deps_url = "https://github.com/beeware/cpython-android-source-deps/releases/download" - for name_ver in ["bzip2-1.0.8-3", "libffi-3.4.4-3", "openssl-3.5.5-0", - "sqlite-3.50.4-0", "xz-5.4.6-1", "zstd-1.5.7-2"]: + for name_ver in [ + "bzip2-1.0.8-3", + "libffi-3.4.4-3", + "openssl-3.5.6-0", + "sqlite-3.50.4-0", + "xz-5.4.6-1", + "zstd-1.5.7-2" + ]: filename = f"{name_ver}-{host}.tar.gz" out_path = download(f"{deps_url}/{name_ver}/{filename}", cache_dir) shutil.unpack_archive(out_path) diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py index 44a991c6c20a93..d94198a309f926 100644 --- a/Platforms/Apple/__main__.py +++ b/Platforms/Apple/__main__.py @@ -319,7 +319,7 @@ def unpack_deps( for name_ver in [ "BZip2-1.0.8-2", "libFFI-3.4.7-2", - "OpenSSL-3.5.5-1", + "OpenSSL-3.5.6-1", "XZ-5.6.4-2", "mpdecimal-4.0.0-2", "zstd-1.5.7-1",