Issue #27782: Fix m_methods handling in multiphase init
Multi-phase extension module import now correctly allows the ``m_methods`` field to be used to add module level functions to instances of non-module types returned from ``Py_create_mod``. Patch by Xiang Zhang.
This commit is contained in:
parent
9c8aa9bffe
commit
8682f578c1
|
@ -324,7 +324,7 @@ The available slot types are:
|
||||||
:c:type:`PyModule_Type`. Any type can be used, as long as it supports
|
:c:type:`PyModule_Type`. Any type can be used, as long as it supports
|
||||||
setting and getting import-related attributes.
|
setting and getting import-related attributes.
|
||||||
However, only ``PyModule_Type`` instances may be returned if the
|
However, only ``PyModule_Type`` instances may be returned if the
|
||||||
``PyModuleDef`` has non-*NULL* ``m_methods``, ``m_traverse``, ``m_clear``,
|
``PyModuleDef`` has non-*NULL* ``m_traverse``, ``m_clear``,
|
||||||
``m_free``; non-zero ``m_size``; or slots other than ``Py_mod_create``.
|
``m_free``; non-zero ``m_size``; or slots other than ``Py_mod_create``.
|
||||||
|
|
||||||
.. c:var:: Py_mod_exec
|
.. c:var:: Py_mod_exec
|
||||||
|
|
|
@ -77,7 +77,7 @@ typedef struct PyModuleDef{
|
||||||
traverseproc m_traverse;
|
traverseproc m_traverse;
|
||||||
inquiry m_clear;
|
inquiry m_clear;
|
||||||
freefunc m_free;
|
freefunc m_free;
|
||||||
}PyModuleDef;
|
} PyModuleDef;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,6 +212,15 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
|
||||||
self.assertNotEqual(type(mod), type(unittest))
|
self.assertNotEqual(type(mod), type(unittest))
|
||||||
self.assertEqual(mod.three, 3)
|
self.assertEqual(mod.three, 3)
|
||||||
|
|
||||||
|
# issue 27782
|
||||||
|
def test_nonmodule_with_methods(self):
|
||||||
|
'''Test creating a non-module object with methods defined'''
|
||||||
|
name = self.name + '_nonmodule_with_methods'
|
||||||
|
mod = self.load_module_by_name(name)
|
||||||
|
self.assertNotEqual(type(mod), type(unittest))
|
||||||
|
self.assertEqual(mod.three, 3)
|
||||||
|
self.assertEqual(mod.bar(10, 1), 9)
|
||||||
|
|
||||||
def test_null_slots(self):
|
def test_null_slots(self):
|
||||||
'''Test that NULL slots aren't a problem'''
|
'''Test that NULL slots aren't a problem'''
|
||||||
name = self.name + '_null_slots'
|
name = self.name + '_null_slots'
|
||||||
|
|
|
@ -1657,6 +1657,7 @@ Nickolai Zeldovich
|
||||||
Yuxiao Zeng
|
Yuxiao Zeng
|
||||||
Uwe Zessin
|
Uwe Zessin
|
||||||
Cheng Zhang
|
Cheng Zhang
|
||||||
|
Xiang Zhang
|
||||||
Kai Zhu
|
Kai Zhu
|
||||||
Tarek Ziadé
|
Tarek Ziadé
|
||||||
Jelle Zijlstra
|
Jelle Zijlstra
|
||||||
|
|
|
@ -10,6 +10,10 @@ Release date: TBA
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #27782: Multi-phase extension module import now correctly allows the
|
||||||
|
``m_methods`` field to be used to add module level functions to instances
|
||||||
|
of non-module types returned from ``Py_create_mod``. Patch by Xiang Zhang.
|
||||||
|
|
||||||
- Issue #27487: Warn if a submodule argument to "python -m" or
|
- Issue #27487: Warn if a submodule argument to "python -m" or
|
||||||
runpy.run_module() is found in sys.modules after parent packages are
|
runpy.run_module() is found in sys.modules after parent packages are
|
||||||
imported, but before the submodule is executed.
|
imported, but before the submodule is executed.
|
||||||
|
|
|
@ -248,6 +248,7 @@ PyInit__testmultiphase(PyObject *spec)
|
||||||
/**** Importing a non-module object ****/
|
/**** Importing a non-module object ****/
|
||||||
|
|
||||||
static PyModuleDef def_nonmodule;
|
static PyModuleDef def_nonmodule;
|
||||||
|
static PyModuleDef def_nonmodule_with_methods;
|
||||||
|
|
||||||
/* Create a SimpleNamespace(three=3) */
|
/* Create a SimpleNamespace(three=3) */
|
||||||
static PyObject*
|
static PyObject*
|
||||||
|
@ -255,7 +256,7 @@ createfunc_nonmodule(PyObject *spec, PyModuleDef *def)
|
||||||
{
|
{
|
||||||
PyObject *dct, *ns, *three;
|
PyObject *dct, *ns, *three;
|
||||||
|
|
||||||
if (def != &def_nonmodule) {
|
if (def != &def_nonmodule && def != &def_nonmodule_with_methods) {
|
||||||
PyErr_SetString(PyExc_SystemError, "def does not match");
|
PyErr_SetString(PyExc_SystemError, "def does not match");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -291,6 +292,36 @@ PyInit__testmultiphase_nonmodule(PyObject *spec)
|
||||||
return PyModuleDef_Init(&def_nonmodule);
|
return PyModuleDef_Init(&def_nonmodule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(nonmodule_bar_doc,
|
||||||
|
"bar(i,j)\n\
|
||||||
|
\n\
|
||||||
|
Return the difference of i - j.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
nonmodule_bar(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
long i, j;
|
||||||
|
long res;
|
||||||
|
if (!PyArg_ParseTuple(args, "ll:bar", &i, &j))
|
||||||
|
return NULL;
|
||||||
|
res = i - j;
|
||||||
|
return PyLong_FromLong(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef nonmodule_methods[] = {
|
||||||
|
{"bar", nonmodule_bar, METH_VARARGS, nonmodule_bar_doc},
|
||||||
|
{NULL, NULL} /* sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyModuleDef def_nonmodule_with_methods = TEST_MODULE_DEF(
|
||||||
|
"_testmultiphase_nonmodule_with_methods", slots_create_nonmodule, nonmodule_methods);
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit__testmultiphase_nonmodule_with_methods(PyObject *spec)
|
||||||
|
{
|
||||||
|
return PyModuleDef_Init(&def_nonmodule_with_methods);
|
||||||
|
}
|
||||||
|
|
||||||
/**** Non-ASCII-named modules ****/
|
/**** Non-ASCII-named modules ****/
|
||||||
|
|
||||||
static PyModuleDef def_nonascii_latin = { \
|
static PyModuleDef def_nonascii_latin = { \
|
||||||
|
|
|
@ -130,6 +130,34 @@ check_api_version(const char *name, int module_api_version)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_add_methods_to_object(PyObject *module, PyObject *name, PyMethodDef *functions)
|
||||||
|
{
|
||||||
|
PyObject *func;
|
||||||
|
PyMethodDef *fdef;
|
||||||
|
|
||||||
|
for (fdef = functions; fdef->ml_name != NULL; fdef++) {
|
||||||
|
if ((fdef->ml_flags & METH_CLASS) ||
|
||||||
|
(fdef->ml_flags & METH_STATIC)) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"module functions cannot set"
|
||||||
|
" METH_CLASS or METH_STATIC");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
func = PyCFunction_NewEx(fdef, (PyObject*)module, name);
|
||||||
|
if (func == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (PyObject_SetAttrString(module, fdef->ml_name, func) != 0) {
|
||||||
|
Py_DECREF(func);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Py_DECREF(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyModule_Create2(struct PyModuleDef* module, int module_api_version)
|
PyModule_Create2(struct PyModuleDef* module, int module_api_version)
|
||||||
{
|
{
|
||||||
|
@ -269,7 +297,7 @@ PyModule_FromDefAndSpec2(struct PyModuleDef* def, PyObject *spec, int module_api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m = PyModule_New(name);
|
m = PyModule_NewObject(nameobj);
|
||||||
if (m == NULL) {
|
if (m == NULL) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -297,7 +325,7 @@ PyModule_FromDefAndSpec2(struct PyModuleDef* def, PyObject *spec, int module_api
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def->m_methods != NULL) {
|
if (def->m_methods != NULL) {
|
||||||
ret = PyModule_AddFunctions(m, def->m_methods);
|
ret = _add_methods_to_object(m, nameobj, def->m_methods);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -331,7 +359,7 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyModule_Check(module) && def->m_size >= 0) {
|
if (def->m_size >= 0) {
|
||||||
PyModuleObject *md = (PyModuleObject*)module;
|
PyModuleObject *md = (PyModuleObject*)module;
|
||||||
if (md->md_state == NULL) {
|
if (md->md_state == NULL) {
|
||||||
/* Always set a state pointer; this serves as a marker to skip
|
/* Always set a state pointer; this serves as a marker to skip
|
||||||
|
@ -387,37 +415,15 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
|
||||||
int
|
int
|
||||||
PyModule_AddFunctions(PyObject *m, PyMethodDef *functions)
|
PyModule_AddFunctions(PyObject *m, PyMethodDef *functions)
|
||||||
{
|
{
|
||||||
PyObject *name, *func;
|
int res;
|
||||||
PyMethodDef *fdef;
|
PyObject *name = PyModule_GetNameObject(m);
|
||||||
|
|
||||||
name = PyModule_GetNameObject(m);
|
|
||||||
if (name == NULL) {
|
if (name == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (fdef = functions; fdef->ml_name != NULL; fdef++) {
|
res = _add_methods_to_object(m, name, functions);
|
||||||
if ((fdef->ml_flags & METH_CLASS) ||
|
|
||||||
(fdef->ml_flags & METH_STATIC)) {
|
|
||||||
PyErr_SetString(PyExc_ValueError,
|
|
||||||
"module functions cannot set"
|
|
||||||
" METH_CLASS or METH_STATIC");
|
|
||||||
Py_DECREF(name);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
func = PyCFunction_NewEx(fdef, (PyObject*)m, name);
|
|
||||||
if (func == NULL) {
|
|
||||||
Py_DECREF(name);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (PyObject_SetAttrString(m, fdef->ml_name, func) != 0) {
|
|
||||||
Py_DECREF(func);
|
|
||||||
Py_DECREF(name);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
Py_DECREF(func);
|
|
||||||
}
|
|
||||||
Py_DECREF(name);
|
Py_DECREF(name);
|
||||||
return 0;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
Loading…
Reference in New Issue