Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-111140: Adds PyLong_AsNativeBytes and PyLong_FromNative[Unsigned]Bytes functions#114886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
zooba merged 22 commits intopython:mainfromzooba:gh-111140
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
22 commits
Select commitHold shift + click to select a range
c86821e
gh-111140: Adds PyLong_AsByteArray functions
zoobaFeb 2, 2024
cd6754a
Statics
zoobaFeb 2, 2024
bf86f85
Better options handling and tests
zoobaFeb 2, 2024
22f3d14
Rework into PyLong_CopyBits
zoobaFeb 2, 2024
0dc0bcc
Merge remote-tracking branch 'upstream/main' into gh-111140
zoobaFeb 2, 2024
c30e65a
Not static
zoobaFeb 2, 2024
9661b65
Add motivating example to tests
zoobaFeb 2, 2024
79d4942
Add FromBits functions
zoobaFeb 2, 2024
bc9e48c
A test and a const
zoobaFeb 2, 2024
b4761be
Space
zoobaFeb 2, 2024
043947f
Merge remote-tracking branch 'upstream/main' into gh-111140
zoobaFeb 5, 2024
761db5c
Docs
zoobaFeb 5, 2024
22c2a64
Merge remote-tracking branch 'upstream/main' into gh-111140
zoobaFeb 7, 2024
6e1a89a
Rename to PyLong_AsNativeBytes
zoobaFeb 7, 2024
7b44648
Buffer overflow in big endian
zoobaFeb 7, 2024
79dd452
Review feedback
zoobaFeb 8, 2024
9693afe
_resolve_endianness
zoobaFeb 8, 2024
376e358
More reliable test
zoobaFeb 9, 2024
1f2cf3a
Doc tweaks
zoobaFeb 9, 2024
7f4431d
Merge remote-tracking branch 'upstream/main' into gh-111140
zoobaFeb 12, 2024
2649d94
Merge remote-tracking branch 'upstream/main' into gh-111140
zoobaFeb 12, 2024
39962de
Switch to Py_ssize_t consistently instead of int and size_t
zoobaFeb 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
Rework into PyLong_CopyBits
  • Loading branch information
@zooba
zooba committedFeb 2, 2024
commit22f3d147165f676940f3a997d0afe1f98e1c94d2
43 changes: 16 additions & 27 deletionsInclude/cpython/longobject.h
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,34 +4,23 @@

PyAPI_FUNC(PyObject*) PyLong_FromUnicodeObject(PyObject *u, int base);

/* PyLong_AsByteArray: Copy the integer value to a native address.
n is the number of bytes available in the buffer.
Uses the current build's default endianness.
PyLong_AsByteArray ensures the MSB matches the int's sign.
PyLong_AsUnsignedByteArray allows the MSB to be set for a
positive Python value provided no significant bits are lost
(that is, all higher bits are a sign extension of the MSB).
Negative values still sign extend to fill the buffer and are
guaranteed to have MSB set. Use PyLong_AsByteArray to guarantee
that the MSB is set iff the int was negative.
/* PyLong_CopyBits: Copy the integer value to a native buffer.
n_bytes is the number of bytes available in the buffer. Pass 0 to request
the required size for the value.
endianness is -1 for native endian, 0 for big endian or 1 for little.

Returns 0 on success or -1 with an exception set, but if the buffer is
not big enough, returns the desired buffer size without setting an
exception. Note that the desired size may be larger than strictly
necessary to avoid precise calculations. */
PyAPI_FUNC(int) PyLong_AsByteArray(PyObject* v, void* buffer, size_t n);
PyAPI_FUNC(int) PyLong_AsUnsignedByteArray(PyObject* v, void* buffer, size_t n);
PyAPI_FUNC(int) PyLong_AsByteArrayWithOptions(PyObject* v, void* buffer,
size_t n, int options);

/* Deliberately avoiding values 0 and 1 to avoid letting people
accidentally pass PY_LITTLE_ENDIAN as a constant. */
#define PYLONG_ASBYTEARRAY_LITTLE_ENDIAN 0x04
#define PYLONG_ASBYTEARRAY_BIG_ENDIAN 0x08
#define PYLONG_ASBYTEARRAY_NATIVE_ENDIAN (0x04|0x08)

