diff --git a/Include/object.h b/Include/object.h index 20c2dab4401fef..549b91ebecf4f2 100644 --- a/Include/object.h +++ b/Include/object.h @@ -490,7 +490,7 @@ given type object has a specified feature. #define Py_TPFLAGS_SEQUENCE (1 << 5) /* Set if instances of the type object are treated as mappings for pattern matching */ #define Py_TPFLAGS_MAPPING (1 << 6) -#endif +#endif // Py_LIMITED_API /* Disallow creating instances of the type: set tp_new to NULL and don't create * the "__new__" key in the type dictionary. */ diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py index e6a8ef9eed6fc4..90b1f57d1c3c9d 100644 --- a/Lib/test/test_capi/test_type.py +++ b/Lib/test/test_capi/test_type.py @@ -2,6 +2,9 @@ import unittest _testcapi = import_helper.import_module('_testcapi') +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + +NULL = None class BuiltinStaticTypesTests(unittest.TestCase): @@ -39,15 +42,18 @@ def test_tp_mro_is_set(self): class TypeTests(unittest.TestCase): def test_get_type_name(self): + # Test PyType_GetName(), PyType_GetQualName(), + # PyType_GetFullyQualifiedName() and PyType_GetModuleName(). + class MyType: pass - from _testcapi import ( + from _testlimitedcapi import ( get_type_name, get_type_qualname, get_type_fullyqualname, get_type_module_name) from collections import OrderedDict - ht = _testcapi.get_heaptype_for_name() + ht = _testlimitedcapi.get_heaptype_for_name() for cls, fullname, modname, qualname, name in ( (int, 'int', @@ -107,6 +113,15 @@ class MyType: MyType.__module__ = 123 self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') + # CRASHES get_type_name(NULL) + # CRASHES get_type_qualname(NULL) + # CRASHES get_type_fullyqualname(NULL) + # CRASHES get_type_module_name(NULL) + # CRASHES get_type_name(object()): argument must be a type + # CRASHES get_type_qualname(object()): argument must be a type + # CRASHES get_type_fullyqualname(object()): argument must be a type + # CRASHES get_type_module_name(object()): argument must be a type + def test_get_base_by_token(self): def get_base_by_token(src, key, comparable=True): def run(use_mro): @@ -215,7 +230,7 @@ class H2(int): pass def test_freeze(self): # test PyType_Freeze() - type_freeze = _testcapi.type_freeze + type_freeze = _testlimitedcapi.type_freeze # simple case, no inherante class MyType: @@ -245,12 +260,15 @@ class D(A, C): pass # as well type_freeze(D) + # CRASHES type_freeze(NULL) + # CRASHES type_freeze(object()): argument must be a type + @unittest.skipIf( Py_GIL_DISABLED and refleak_helper.hunting_for_refleaks(), "Specialization failure triggers gh-127773") def test_freeze_meta(self): """test PyType_Freeze() with overridden MRO""" - type_freeze = _testcapi.type_freeze + type_freeze = _testlimitedcapi.type_freeze class Base: value = 1 @@ -299,3 +317,181 @@ def test_extension_managed_weakref_nogc_type(self): "flag but not Py_TPFLAGS_HAVE_GC flag") with self.assertRaisesRegex(SystemError, msg): _testcapi.create_managed_weakref_nogc_type() + + def test_type_ready(self): + # Test PyType_Ready(): calling it on initialized types + # must not raise an exception. + type_ready = _testlimitedcapi.type_ready + + class HeapType: + pass + + type_ready(int) + type_ready(dict) + type_ready(HeapType) + + # CRASHES type_ready(NULL) + # CRASHES type_ready(123): argument must be a type + + def test_type_clearcache(self): + # Test PyType_ClearCache() + type_clearcache = _testlimitedcapi.type_clearcache + version_tag = type_clearcache() + self.assertEqual(type(version_tag), int) + self.assertGreaterEqual(version_tag, 0) + + def test_type_getflags(self): + # Test PyType_GetFlags() + type_getflags = _testlimitedcapi.type_getflags + + from _testlimitedcapi import ( + Py_TPFLAGS_HEAPTYPE, + Py_TPFLAGS_HAVE_GC, + Py_TPFLAGS_HAVE_FINALIZE, + Py_TPFLAGS_HAVE_VERSION_TAG, + Py_TPFLAGS_VALID_VERSION_TAG, + Py_TPFLAGS_HAVE_VECTORCALL, + Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE, + Py_TPFLAGS_READY, + Py_TPFLAGS_READYING, + Py_TPFLAGS_LONG_SUBCLASS, + Py_TPFLAGS_LIST_SUBCLASS, + Py_TPFLAGS_TUPLE_SUBCLASS, + Py_TPFLAGS_BYTES_SUBCLASS, + Py_TPFLAGS_UNICODE_SUBCLASS, + Py_TPFLAGS_DICT_SUBCLASS, + Py_TPFLAGS_BASE_EXC_SUBCLASS, + Py_TPFLAGS_TYPE_SUBCLASS, + Py_TPFLAGS_IS_ABSTRACT, + Py_TPFLAGS_BASETYPE, + _Py_TPFLAGS_MATCH_SELF, + Py_TPFLAGS_ITEMS_AT_END, + Py_TPFLAGS_METHOD_DESCRIPTOR, + ) + from _testcapi import ( + _Py_TPFLAGS_STATIC_BUILTIN, + Py_TPFLAGS_SEQUENCE, + Py_TPFLAGS_MAPPING, + Py_TPFLAGS_INLINE_VALUES, + Py_TPFLAGS_MANAGED_WEAKREF, + Py_TPFLAGS_MANAGED_DICT, + ) + + def check_flag(flags, flag, expected): + self.assertEqual(bool(flags & flag), expected) + + def check_subclasses(test_type, flags): + for flag, base_type in ( + (Py_TPFLAGS_LONG_SUBCLASS, int), + (Py_TPFLAGS_LIST_SUBCLASS, list), + (Py_TPFLAGS_TUPLE_SUBCLASS, tuple), + (Py_TPFLAGS_BYTES_SUBCLASS, bytes), + (Py_TPFLAGS_UNICODE_SUBCLASS, str), + (Py_TPFLAGS_DICT_SUBCLASS, dict), + (Py_TPFLAGS_BASE_EXC_SUBCLASS, BaseException), + (Py_TPFLAGS_TYPE_SUBCLASS, type), + ): + with self.subTest(test_type=test_type, flag=flag, base_type=base_type): + check_flag(flags, flag, issubclass(test_type, base_type)) + + def check_type(test_type, static_type, have_gc=False, have_vectorcall=False, + is_base_type=True, sequence=False, mapping=False, + match_self=True, items_at_end=False): + heap_type = not static_type + + flags = type_getflags(test_type) + check_flag(flags, _Py_TPFLAGS_STATIC_BUILTIN, static_type) + check_flag(flags, Py_TPFLAGS_HEAPTYPE, heap_type) + check_flag(flags, Py_TPFLAGS_HAVE_GC, have_gc) + check_subclasses(test_type, flags) + check_flag(flags, Py_TPFLAGS_HAVE_VECTORCALL, have_vectorcall) + check_flag(flags, Py_TPFLAGS_DISALLOW_INSTANTIATION, False) + check_flag(flags, Py_TPFLAGS_IMMUTABLETYPE, static_type) + check_flag(flags, Py_TPFLAGS_READY, True) + check_flag(flags, Py_TPFLAGS_READYING, False) + check_flag(flags, Py_TPFLAGS_IS_ABSTRACT, False) + check_flag(flags, Py_TPFLAGS_BASETYPE, is_base_type) + check_flag(flags, Py_TPFLAGS_SEQUENCE, sequence) + check_flag(flags, Py_TPFLAGS_MAPPING, mapping) + + check_flag(flags, Py_TPFLAGS_INLINE_VALUES, heap_type) + check_flag(flags, Py_TPFLAGS_MANAGED_WEAKREF, heap_type) + check_flag(flags, Py_TPFLAGS_MANAGED_DICT, heap_type) + check_flag(flags, Py_TPFLAGS_ITEMS_AT_END, items_at_end) + check_flag(flags, Py_TPFLAGS_METHOD_DESCRIPTOR, False) + + check_flag(flags, _Py_TPFLAGS_MATCH_SELF, match_self) + + # Flags kept for backward compatibility + check_flag(flags, Py_TPFLAGS_HAVE_FINALIZE, False) + check_flag(flags, Py_TPFLAGS_HAVE_VERSION_TAG, False) + check_flag(flags, Py_TPFLAGS_VALID_VERSION_TAG, False) + + # Scalar types + check_type(int, static_type=True) + check_type(bool, static_type=True, + is_base_type=False) + check_type(float, static_type=True) + check_type(complex, static_type=True, + match_self=False) + check_type(bytes, static_type=True) + check_type(bytearray, static_type=True) + check_type(str, static_type=True) + + # Collection types + check_type(tuple, static_type=True, have_gc=True, + sequence=True) + check_type(list, static_type=True, have_gc=True, + sequence=True) + check_type(dict, static_type=True, have_gc=True, + mapping=True) + check_type(frozendict, static_type=True, have_gc=True, + mapping=True) + check_type(set, static_type=True, have_gc=True) + check_type(frozenset, static_type=True, have_gc=True) + + # Other types + check_type(BaseException, static_type=True, have_gc=True, + match_self=False) + check_type(type, static_type=True, have_gc=True, + have_vectorcall=True, + match_self=False, + items_at_end=True) + + # Heap type + class HeapType: + pass + check_type(HeapType, static_type=False, have_gc=True, match_self=False) + + # CRASHES type_getflags(NULL) + + def test_type_issubtype(self): + # Test PyType_IsSubtype() + _type_issubtype = _testlimitedcapi.type_issubtype + + def type_issubtype(type1, type2): + res = _type_issubtype(type1, type2) + self.assertIn(res, (0, 1)) + return bool(res) + + class MyList(list): + pass + + self.assertTrue(type_issubtype(bool, int)) + self.assertTrue(type_issubtype(MyList, list)) + + self.assertFalse(type_issubtype(int, type)) + self.assertFalse(type_issubtype(frozendict, dict)) + self.assertFalse(type_issubtype(MyList, tuple)) + + def test_type_modified(self): + # Test PyType_Modified() + type_modified = _testlimitedcapi.type_modified + + class MyType: + pass + type_modified(MyType) + + # CRASHES type_modified(NULL) + # CRASHES type_modified({}): argument must be a type diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 9827f2498554a5..0031e4b59c67c9 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -11,13 +11,15 @@ except ImportError: _clear_type_cache = None -# Skip this test if the _testcapi module isn't available. +# Skip this test if the _testcapi modules are not available. _testcapi = import_helper.import_module("_testcapi") +_testlimitedcapi = import_helper.import_module("_testlimitedcapi") _testinternalcapi = import_helper.import_module("_testinternalcapi") + type_get_version = _testcapi.type_get_version type_assign_specific_version_unsafe = _testinternalcapi.type_assign_specific_version_unsafe type_assign_version = _testcapi.type_assign_version -type_modified = _testcapi.type_modified +type_modified = _testlimitedcapi.type_modified def clear_type_cache(): with warnings.catch_warnings(): diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index eaf8777a56c59b..440d5a71608ba9 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -174,7 +174,7 @@ @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 _testinternalcapi/tuple.c _testinternalcapi/typecache.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 _testcapi/weakref.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.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/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.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/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/type.c b/Modules/_testcapi/type.c index f566efa0ca15ae..91203c7bf0f678 100644 --- a/Modules/_testcapi/type.c +++ b/Modules/_testcapi/type.c @@ -1,3 +1,7 @@ +// Thin wrappers to PyType functions. +// Do no check PyType_Check() so Python tests can pass arbitrary objects, +// even if it's likely to crash. + #include "parts.h" #include "util.h" @@ -13,50 +17,11 @@ static PyType_Spec HeapTypeNameType_Spec = { .slots = HeapTypeNameType_slots, }; -static PyObject * -get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ - return PyType_FromSpec(&HeapTypeNameType_Spec); -} - - -static PyObject * -get_type_name(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetName((PyTypeObject *)type); -} - - -static PyObject * -get_type_qualname(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetQualName((PyTypeObject *)type); -} - - -static PyObject * -get_type_fullyqualname(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetFullyQualifiedName((PyTypeObject *)type); -} - - -static PyObject * -get_type_module_name(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetModuleName((PyTypeObject *)type); -} - +// Test for PyType_GetDict() static PyObject * test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored)) { - /* Test for PyType_GetDict */ - // Assert ints have a `to_bytes` method PyObject *long_dict = PyType_GetDict(&PyLong_Type); assert(long_dict); @@ -77,6 +42,7 @@ test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored)) } +// Test PyType_GetSlot() static PyObject * test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) { @@ -135,14 +101,12 @@ test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) // Get type->tp_version_tag static PyObject * -type_get_version(PyObject *self, PyObject *type) +type_get_version(PyObject *self, PyObject *arg) { - if (!PyType_Check(type)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } - PyObject *res = PyLong_FromUnsignedLong( - ((PyTypeObject *)type)->tp_version_tag); + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + PyObject *res = PyLong_FromUnsignedLong(type->tp_version_tag); if (res == NULL) { assert(PyErr_Occurred()); return NULL; @@ -150,27 +114,12 @@ type_get_version(PyObject *self, PyObject *type) return res; } -static PyObject * -type_modified(PyObject *self, PyObject *arg) -{ - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } - PyTypeObject *type = (PyTypeObject*)arg; - - PyType_Modified(type); - Py_RETURN_NONE; -} - +// Test PyUnstable_Type_AssignVersionTag() static PyObject * type_assign_version(PyObject *self, PyObject *arg) { - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } + NULLABLE(arg); PyTypeObject *type = (PyTypeObject*)arg; int res = PyUnstable_Type_AssignVersionTag(type); @@ -178,13 +127,11 @@ type_assign_version(PyObject *self, PyObject *arg) } +// Get PyTypeObject.tp_bases static PyObject * type_get_tp_bases(PyObject *self, PyObject *arg) { - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } + NULLABLE(arg); PyTypeObject *type = (PyTypeObject*)arg; PyObject *bases = type->tp_bases; @@ -194,16 +141,15 @@ type_get_tp_bases(PyObject *self, PyObject *arg) return Py_NewRef(bases); } + +// Get PyTypeObject.tp_mro static PyObject * type_get_tp_mro(PyObject *self, PyObject *arg) { - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } + NULLABLE(arg); PyTypeObject *type = (PyTypeObject*)arg; - PyObject *mro = ((PyTypeObject *)type)->tp_mro; + PyObject *mro = type->tp_mro; if (mro == NULL) { Py_RETURN_NONE; } @@ -211,41 +157,38 @@ type_get_tp_mro(PyObject *self, PyObject *arg) } -static PyObject * -type_freeze(PyObject *module, PyObject *arg) -{ - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } - PyTypeObject *type = (PyTypeObject*)arg; - - if (PyType_Freeze(type) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - - static PyMethodDef test_methods[] = { - {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS}, - {"get_type_name", get_type_name, METH_O}, - {"get_type_qualname", get_type_qualname, METH_O}, - {"get_type_fullyqualname", get_type_fullyqualname, METH_O}, - {"get_type_module_name", get_type_module_name, METH_O}, {"test_get_type_dict", test_get_type_dict, METH_NOARGS}, {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS}, {"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")}, - {"type_modified", type_modified, METH_O, PyDoc_STR("PyType_Modified")}, {"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")}, {"type_get_tp_bases", type_get_tp_bases, METH_O}, {"type_get_tp_mro", type_get_tp_mro, METH_O}, - {"type_freeze", type_freeze, METH_O}, {NULL}, }; int _PyTestCapi_Init_Type(PyObject *m) { - return PyModule_AddFunctions(m, test_methods); + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + +#define ADD_INT(macro) \ + do { \ + if (PyModule_AddIntConstant(m, #macro, macro) < 0) { \ + return -1; \ + } \ + } while (0) + + // Flags excluded from the limited C API + ADD_INT(_Py_TPFLAGS_STATIC_BUILTIN); + ADD_INT(Py_TPFLAGS_INLINE_VALUES); + ADD_INT(Py_TPFLAGS_MANAGED_WEAKREF); + ADD_INT(Py_TPFLAGS_MANAGED_DICT); + ADD_INT(Py_TPFLAGS_SEQUENCE); + ADD_INT(Py_TPFLAGS_MAPPING); + +#undef ADD_INT + return 0; } diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index 0a562ea9c03110..b602d3219f5a52 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -107,5 +107,8 @@ PyInit__testlimitedcapi(void) if (_PyTestLimitedCAPI_Init_Run(mod) < 0) { return NULL; } + if (_PyTestLimitedCAPI_Init_Type(mod) < 0) { + return NULL; + } return mod; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index 32c1bbc1b71c97..35fbdea1d434e0 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -48,5 +48,6 @@ int _PyTestLimitedCAPI_Init_Version(PyObject *module); int _PyTestLimitedCAPI_Init_File(PyObject *module); int _PyTestLimitedCAPI_Init_Weakref(PyObject *module); int _PyTestLimitedCAPI_Init_Run(PyObject *module); +int _PyTestLimitedCAPI_Init_Type(PyObject *module); #endif // Py_TESTLIMITEDCAPI_PARTS_H diff --git a/Modules/_testlimitedcapi/type.c b/Modules/_testlimitedcapi/type.c new file mode 100644 index 00000000000000..640468ae08dcbb --- /dev/null +++ b/Modules/_testlimitedcapi/type.c @@ -0,0 +1,222 @@ +// Thin wrappers to PyType functions. +// Do no check PyType_Check() so Python tests can pass arbitrary objects, +// even if it's likely to crash. + +// Need limited C API version 3.14 for PyType_Freeze() +#include "pyconfig.h" // Py_GIL_DISABLED +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +# define Py_LIMITED_API 0x030e0000 +#endif + +#include "parts.h" +#include "util.h" + + +static PyType_Slot HeapTypeNameType_slots[] = { + {0}, +}; + +static PyType_Spec HeapTypeNameType_Spec = { + .name = "_testcapi.HeapTypeNameType", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT, + .slots = HeapTypeNameType_slots, +}; + + +// Test PyType_FromSpec() with a minimum PyType_Spec +static PyObject* +get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return PyType_FromSpec(&HeapTypeNameType_Spec); +} + + +// Test PyType_GetName() +static PyObject* +get_type_name(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetName(type); +} + + +// Test PyType_GetQualName() +static PyObject* +get_type_qualname(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetQualName(type); +} + + +// Test PyType_GetFullyQualifiedName() +static PyObject* +get_type_fullyqualname(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetFullyQualifiedName(type); +} + + +// Test PyType_GetModuleName() +static PyObject* +get_type_module_name(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetModuleName(type); +} + + +// Test PyType_Modified() +static PyObject* +type_modified(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + PyType_Modified(type); + Py_RETURN_NONE; +} + + +// Test PyType_Ready() +static PyObject* +type_ready(PyObject *self, PyObject *arg) +{ + assert(!PyErr_Occurred()); + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + if (PyType_Ready(type) < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + Py_RETURN_NONE; +} + + +// Test PyType_Freeze() +static PyObject * +type_freeze(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + if (PyType_Freeze(type) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyType_ClearCache() +static PyObject * +type_clearcache(PyObject *module, PyObject *Py_UNUSED(arg)) +{ + // Since Python 3.16, PyType_ClearCache() is a no-op as the type cache is + // now implemented per-type. It still returns the current version tag. + unsigned int version_tag = PyType_ClearCache(); + assert(!PyErr_Occurred()); + return PyLong_FromUnsignedLong(version_tag); +} + + +// Test PyType_GetFlags() +static PyObject * +type_getflags(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + unsigned long flags = PyType_GetFlags(type); + assert(!PyErr_Occurred()); + return PyLong_FromUnsignedLong(flags); +} + + +// Test PyType_IsSubtype() +static PyObject * +type_issubtype(PyObject *module, PyObject *args) +{ + PyTypeObject *type1, *type2; + if (!PyArg_ParseTuple(args, "O!O!", + &PyType_Type, &type1, + &PyType_Type, &type2)) { + return NULL; + } + + int is_subtype = PyType_IsSubtype(type1, type2); + return PyLong_FromLong(is_subtype); +} + + +static PyMethodDef test_methods[] = { + {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS}, + {"get_type_name", get_type_name, METH_O}, + {"get_type_qualname", get_type_qualname, METH_O}, + {"get_type_fullyqualname", get_type_fullyqualname, METH_O}, + {"get_type_module_name", get_type_module_name, METH_O}, + {"type_ready", type_ready, METH_O}, + {"type_modified", type_modified, METH_O}, + {"type_freeze", type_freeze, METH_O}, + {"type_clearcache", type_clearcache, METH_NOARGS}, + {"type_getflags", type_getflags, METH_O}, + {"type_issubtype", type_issubtype, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Type(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + +#define ADD_INT(macro) \ + do { \ + if (PyModule_AddIntConstant(m, #macro, macro) < 0) { \ + return -1; \ + } \ + } while (0) + + ADD_INT(Py_TPFLAGS_DEFAULT); + + ADD_INT(Py_TPFLAGS_HAVE_FINALIZE); + ADD_INT(Py_TPFLAGS_HAVE_GC); + ADD_INT(Py_TPFLAGS_HAVE_VERSION_TAG); + ADD_INT(Py_TPFLAGS_HAVE_VECTORCALL); + + ADD_INT(Py_TPFLAGS_DISALLOW_INSTANTIATION ); + ADD_INT(Py_TPFLAGS_IMMUTABLETYPE); + ADD_INT(Py_TPFLAGS_HEAPTYPE); + ADD_INT(Py_TPFLAGS_BASETYPE); + ADD_INT(Py_TPFLAGS_READY); + ADD_INT(Py_TPFLAGS_READYING); + ADD_INT(Py_TPFLAGS_METHOD_DESCRIPTOR); + ADD_INT(Py_TPFLAGS_VALID_VERSION_TAG); + ADD_INT(Py_TPFLAGS_IS_ABSTRACT); + ADD_INT(_Py_TPFLAGS_MATCH_SELF); + ADD_INT(Py_TPFLAGS_ITEMS_AT_END); + + ADD_INT(Py_TPFLAGS_LONG_SUBCLASS); + ADD_INT(Py_TPFLAGS_LIST_SUBCLASS); + ADD_INT(Py_TPFLAGS_TUPLE_SUBCLASS); + ADD_INT(Py_TPFLAGS_BYTES_SUBCLASS); + ADD_INT(Py_TPFLAGS_UNICODE_SUBCLASS); + ADD_INT(Py_TPFLAGS_DICT_SUBCLASS); + ADD_INT(Py_TPFLAGS_BASE_EXC_SUBCLASS); + ADD_INT(Py_TPFLAGS_TYPE_SUBCLASS); + +#undef ADD_INT + return 0; +} diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index 785bb151e08129..c2bdea923f0f6d 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -120,6 +120,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index 51dc9950a10376..4c999949474b97 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -36,6 +36,7 @@ +