Skip to content

Commit 79dd452

Browse files
committed
Review feedback
1 parent 7b44648 commit 79dd452

File tree

5 files changed

+56
-21
lines changed

5 files changed

+56
-21
lines changed

Doc/c-api/long.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,12 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
387387
bytes were copied into the buffer.
388388
389389
Passing *n_bytes* of zero will always return the requested buffer size.
390-
The requested buffer size may be larger than necessary, but only when the
391-
larger size is required to contain the full value. This function is not an
392-
accurate way to determine the bit length of a value.
390+
391+
.. note::
392+
393+
When the value does not fit in the provided buffer, the requested size
394+
returned from the function may be larger than necessary. Passing 0 to this
395+
function is not an accurate way to determine the bit length of a value.
393396
394397
.. versionadded:: 3.13
395398

Doc/whatsnew/3.13.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,7 @@ New Features
14931493

14941494
* Add :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and
14951495
:c:func:`PyLong_FromUnsignedNativeBytes` functions to simplify converting
1496-
between native integer types and Python ``int`` objects.
1496+
between native integer types and Python :class:`int` objects.
14971497
(Contributed by Steve Dower in :gh:`111140`.)
14981498

14991499

Lib/test/test_capi/test_long.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,11 @@ def test_long_asvoidptr(self):
426426

427427
def test_long_asnativebytes(self):
428428
import math
429-
from _testcapi import pylong_asnativebytes as asnativebytes, SIZE_MAX
429+
from _testcapi import (
430+
pylong_asnativebytes as asnativebytes,
431+
INT_MAX,
432+
SIZE_MAX,
433+
)
430434

431435
# Abbreviate sizeof(Py_ssize_t) to SZ because we use it a lot
432436
SZ = int(math.ceil(math.log(SIZE_MAX + 1) / math.log(2)) / 8)
@@ -453,6 +457,9 @@ def test_long_asnativebytes(self):
453457
buffer = bytearray(1)
454458
self.assertEqual(expect, asnativebytes(v, buffer, 0, -1),
455459
"PyLong_AsNativeBytes(v, NULL, 0, -1)")
460+
# Also check via the __index__ path
461+
self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, -1),
462+
"PyLong_AsNativeBytes(Index(v), NULL, 0, -1)")
456463

457464
# We request as many bytes as `expect_be` contains, and always check
458465
# the result (both big and little endian). We check the return value
@@ -514,6 +521,19 @@ def test_long_asnativebytes(self):
514521
f"PyLong_AsNativeBytes(v, buffer, {n}, <little>)")
515522
self.assertEqual(expect_le, buffer[:n], "<little>")
516523

524+
# Check a few error conditions. These are validated in code, but are
525+
# unspecified in docs, so if we make changes to the implementation, it's
526+
# fine to just update these tests rather than preserve the behaviour.
527+
with self.assertRaises(SystemError):
528+
asnativebytes(1, buffer, 0, 2)
529+
with self.assertRaises(TypeError):
530+
asnativebytes('not a number', buffer, 0, -1)
531+
with self.assertRaises(SystemError):
532+
# n_bytes is taken as size_t and clamped to 'int'. With the sign
533+
# change, we should always be able to pass INT_MAX+1 and see this
534+
# failure.
535+
asnativebytes(1, buffer, INT_MAX + 1, -1)
536+
517537
def test_long_fromnativebytes(self):
518538
import math
519539
from _testcapi import (
@@ -546,5 +566,14 @@ def test_long_fromnativebytes(self):
546566
self.assertEqual(expect_u, fromnativebytes(v_le, n, 1, 0),
547567
f"PyLong_FromUnsignedNativeBytes(buffer, {n}, <little>)")
548568

569+
# Check native endian when the result would be the same either
570+
# way and we can test it.
571+
if v_be == v_le:
572+
self.assertEqual(expect_s, fromnativebytes(v_be, n, -1, 1),
573+
f"PyLong_FromNativeBytes(buffer, {n}, <native>)")
574+
self.assertEqual(expect_u, fromnativebytes(v_be, n, -1, 0),
575+
f"PyLong_FromUnsignedNativeBytes(buffer, {n}, <native>)")
576+
577+
549578
if __name__ == "__main__":
550579
unittest.main()

Modules/_testcapi/long.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,9 @@ pylong_asnativebytes(PyObject *module, PyObject *args)
790790
PyBuffer_Release(&buffer);
791791
return NULL;
792792
}
793-
if (buffer.len < n) {
793+
// Allow n > INT_MAX for tests - it will error out without writing to the
794+
// buffer, so no overrun issues.
795+
if (buffer.len < n && n <= INT_MAX) {
794796
PyErr_SetString(PyExc_ValueError, "buffer must be at least 'n' bytes");
795797
PyBuffer_Release(&buffer);
796798
return NULL;

Objects/longobject.c

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,19 @@ _fits_in_n_bits(Py_ssize_t v, Py_ssize_t n)
10801080
return v_extended == 0 || v_extended == -1;
10811081
}
10821082

1083+
static inline int
1084+
resolve_endianness(int *endianness)
1085+
{
1086+
if (*endianness < 0) {
1087+
*endianness = PY_LITTLE_ENDIAN;
1088+
}
1089+
if (*endianness != 0 && *endianness != 1) {
1090+
PyErr_SetString(PyExc_SystemError, "invalid 'endianness' value");
1091+
return -1;
1092+
}
1093+
return 0;
1094+
}
1095+
10831096
int
10841097
PyLong_AsNativeBytes(PyObject* vv, void* buffer, size_t n, int endianness)
10851098
{
@@ -1102,11 +1115,7 @@ PyLong_AsNativeBytes(PyObject* vv, void* buffer, size_t n, int endianness)
11021115
}
11031116

11041117
int little_endian = endianness;
1105-
if (endianness < 0) {
1106-
endianness = PY_LITTLE_ENDIAN;
1107-
}
1108-
if (endianness != 0 && endianness != 1) {
1109-
PyErr_SetString(PyExc_SystemError, "invalid 'endianness' value");
1118+
if (resolve_endianness(&little_endian) < 0) {
11101119
return -1;
11111120
}
11121121

@@ -1226,11 +1235,7 @@ PyLong_FromNativeBytes(const void* buffer, size_t n, int endianness)
12261235
}
12271236

12281237
int little_endian = endianness;
1229-
if (endianness < 0) {
1230-
endianness = PY_LITTLE_ENDIAN;
1231-
}
1232-
if (endianness != 0 && endianness != 1) {
1233-
PyErr_SetString(PyExc_SystemError, "invalid 'endianness' value");
1238+
if (resolve_endianness(&little_endian) < 0) {
12341239
return NULL;
12351240
}
12361241

@@ -1248,11 +1253,7 @@ PyLong_FromUnsignedNativeBytes(const void* buffer, size_t n, int endianness)
12481253
}
12491254

12501255
int little_endian = endianness;
1251-
if (endianness < 0) {
1252-
endianness = PY_LITTLE_ENDIAN;
1253-
}
1254-
if (endianness != 0 && endianness != 1) {
1255-
PyErr_SetString(PyExc_SystemError, "invalid 'endianness' value");
1256+
if (resolve_endianness(&little_endian) < 0) {
12561257
return NULL;
12571258
}
12581259

0 commit comments

Comments
 (0)