summaryrefslogtreecommitdiffstats
path: root/Modules/_testcapimodule.c
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2022-06-09 15:11:08 (GMT)
committerGitHub <noreply@github.com>2022-06-09 15:11:08 (GMT)
commit7fef8476629775c790f522a073ab279887bd81f9 (patch)
tree5a4e5e7c744da6322caf13fe0df5cecf787924ce /Modules/_testcapimodule.c
parenta5ba0f4ebca5020f6c77718a20663e0ac6e194ac (diff)
downloadcpython-7fef8476629775c790f522a073ab279887bd81f9.zip
cpython-7fef8476629775c790f522a073ab279887bd81f9.tar.gz
cpython-7fef8476629775c790f522a073ab279887bd81f9.tar.bz2
bpo-45383: Get metaclass from bases in PyType_From* (GH-28748)
This checks the bases of of a type created using the FromSpec API to inherit the bases metaclasses. The metaclass's alloc function will be called as is done in `tp_new` for classes created in Python. Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@protonmail.com>
Diffstat (limited to 'Modules/_testcapimodule.c')
-rw-r--r--Modules/_testcapimodule.c160
1 files changed, 160 insertions, 0 deletions
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index ac0c96a..b75e03c 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -1208,6 +1208,161 @@ test_get_type_name(PyObject *self, PyObject *Py_UNUSED(ignored))
}
+static PyType_Slot empty_type_slots[] = {
+ {0, 0},
+};
+
+static PyType_Spec MinimalMetaclass_spec = {
+ .name = "_testcapi.MinimalMetaclass",
+ .basicsize = sizeof(PyHeapTypeObject),
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .slots = empty_type_slots,
+};
+
+static PyType_Spec MinimalType_spec = {
+ .name = "_testcapi.MinimalSpecType",
+ .basicsize = sizeof(PyObject),
+ .flags = Py_TPFLAGS_DEFAULT,
+ .slots = empty_type_slots,
+};
+
+static PyObject *
+test_from_spec_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *metaclass = NULL;
+ PyObject *class = NULL;
+ PyObject *new = NULL;
+ PyObject *subclasses = NULL;
+ PyObject *result = NULL;
+ int r;
+
+ metaclass = PyType_FromSpecWithBases(&MinimalMetaclass_spec, (PyObject*)&PyType_Type);
+ if (metaclass == NULL) {
+ goto finally;
+ }
+ class = PyObject_CallFunction(metaclass, "s(){}", "TestClass");
+ if (class == NULL) {
+ goto finally;
+ }
+
+ new = PyType_FromSpecWithBases(&MinimalType_spec, class);
+ if (new == NULL) {
+ goto finally;
+ }
+ if (Py_TYPE(new) != (PyTypeObject*)metaclass) {
+ PyErr_SetString(PyExc_AssertionError,
+ "Metaclass not set properly!");
+ goto finally;
+ }
+
+ /* Assert that __subclasses__ is updated */
+ subclasses = PyObject_CallMethod(class, "__subclasses__", "");
+ if (!subclasses) {
+ goto finally;
+ }
+ r = PySequence_Contains(subclasses, new);
+ if (r < 0) {
+ goto finally;
+ }
+ if (r == 0) {
+ PyErr_SetString(PyExc_AssertionError,
+ "subclasses not set properly!");
+ goto finally;
+ }
+
+ result = Py_NewRef(Py_None);
+
+finally:
+ Py_XDECREF(metaclass);
+ Py_XDECREF(class);
+ Py_XDECREF(new);
+ Py_XDECREF(subclasses);
+ return result;
+}
+
+
+static PyObject *
+test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *metaclass_a = NULL;
+ PyObject *metaclass_b = NULL;
+ PyObject *class_a = NULL;
+ PyObject *class_b = NULL;
+ PyObject *bases = NULL;
+ PyObject *new = NULL;
+ PyObject *meta_error_string = NULL;
+ PyObject *exc_type = NULL;
+ PyObject *exc_value = NULL;
+ PyObject *exc_traceback = NULL;
+ PyObject *result = NULL;
+
+ metaclass_a = PyType_FromSpecWithBases(&MinimalMetaclass_spec, (PyObject*)&PyType_Type);
+ if (metaclass_a == NULL) {
+ goto finally;
+ }
+ metaclass_b = PyType_FromSpecWithBases(&MinimalMetaclass_spec, (PyObject*)&PyType_Type);
+ if (metaclass_b == NULL) {
+ goto finally;
+ }
+ class_a = PyObject_CallFunction(metaclass_a, "s(){}", "TestClassA");
+ if (class_a == NULL) {
+ goto finally;
+ }
+
+ class_b = PyObject_CallFunction(metaclass_b, "s(){}", "TestClassB");
+ if (class_b == NULL) {
+ goto finally;
+ }
+
+ bases = PyTuple_Pack(2, class_a, class_b);
+ if (bases == NULL) {
+ goto finally;
+ }
+
+ /*
+ * The following should raise a TypeError due to a MetaClass conflict.
+ */
+ new = PyType_FromSpecWithBases(&MinimalType_spec, bases);
+ if (new != NULL) {
+ PyErr_SetString(PyExc_AssertionError,
+ "MetaType conflict not recognized by PyType_FromSpecWithBases");
+ goto finally;
+ }
+
+ // Assert that the correct exception was raised
+ if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+ PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
+
+ meta_error_string = PyUnicode_FromString("metaclass conflict:");
+ if (meta_error_string == NULL) {
+ goto finally;
+ }
+ int res = PyUnicode_Contains(exc_value, meta_error_string);
+ if (res < 0) {
+ goto finally;
+ }
+ if (res == 0) {
+ PyErr_SetString(PyExc_AssertionError,
+ "TypeError did not inlclude expected message.");
+ goto finally;
+ }
+ result = Py_NewRef(Py_None);
+ }
+finally:
+ Py_XDECREF(metaclass_a);
+ Py_XDECREF(metaclass_b);
+ Py_XDECREF(bases);
+ Py_XDECREF(new);
+ Py_XDECREF(meta_error_string);
+ Py_XDECREF(exc_type);
+ Py_XDECREF(exc_value);
+ Py_XDECREF(exc_traceback);
+ Py_XDECREF(class_a);
+ Py_XDECREF(class_b);
+ return result;
+}
+
+
static PyObject *
simple_str(PyObject *self) {
return PyUnicode_FromString("<test>");
@@ -5952,6 +6107,11 @@ static PyMethodDef TestMethods[] = {
{"test_get_type_name", test_get_type_name, METH_NOARGS},
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
+ {"test_from_spec_metatype_inheritance", test_from_spec_metatype_inheritance,
+ METH_NOARGS},
+ {"test_from_spec_invalid_metatype_inheritance",
+ test_from_spec_invalid_metatype_inheritance,
+ METH_NOARGS},
{"get_kwargs", _PyCFunction_CAST(get_kwargs),
METH_VARARGS|METH_KEYWORDS},
{"getargs_tuple", getargs_tuple, METH_VARARGS},