Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-1635741: Add PyModule_AddObjectRef() function #23122

Merged
merged 2 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 90 additions & 14 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ of the following two module creation functions:
instead; only use this if you are sure you need it.

Before it is returned from in the initialization function, the resulting module
object is typically populated using functions like :c:func:`PyModule_AddObject`.
object is typically populated using functions like :c:func:`PyModule_AddObjectRef`.

.. _multi-phase-initialization:

Expand Down Expand Up @@ -437,26 +437,102 @@ a function called from a module execution slot (if using multi-phase
initialization), can use the following functions to help initialize the module
state:

.. c:function:: int PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value)

Add an object to *module* as *name*. This is a convenience function which
can be used from the module's initialization function.

On success, return ``0``. On error, raise an exception and return ``-1``.

Return ``NULL`` if *value* is ``NULL``. It must be called with an exception
raised in this case.

Example usage::

static int
add_spam(PyObject *module, int value)
{
PyObject *obj = PyLong_FromLong(value);
if (obj == NULL) {
return -1;
}
int res = PyModule_AddObjectRef(module, "spam", obj);
Py_DECREF(obj);
return res;
}

The example can also be written without checking explicitly if *obj* is
``NULL``::

static int
add_spam(PyObject *module, int value)
{
PyObject *obj = PyLong_FromLong(value);
int res = PyModule_AddObjectRef(module, "spam", obj);
Py_XDECREF(obj);
return res;
}

Note that ``Py_XDECREF()`` should be used instead of ``Py_DECREF()`` in
this case, since *obj* can be ``NULL``.

.. versionadded:: 3.10


.. c:function:: int PyModule_AddObject(PyObject *module, const char *name, PyObject *value)

Add an object to *module* as *name*. This is a convenience function which can
be used from the module's initialization function. This steals a reference to
*value* on success. Return ``-1`` on error, ``0`` on success.
Similar to :c:func:`PyModule_AddObjectRef`, but steals a reference to
*value* on success (if it returns ``0``).

The new :c:func:`PyModule_AddObjectRef` function is recommended, since it is
easy to introduce reference leaks by misusing the
:c:func:`PyModule_AddObject` function.

.. note::

Unlike other functions that steal references, ``PyModule_AddObject()`` only
decrements the reference count of *value* **on success**.
Unlike other functions that steal references, ``PyModule_AddObject()``
only decrements the reference count of *value* **on success**.

This means that its return value must be checked, and calling code must
:c:func:`Py_DECREF` *value* manually on error. Example usage::

Py_INCREF(spam);
if (PyModule_AddObject(module, "spam", spam) < 0) {
Py_DECREF(module);
Py_DECREF(spam);
return NULL;
}
:c:func:`Py_DECREF` *value* manually on error.

Example usage::

static int
add_spam(PyObject *module, int value)
{
PyObject *obj = PyLong_FromLong(value);
if (obj == NULL) {
return -1;
}
if (PyModule_AddObject(module, "spam", obj) < 0) {
Py_DECREF(obj);
return -1;
}
// PyModule_AddObject() stole a reference to obj:
// Py_DECREF(obj) is not needed here
return 0;
}

The example can also be written without checking explicitly if *obj* is
``NULL``::

static int
add_spam(PyObject *module, int value)
{
PyObject *obj = PyLong_FromLong(value);
if (PyModule_AddObject(module, "spam", obj) < 0) {
Py_XDECREF(obj);
return -1;
}
// PyModule_AddObject() stole a reference to obj:
// Py_DECREF(obj) is not needed here
return 0;
}

Note that ``Py_XDECREF()`` should be used instead of ``Py_DECREF()`` in
this case, since *obj* can be ``NULL``.


.. c:function:: int PyModule_AddIntConstant(PyObject *module, const char *name, long value)

Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ New Features
* Added :c:func:`PyUnicode_AsUTF8AndSize` to the limited C API.
(Contributed by Alex Gaynor in :issue:`41784`.)

* Added :c:func:`PyModule_AddObjectRef` function: similar to
:c:func:`PyModule_AddObjectRef` but don't steal a reference to the value on
success.
(Contributed by Victor Stinner in :issue:`1635741`.)


