Skip to content

Commit 67c8a97

Browse files
committed
BUG: Inherit MetaClass from bases in FromSpec API
This checks the bases of of a type created using the FromSpec API to inherit the bases metaclasses. The MetaClasses alloc function will be called as is done in `tp_new` for classes created in Python.
1 parent d22a700 commit 67c8a97

File tree

2 files changed

+181
-29
lines changed

2 files changed

+181
-29
lines changed

Modules/_testcapimodule.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,143 @@ test_get_type_name(PyObject *self, PyObject *Py_UNUSED(ignored))
11731173
}
11741174

11751175

1176+
/*
1177+
* Small helper to import abc.ABC and ctypes.Array for testing. Both
1178+
* are (incompatible) MetaClass instances. If Array is NULL it is not filled.
1179+
*/
1180+
static int
1181+
import_abc_and_array(PyObject **ABC, PyObject **Array)
1182+
{
1183+
PyObject *abc_mod = PyImport_ImportModule("abc");
1184+
if (abc_mod == NULL) {
1185+
return -1;
1186+
}
1187+
*ABC = PyObject_GetAttrString(abc_mod, "ABC");
1188+
Py_DECREF(abc_mod);
1189+
if (*ABC == NULL) {
1190+
return -1;
1191+
}
1192+
if (Array == NULL) {
1193+
return 0;
1194+
}
1195+
1196+
PyObject *ctypes_mod = PyImport_ImportModule("ctypes");
1197+
if (ctypes_mod == NULL) {
1198+
Py_CLEAR(*ABC);
1199+
return -1;
1200+
}
1201+
*Array = PyObject_GetAttrString(ctypes_mod, "Array");
1202+
Py_DECREF(ctypes_mod);
1203+
if (*Array == NULL) {
1204+
Py_CLEAR(*ABC);
1205+
return -1;
1206+
}
1207+
return 0;
1208+
}
1209+
1210+
1211+
static PyType_Slot MinimalType_slots[] = {
1212+
{0, 0},
1213+
};
1214+
1215+
static PyType_Spec MinimalType_spec = {
1216+
"_testcapi.MinimalSpecType",
1217+
0,
1218+
0,
1219+
Py_TPFLAGS_DEFAULT,
1220+
MinimalType_slots
1221+
};
1222+
1223+
1224+
static PyObject *
1225+
test_from_spec_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED(ignored))
1226+
{
1227+
/* Get two (incompatible) MetaTypes */
1228+
PyObject *ABC;
1229+
if (import_abc_and_array(&ABC, NULL) < 0) {
1230+
return NULL;
1231+
}
1232+
1233+
PyObject *bases = PyTuple_Pack(1, ABC);
1234+
if (bases == NULL) {
1235+
Py_DECREF(ABC);
1236+
return NULL;
1237+
}
1238+
PyObject *new = PyType_FromSpecWithBases(&MinimalType_spec, bases);
1239+
Py_DECREF(bases);
1240+
if (new == NULL) {
1241+
Py_DECREF(ABC);
1242+
return NULL;
1243+
}
1244+
if (Py_TYPE(new) != Py_TYPE(ABC)) {
1245+
PyErr_SetString(PyExc_AssertionError,
1246+
"MetaType appears not correctly inherited from ABC!");
1247+
Py_DECREF(ABC);
1248+
Py_DECREF(new);
1249+
return NULL;
1250+
}
1251+
Py_DECREF(ABC);
1252+
Py_DECREF(new);
1253+
Py_RETURN_NONE;
1254+
}
1255+
1256+
1257+
static PyObject *
1258+
test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED(ignored))
1259+
{
1260+
/* Get two (incompatible) MetaTypes */
1261+
PyObject *ABC, *Array;
1262+
1263+
if (import_abc_and_array(&ABC, &Array) < 0) {
1264+
return NULL;
1265+
}
1266+
1267+
PyObject *bases = PyTuple_Pack(2, ABC, Array);
1268+
Py_DECREF(ABC);
1269+
Py_DECREF(Array);
1270+
if (bases == NULL) {
1271+
return NULL;
1272+
}
1273+
/*
1274+
* The following should raise a TypeError due to a MetaClass conflict.
1275+
*/
1276+
PyObject *new = PyType_FromSpecWithBases(&MinimalType_spec, bases);
1277+
Py_DECREF(bases);
1278+
if (new != NULL) {
1279+
Py_DECREF(new);
1280+
PyErr_SetString(PyExc_AssertionError,
1281+
"MetaType conflict not recognized by PyType_FromSpecWithBases");
1282+
return NULL;
1283+
}
1284+
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
1285+
PyObject *type, *value, *traceback, *meta_error_string;
1286+
1287+
PyErr_Fetch(&type, &value, &traceback);
1288+
Py_DECREF(type);
1289+
Py_XDECREF(traceback);
1290+
1291+
meta_error_string = PyUnicode_FromString("metaclass conflict:");
1292+
if (meta_error_string == NULL) {
1293+
Py_DECREF(value);
1294+
return NULL;
1295+
}
1296+
int res = PyUnicode_Contains(value, meta_error_string);
1297+
Py_DECREF(value);
1298+
Py_DECREF(meta_error_string);
1299+
if (res < 0) {
1300+
return NULL;
1301+
}
1302+
if (res == 0) {
1303+
PyErr_SetString(PyExc_AssertionError,
1304+
"TypeError did not inlclude expected message.");
1305+
return NULL;
1306+
}
1307+
Py_RETURN_NONE;
1308+
}
1309+
return NULL;
1310+
}
1311+
1312+
11761313
static PyObject *
11771314
test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
11781315
{
@@ -5722,6 +5859,11 @@ static PyMethodDef TestMethods[] = {
57225859
{"get_args", get_args, METH_VARARGS},
57235860
{"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
57245861
{"test_get_type_name", test_get_type_name, METH_NOARGS},
5862+
{"test_from_spec_metatype_inheritance", test_from_spec_metatype_inheritance,
5863+
METH_NOARGS},
5864+
{"test_from_spec_invalid_metatype_inheritance",
5865+
test_from_spec_invalid_metatype_inheritance,
5866+
METH_NOARGS},
57255867
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
57265868
{"get_kwargs", (PyCFunction)(void(*)(void))get_kwargs,
57275869
METH_VARARGS|METH_KEYWORDS},

Objects/typeobject.c

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3398,37 +3398,12 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
33983398
}
33993399
}
34003400

3401-
res = (PyHeapTypeObject*)PyType_GenericAlloc(&PyType_Type, nmembers);
3402-
if (res == NULL)
3403-
return NULL;
3404-
res_start = (char*)res;
3405-
34063401
if (spec->name == NULL) {
34073402
PyErr_SetString(PyExc_SystemError,
34083403
"Type spec does not define the name field.");
3409-
goto fail;
3404+
return NULL;
34103405
}
34113406

3412-
/* Set the type name and qualname */
3413-
const char *s = strrchr(spec->name, '.');
3414-
if (s == NULL)
3415-
s = spec->name;
3416-
else
3417-
s++;
3418-
3419-
type = &res->ht_type;
3420-
/* The flags must be initialized early, before the GC traverses us */
3421-
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
3422-
res->ht_name = PyUnicode_FromString(s);
3423-
if (!res->ht_name)
3424-
goto fail;
3425-
res->ht_qualname = res->ht_name;
3426-
Py_INCREF(res->ht_qualname);
3427-
type->tp_name = spec->name;
3428-
3429-
Py_XINCREF(module);
3430-
res->ht_module = module;
3431-
34323407
/* Adjust for empty tuple bases */
34333408
if (!bases) {
34343409
base = &PyBaseObject_Type;
@@ -3443,11 +3418,11 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
34433418
if (!bases) {
34443419
bases = PyTuple_Pack(1, base);
34453420
if (!bases)
3446-
goto fail;
3421+
return NULL;
34473422
}
34483423
else if (!PyTuple_Check(bases)) {
34493424
PyErr_SetString(PyExc_SystemError, "Py_tp_bases is not a tuple");
3450-
goto fail;
3425+
return NULL;
34513426
}
34523427
else {
34533428
Py_INCREF(bases);
@@ -3456,12 +3431,47 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
34563431
else if (!PyTuple_Check(bases)) {
34573432
bases = PyTuple_Pack(1, bases);
34583433
if (!bases)
3459-
goto fail;
3434+
return NULL;
34603435
}
34613436
else {
34623437
Py_INCREF(bases);
34633438
}
34643439

3440+
/* NOTE: Missing API to replace `&PyType_Type` below, see bpo-15870 */
3441+
PyTypeObject *metatype = _PyType_CalculateMetaclass(&PyType_Type, bases);
3442+
if (metatype == NULL) {
3443+
Py_DECREF(bases);
3444+
return NULL;
3445+
}
3446+
res = (PyHeapTypeObject*)metatype->tp_alloc(metatype, nmembers);
3447+
if (res == NULL) {
3448+
Py_DECREF(bases);
3449+
return NULL;
3450+
}
3451+
res_start = (char*)res;
3452+
3453+
/* Set the type name and qualname */
3454+
const char *s = strrchr(spec->name, '.');
3455+
if (s == NULL)
3456+
s = spec->name;
3457+
else
3458+
s++;
3459+
3460+
type = &res->ht_type;
3461+
/* The flags must be initialized early, before the GC traverses us */
3462+
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
3463+
res->ht_name = PyUnicode_FromString(s);
3464+
if (!res->ht_name) {
3465+
Py_DECREF(bases);
3466+
goto fail;
3467+
}
3468+
res->ht_qualname = res->ht_name;
3469+
Py_INCREF(res->ht_qualname);
3470+
type->tp_name = spec->name;
3471+
3472+
Py_XINCREF(module);
3473+
res->ht_module = module;
3474+
34653475
/* Calculate best base, and check that all bases are type objects */
34663476
base = best_base(bases);
34673477
if (base == NULL) {

0 commit comments

Comments
 (0)