mirror of https://github.com/python/cpython
The Py_CLEAR(), Py_SETREF() and Py_XSETREF() macros now only evaluate their arguments once. If an argument has side effects, these side effects are no longer duplicated. Use temporary variables to avoid duplicating side effects of macro arguments. If available, use _Py_TYPEOF() to avoid type punning. Otherwise, use memcpy() for the assignment to prevent a miscompilation with strict aliasing caused by type punning. Add _Py_TYPEOF() macro: __typeof__() on GCC and clang. Add test_py_clear() and test_py_setref() unit tests to _testcapi.
This commit is contained in:
parent
7031275776
commit
b11a384dc7
|
@ -7,8 +7,8 @@
|
|||
Reference Counting
|
||||
******************
|
||||
|
||||
The macros in this section are used for managing reference counts of Python
|
||||
objects.
|
||||
The functions and macros in this section are used for managing reference counts
|
||||
of Python objects.
|
||||
|
||||
|
||||
.. c:function:: Py_ssize_t Py_REFCNT(PyObject *o)
|
||||
|
@ -129,6 +129,11 @@ objects.
|
|||
It is a good idea to use this macro whenever decrementing the reference
|
||||
count of an object that might be traversed during garbage collection.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
The macro argument is now only evaluated once. If the argument has side
|
||||
effects, these are no longer duplicated.
|
||||
|
||||
|
||||
.. c:function:: void Py_IncRef(PyObject *o)
|
||||
|
||||
Increment the reference count for object *o*. A function version of :c:func:`Py_XINCREF`.
|
||||
|
@ -139,3 +144,40 @@ objects.
|
|||
|
||||
Decrement the reference count for object *o*. A function version of :c:func:`Py_XDECREF`.
|
||||
It can be used for runtime dynamic embedding of Python.
|
||||
|
||||
|
||||
.. c:macro:: Py_SETREF(dst, src)
|
||||
|
||||
Macro safely decrementing the `dst` reference count and setting `dst` to
|
||||
`src`.
|
||||
|
||||
As in case of :c:func:`Py_CLEAR`, "the obvious" code can be deadly::
|
||||
|
||||
Py_DECREF(dst);
|
||||
dst = src;
|
||||
|
||||
The safe way is::
|
||||
|
||||
Py_SETREF(dst, src);
|
||||
|
||||
That arranges to set `dst` to `src` _before_ decrementing reference count of
|
||||
*dst* old value, so that any code triggered as a side-effect of `dst`
|
||||
getting torn down no longer believes `dst` points to a valid object.
|
||||
|
||||
.. versionadded:: 3.6
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
The macro arguments are now only evaluated once. If an argument has side
|
||||
effects, these are no longer duplicated.
|
||||
|
||||
|
||||
.. c:macro:: Py_XSETREF(dst, src)
|
||||
|
||||
Variant of :c:macro:`Py_SETREF` macro that uses :c:func:`Py_XDECREF` instead
|
||||
of :c:func:`Py_DECREF`.
|
||||
|
||||
.. versionadded:: 3.6
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
The macro arguments are now only evaluated once. If an argument has side
|
||||
effects, these are no longer duplicated.
|
||||
|
|
|
@ -851,6 +851,11 @@ Porting to Python 3.12
|
|||
:class:`bytes` type is accepted for bytes strings.
|
||||
(Contributed by Victor Stinner in :gh:`98393`.)
|
||||
|
||||
* The :c:macro:`Py_CLEAR`, :c:macro:`Py_SETREF` and :c:macro:`Py_XSETREF`
|
||||
macros now only evaluate their arguments once. If an argument has side
|
||||
effects, these side effects are no longer duplicated.
|
||||
(Contributed by Victor Stinner in :gh:`98724`.)
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
|
|
|
@ -305,38 +305,69 @@ _PyObject_GenericSetAttrWithDict(PyObject *, PyObject *,
|
|||
|
||||
PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
|
||||
|
||||
/* Safely decref `op` and set `op` to `op2`.
|
||||
/* Safely decref `dst` and set `dst` to `src`.
|
||||
*
|
||||
* As in case of Py_CLEAR "the obvious" code can be deadly:
|
||||
*
|
||||
* Py_DECREF(op);
|
||||
* op = op2;
|
||||
* Py_DECREF(dst);
|
||||
* dst = src;
|
||||
*
|
||||
* The safe way is:
|
||||
*
|
||||
* Py_SETREF(op, op2);
|
||||
* Py_SETREF(dst, src);
|
||||
*
|
||||
* 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.
|
||||
* That arranges to set `dst` to `src` _before_ decref'ing, so that any code
|
||||
* triggered as a side-effect of `dst` getting torn down no longer believes
|
||||
* `dst` points to a valid object.
|
||||
*
|
||||
* Py_XSETREF is a variant of Py_SETREF that uses Py_XDECREF instead of
|
||||
* Py_DECREF.
|
||||
* Temporary variables are used to only evalutate macro arguments once and so
|
||||
* avoid the duplication of side effects. _Py_TYPEOF() or memcpy() is used to
|
||||
* avoid a miscompilation caused by type punning. See Py_CLEAR() comment for
|
||||
* implementation details about type punning.
|
||||
*
|
||||
* The memcpy() implementation does not emit a compiler warning if 'src' has
|
||||
* not the same type than 'src': any pointer type is accepted for 'src'.
|
||||
*/
|
||||
|
||||
#define Py_SETREF(op, op2) \
|
||||
#ifdef _Py_TYPEOF
|
||||
#define Py_SETREF(dst, src) \
|
||||
do { \
|
||||
PyObject *_py_tmp = _PyObject_CAST(op); \
|
||||
(op) = (op2); \
|
||||
Py_DECREF(_py_tmp); \
|
||||
_Py_TYPEOF(dst)* _tmp_dst_ptr = &(dst); \
|
||||
_Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \
|
||||
*_tmp_dst_ptr = (src); \
|
||||
Py_DECREF(_tmp_old_dst); \
|
||||
} while (0)
|
||||
|
||||
#define Py_XSETREF(op, op2) \
|
||||
#else
|
||||
#define Py_SETREF(dst, src) \
|
||||
do { \
|
||||
PyObject *_py_tmp = _PyObject_CAST(op); \
|
||||
(op) = (op2); \
|
||||
Py_XDECREF(_py_tmp); \
|
||||
PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
|
||||
PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
|
||||
PyObject *_tmp_src = _PyObject_CAST(src); \
|
||||
memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
|
||||
Py_DECREF(_tmp_old_dst); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/* Py_XSETREF() is a variant of Py_SETREF() that uses Py_XDECREF() instead of
|
||||
* Py_DECREF().
|
||||
*/
|
||||
#ifdef _Py_TYPEOF
|
||||
#define Py_XSETREF(dst, src) \
|
||||
do { \
|
||||
_Py_TYPEOF(dst)* _tmp_dst_ptr = &(dst); \
|
||||
_Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \
|
||||
*_tmp_dst_ptr = (src); \
|
||||
Py_XDECREF(_tmp_old_dst); \
|
||||
} while (0)
|
||||
#else
|
||||
#define Py_XSETREF(dst, src) \
|
||||
do { \
|
||||
PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
|
||||
PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
|
||||
PyObject *_tmp_src = _PyObject_CAST(src); \
|
||||
memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
|
||||
Py_XDECREF(_tmp_old_dst); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
|
||||
PyAPI_DATA(PyTypeObject) _PyNone_Type;
|
||||
|
|
|
@ -598,15 +598,44 @@ static inline void Py_DECREF(PyObject *op)
|
|||
* one of those can't cause problems -- but in part that relies on that
|
||||
* 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.
|
||||
*
|
||||
* gh-98724: Use a temporary variable to only evaluate the macro argument once,
|
||||
* to avoid the duplication of side effects if the argument has side effects.
|
||||
*
|
||||
* gh-99701: If the PyObject* type is used with casting arguments to PyObject*,
|
||||
* the code can be miscompiled with strict aliasing because of type punning.
|
||||
* With strict aliasing, a compiler considers that two pointers of different
|
||||
* types cannot read or write the same memory which enables optimization
|
||||
* opportunities.
|
||||
*
|
||||
* If available, use _Py_TYPEOF() to use the 'op' type for temporary variables,
|
||||
* and so avoid type punning. Otherwise, use memcpy() which causes type erasure
|
||||
* and so prevents the compiler to reuse an old cached 'op' value after
|
||||
* Py_CLEAR().
|
||||
*/
|
||||
#ifdef _Py_TYPEOF
|
||||
#define Py_CLEAR(op) \
|
||||
do { \
|
||||
PyObject *_py_tmp = _PyObject_CAST(op); \
|
||||
if (_py_tmp != NULL) { \
|
||||
(op) = NULL; \
|
||||
Py_DECREF(_py_tmp); \
|
||||
_Py_TYPEOF(op)* _tmp_op_ptr = &(op); \
|
||||
_Py_TYPEOF(op) _tmp_old_op = (*_tmp_op_ptr); \
|
||||
if (_tmp_old_op != NULL) { \
|
||||
*_tmp_op_ptr = _Py_NULL; \
|
||||
Py_DECREF(_tmp_old_op); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define Py_CLEAR(op) \
|
||||
do { \
|
||||
PyObject **_tmp_op_ptr = _Py_CAST(PyObject**, &(op)); \
|
||||
PyObject *_tmp_old_op = (*_tmp_op_ptr); \
|
||||
if (_tmp_old_op != NULL) { \
|
||||
PyObject *_null_ptr = _Py_NULL; \
|
||||
memcpy(_tmp_op_ptr, &_null_ptr, sizeof(PyObject*)); \
|
||||
Py_DECREF(_tmp_old_op); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
|
||||
/* Function to use in case the object pointer can be NULL: */
|
||||
static inline void Py_XINCREF(PyObject *op)
|
||||
|
|
|
@ -698,6 +698,15 @@ extern char * _getpty(int *, int, mode_t, int);
|
|||
# define _Py__has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
// _Py_TYPEOF(expr) gets the type of an expression.
|
||||
//
|
||||
// Example: _Py_TYPEOF(x) x_copy = (x);
|
||||
//
|
||||
// The macro is only defined if GCC or clang compiler is used.
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
# define _Py_TYPEOF(expr) __typeof__(expr)
|
||||
#endif
|
||||
|
||||
|
||||
/* A convenient way for code to know if sanitizers are enabled. */
|
||||
#if defined(__has_feature)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
The :c:macro:`Py_CLEAR`, :c:macro:`Py_SETREF` and :c:macro:`Py_XSETREF` macros
|
||||
now only evaluate their arguments once. If an argument has side effects, these
|
||||
side effects are no longer duplicated. Patch by Victor Stinner.
|
|
@ -2589,6 +2589,91 @@ test_set_type_size(PyObject *self, PyObject *Py_UNUSED(ignored))
|
|||
}
|
||||
|
||||
|
||||
// Test Py_CLEAR() macro
|
||||
static PyObject*
|
||||
test_py_clear(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
// simple case with a variable
|
||||
PyObject *obj = PyList_New(0);
|
||||
if (obj == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_CLEAR(obj);
|
||||
assert(obj == NULL);
|
||||
|
||||
// gh-98724: complex case, Py_CLEAR() argument has a side effect
|
||||
PyObject* array[1];
|
||||
array[0] = PyList_New(0);
|
||||
if (array[0] == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject **p = array;
|
||||
Py_CLEAR(*p++);
|
||||
assert(array[0] == NULL);
|
||||
assert(p == array + 1);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
// Test Py_SETREF() and Py_XSETREF() macros, similar to test_py_clear()
|
||||
static PyObject*
|
||||
test_py_setref(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
// Py_SETREF() simple case with a variable
|
||||
PyObject *obj = PyList_New(0);
|
||||
if (obj == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_SETREF(obj, NULL);
|
||||
assert(obj == NULL);
|
||||
|
||||
// Py_XSETREF() simple case with a variable
|
||||
PyObject *obj2 = PyList_New(0);
|
||||
if (obj2 == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_XSETREF(obj2, NULL);
|
||||
assert(obj2 == NULL);
|
||||
// test Py_XSETREF() when the argument is NULL
|
||||
Py_XSETREF(obj2, NULL);
|
||||
assert(obj2 == NULL);
|
||||
|
||||
// gh-98724: complex case, Py_SETREF() argument has a side effect
|
||||
PyObject* array[1];
|
||||
array[0] = PyList_New(0);
|
||||
if (array[0] == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject **p = array;
|
||||
Py_SETREF(*p++, NULL);
|
||||
assert(array[0] == NULL);
|
||||
assert(p == array + 1);
|
||||
|
||||
// gh-98724: complex case, Py_XSETREF() argument has a side effect
|
||||
PyObject* array2[1];
|
||||
array2[0] = PyList_New(0);
|
||||
if (array2[0] == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject **p2 = array2;
|
||||
Py_XSETREF(*p2++, NULL);
|
||||
assert(array2[0] == NULL);
|
||||
assert(p2 == array2 + 1);
|
||||
|
||||
// test Py_XSETREF() when the argument is NULL
|
||||
p2 = array2;
|
||||
Py_XSETREF(*p2++, NULL);
|
||||
assert(array2[0] == NULL);
|
||||
assert(p2 == array2 + 1);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
#define TEST_REFCOUNT() \
|
||||
do { \
|
||||
PyObject *obj = PyList_New(0); \
|
||||
|
@ -3252,6 +3337,8 @@ static PyMethodDef TestMethods[] = {
|
|||
{"pynumber_tobase", pynumber_tobase, METH_VARARGS},
|
||||
{"without_gc", without_gc, METH_O},
|
||||
{"test_set_type_size", test_set_type_size, METH_NOARGS},
|
||||
{"test_py_clear", test_py_clear, METH_NOARGS},
|
||||
{"test_py_setref", test_py_setref, METH_NOARGS},
|
||||
{"test_refcount_macros", test_refcount_macros, METH_NOARGS},
|
||||
{"test_refcount_funcs", test_refcount_funcs, METH_NOARGS},
|
||||
{"test_py_is_macros", test_py_is_macros, METH_NOARGS},
|
||||
|
|
Loading…
Reference in New Issue