Porting to Python 3.10
----------------------
Expand Down
10 changes: 9 additions & 1 deletion Include/modsupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,15 @@ PyAPI_FUNC(PyObject * const *) _PyArg_UnpackKeywords(
void _PyArg_Fini(void);
#endif /* Py_LIMITED_API */

PyAPI_FUNC(int) PyModule_AddObject(PyObject *, const char *, PyObject *);
// Add an attribute with name 'name' and value 'obj' to the module 'mod.
// On success, return 0 on success.
// On error, raise an exception and return -1.
PyAPI_FUNC(int) PyModule_AddObjectRef(PyObject *mod, const char *name, PyObject *value);

// Similar to PyModule_AddObjectRef() but steal a reference to 'obj'
// (Py_DECREF(obj)) on success (if it returns 0).
PyAPI_FUNC(int) PyModule_AddObject(PyObject *mod, const char *, PyObject *value);

PyAPI_FUNC(int) PyModule_AddIntConstant(PyObject *, const char *, long);
PyAPI_FUNC(int) PyModule_AddStringConstant(PyObject *, const char *, const char *);
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added :c:func:`PyModule_AddObjectRef` function: similar to
:c:func:`PyModule_AddObjectRef` but don't steal a reference to the value on
success. Patch by Victor Stinner.
70 changes: 39 additions & 31 deletions Python/modsupport.c
Original file line number Diff line number Diff line change
Expand Up @@ -634,56 +634,70 @@ va_build_stack(PyObject **small_stack, Py_ssize_t small_stack_len,


int
PyModule_AddObject(PyObject *m, const char *name, PyObject *o)
PyModule_AddObjectRef(PyObject *mod, const char *name, PyObject *value)
{
PyObject *dict;
if (!PyModule_Check(m)) {
if (!PyModule_Check(mod)) {
PyErr_SetString(PyExc_TypeError,
"PyModule_AddObject() needs module as first arg");
"PyModule_AddObjectRef() first argument "
"must be a module");
return -1;
}
if (!o) {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_TypeError,
"PyModule_AddObject() needs non-NULL value");
if (!value) {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_SystemError,
"PyModule_AddObjectRef() must be called "
"with an exception raised if value is NULL");
}
return -1;
}

dict = PyModule_GetDict(m);
PyObject *dict = PyModule_GetDict(mod);
if (dict == NULL) {
/* Internal error -- modules must have a dict! */
PyErr_Format(PyExc_SystemError, "module '%s' has no __dict__",
PyModule_GetName(m));
PyModule_GetName(mod));
return -1;
}
if (PyDict_SetItemString(dict, name, o))

if (PyDict_SetItemString(dict, name, value)) {
return -1;
Py_DECREF(o);
}
return 0;
}


int
PyModule_AddObject(PyObject *mod, const char *name, PyObject *value)
{
int res = PyModule_AddObjectRef(mod, name, value);
if (res == 0) {
Py_DECREF(value);
}
return res;
}

int
PyModule_AddIntConstant(PyObject *m, const char *name, long value)
{
PyObject *o = PyLong_FromLong(value);
if (!o)
PyObject *obj = PyLong_FromLong(value);
if (!obj) {
return -1;
if (PyModule_AddObject(m, name, o) == 0)
return 0;
Py_DECREF(o);
return -1;
}
int res = PyModule_AddObjectRef(m, name, obj);
Py_DECREF(obj);
return res;
}

int
PyModule_AddStringConstant(PyObject *m, const char *name, const char *value)
{
PyObject *o = PyUnicode_FromString(value);
if (!o)
PyObject *obj = PyUnicode_FromString(value);
if (!obj) {
return -1;
if (PyModule_AddObject(m, name, o) == 0)
return 0;
Py_DECREF(o);
return -1;
}
int res = PyModule_AddObjectRef(m, name, obj);
Py_DECREF(obj);
return res;
}

int
Expand All @@ -696,11 +710,5 @@ PyModule_AddType(PyObject *module, PyTypeObject *type)
const char *name = _PyType_Name(type);
assert(name != NULL);

Py_INCREF(type);
if (PyModule_AddObject(module, name, (PyObject *)type) < 0) {
Py_DECREF(type);
return -1;
}

return 0;
return PyModule_AddObjectRef(module, name, (PyObject *)type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for not decref'ing here, as opposed to PyModule_AddStringConstant and PyModule_AddIntConstant? PyModule_AddType used to steal a reference, but now it doesn't, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller holds a strong reference to type.

Reference counting is hard to get right. That's why I added a new function. Previously, Py_INCREF(type) was needed before calling PyModule_AddObject(). I let you count +1 and -1 on the ref count in all code paths and check manually if my PR doesn't anything (hint: it should not ;-)).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller holds a strong reference to type.

Reference counting is hard to get right. That's why I added a new function. Previously, Py_INCREF(type) was needed before calling PyModule_AddObject(). I let you count +1 and -1 on the ref count in all code paths and check manually if my PR doesn't anything (hint: it should not ;-)).

Argh, I though I had it right :) Thanks for the explanation! 🙏🏻

}