diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 46db02593ad106..23ef7230419e3b 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -26,6 +26,9 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); PyAPI_FUNC(PyObject *)_PyTuple_FromStackRefStealOnSuccess(const union _PyStackRef *, Py_ssize_t); PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); +PyAPI_FUNC(PyObject *)_PyTuple_FromPair(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *)_PyTuple_FromPairSteal(PyObject *, PyObject *); + typedef struct { PyObject_HEAD Py_ssize_t it_index; diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index d6669d7802c5b8..a14b158bbf8348 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -1,9 +1,11 @@ import unittest import gc +from sys import getrefcount from test.support import import_helper _testcapi = import_helper.import_module('_testcapi') _testlimitedcapi = import_helper.import_module('_testlimitedcapi') +_testinternalcapi = import_helper.import_module('_testinternalcapi') NULL = None PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN @@ -118,6 +120,36 @@ def test_tuple_pack(self): # CRASHES pack(1, NULL) # CRASHES pack(2, [1]) + def test_tuple_from_pair(self): + # Test _PyTuple_FromPair, _PyTuple_FromPairSteal + ctors = (("_PyTuple_FromPair", _testinternalcapi._tuple_from_pair), + ("_PyTuple_FromPairSteal", _testinternalcapi._tuple_from_pair_steal)) + + for name, ctor in ctors: + with self.subTest(name): + self.assertEqual(ctor(1, 2), (1, 2)) + self.assertEqual(ctor(None, None), (None, None)) + self.assertEqual(ctor(True, False), (True, False)) + + # user class supports gc + class Temp: + pass + temp = Temp() + temp_rc = getrefcount(temp) + self.assertEqual(ctor(temp, temp), (temp, temp)) + self.assertEqual(getrefcount(temp), temp_rc) + + self.assertRaises(TypeError, ctor, 1, 2, 3) + self.assertRaises(TypeError, ctor, 1) + self.assertRaises(TypeError, ctor) + + self.assertFalse(gc.is_tracked(ctor(1, 2))) + self.assertFalse(gc.is_tracked(ctor(None, None))) + self.assertFalse(gc.is_tracked(ctor(True, False))) + self.assertTrue(gc.is_tracked(ctor(temp, (1, 2)))) + self.assertTrue(gc.is_tracked(ctor(temp, 1))) + self.assertTrue(gc.is_tracked(ctor([], {}))) + def test_tuple_size(self): # Test PyTuple_Size() size = _testlimitedcapi.tuple_size diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 1dd0512832adf7..97d8fafa0c975c 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -174,7 +174,7 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 22cfa3f58a9d83..b17faa05f69ec3 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2981,6 +2981,9 @@ module_exec(PyObject *module) if (_PyTestInternalCapi_Init_CriticalSection(module) < 0) { return 1; } + if (_PyTestInternalCapi_Init_Tuple(module) < 0) { + return 1; + } Py_ssize_t sizeof_gc_head = 0; #ifndef Py_GIL_DISABLED diff --git a/Modules/_testinternalcapi/parts.h b/Modules/_testinternalcapi/parts.h index 03557d5bf5957f..81f536c3babb18 100644 --- a/Modules/_testinternalcapi/parts.h +++ b/Modules/_testinternalcapi/parts.h @@ -15,5 +15,6 @@ int _PyTestInternalCapi_Init_PyTime(PyObject *module); int _PyTestInternalCapi_Init_Set(PyObject *module); int _PyTestInternalCapi_Init_Complex(PyObject *module); int _PyTestInternalCapi_Init_CriticalSection(PyObject *module); +int _PyTestInternalCapi_Init_Tuple(PyObject *module); #endif // Py_TESTINTERNALCAPI_PARTS_H diff --git a/Modules/_testinternalcapi/tuple.c b/Modules/_testinternalcapi/tuple.c new file mode 100644 index 00000000000000..ee95dee2b21bcc --- /dev/null +++ b/Modules/_testinternalcapi/tuple.c @@ -0,0 +1,43 @@ +#include "parts.h" + +#include "pycore_tuple.h" + + +static PyObject * +_tuple_from_pair(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *one, *two; + if (!PyArg_ParseTuple(args, "OO", &one, &two)) { + return NULL; + } + + return _PyTuple_FromPair(one, two); +} + +static PyObject * +_tuple_from_pair_steal(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *one, *two; + if (!PyArg_ParseTuple(args, "OO", &one, &two)) { + return NULL; + } + + return _PyTuple_FromPairSteal(Py_NewRef(one), Py_NewRef(two)); +} + + +static PyMethodDef test_methods[] = { + {"_tuple_from_pair", _tuple_from_pair, METH_VARARGS}, + {"_tuple_from_pair_steal", _tuple_from_pair_steal, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestInternalCapi_Init_Tuple(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + + return 0; +} diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 169ac69701da11..d91328786f41bf 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -202,6 +202,58 @@ PyTuple_Pack(Py_ssize_t n, ...) return (PyObject *)result; } +static PyTupleObject * +tuple_alloc_2(void) +{ + Py_ssize_t size = 2; + Py_ssize_t index = size - 1; + PyTupleObject *result = _Py_FREELIST_POP(PyTupleObject, tuples[index]); + if (result == NULL) { + result = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size); + } + if (result != NULL) { + _PyTuple_RESET_HASH_CACHE(result); + } + return result; +} + +PyObject * +_PyTuple_FromPair(PyObject *one, PyObject *two) +{ + assert (one != NULL); + assert (two != NULL); + + PyTupleObject *op = tuple_alloc_2(); + if (op == NULL) { + return NULL; + } + op->ob_item[0] = Py_NewRef(one); + op->ob_item[1] = Py_NewRef(two); + if (maybe_tracked(one) || maybe_tracked(two)) { + _PyObject_GC_TRACK(op); + } + return (PyObject *)op; +} + +PyObject * +_PyTuple_FromPairSteal(PyObject *one, PyObject *two) +{ + assert (one != NULL); + assert (two != NULL); + + PyTupleObject *op = tuple_alloc_2(); + if (op == NULL) { + Py_DECREF(one); + Py_DECREF(two); + return NULL; + } + op->ob_item[0] = one; + op->ob_item[1] = two; + if (maybe_tracked(one) || maybe_tracked(two)) { + _PyObject_GC_TRACK(op); + } + return (PyObject *)op; +} /* Methods */ diff --git a/PCbuild/_testinternalcapi.vcxproj b/PCbuild/_testinternalcapi.vcxproj index 3818e6d3f7bbd2..f3e423fa04668e 100644 --- a/PCbuild/_testinternalcapi.vcxproj +++ b/PCbuild/_testinternalcapi.vcxproj @@ -100,6 +100,7 @@ + diff --git a/PCbuild/_testinternalcapi.vcxproj.filters b/PCbuild/_testinternalcapi.vcxproj.filters index 012d709bd1ce5d..7ab242c2c230b6 100644 --- a/PCbuild/_testinternalcapi.vcxproj.filters +++ b/PCbuild/_testinternalcapi.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files +