From f2585646fe3a4a4d8d1ac3c9c03248165c24db1a Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 9 Jun 2025 21:45:45 -0700 Subject: [PATCH 1/2] gh-135326: Revert "[3.13] gh-133489: Remove size restrictions on getrandbits() and randbytes() (GH-133658) (GH-134965)" This reverts commit 3e1b8d6e678ed0d834c9bc37bbb599cd63e88a82. This caused a regression in Python 3.13.4, getrandbit() no longer accepted an integer-like object via `__index__`. --- Lib/test/test_random.py | 28 ---------------------------- Modules/_randommodule.c | 20 +++++++++++--------- Modules/clinic/_randommodule.c.h | 10 +++++----- Objects/longobject.c | 13 ++++++++++--- 4 files changed, 26 insertions(+), 45 deletions(-) diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 22b097b974c640..96f6cc86219a5d 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -392,8 +392,6 @@ def test_getrandbits(self): self.assertRaises(TypeError, self.gen.getrandbits) self.assertRaises(TypeError, self.gen.getrandbits, 1, 2) self.assertRaises(ValueError, self.gen.getrandbits, -1) - self.assertRaises(OverflowError, self.gen.getrandbits, 1<<1000) - self.assertRaises(ValueError, self.gen.getrandbits, -1<<1000) self.assertRaises(TypeError, self.gen.getrandbits, 10.1) def test_pickling(self): @@ -437,8 +435,6 @@ def test_randbytes(self): self.assertRaises(TypeError, self.gen.randbytes) self.assertRaises(TypeError, self.gen.randbytes, 1, 2) self.assertRaises(ValueError, self.gen.randbytes, -1) - self.assertRaises(OverflowError, self.gen.randbytes, 1<<1000) - self.assertRaises((ValueError, OverflowError), self.gen.randbytes, -1<<1000) self.assertRaises(TypeError, self.gen.randbytes, 1.0) def test_mu_sigma_default_args(self): @@ -810,22 +806,6 @@ def test_getrandbits(self): self.assertEqual(self.gen.getrandbits(100), 97904845777343510404718956115) - def test_getrandbits_2G_bits(self): - size = 2**31 - self.gen.seed(1234567) - x = self.gen.getrandbits(size) - self.assertEqual(x.bit_length(), size) - self.assertEqual(x & (2**100-1), 890186470919986886340158459475) - self.assertEqual(x >> (size-100), 1226514312032729439655761284440) - - @support.bigmemtest(size=2**32, memuse=1/8+2/15, dry_run=False) - def test_getrandbits_4G_bits(self, size): - self.gen.seed(1234568) - x = self.gen.getrandbits(size) - self.assertEqual(x.bit_length(), size) - self.assertEqual(x & (2**100-1), 287241425661104632871036099814) - self.assertEqual(x >> (size-100), 739728759900339699429794460738) - def test_randrange_uses_getrandbits(self): # Verify use of getrandbits by randrange # Use same seed as in the cross-platform repeatability test @@ -982,14 +962,6 @@ def test_randbytes_getrandbits(self): self.assertEqual(self.gen.randbytes(n), gen2.getrandbits(n * 8).to_bytes(n, 'little')) - @support.bigmemtest(size=2**29, memuse=1+16/15, dry_run=False) - def test_randbytes_256M(self, size): - self.gen.seed(2849427419) - x = self.gen.randbytes(size) - self.assertEqual(len(x), size) - self.assertEqual(x[:12].hex(), 'f6fd9ae63855ab91ea238b4f') - self.assertEqual(x[-12:].hex(), '0e7af69a84ee99bf4a11becc') - def test_sample_counts_equivalence(self): # Test the documented strong equivalence to a sample with repeated elements. # We run this test on random.Random() which makes deterministic selections diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 8fb040fb6c5b35..140640ae8fbf3a 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -495,32 +495,34 @@ _random_Random_setstate_impl(RandomObject *self, PyObject *state) _random.Random.getrandbits self: self(type="RandomObject *") - k: unsigned_long_long(bitwise=False) + k: int / getrandbits(k) -> x. Generates an int with k random bits. [clinic start generated code]*/ static PyObject * -_random_Random_getrandbits_impl(RandomObject *self, unsigned long long k) -/*[clinic end generated code: output=25a604fab95885d4 input=88e51091eea2f042]*/ +_random_Random_getrandbits_impl(RandomObject *self, int k) +/*[clinic end generated code: output=b402f82a2158887f input=87603cd60f79f730]*/ { - Py_ssize_t i, words; + int i, words; uint32_t r; uint32_t *wordarray; PyObject *result; + if (k < 0) { + PyErr_SetString(PyExc_ValueError, + "number of bits must be non-negative"); + return NULL; + } + if (k == 0) return PyLong_FromLong(0); if (k <= 32) /* Fast path */ return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k)); - if ((k - 1u) / 32u + 1u > PY_SSIZE_T_MAX / 4u) { - PyErr_NoMemory(); - return NULL; - } - words = (k - 1u) / 32u + 1u; + words = (k - 1) / 32 + 1; wordarray = (uint32_t *)PyMem_Malloc(words * 4); if (wordarray == NULL) { PyErr_NoMemory(); diff --git a/Modules/clinic/_randommodule.c.h b/Modules/clinic/_randommodule.c.h index 12c845d4c44d00..6193acac67e7ac 100644 --- a/Modules/clinic/_randommodule.c.h +++ b/Modules/clinic/_randommodule.c.h @@ -3,7 +3,6 @@ preserve [clinic start generated code]*/ #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() -#include "pycore_long.h" // _PyLong_UnsignedLongLong_Converter() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(_random_Random_random__doc__, @@ -125,15 +124,16 @@ PyDoc_STRVAR(_random_Random_getrandbits__doc__, {"getrandbits", (PyCFunction)_random_Random_getrandbits, METH_O, _random_Random_getrandbits__doc__}, static PyObject * -_random_Random_getrandbits_impl(RandomObject *self, unsigned long long k); +_random_Random_getrandbits_impl(RandomObject *self, int k); static PyObject * _random_Random_getrandbits(RandomObject *self, PyObject *arg) { PyObject *return_value = NULL; - unsigned long long k; + int k; - if (!_PyLong_UnsignedLongLong_Converter(arg, &k)) { + k = PyLong_AsInt(arg); + if (k == -1 && PyErr_Occurred()) { goto exit; } Py_BEGIN_CRITICAL_SECTION(self); @@ -143,4 +143,4 @@ _random_Random_getrandbits(RandomObject *self, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=e9a5c68295678cff input=a9049054013a1b77]*/ +/*[clinic end generated code: output=bf49ece1d341b1b6 input=a9049054013a1b77]*/ diff --git a/Objects/longobject.c b/Objects/longobject.c index be5cbf0872b446..03d618aeedcf0a 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -874,9 +874,16 @@ _PyLong_FromByteArray(const unsigned char* bytes, size_t n, ++numsignificantbytes; } - /* avoid integer overflow */ - ndigits = numsignificantbytes / PyLong_SHIFT * 8 - + (numsignificantbytes % PyLong_SHIFT * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT; + /* How many Python int digits do we need? We have + 8*numsignificantbytes bits, and each Python int digit has + PyLong_SHIFT bits, so it's the ceiling of the quotient. */ + /* catch overflow before it happens */ + if (numsignificantbytes > (PY_SSIZE_T_MAX - PyLong_SHIFT) / 8) { + PyErr_SetString(PyExc_OverflowError, + "byte array too long to convert to int"); + return NULL; + } + ndigits = (numsignificantbytes * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT; v = _PyLong_New(ndigits); if (v == NULL) return NULL; From b8b0f4de620103e7c59ff54ebc686721a15dbd78 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 9 Jun 2025 21:53:37 -0700 Subject: [PATCH 2/2] NEWS entry for the revert --- .../Library/2025-06-09-21-53-34.gh-issue-135326.A91xWm.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-06-09-21-53-34.gh-issue-135326.A91xWm.rst diff --git a/Misc/NEWS.d/next/Library/2025-06-09-21-53-34.gh-issue-135326.A91xWm.rst b/Misc/NEWS.d/next/Library/2025-06-09-21-53-34.gh-issue-135326.A91xWm.rst new file mode 100644 index 00000000000000..d32d4ec05c50df --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-09-21-53-34.gh-issue-135326.A91xWm.rst @@ -0,0 +1,6 @@ +Reverted the 3.13.4 changes to :func:`random.getrandbits` and +:func:`random.randbytes` for :gh:`133489` as they caused a behavior +regression vs 3.13.3 and earlier due to a 3.13 specific internal +implementation detail: ``int``-like objects with a ``__index__`` method were +no longer accepted as parameters. Some code depends on this. (ex: passing in +a ``numpy.int64``).