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

[WIP] bpo-42294: Add Py_SetRef() and Py_XSetRef() #23209

Closed
wants to merge 2 commits into from
Closed
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
63 changes: 54 additions & 9 deletions Doc/c-api/refcounting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ objects.

.. c:function:: void Py_XINCREF(PyObject *o)

Increment the reference count for object *o*. The object may be ``NULL``, in
Increment the reference count for object *o*. The object can be ``NULL``, in
which case the macro has no effect.

See also :c:func:`Py_XNewRef`.
Expand Down Expand Up @@ -78,6 +78,9 @@ objects.
The object must not be ``NULL``; if you aren't sure that it isn't ``NULL``,
use :c:func:`Py_XDECREF`.

Use :c:func:`Py_CLEAR` to set a variable to ``NULL``. See also
:c:func:`Py_SetRef`.

.. warning::

The deallocation function can cause arbitrary Python code to be invoked (e.g.
Expand All @@ -92,19 +95,61 @@ objects.

.. c:function:: void Py_XDECREF(PyObject *o)

Decrement the reference count for object *o*. The object may be ``NULL``, in
Decrement the reference count for object *o*. The object can be ``NULL``, in
which case the macro has no effect; otherwise the effect is the same as for
:c:func:`Py_DECREF`, and the same warning applies.

Use :c:func:`Py_CLEAR` to set a variable to ``NULL``. See also
:c:func:`Py_XSetRef`.

.. c:function:: void Py_CLEAR(PyObject *o)

Decrement the reference count for object *o*. The object may be ``NULL``, in
which case the macro has no effect; otherwise the effect is the same as for
:c:func:`Py_DECREF`, except that the argument is also set to ``NULL``. The warning
for :c:func:`Py_DECREF` does not apply with respect to the object passed because
the macro carefully uses a temporary variable and sets the argument to ``NULL``
before decrementing its reference count.
.. c:function:: void Py_SetRef(PyObject **ref, PyObject *new_obj)

Set the :term:`strong reference` ``*ref`` to *new_obj* object (without
increasing its reference count), and then decrement the reference count of
the object previously pointed by ``*ref``.

*ref* and ``*ref`` must not be ``NULL``.

This function avoids creating a temporary dangling pointer. Example::

Py_DECREF(global_var); // unsafe
global_var = new_obj;

If the :c:func:`Py_DECREF` call destroys the object previously pointed by
*global_var*, the object finalizer can run arbitrary code which can use the
*global_var* variable. Since the *global_var* became a dangling pointer,
using it crashs Python. The example can be fixed by using
:c:func:`Py_SetRef`:

Py_SetRef(&global_var, new_obj); // safe

See also the :c:func:`Py_CLEAR` macro.

.. versionadded:: 3.10


.. c:function:: void Py_XSetRef(PyObject **ref, PyObject *new_obj)

Similar to :c:func:`Py_SetRef`, but ``*ref`` can be ``NULL``.

If ``*ref`` is ``NULL``, do nothing.

See also :c:func:`Py_CLEAR` macro.

.. versionadded:: 3.10


.. c:function:: void Py_CLEAR(PyObject *op)

Clear a :term:`strong reference`: decrement the reference count for object
*op* and set *op* to ``NULL``.

If *op* is ``NULL``, do nothing.

The ``Py_CLEAR(var)`` macro is implemented with the :c:func:`Py_XSetRef`
function as ``Py_XSetRef(&var, NULL)`` to avoid creating a temporary
dangling pointer.

It is a good idea to use this macro whenever decrementing the reference
count of an object that might be traversed during garbage collection.
Expand Down
9 changes: 6 additions & 3 deletions Doc/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1118,9 +1118,12 @@ Glossary
decrements the object reference count when it is deleted.

The :c:func:`Py_NewRef` function can be used to create a strong reference
to an object. Usually, the :c:func:`Py_DECREF` function must be called on
the strong reference before exiting the scope of the strong reference, to
avoid leaking one reference.
to an object. Usually, the :c:func:`Py_CLEAR` or :c:func:`Py_DECREF`
function must be called on the strong reference before exiting the scope
of the strong reference, to avoid leaking a reference.

The :c:func:`Py_SetRef` function can be used to set a strong reference to
a new object without creating a temporary dangling pointer.

See also :term:`borrowed reference`.

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 @@ -429,6 +429,11 @@ New Features
slot.
(Contributed by Hai Shi in :issue:`41832`.)

* Added :c:func:`Py_SetRef` and :c:func:`Py_XSetRef` functions to set a
:term:`strong reference` to a new object without a creating temporary
dangling pointer.
(Contributed by Victor Stinner in :issue:`42294`.)


Porting to Python 3.10
----------------------
Expand Down
41 changes: 9 additions & 32 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,38 +337,15 @@ _PyObject_GenericSetAttrWithDict(PyObject *, PyObject *,

PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);

/* Safely decref `op` and set `op` to `op2`.
*
* As in case of Py_CLEAR "the obvious" code can be deadly:
*
* Py_DECREF(op);
* op = op2;
*
* The safe way is:
*
* Py_SETREF(op, op2);
*
* That arranges to set `op` to `op2` _before_ decref'ing, so that any code
* triggered as a side-effect of `op` getting torn down no longer believes
* `op` points to a valid object.
*
* Py_XSETREF is a variant of Py_SETREF that uses Py_XDECREF instead of
* Py_DECREF.
*/

#define Py_SETREF(op, op2) \
do { \
PyObject *_py_tmp = _PyObject_CAST(op); \
(op) = (op2); \
Py_DECREF(_py_tmp); \
} while (0)

#define Py_XSETREF(op, op2) \
do { \
PyObject *_py_tmp = _PyObject_CAST(op); \
(op) = (op2); \
Py_XDECREF(_py_tmp); \
} while (0)
// Set op to new_obj without creating a temporary dangling pointer (op).
// See also the Py_SetRef() function and the Py_CLEAR() macro.
#define Py_SETREF(op, new_obj) \
Py_SetRef(_PyObjectPtr_CAST(&(op)), _PyObject_CAST(new_obj))

// Similar to Py_SETREF() but op can be NULL.
// See also the Py_XSetRef() function and the Py_CLEAR() macro.
#define Py_XSETREF(op, new_obj) \
Py_XSetRef(_PyObjectPtr_CAST(&(op)), _PyObject_CAST(new_obj))


PyAPI_DATA(PyTypeObject) _PyNone_Type;
Expand Down
41 changes: 33 additions & 8 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ typedef struct _object {
#define _PyObject_CAST(op) ((PyObject*)(op))
#define _PyObject_CAST_CONST(op) ((const PyObject*)(op))

/* Cast argument to PyObject** type. */
#define _PyObjectPtr_CAST(op) ((PyObject**)(op))

typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
Expand Down Expand Up @@ -491,14 +494,7 @@ static inline void _Py_DECREF(
* Python integers aren't currently weakly referencable. Best practice is
* to use Py_CLEAR() even if you can't think of a reason for why you need to.
*/
#define Py_CLEAR(op) \
do { \
PyObject *_py_tmp = _PyObject_CAST(op); \
if (_py_tmp != NULL) { \
(op) = NULL; \
Py_DECREF(_py_tmp); \
} \
} while (0)
#define Py_CLEAR(op) Py_XSetRef(_PyObjectPtr_CAST(&(op)), NULL)

/* Function to use in case the object pointer can be NULL: */
static inline void _Py_XINCREF(PyObject *op)
Expand Down Expand Up @@ -551,6 +547,35 @@ static inline PyObject* _Py_XNewRef(PyObject *obj)
#define Py_NewRef(obj) _Py_NewRef(obj)
#define Py_XNewRef(obj) _Py_XNewRef(obj)

// Set a strong reference *ref to obj and then decrement the reference count
// of the object previously pointed by *ref.
// See also the Py_SETREF() macro.
PyAPI_FUNC(void) Py_SetRef(PyObject **ref, PyObject *new_obj);

// Similar to Py_SetRef(), but *ref can be NULL.
// See also the Py_XSETREF() macros.
PyAPI_FUNC(void) Py_XSetRef(PyObject **ref, PyObject *new_obj);

static inline void _Py_SetRef(PyObject **ref, PyObject *new_obj)
{
PyObject *old = *ref;
*ref = new_obj;
Py_DECREF(old);
}

static inline void _Py_XSetRef(PyObject **ref, PyObject *new_obj)
{
PyObject *old = *ref;
*ref = new_obj;
Py_XDECREF(old);
}

// Py_SetRef() and Py_XSetRef() are exported as functions for the stable ABI.
// Names overriden with macros by static inline functions for best
// performances.
#define Py_SetRef(ref, new_obj) _Py_SetRef(ref, new_obj)
#define Py_XSetRef(ref, new_obj) _Py_XSetRef(ref, new_obj)


/*
_Py_NoneStruct is an object of undefined type which can be used in contexts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`Py_SetRef` and :c:func:`Py_XSetRef` functions to set a
:term:`strong reference` to a new reference without creating a temporary
dangling pointer. Patch by Victor Stinner.
17 changes: 17 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,23 @@ Py_XNewRef(PyObject *obj)
return _Py_XNewRef(obj);
}


#undef Py_SetRef
#undef Py_XSetRef

// Export Py_SetRef() and Py_XSetRef() as regular functions for the stable ABI.
void
Py_SetRef(PyObject **ref, PyObject *new_obj)
{
return _Py_SetRef(ref, new_obj);
}

void
Py_XSetRef(PyObject **ref, PyObject *new_obj)
{
return _Py_XSetRef(ref, new_obj);
}

#ifdef __cplusplus
}
#endif
2 changes: 2 additions & 0 deletions PC/python3dll.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ EXPORT_FUNC(Py_SetPath)
EXPORT_FUNC(Py_SetProgramName)
EXPORT_FUNC(Py_SetPythonHome)
EXPORT_FUNC(Py_SetRecursionLimit)
EXPORT_FUNC(Py_SetRef)
EXPORT_FUNC(Py_SymtableString)
EXPORT_FUNC(Py_VaBuildValue)
EXPORT_FUNC(Py_XNewRef)
EXPORT_FUNC(Py_XSetRef)
EXPORT_FUNC(PyArg_Parse)
EXPORT_FUNC(PyArg_ParseTuple)
EXPORT_FUNC(PyArg_ParseTupleAndKeywords)
Expand Down