#define PYLONG_ASBYTEARRAY_SIGNED 0x10
#define PYLONG_ASBYTEARRAY_UNSIGNED 0x20
If an exception is raised, returns a negative value.
Otherwise, returns the number of bytes that are required to store the value.
To check that the full value is represented, ensure that the return value is
equal or less than n_bytes.
All n_bytes are guaranteed to be written (unless an exception occurs), and
so ignoring a positive return value is the equivalent of a downcast in C.
In cases where the full value could not be represented, the returned value
may be larger than necessary - this function is not an accurate way to
calculate the bit length of an integer object.
*/
PyAPI_FUNC(int) PyLong_CopyBits(PyObject* v, void* buffer, size_t n_bytes,
int endianness);

/* PyLong_FromByteArray: Create an integer value containing the number from
a native buffer.
Expand Down
184 changes: 71 additions & 113 deletionsLib/test/test_capi/test_long.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -424,137 +424,95 @@ def test_long_asvoidptr(self):
self.assertRaises(OverflowError, asvoidptr, -2**1000)
# CRASHES asvoidptr(NULL)

deftest_long_asbytearray(self):
deftest_long_copybits(self):
import math
from _testcapi import (
pylong_asbytearray as asbytearray,
pylong_asunsignedbytearray as asunsignedbytearray,
pylong_asbytearraywithoptions as asbytearraywithoptions,
pylong_copybits as copybits,
SIZE_MAX,
PYLONG_ASBYTEARRAY_NATIVE_ENDIAN,
PYLONG_ASBYTEARRAY_LITTLE_ENDIAN,
PYLONG_ASBYTEARRAY_BIG_ENDIAN,
PYLONG_ASBYTEARRAY_SIGNED,
PYLONG_ASBYTEARRAY_UNSIGNED,
)

def log2(x):
return math.log(x) / math.log(2)

SIZEOF_SIZE = int(math.ceil(log2(SIZE_MAX + 1)) / 8)
MAX_SSIZE = 2 ** (SIZEOF_SIZE * 8 - 1) - 1
MAX_USIZE = 2 ** (SIZEOF_SIZE * 8) - 1
# Abbreviated sizeof(Py_ssize_t) because we use it a lot
SZ = int(math.ceil(log2(SIZE_MAX + 1)) / 8)
MAX_SSIZE = 2 ** (SZ * 8 - 1) - 1
MAX_USIZE = 2 ** (SZ * 8) - 1
if support.verbose:
print(f"{SIZEOF_SIZE=}\n{MAX_SSIZE=:016X}\n{MAX_USIZE=:016X}")

U_LE = PYLONG_ASBYTEARRAY_UNSIGNED | PYLONG_ASBYTEARRAY_LITTLE_ENDIAN
U_BE = PYLONG_ASBYTEARRAY_UNSIGNED | PYLONG_ASBYTEARRAY_BIG_ENDIAN
S_LE = PYLONG_ASBYTEARRAY_SIGNED | PYLONG_ASBYTEARRAY_LITTLE_ENDIAN
S_BE = PYLONG_ASBYTEARRAY_SIGNED | PYLONG_ASBYTEARRAY_BIG_ENDIAN

# Ensure options of 0 and 1 raise
with self.assertRaises(SystemError):
asbytearraywithoptions(0, bytearray(1), 0, 0)
with self.assertRaises(SystemError):
asbytearraywithoptions(0, bytearray(1), 0, 1)

# These tests request the required buffer size for both unsigned and
# signed options.
for v, expect_u, expect_s in [
(0, SIZEOF_SIZE, SIZEOF_SIZE),
(512, SIZEOF_SIZE, SIZEOF_SIZE),
(-512, SIZEOF_SIZE, SIZEOF_SIZE),
(MAX_SSIZE, SIZEOF_SIZE, SIZEOF_SIZE),
(MAX_USIZE, SIZEOF_SIZE, SIZEOF_SIZE + 1),
(-MAX_SSIZE, SIZEOF_SIZE, SIZEOF_SIZE),
(-MAX_USIZE, SIZEOF_SIZE, SIZEOF_SIZE + 1),
(2**255-1, 32, 32),
(-(2**255-1), 32, 32),
(2**256-1, 32, 33),
(-(2**256-1), 32, 33),
print(f"SIZEOF_SIZE={SZ}\n{MAX_SSIZE=:016X}\n{MAX_USIZE=:016X}")

# These tests check that the requested buffer size is correct
for v, expect in [
(0, SZ),
(512, SZ),
(-512, SZ),
(MAX_SSIZE, SZ),
(MAX_USIZE, SZ + 1),
(-MAX_SSIZE, SZ),
(-MAX_USIZE, SZ + 1),
(2**255-1, 32),
(-(2**255-1), 32),
(2**256-1, 33),
(-(2**256-1), 33),
]:
with self.subTest(f"sizeof-{v:X}"):
buffer = bytearray(1)
self.assertEqual(expect_u, asunsignedbytearray(v, buffer, 0),
"PyLong_AsUnsignedByteArray(v, NULL, 0)")
self.assertEqual(expect_s, asbytearray(v, buffer, 0),
"PyLong_AsByteArray(v, NULL, 0)")
self.assertEqual(expect_u, asbytearraywithoptions(v, buffer, 0, U_LE),
"PyLong_AsByteArrayWithOptions(v, NULL, 0, unsigned|little)")
self.assertEqual(expect_u, asbytearraywithoptions(v, buffer, 0, U_BE),
"PyLong_AsByteArrayWithOptions(v, NULL, 0, unsigned|big)")
self.assertEqual(expect_s, asbytearraywithoptions(v, buffer, 0, S_LE),
"PyLong_AsByteArrayWithOptions(v, NULL, 0, signed|little)")
self.assertEqual(expect_s, asbytearraywithoptions(v, buffer, 0, S_BE),
"PyLong_AsByteArrayWithOptions(v, NULL, 0, signed|big)")

# We request as many bytes as `expect_be` contains, so tests for the
# same value may test different things with a longer expected result.
for v, expect_be, signed_fails in [
(0, b'\x00', False),
(0, b'\x00' * 2, False),
(0, b'\x00' * 8, False),
(1, b'\x01', False),
(1, b'\x00' * 10 + b'\x01', False),
(42, b'\x2a', False),
(42, b'\x00' * 10 + b'\x2a', False),
(-1, b'\xff', False),
(-1, b'\xff' * 10, False),
(-42, b'\xd6', False),
(-42, b'\xff' * 10 + b'\xd6', False),
# Only unsigned will extract 255 into a single byte
(255, b'\xff', True),
(255, b'\x00\xff', False),
(256, b'\x01\x00', False),
(2**63, b'\x80\x00\x00\x00\x00\x00\x00\x00', True),
(-2**63, b'\x80\x00\x00\x00\x00\x00\x00\x00', False),
(2**63, b'\x00\x80\x00\x00\x00\x00\x00\x00\x00', False),
(-2**63, b'\xff\x80\x00\x00\x00\x00\x00\x00\x00', False),
(2**255-1, b'\x7f' + b'\xff' * 31, False),
(-(2**255-1), b'\x80' + b'\x00' * 30 + b'\x01', False),
(-(2**255-1), b'\xff\x80' + b'\x00' * 30 + b'\x01', False),
# Cannot extract 256 bits into 32 bytes as signed ...
(2**256-1, b'\xff' * 32, True),
# ... but can extract into 33 bytes (zero extended)
(2**256-1, b'\x00' + b'\xff' * 32, False),

# Unsigned extract of this negative number to 32 bytes will succeed,
# sacrificing the sign bit and overall magnitude, just like a C
# cast (int256_t)0x1_0000_..._0001 would. But signed extract fails
(-(2**256-1), b'\x00' * 31 + b'\x01', True),
# Extract to 33 bytes preserves the top-most bits in all cases.
(-(2**256-1), b'\xff' + b'\x00' * 31 + b'\x01', False),
self.assertEqual(expect, copybits(v, buffer, 0, -1),
"PyLong_CopyBits(v, NULL, 0, -1)")

# We request as many bytes as `expect_be` contains, and always check
# the result (both big and little endian). We check the return value
# independently, since the buffer should always be filled correctly even
# if we need more bytes
for v, expect_be, expect_n in [
(0, b'\x00', 1),
(0, b'\x00' * 2, 2),
(0, b'\x00' * 8, 8),
(1, b'\x01', 1),
(1, b'\x00' * 10 + b'\x01', min(11, SZ)),
(42, b'\x2a', 1),
(42, b'\x00' * 10 + b'\x2a', min(11, SZ)),
(-1, b'\xff', 1),
(-1, b'\xff' * 10, min(11, SZ)),
(-42, b'\xd6', 1),
(-42, b'\xff' * 10 + b'\xd6', min(11, SZ)),
# Extracts 255 into a single byte, but requests sizeof(Py_ssize_t)
(255, b'\xff', SZ),
(255, b'\x00\xff', 2),
(256, b'\x01\x00', 2),
# Extracts successfully (unsigned), but requests 9 bytes
(2**63, b'\x80' + b'\x00' * 7, 9),
# "Extracts", but requests 9 bytes
(-2**63, b'\x80' + b'\x00' * 7, 9),
(2**63, b'\x00\x80' + b'\x00' * 7, 9),
(-2**63, b'\xff\x80' + b'\x00' * 7, 9),

(2**255-1, b'\x7f' + b'\xff' * 31, 32),
(-(2**255-1), b'\x80' + b'\x00' * 30 + b'\x01', 32),
# Request extra bytes, but result says we only needed 32
(-(2**255-1), b'\xff\x80' + b'\x00' * 30 + b'\x01', 32),
(-(2**255-1), b'\xff\xff\x80' + b'\x00' * 30 + b'\x01', 32),

# Extracting 256 bits of integer will request 33 bytes, but still
# copy as many bits as possible into the buffer. So we *can* copy
# into a 32-byte buffer, though negative number may be unrecoverable
(2**256-1, b'\xff' * 32, 33),
(2**256-1, b'\x00' + b'\xff' * 32, 33),
(-(2**256-1), b'\x00' * 31 + b'\x01', 33),
(-(2**256-1), b'\xff' + b'\x00' * 31 + b'\x01', 33),
(-(2**256-1), b'\xff\xff' + b'\x00' * 31 + b'\x01', 33),
]:
with self.subTest(f"{v:X}-{len(expect_be)}bytes"):
n = len(expect_be)
buffer = bytearray(n)
expect_le = expect_be[::-1]

self.assertEqual(0, asbytearraywithoptions(v, buffer, n, U_LE),
f"PyLong_AsByteArrayWithOptions(v, buffer, {n}, unsigned+little)")
self.assertEqual(expect_le, buffer[:n], "unsigned+little")
self.assertEqual(0, asbytearraywithoptions(v, buffer, n, U_BE),
f"PyLong_AsByteArrayWithOptions(v, buffer, {n}, unsigned+big)")
self.assertEqual(expect_be, buffer[:n], "unsigned+big")

if signed_fails:
self.assertEqual(
max(n + 1, SIZEOF_SIZE),
asbytearraywithoptions(v, buffer, n, S_LE),
f"PyLong_AsByteArrayWithOptions(v, buffer, {n}, signed+little)",
)
self.assertEqual(
max(n + 1, SIZEOF_SIZE),
asbytearraywithoptions(v, buffer, n, S_BE),
f"PyLong_AsByteArrayWithOptions(v, buffer, {n}, signed+big)",
)
else:
self.assertEqual(0, asbytearraywithoptions(v, buffer, n, S_LE),
f"PyLong_AsByteArrayWithOptions(v, buffer, {n}, signed+little)")
self.assertEqual(expect_le, buffer[:n], "signed+little")
self.assertEqual(0, asbytearraywithoptions(v, buffer, n, S_BE),
f"PyLong_AsByteArrayWithOptions(v, buffer, {n}, signed+big)")
self.assertEqual(expect_be, buffer[:n], "signed+big")
self.assertEqual(expect_n, copybits(v, buffer, n, 0),
f"PyLong_CopyBits(v, buffer, {n}, <big>)")
self.assertEqual(expect_be, buffer[:n], "<big>")
self.assertEqual(expect_n, copybits(v, buffer, n, 1),
f"PyLong_CopyBits(v, buffer, {n}, <little>)")
self.assertEqual(expect_le, buffer[:n], "<little>")

if __name__ == "__main__":
unittest.main()
65 changes: 5 additions & 60 deletionsModules/_testcapi/long.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -777,12 +777,12 @@ pylong_asvoidptr(PyObject *module, PyObject *arg)
}

static PyObject *
pylong_asbytearray(PyObject *module, PyObject *args)
pylong_copybits(PyObject *module, PyObject *args)
{
PyObject *v;
Py_buffer buffer;
Py_ssize_t n;
if (!PyArg_ParseTuple(args, "Ow*n", &v, &buffer, &n)) {
Py_ssize_t n, endianness;
if (!PyArg_ParseTuple(args, "Ow*nn", &v, &buffer, &n, &endianness)) {
return NULL;
}
if (buffer.readonly) {
Expand All@@ -795,55 +795,7 @@ pylong_asbytearray(PyObject *module, PyObject *args)
PyBuffer_Release(&buffer);
return NULL;
}
int res = PyLong_AsByteArray(v, buffer.buf, n);
PyBuffer_Release(&buffer);
return res >= 0 ? PyLong_FromLong(res) : NULL;
}

static PyObject *
pylong_asunsignedbytearray(PyObject *module, PyObject *args)
{
PyObject *v;
Py_buffer buffer;
Py_ssize_t n;
if (!PyArg_ParseTuple(args, "Ow*n", &v, &buffer, &n)) {
return NULL;
}
if (buffer.readonly) {
PyErr_SetString(PyExc_TypeError, "buffer must be writable");
PyBuffer_Release(&buffer);
return NULL;
}
if (buffer.len < n) {
PyErr_SetString(PyExc_ValueError, "buffer must be at least 'n' bytes");
PyBuffer_Release(&buffer);
return NULL;
}
int res = PyLong_AsUnsignedByteArray(v, buffer.buf, n);
PyBuffer_Release(&buffer);
return res >= 0 ? PyLong_FromLong(res) : NULL;
}

static PyObject *
pylong_asbytearraywithoptions(PyObject *module, PyObject *args)
{
PyObject *v;
Py_buffer buffer;
Py_ssize_t n, options;
if (!PyArg_ParseTuple(args, "Ow*nn", &v, &buffer, &n, &options)) {
return NULL;
}
if (buffer.readonly) {
PyErr_SetString(PyExc_TypeError, "buffer must be writable");
PyBuffer_Release(&buffer);
return NULL;
}
if (buffer.len < n) {
PyErr_SetString(PyExc_ValueError, "buffer must be at least 'n' bytes");
PyBuffer_Release(&buffer);
return NULL;
}
int res = PyLong_AsByteArrayWithOptions(v, buffer.buf, n, (int)options);
int res = PyLong_CopyBits(v, buffer.buf, n, (int)endianness);
PyBuffer_Release(&buffer);
return res >= 0 ? PyLong_FromLong(res) : NULL;
}
Expand DownExpand Up@@ -876,9 +828,7 @@ static PyMethodDef test_methods[] = {
{"pylong_as_size_t", pylong_as_size_t, METH_O},
{"pylong_asdouble", pylong_asdouble, METH_O},
{"pylong_asvoidptr", pylong_asvoidptr, METH_O},
{"pylong_asbytearray", pylong_asbytearray, METH_VARARGS},
{"pylong_asunsignedbytearray", pylong_asunsignedbytearray, METH_VARARGS},
{"pylong_asbytearraywithoptions", pylong_asbytearraywithoptions, METH_VARARGS},
{"pylong_copybits", pylong_copybits, METH_VARARGS},
{NULL},
};

Expand All@@ -888,10 +838,5 @@ _PyTestCapi_Init_Long(PyObject *mod)
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
if (PyModule_AddIntMacro(mod, PYLONG_ASBYTEARRAY_NATIVE_ENDIAN)) return -1;
if (PyModule_AddIntMacro(mod, PYLONG_ASBYTEARRAY_LITTLE_ENDIAN)) return -1;
if (PyModule_AddIntMacro(mod, PYLONG_ASBYTEARRAY_BIG_ENDIAN)) return -1;
if (PyModule_AddIntMacro(mod, PYLONG_ASBYTEARRAY_SIGNED)) return -1;
if (PyModule_AddIntMacro(mod, PYLONG_ASBYTEARRAY_UNSIGNED)) return -1;
return 0;
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp