From 1420b38bab0347a3fce97c790377eae47db80bec Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 10 Oct 2023 15:57:22 +0300 Subject: [PATCH 1/4] gh-110628: Add tests for PyLong C API --- Lib/test/test_capi/test_long.py | 396 ++++++++++++++++++++++++++++++-- Modules/_testcapi/long.c | 233 +++++++++++++++++++ Modules/_testcapimodule.c | 2 + 3 files changed, 614 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 101fe1f0de77f1..daf5a9ec0b11c2 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -6,6 +6,25 @@ # Skip this test if the _testcapi module isn't available. _testcapi = import_helper.import_module('_testcapi') +NULL = None + +class IntSubclass(int): + pass + +class Index: + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + +# use __index__(), not __int__() +class MyIndexAndInt: + def __index__(self): + return 10 + def __int__(self): + return 22 + class LongTests(unittest.TestCase): @@ -34,35 +53,378 @@ def test_compact_known(self): self.assertEqual(_testcapi.call_long_compact_api(sys.maxsize), (False, -1)) + def test_long_check(self): + """Test PyLong_Check()""" + check = _testcapi.pylong_check + self.assertTrue(check(1)) + self.assertTrue(check(123456789012345678901234567890)) + self.assertTrue(check(-1)) + self.assertTrue(check(True)) + self.assertTrue(check(IntSubclass(1))) + self.assertFalse(check(1.0)) + self.assertFalse(check(object())) + # CRASHES check(NULL) + + def test_long_checkexact(self): + """Test PyLong_CheckExact()""" + check = _testcapi.pylong_checkexact + self.assertTrue(check(1)) + self.assertTrue(check(123456789012345678901234567890)) + self.assertTrue(check(-1)) + self.assertFalse(check(True)) + self.assertFalse(check(IntSubclass(1))) + self.assertFalse(check(1.0)) + self.assertFalse(check(object())) + # CRASHES check(NULL) + + def test_long_fromdouble(self): + """Test PyLong_FromDouble()""" + fromdouble = _testcapi.pylong_fromdouble + self.assertEqual(fromdouble(5.0), 5) + self.assertEqual(fromdouble(5.1), 5) + self.assertEqual(fromdouble(5.9), 5) + self.assertEqual(fromdouble(-5.1), -5) + self.assertEqual(fromdouble(-5.9), -5) + self.assertEqual(fromdouble(sys.float_info.max), int(sys.float_info.max)) + self.assertEqual(fromdouble(-sys.float_info.max), -int(sys.float_info.max)) + self.assertRaises(OverflowError, fromdouble, float('inf')) + self.assertRaises(OverflowError, fromdouble, float('-inf')) + self.assertRaises(ValueError, fromdouble, float('nan')) + + def test_long_fromvoidptr(self): + """Test PyLong_FromVoidPtr()""" + fromvoidptr = _testcapi.pylong_fromvoidptr + obj = object() + x = fromvoidptr(obj) + y = fromvoidptr(NULL) + self.assertIsInstance(x, int) + self.assertGreaterEqual(x, 0) + self.assertIsInstance(y, int) + self.assertGreaterEqual(y, 0) + self.assertNotEqual(x, y) + + def test_long_fromstring(self): + """Test PyLong_FromString()""" + fromstring = _testcapi.pylong_fromstring + self.assertEqual(fromstring(b'123', 10), (123, 3)) + self.assertEqual(fromstring(b'cafe', 16), (0xcafe, 4)) + self.assertEqual(fromstring(b'xyz', 36), (44027, 3)) + self.assertEqual(fromstring(b'123', 0), (123, 3)) + self.assertEqual(fromstring(b'0xcafe', 0), (0xcafe, 6)) + self.assertRaises(ValueError, fromstring, b'cafe', 0) + self.assertEqual(fromstring(b'-123', 10), (-123, 4)) + self.assertEqual(fromstring(b' -123 ', 10), (-123, 6)) + self.assertEqual(fromstring(b'1_23', 10), (123, 4)) + self.assertRaises(ValueError, fromstring, b'- 123', 10) + self.assertRaises(ValueError, fromstring, b'', 10) + + self.assertRaises(ValueError, fromstring, b'123', 1) + self.assertRaises(ValueError, fromstring, b'123', -1) + self.assertRaises(ValueError, fromstring, b'123', 37) + + self.assertRaises(ValueError, fromstring, '١٢٣٤٥٦٧٨٩٠'.encode(), 0) + self.assertRaises(ValueError, fromstring, '١٢٣٤٥٦٧٨٩٠'.encode(), 16) + + self.assertEqual(fromstring(b'123\x00', 0), (123, 3)) + self.assertEqual(fromstring(b'123\x00456', 0), (123, 3)) + self.assertEqual(fromstring(b'123\x00', 16), (0x123, 3)) + self.assertEqual(fromstring(b'123\x00456', 16), (0x123, 3)) + + # CRASHES fromstring(NULL, 0) + # CRASHES fromstring(NULL, 16) + + def test_long_fromunicodeobject(self): + """Test PyLong_FromUnicodeObject()""" + fromunicodeobject = _testcapi.pylong_fromunicodeobject + self.assertEqual(fromunicodeobject('123', 10), 123) + self.assertEqual(fromunicodeobject('cafe', 16), 0xcafe) + self.assertEqual(fromunicodeobject('xyz', 36), 44027) + self.assertEqual(fromunicodeobject('123', 0), 123) + self.assertEqual(fromunicodeobject('0xcafe', 0), 0xcafe) + self.assertRaises(ValueError, fromunicodeobject, 'cafe', 0) + self.assertEqual(fromunicodeobject('-123', 10), -123) + self.assertEqual(fromunicodeobject(' -123 ', 10), -123) + self.assertEqual(fromunicodeobject('1_23', 10), 123) + self.assertRaises(ValueError, fromunicodeobject, '- 123', 10) + self.assertRaises(ValueError, fromunicodeobject, '', 10) + + self.assertRaises(ValueError, fromunicodeobject, '123', 1) + self.assertRaises(ValueError, fromunicodeobject, '123', -1) + self.assertRaises(ValueError, fromunicodeobject, '123', 37) + + self.assertEqual(fromunicodeobject('١٢٣٤٥٦٧٨٩٠', 0), 1234567890) + self.assertEqual(fromunicodeobject('١٢٣٤٥٦٧٨٩٠', 16), 0x1234567890) + + self.assertRaises(ValueError, fromunicodeobject, '123\x00', 0) + self.assertRaises(ValueError, fromunicodeobject, '123\x00456', 0) + self.assertRaises(ValueError, fromunicodeobject, '123\x00', 16) + self.assertRaises(ValueError, fromunicodeobject, '123\x00456', 16) + + # CRASHES fromunicodeobject(NULL, 0) + # CRASHES fromunicodeobject(NULL, 16) + def test_long_asint(self): + """Test PyLong_AsInt()""" PyLong_AsInt = _testcapi.PyLong_AsInt - INT_MIN = _testcapi.INT_MIN - INT_MAX = _testcapi.INT_MAX + from _testcapi import INT_MIN, INT_MAX # round trip (object -> int -> object) for value in (INT_MIN, INT_MAX, -1, 0, 1, 123): with self.subTest(value=value): self.assertEqual(PyLong_AsInt(value), value) - - # use __index__(), not __int__() - class MyIndex: - def __index__(self): - return 10 - def __int__(self): - return 22 - self.assertEqual(PyLong_AsInt(MyIndex()), 10) + self.assertEqual(PyLong_AsInt(IntSubclass(42)), 42) + self.assertEqual(PyLong_AsInt(Index(42)), 42) + self.assertEqual(PyLong_AsInt(MyIndexAndInt()), 10) # bound checking - with self.assertRaises(OverflowError): - PyLong_AsInt(INT_MIN - 1) - with self.assertRaises(OverflowError): - PyLong_AsInt(INT_MAX + 1) + self.assertRaises(OverflowError, PyLong_AsInt, INT_MIN - 1) + self.assertRaises(OverflowError, PyLong_AsInt, INT_MAX + 1) # invalid type - for value in (1.0, b'2', '3'): + self.assertRaises(TypeError, PyLong_AsInt, 1.0) + self.assertRaises(TypeError, PyLong_AsInt, b'2') + self.assertRaises(TypeError, PyLong_AsInt, '3') + self.assertRaises(SystemError, PyLong_AsInt, NULL) + + def test_long_aslong(self): + """Test PyLong_AsLong() and PyLong_FromLong()""" + aslong = _testcapi.pylong_aslong + from _testcapi import LONG_MIN, LONG_MAX + # round trip (object -> long -> object) + for value in (LONG_MIN, LONG_MAX, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(aslong(value), value) + + self.assertEqual(aslong(IntSubclass(42)), 42) + self.assertEqual(aslong(Index(42)), 42) + self.assertEqual(aslong(MyIndexAndInt()), 10) + + self.assertRaises(OverflowError, aslong, LONG_MIN - 1) + self.assertRaises(OverflowError, aslong, LONG_MAX + 1) + self.assertRaises(TypeError, aslong, 1.0) + self.assertRaises(TypeError, aslong, b'2') + self.assertRaises(TypeError, aslong, '3') + self.assertRaises(SystemError, aslong, NULL) + + def test_long_aslongandoverflow(self): + """Test PyLong_AsLongAndOverflow()""" + aslongandoverflow = _testcapi.pylong_aslongandoverflow + from _testcapi import LONG_MIN, LONG_MAX + # round trip (object -> long -> object) + for value in (LONG_MIN, LONG_MAX, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(aslongandoverflow(value), (value, 0)) + + self.assertEqual(aslongandoverflow(IntSubclass(42)), (42, 0)) + self.assertEqual(aslongandoverflow(Index(42)), (42, 0)) + self.assertEqual(aslongandoverflow(MyIndexAndInt()), (10, 0)) + + self.assertEqual(aslongandoverflow(LONG_MIN - 1), (-1, -1)) + self.assertEqual(aslongandoverflow(LONG_MAX + 1), (-1, 1)) + # CRASHES aslongandoverflow(1.0) + # CRASHES aslongandoverflow(NULL) + + def test_long_asunsignedlong(self): + """Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()""" + asunsignedlong = _testcapi.pylong_asunsignedlong + from _testcapi import ULONG_MAX + # round trip (object -> unsigned long -> object) + for value in (ULONG_MAX, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(asunsignedlong(value), value) + + self.assertEqual(asunsignedlong(IntSubclass(42)), 42) + self.assertRaises(TypeError, asunsignedlong, Index(42)) + self.assertRaises(TypeError, asunsignedlong, MyIndexAndInt()) + + self.assertRaises(OverflowError, asunsignedlong, -1) + self.assertRaises(OverflowError, asunsignedlong, ULONG_MAX + 1) + self.assertRaises(TypeError, asunsignedlong, 1.0) + self.assertRaises(TypeError, asunsignedlong, b'2') + self.assertRaises(TypeError, asunsignedlong, '3') + self.assertRaises(SystemError, asunsignedlong, NULL) + + def test_long_asunsignedlongmask(self): + """Test PyLong_AsUnsignedLongMask()""" + asunsignedlongmask = _testcapi.pylong_asunsignedlongmask + from _testcapi import ULONG_MAX + # round trip (object -> unsigned long -> object) + for value in (ULONG_MAX, 0, 1, 1234): with self.subTest(value=value): - with self.assertRaises(TypeError): - PyLong_AsInt(value) + self.assertEqual(asunsignedlongmask(value), value) + + self.assertEqual(asunsignedlongmask(IntSubclass(42)), 42) + self.assertEqual(asunsignedlongmask(Index(42)), 42) + self.assertEqual(asunsignedlongmask(MyIndexAndInt()), 10) + + self.assertEqual(asunsignedlongmask(-1), ULONG_MAX) + self.assertEqual(asunsignedlongmask(ULONG_MAX + 1), 0) + self.assertRaises(TypeError, asunsignedlongmask, 1.0) + self.assertRaises(TypeError, asunsignedlongmask, b'2') + self.assertRaises(TypeError, asunsignedlongmask, '3') + self.assertRaises(SystemError, asunsignedlongmask, NULL) + + def test_long_aslonglong(self): + """Test PyLong_AsLongLong() and PyLong_FromLongLong()""" + aslonglong = _testcapi.pylong_aslonglong + from _testcapi import LLONG_MIN, LLONG_MAX + # round trip (object -> long long -> object) + for value in (LLONG_MIN, LLONG_MAX, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(aslonglong(value), value) + + self.assertEqual(aslonglong(IntSubclass(42)), 42) + self.assertEqual(aslonglong(Index(42)), 42) + self.assertEqual(aslonglong(MyIndexAndInt()), 10) + + self.assertRaises(OverflowError, aslonglong, LLONG_MIN - 1) + self.assertRaises(OverflowError, aslonglong, LLONG_MAX + 1) + self.assertRaises(TypeError, aslonglong, 1.0) + self.assertRaises(TypeError, aslonglong, b'2') + self.assertRaises(TypeError, aslonglong, '3') + self.assertRaises(SystemError, aslonglong, NULL) + + def test_long_aslonglongandoverflow(self): + """Test PyLong_AsLongLongAndOverflow()""" + aslonglongandoverflow = _testcapi.pylong_aslonglongandoverflow + from _testcapi import LLONG_MIN, LLONG_MAX + # round trip (object -> long long -> object) + for value in (LLONG_MIN, LLONG_MAX, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(aslonglongandoverflow(value), (value, 0)) + + self.assertEqual(aslonglongandoverflow(IntSubclass(42)), (42, 0)) + self.assertEqual(aslonglongandoverflow(Index(42)), (42, 0)) + self.assertEqual(aslonglongandoverflow(MyIndexAndInt()), (10, 0)) + + self.assertEqual(aslonglongandoverflow(LLONG_MIN - 1), (-1, -1)) + self.assertEqual(aslonglongandoverflow(LLONG_MAX + 1), (-1, 1)) + # CRASHES aslonglongandoverflow(1.0) + # CRASHES aslonglongandoverflow(NULL) + + def test_long_asunsignedlonglong(self): + """Test PyLong_AsUnsignedLongLong() and PyLong_FromUnsignedLongLong()""" + asunsignedlonglong = _testcapi.pylong_asunsignedlonglong + from _testcapi import ULLONG_MAX + # round trip (object -> unsigned long long -> object) + for value in (ULLONG_MAX, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(asunsignedlonglong(value), value) + + self.assertEqual(asunsignedlonglong(IntSubclass(42)), 42) + self.assertRaises(TypeError, asunsignedlonglong, Index(42)) + self.assertRaises(TypeError, asunsignedlonglong, MyIndexAndInt()) + + self.assertRaises(OverflowError, asunsignedlonglong, -1) + self.assertRaises(OverflowError, asunsignedlonglong, ULLONG_MAX + 1) + self.assertRaises(TypeError, asunsignedlonglong, 1.0) + self.assertRaises(TypeError, asunsignedlonglong, b'2') + self.assertRaises(TypeError, asunsignedlonglong, '3') + self.assertRaises(SystemError, asunsignedlonglong, NULL) + + def test_long_asunsignedlonglongmask(self): + """Test PyLong_AsUnsignedLongLongMask()""" + asunsignedlonglongmask = _testcapi.pylong_asunsignedlonglongmask + from _testcapi import ULLONG_MAX + # round trip (object -> unsigned long long -> object) + for value in (ULLONG_MAX, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(asunsignedlonglongmask(value), value) + + self.assertEqual(asunsignedlonglongmask(IntSubclass(42)), 42) + self.assertEqual(asunsignedlonglongmask(Index(42)), 42) + self.assertEqual(asunsignedlonglongmask(MyIndexAndInt()), 10) + + self.assertEqual(asunsignedlonglongmask(-1), ULLONG_MAX) + self.assertEqual(asunsignedlonglongmask(ULLONG_MAX + 1), 0) + self.assertRaises(TypeError, asunsignedlonglongmask, 1.0) + self.assertRaises(TypeError, asunsignedlonglongmask, b'2') + self.assertRaises(TypeError, asunsignedlonglongmask, '3') + self.assertRaises(SystemError, asunsignedlonglongmask, NULL) + + def test_long_asssize_t(self): + """Test PyLong_AsSsize_t() and PyLong_FromSsize_t()""" + asssize_t = _testcapi.pylong_asssize_t + from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX + # round trip (object -> Py_ssize_t -> object) + for value in (PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(asssize_t(value), value) + + self.assertEqual(asssize_t(IntSubclass(42)), 42) + self.assertRaises(TypeError, asssize_t, Index(42)) + self.assertRaises(TypeError, asssize_t, MyIndexAndInt()) + + self.assertRaises(OverflowError, asssize_t, PY_SSIZE_T_MIN - 1) + self.assertRaises(OverflowError, asssize_t, PY_SSIZE_T_MAX + 1) + self.assertRaises(TypeError, asssize_t, 1.0) + self.assertRaises(TypeError, asssize_t, b'2') + self.assertRaises(TypeError, asssize_t, '3') + self.assertRaises(SystemError, asssize_t, NULL) + + def test_long_assize_t(self): + """Test PyLong_AsSize_t() and PyLong_FromSize_t()""" + assize_t = _testcapi.pylong_assize_t + from _testcapi import SIZE_MAX + # round trip (object -> size_t -> object) + for value in (SIZE_MAX, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(assize_t(value), value) + + self.assertEqual(assize_t(IntSubclass(42)), 42) + self.assertRaises(TypeError, assize_t, Index(42)) + self.assertRaises(TypeError, assize_t, MyIndexAndInt()) + + self.assertRaises(OverflowError, assize_t, -1) + self.assertRaises(OverflowError, assize_t, SIZE_MAX + 1) + self.assertRaises(TypeError, assize_t, 1.0) + self.assertRaises(TypeError, assize_t, b'2') + self.assertRaises(TypeError, assize_t, '3') + self.assertRaises(SystemError, assize_t, NULL) + + def test_long_asdouble(self): + """Test PyLong_AsDouble()""" + asdouble = _testcapi.pylong_asdouble + MAX = int(sys.float_info.max) + for value in (-MAX, MAX, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(asdouble(value), float(value)) + self.assertIsInstance(asdouble(value), float) + + self.assertEqual(asdouble(IntSubclass(42)), 42.0) + self.assertRaises(TypeError, asdouble, Index(42)) + self.assertRaises(TypeError, asdouble, MyIndexAndInt()) + + self.assertRaises(OverflowError, asdouble, 2 * MAX) + self.assertRaises(OverflowError, asdouble, -2 * MAX) + self.assertRaises(TypeError, asdouble, 1.0) + self.assertRaises(TypeError, asdouble, b'2') + self.assertRaises(TypeError, asdouble, '3') + self.assertRaises(SystemError, asdouble, NULL) + + def test_long_asvoidptr(self): + """Test PyLong_AsVoidPtr()""" + fromvoidptr = _testcapi.pylong_fromvoidptr + asvoidptr = _testcapi.pylong_asvoidptr + obj = object() + x = fromvoidptr(obj) + y = fromvoidptr(NULL) + self.assertIs(asvoidptr(x), obj) + self.assertIs(asvoidptr(y), NULL) + self.assertIs(asvoidptr(IntSubclass(x)), obj) + + # negative values + M = (1 << _testcapi.SIZEOF_VOID_P * 8) + if x >= M//2: + self.assertIs(asvoidptr(x - M), obj) + if y >= M//2: + self.assertIs(asvoidptr(y - M), NULL) + + self.assertRaises(TypeError, asvoidptr, Index(x)) + self.assertRaises(TypeError, asvoidptr, object()) + self.assertRaises(OverflowError, asvoidptr, 2**1000) + self.assertRaises(OverflowError, asvoidptr, -2**1000) + # CRASHES asvoidptr(NULL) if __name__ == "__main__": diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index 4362f431fc3f4d..ba1babdccd19f1 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -3,6 +3,7 @@ #endif #include "parts.h" +#include "util.h" #include "clinic/long.c.h" /*[clinic input] @@ -554,6 +555,79 @@ _testcapi_call_long_compact_api(PyObject *module, PyObject *arg) return Py_BuildValue("in", is_compact, value); } +static PyObject * +pylong_check(PyObject *module, PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyLong_Check(obj)); +} + +static PyObject * +pylong_checkexact(PyObject *module, PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyLong_CheckExact(obj)); +} + +static PyObject * +pylong_fromlong(PyObject *module, PyObject *arg) +{ + long value; + if (!PyArg_Parse(arg, "l", &value)) { + return NULL; + } + return PyLong_FromLong(value); +} + +static PyObject * +pylong_fromdouble(PyObject *module, PyObject *arg) +{ + double value; + if (!PyArg_Parse(arg, "d", &value)) { + return NULL; + } + return PyLong_FromDouble(value); +} + +static PyObject * +pylong_fromstring(PyObject *module, PyObject *args) +{ + const char *str; + Py_ssize_t len; + int base; + char *end = UNINITIALIZED_PTR; + if (!PyArg_ParseTuple(args, "z#i", &str, &len, &base)) { + return NULL; + } + + PyObject *result = PyLong_FromString(str, &end, base); + if (result == NULL) { + // XXX 'end' is not always set. + return NULL; + } + return Py_BuildValue("Nn", result, (Py_ssize_t)(end - str)); +} + +static PyObject * +pylong_fromunicodeobject(PyObject *module, PyObject *args) +{ + PyObject *unicode; + int base; + if (!PyArg_ParseTuple(args, "Oi", &unicode, &base)) { + return NULL; + } + + NULLABLE(unicode); + return PyLong_FromUnicodeObject(unicode, base); +} + +static PyObject * +pylong_fromvoidptr(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + return PyLong_FromVoidPtr((void *)arg); +} + /*[clinic input] _testcapi.PyLong_AsInt arg: object @@ -564,6 +638,7 @@ static PyObject * _testcapi_PyLong_AsInt(PyObject *module, PyObject *arg) /*[clinic end generated code: output=0df9f19de5fa575b input=9561b97105493a67]*/ { + NULLABLE(arg); assert(!PyErr_Occurred()); int value = PyLong_AsInt(arg); if (value == -1 && PyErr_Occurred()) { @@ -572,6 +647,145 @@ _testcapi_PyLong_AsInt(PyObject *module, PyObject *arg) return PyLong_FromLong(value); } +static PyObject * +pylong_aslong(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + long value = PyLong_AsLong(arg); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromLong(value); +} + +static PyObject * +pylong_aslongandoverflow(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + int overflow = UNINITIALIZED_INT; + long value = PyLong_AsLongAndOverflow(arg, &overflow); + if (value == -1 && PyErr_Occurred()) { + assert(overflow == -1); + return NULL; + } + return Py_BuildValue("li", value, overflow); +} + +static PyObject * +pylong_asunsignedlong(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + unsigned long value = PyLong_AsUnsignedLong(arg); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromUnsignedLong(value); +} + +static PyObject * +pylong_asunsignedlongmask(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + unsigned long value = PyLong_AsUnsignedLongMask(arg); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromUnsignedLong(value); +} + +static PyObject * +pylong_aslonglong(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + long long value = PyLong_AsLongLong(arg); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromLongLong(value); +} + +static PyObject * +pylong_aslonglongandoverflow(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + int overflow = UNINITIALIZED_INT; + long long value = PyLong_AsLongLongAndOverflow(arg, &overflow); + if (value == -1 && PyErr_Occurred()) { + assert(overflow == -1); + return NULL; + } + return Py_BuildValue("Li", value, overflow); +} + +static PyObject * +pylong_asunsignedlonglong(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + unsigned long long value = PyLong_AsUnsignedLongLong(arg); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromUnsignedLongLong(value); +} + +static PyObject * +pylong_asunsignedlonglongmask(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + unsigned long long value = PyLong_AsUnsignedLongLongMask(arg); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromUnsignedLongLong(value); +} + +static PyObject * +pylong_asssize_t(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + Py_ssize_t value = PyLong_AsSsize_t(arg); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromSsize_t(value); +} + +static PyObject * +pylong_assize_t(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + size_t value = PyLong_AsSize_t(arg); + if (value == (size_t)-1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromSize_t(value); +} + +static PyObject * +pylong_asdouble(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + double value = PyLong_AsDouble(arg); + if (value == -1.0 && PyErr_Occurred()) { + return NULL; + } + return PyFloat_FromDouble(value); +} + +static PyObject * +pylong_asvoidptr(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + void *value = PyLong_AsVoidPtr(arg); + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_NONE; + } + return Py_NewRef((PyObject *)value); +} + static PyMethodDef test_methods[] = { _TESTCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF _TESTCAPI_TEST_LONG_API_METHODDEF @@ -581,7 +795,26 @@ static PyMethodDef test_methods[] = { _TESTCAPI_TEST_LONG_LONG_AND_OVERFLOW_METHODDEF _TESTCAPI_TEST_LONGLONG_API_METHODDEF _TESTCAPI_CALL_LONG_COMPACT_API_METHODDEF + {"pylong_check", pylong_check, METH_O}, + {"pylong_checkexact", pylong_checkexact, METH_O}, + {"pylong_fromlong", pylong_fromlong, METH_O}, + {"pylong_fromdouble", pylong_fromdouble, METH_O}, + {"pylong_fromstring", pylong_fromstring, METH_VARARGS}, + {"pylong_fromunicodeobject", pylong_fromunicodeobject, METH_VARARGS}, + {"pylong_fromvoidptr", pylong_fromvoidptr, METH_O}, _TESTCAPI_PYLONG_ASINT_METHODDEF + {"pylong_aslong", pylong_aslong, METH_O}, + {"pylong_aslongandoverflow", pylong_aslongandoverflow, METH_O}, + {"pylong_asunsignedlong", pylong_asunsignedlong, METH_O}, + {"pylong_asunsignedlongmask", pylong_asunsignedlongmask, METH_O}, + {"pylong_aslonglong", pylong_aslonglong, METH_O}, + {"pylong_aslonglongandoverflow", pylong_aslonglongandoverflow, METH_O}, + {"pylong_asunsignedlonglong", pylong_asunsignedlonglong, METH_O}, + {"pylong_asunsignedlonglongmask", pylong_asunsignedlonglongmask, METH_O}, + {"pylong_asssize_t", pylong_asssize_t, METH_O}, + {"pylong_assize_t", pylong_assize_t, METH_O}, + {"pylong_asdouble", pylong_asdouble, METH_O}, + {"pylong_asvoidptr", pylong_asvoidptr, METH_O}, {NULL}, }; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ce3d0b1b1b005c..c58cef1f10dc2c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3923,7 +3923,9 @@ PyInit__testcapi(void) PyModule_AddObject(m, "ULLONG_MAX", PyLong_FromUnsignedLongLong(ULLONG_MAX)); PyModule_AddObject(m, "PY_SSIZE_T_MAX", PyLong_FromSsize_t(PY_SSIZE_T_MAX)); PyModule_AddObject(m, "PY_SSIZE_T_MIN", PyLong_FromSsize_t(PY_SSIZE_T_MIN)); + PyModule_AddObject(m, "SIZE_MAX", PyLong_FromSize_t(SIZE_MAX)); PyModule_AddObject(m, "SIZEOF_WCHAR_T", PyLong_FromSsize_t(sizeof(wchar_t))); + PyModule_AddObject(m, "SIZEOF_VOID_P", PyLong_FromSsize_t(sizeof(void*))); PyModule_AddObject(m, "SIZEOF_TIME_T", PyLong_FromSsize_t(sizeof(time_t))); PyModule_AddObject(m, "Py_Version", PyLong_FromUnsignedLong(Py_Version)); Py_INCREF(&PyInstanceMethod_Type); From ad08600dfbb3f24d3e30bc7a1a9a56469b7c72e4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 10 Oct 2023 16:25:16 +0300 Subject: [PATCH 2/4] Add underscores for readability. --- Lib/test/test_capi/test_long.py | 52 ++++++++++++++++----------------- Modules/_testcapi/long.c | 19 +++--------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index daf5a9ec0b11c2..5b6d853de6ce9e 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -342,45 +342,45 @@ def test_long_asunsignedlonglongmask(self): self.assertRaises(TypeError, asunsignedlonglongmask, '3') self.assertRaises(SystemError, asunsignedlonglongmask, NULL) - def test_long_asssize_t(self): + def test_long_as_ssize_t(self): """Test PyLong_AsSsize_t() and PyLong_FromSsize_t()""" - asssize_t = _testcapi.pylong_asssize_t + as_ssize_t = _testcapi.pylong_as_ssize_t from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX # round trip (object -> Py_ssize_t -> object) for value in (PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, -1, 0, 1, 1234): with self.subTest(value=value): - self.assertEqual(asssize_t(value), value) + self.assertEqual(as_ssize_t(value), value) - self.assertEqual(asssize_t(IntSubclass(42)), 42) - self.assertRaises(TypeError, asssize_t, Index(42)) - self.assertRaises(TypeError, asssize_t, MyIndexAndInt()) + self.assertEqual(as_ssize_t(IntSubclass(42)), 42) + self.assertRaises(TypeError, as_ssize_t, Index(42)) + self.assertRaises(TypeError, as_ssize_t, MyIndexAndInt()) - self.assertRaises(OverflowError, asssize_t, PY_SSIZE_T_MIN - 1) - self.assertRaises(OverflowError, asssize_t, PY_SSIZE_T_MAX + 1) - self.assertRaises(TypeError, asssize_t, 1.0) - self.assertRaises(TypeError, asssize_t, b'2') - self.assertRaises(TypeError, asssize_t, '3') - self.assertRaises(SystemError, asssize_t, NULL) + self.assertRaises(OverflowError, as_ssize_t, PY_SSIZE_T_MIN - 1) + self.assertRaises(OverflowError, as_ssize_t, PY_SSIZE_T_MAX + 1) + self.assertRaises(TypeError, as_ssize_t, 1.0) + self.assertRaises(TypeError, as_ssize_t, b'2') + self.assertRaises(TypeError, as_ssize_t, '3') + self.assertRaises(SystemError, as_ssize_t, NULL) - def test_long_assize_t(self): + def test_long_as_size_t(self): """Test PyLong_AsSize_t() and PyLong_FromSize_t()""" - assize_t = _testcapi.pylong_assize_t + as_size_t = _testcapi.pylong_as_size_t from _testcapi import SIZE_MAX # round trip (object -> size_t -> object) for value in (SIZE_MAX, 0, 1, 1234): with self.subTest(value=value): - self.assertEqual(assize_t(value), value) - - self.assertEqual(assize_t(IntSubclass(42)), 42) - self.assertRaises(TypeError, assize_t, Index(42)) - self.assertRaises(TypeError, assize_t, MyIndexAndInt()) - - self.assertRaises(OverflowError, assize_t, -1) - self.assertRaises(OverflowError, assize_t, SIZE_MAX + 1) - self.assertRaises(TypeError, assize_t, 1.0) - self.assertRaises(TypeError, assize_t, b'2') - self.assertRaises(TypeError, assize_t, '3') - self.assertRaises(SystemError, assize_t, NULL) + self.assertEqual(as_size_t(value), value) + + self.assertEqual(as_size_t(IntSubclass(42)), 42) + self.assertRaises(TypeError, as_size_t, Index(42)) + self.assertRaises(TypeError, as_size_t, MyIndexAndInt()) + + self.assertRaises(OverflowError, as_size_t, -1) + self.assertRaises(OverflowError, as_size_t, SIZE_MAX + 1) + self.assertRaises(TypeError, as_size_t, 1.0) + self.assertRaises(TypeError, as_size_t, b'2') + self.assertRaises(TypeError, as_size_t, '3') + self.assertRaises(SystemError, as_size_t, NULL) def test_long_asdouble(self): """Test PyLong_AsDouble()""" diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index ba1babdccd19f1..32ad8d32ab8523 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -569,16 +569,6 @@ pylong_checkexact(PyObject *module, PyObject *obj) return PyLong_FromLong(PyLong_CheckExact(obj)); } -static PyObject * -pylong_fromlong(PyObject *module, PyObject *arg) -{ - long value; - if (!PyArg_Parse(arg, "l", &value)) { - return NULL; - } - return PyLong_FromLong(value); -} - static PyObject * pylong_fromdouble(PyObject *module, PyObject *arg) { @@ -740,7 +730,7 @@ pylong_asunsignedlonglongmask(PyObject *module, PyObject *arg) } static PyObject * -pylong_asssize_t(PyObject *module, PyObject *arg) +pylong_as_ssize_t(PyObject *module, PyObject *arg) { NULLABLE(arg); Py_ssize_t value = PyLong_AsSsize_t(arg); @@ -751,7 +741,7 @@ pylong_asssize_t(PyObject *module, PyObject *arg) } static PyObject * -pylong_assize_t(PyObject *module, PyObject *arg) +pylong_as_size_t(PyObject *module, PyObject *arg) { NULLABLE(arg); size_t value = PyLong_AsSize_t(arg); @@ -797,7 +787,6 @@ static PyMethodDef test_methods[] = { _TESTCAPI_CALL_LONG_COMPACT_API_METHODDEF {"pylong_check", pylong_check, METH_O}, {"pylong_checkexact", pylong_checkexact, METH_O}, - {"pylong_fromlong", pylong_fromlong, METH_O}, {"pylong_fromdouble", pylong_fromdouble, METH_O}, {"pylong_fromstring", pylong_fromstring, METH_VARARGS}, {"pylong_fromunicodeobject", pylong_fromunicodeobject, METH_VARARGS}, @@ -811,8 +800,8 @@ static PyMethodDef test_methods[] = { {"pylong_aslonglongandoverflow", pylong_aslonglongandoverflow, METH_O}, {"pylong_asunsignedlonglong", pylong_asunsignedlonglong, METH_O}, {"pylong_asunsignedlonglongmask", pylong_asunsignedlonglongmask, METH_O}, - {"pylong_asssize_t", pylong_asssize_t, METH_O}, - {"pylong_assize_t", pylong_assize_t, METH_O}, + {"pylong_as_ssize_t", pylong_as_ssize_t, METH_O}, + {"pylong_as_size_t", pylong_as_size_t, METH_O}, {"pylong_asdouble", pylong_asdouble, METH_O}, {"pylong_asvoidptr", pylong_asvoidptr, METH_O}, {NULL}, From 0a0c22b52cac4ddfbde9e4612d1650c7537d59e5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 10 Oct 2023 23:53:29 +0300 Subject: [PATCH 3/4] Turn docstrings into comments. --- Lib/test/test_capi/test_long.py | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 5b6d853de6ce9e..b34008cecfc141 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -54,7 +54,7 @@ def test_compact_known(self): (False, -1)) def test_long_check(self): - """Test PyLong_Check()""" + # Test PyLong_Check() check = _testcapi.pylong_check self.assertTrue(check(1)) self.assertTrue(check(123456789012345678901234567890)) @@ -66,7 +66,7 @@ def test_long_check(self): # CRASHES check(NULL) def test_long_checkexact(self): - """Test PyLong_CheckExact()""" + # Test PyLong_CheckExact() check = _testcapi.pylong_checkexact self.assertTrue(check(1)) self.assertTrue(check(123456789012345678901234567890)) @@ -78,7 +78,7 @@ def test_long_checkexact(self): # CRASHES check(NULL) def test_long_fromdouble(self): - """Test PyLong_FromDouble()""" + # Test PyLong_FromDouble() fromdouble = _testcapi.pylong_fromdouble self.assertEqual(fromdouble(5.0), 5) self.assertEqual(fromdouble(5.1), 5) @@ -92,7 +92,7 @@ def test_long_fromdouble(self): self.assertRaises(ValueError, fromdouble, float('nan')) def test_long_fromvoidptr(self): - """Test PyLong_FromVoidPtr()""" + # Test PyLong_FromVoidPtr() fromvoidptr = _testcapi.pylong_fromvoidptr obj = object() x = fromvoidptr(obj) @@ -104,7 +104,7 @@ def test_long_fromvoidptr(self): self.assertNotEqual(x, y) def test_long_fromstring(self): - """Test PyLong_FromString()""" + # Test PyLong_FromString() fromstring = _testcapi.pylong_fromstring self.assertEqual(fromstring(b'123', 10), (123, 3)) self.assertEqual(fromstring(b'cafe', 16), (0xcafe, 4)) @@ -134,7 +134,7 @@ def test_long_fromstring(self): # CRASHES fromstring(NULL, 16) def test_long_fromunicodeobject(self): - """Test PyLong_FromUnicodeObject()""" + # Test PyLong_FromUnicodeObject() fromunicodeobject = _testcapi.pylong_fromunicodeobject self.assertEqual(fromunicodeobject('123', 10), 123) self.assertEqual(fromunicodeobject('cafe', 16), 0xcafe) @@ -164,7 +164,7 @@ def test_long_fromunicodeobject(self): # CRASHES fromunicodeobject(NULL, 16) def test_long_asint(self): - """Test PyLong_AsInt()""" + # Test PyLong_AsInt() PyLong_AsInt = _testcapi.PyLong_AsInt from _testcapi import INT_MIN, INT_MAX @@ -187,7 +187,7 @@ def test_long_asint(self): self.assertRaises(SystemError, PyLong_AsInt, NULL) def test_long_aslong(self): - """Test PyLong_AsLong() and PyLong_FromLong()""" + # Test PyLong_AsLong() and PyLong_FromLong() aslong = _testcapi.pylong_aslong from _testcapi import LONG_MIN, LONG_MAX # round trip (object -> long -> object) @@ -207,7 +207,7 @@ def test_long_aslong(self): self.assertRaises(SystemError, aslong, NULL) def test_long_aslongandoverflow(self): - """Test PyLong_AsLongAndOverflow()""" + # Test PyLong_AsLongAndOverflow() aslongandoverflow = _testcapi.pylong_aslongandoverflow from _testcapi import LONG_MIN, LONG_MAX # round trip (object -> long -> object) @@ -225,7 +225,7 @@ def test_long_aslongandoverflow(self): # CRASHES aslongandoverflow(NULL) def test_long_asunsignedlong(self): - """Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()""" + # Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong() asunsignedlong = _testcapi.pylong_asunsignedlong from _testcapi import ULONG_MAX # round trip (object -> unsigned long -> object) @@ -245,7 +245,7 @@ def test_long_asunsignedlong(self): self.assertRaises(SystemError, asunsignedlong, NULL) def test_long_asunsignedlongmask(self): - """Test PyLong_AsUnsignedLongMask()""" + # Test PyLong_AsUnsignedLongMask() asunsignedlongmask = _testcapi.pylong_asunsignedlongmask from _testcapi import ULONG_MAX # round trip (object -> unsigned long -> object) @@ -265,7 +265,7 @@ def test_long_asunsignedlongmask(self): self.assertRaises(SystemError, asunsignedlongmask, NULL) def test_long_aslonglong(self): - """Test PyLong_AsLongLong() and PyLong_FromLongLong()""" + # Test PyLong_AsLongLong() and PyLong_FromLongLong() aslonglong = _testcapi.pylong_aslonglong from _testcapi import LLONG_MIN, LLONG_MAX # round trip (object -> long long -> object) @@ -285,7 +285,7 @@ def test_long_aslonglong(self): self.assertRaises(SystemError, aslonglong, NULL) def test_long_aslonglongandoverflow(self): - """Test PyLong_AsLongLongAndOverflow()""" + # Test PyLong_AsLongLongAndOverflow() aslonglongandoverflow = _testcapi.pylong_aslonglongandoverflow from _testcapi import LLONG_MIN, LLONG_MAX # round trip (object -> long long -> object) @@ -303,7 +303,7 @@ def test_long_aslonglongandoverflow(self): # CRASHES aslonglongandoverflow(NULL) def test_long_asunsignedlonglong(self): - """Test PyLong_AsUnsignedLongLong() and PyLong_FromUnsignedLongLong()""" + # Test PyLong_AsUnsignedLongLong() and PyLong_FromUnsignedLongLong() asunsignedlonglong = _testcapi.pylong_asunsignedlonglong from _testcapi import ULLONG_MAX # round trip (object -> unsigned long long -> object) @@ -323,7 +323,7 @@ def test_long_asunsignedlonglong(self): self.assertRaises(SystemError, asunsignedlonglong, NULL) def test_long_asunsignedlonglongmask(self): - """Test PyLong_AsUnsignedLongLongMask()""" + # Test PyLong_AsUnsignedLongLongMask() asunsignedlonglongmask = _testcapi.pylong_asunsignedlonglongmask from _testcapi import ULLONG_MAX # round trip (object -> unsigned long long -> object) @@ -343,7 +343,7 @@ def test_long_asunsignedlonglongmask(self): self.assertRaises(SystemError, asunsignedlonglongmask, NULL) def test_long_as_ssize_t(self): - """Test PyLong_AsSsize_t() and PyLong_FromSsize_t()""" + # Test PyLong_AsSsize_t() and PyLong_FromSsize_t() as_ssize_t = _testcapi.pylong_as_ssize_t from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX # round trip (object -> Py_ssize_t -> object) @@ -363,7 +363,7 @@ def test_long_as_ssize_t(self): self.assertRaises(SystemError, as_ssize_t, NULL) def test_long_as_size_t(self): - """Test PyLong_AsSize_t() and PyLong_FromSize_t()""" + # Test PyLong_AsSize_t() and PyLong_FromSize_t() as_size_t = _testcapi.pylong_as_size_t from _testcapi import SIZE_MAX # round trip (object -> size_t -> object) @@ -383,7 +383,7 @@ def test_long_as_size_t(self): self.assertRaises(SystemError, as_size_t, NULL) def test_long_asdouble(self): - """Test PyLong_AsDouble()""" + # Test PyLong_AsDouble() asdouble = _testcapi.pylong_asdouble MAX = int(sys.float_info.max) for value in (-MAX, MAX, -1, 0, 1, 1234): @@ -403,7 +403,7 @@ def test_long_asdouble(self): self.assertRaises(SystemError, asdouble, NULL) def test_long_asvoidptr(self): - """Test PyLong_AsVoidPtr()""" + # Test PyLong_AsVoidPtr() fromvoidptr = _testcapi.pylong_fromvoidptr asvoidptr = _testcapi.pylong_asvoidptr obj = object() From c908ae794b23fd45bbfdddd5e7781d8ec8998ddb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 11 Oct 2023 00:16:33 +0300 Subject: [PATCH 4/4] Address review comments. --- Lib/test/test_capi/test_long.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index b34008cecfc141..8e3ef25d1ff86f 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -80,13 +80,10 @@ def test_long_checkexact(self): def test_long_fromdouble(self): # Test PyLong_FromDouble() fromdouble = _testcapi.pylong_fromdouble - self.assertEqual(fromdouble(5.0), 5) - self.assertEqual(fromdouble(5.1), 5) - self.assertEqual(fromdouble(5.9), 5) - self.assertEqual(fromdouble(-5.1), -5) - self.assertEqual(fromdouble(-5.9), -5) - self.assertEqual(fromdouble(sys.float_info.max), int(sys.float_info.max)) - self.assertEqual(fromdouble(-sys.float_info.max), -int(sys.float_info.max)) + float_max = sys.float_info.max + for value in (5.0, 5.1, 5.9, -5.1, -5.9, 0.0, -0.0, float_max, -float_max): + with self.subTest(value=value): + self.assertEqual(fromdouble(value), int(value)) self.assertRaises(OverflowError, fromdouble, float('inf')) self.assertRaises(OverflowError, fromdouble, float('-inf')) self.assertRaises(ValueError, fromdouble, float('nan')) @@ -100,7 +97,7 @@ def test_long_fromvoidptr(self): self.assertIsInstance(x, int) self.assertGreaterEqual(x, 0) self.assertIsInstance(y, int) - self.assertGreaterEqual(y, 0) + self.assertEqual(y, 0) self.assertNotEqual(x, y) def test_long_fromstring(self):