Skip to content

Commit

Permalink
pythongh-127271: Replace use of PyCell_GET/SET
Browse files Browse the repository at this point in the history
These macros are not safe to use in the free-threaded build.  Use
`PyCell_GetRef()` and `PyCell_SetTakeRef()` instead. Add `PyCell_GET` to
the free-threading howto table of APIs that return borrowed refs. Add
critical sections to `PyCell_GET` and `PyCell_SET`.
  • Loading branch information
nascheme committed Nov 26, 2024
1 parent 6da9d25 commit 10ac6e6
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 34 deletions.
2 changes: 2 additions & 0 deletions Doc/howto/free-threading-extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ that return :term:`strong references <strong reference>`.
+-----------------------------------+-----------------------------------+
| :c:func:`PyImport_AddModule` | :c:func:`PyImport_AddModuleRef` |
+-----------------------------------+-----------------------------------+
| :c:func:`PyCell_GET` | :c:func:`PyCell_Get` |
+-----------------------------------+-----------------------------------+

Not all APIs that return borrowed references are problematic. For
example, :c:func:`PyTuple_GetItem` is safe because tuples are immutable.
Expand Down
2 changes: 1 addition & 1 deletion Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
#include "pystats.h"
#include "pyatomic.h"
#include "lock.h"
#include "critical_section.h"
#include "object.h"
#include "refcount.h"
#include "objimpl.h"
Expand Down Expand Up @@ -130,7 +131,6 @@
#include "import.h"
#include "abstract.h"
#include "bltinmodule.h"
#include "critical_section.h"
#include "cpython/pyctype.h"
#include "pystrtod.h"
#include "pystrcmp.h"
Expand Down
8 changes: 7 additions & 1 deletion Include/cpython/cellobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@ PyAPI_FUNC(PyObject *) PyCell_Get(PyObject *);
PyAPI_FUNC(int) PyCell_Set(PyObject *, PyObject *);

static inline PyObject* PyCell_GET(PyObject *op) {
PyObject *res;
PyCellObject *cell;
assert(PyCell_Check(op));
cell = _Py_CAST(PyCellObject*, op);
return cell->ob_ref;
Py_BEGIN_CRITICAL_SECTION(cell);
res = cell->ob_ref;
Py_END_CRITICAL_SECTION();
return res;
}
#define PyCell_GET(op) PyCell_GET(_PyObject_CAST(op))

static inline void PyCell_SET(PyObject *op, PyObject *value) {
PyCellObject *cell;
assert(PyCell_Check(op));
cell = _Py_CAST(PyCellObject*, op);
Py_BEGIN_CRITICAL_SECTION(cell);
cell->ob_ref = value;
Py_END_CRITICAL_SECTION();
}
#define PyCell_SET(op, value) PyCell_SET(_PyObject_CAST(op), (value))

Expand Down
28 changes: 16 additions & 12 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "pycore_moduleobject.h" // _PyModule_GetDict()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_cell.h" // PyCell_GetRef() PyCell_SetTakeRef()
#include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches


Expand Down Expand Up @@ -187,11 +188,8 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
}
}
if (cell != NULL) {
PyObject *oldvalue_o = PyCell_GET(cell);
if (value != oldvalue_o) {
PyCell_SET(cell, Py_XNewRef(value));
Py_XDECREF(oldvalue_o);
}
Py_XINCREF(value);
PyCell_SetTakeRef((PyCellObject *)cell, value);
} else if (value != PyStackRef_AsPyObjectBorrow(oldvalue)) {
PyStackRef_XCLOSE(fast[i]);
fast[i] = PyStackRef_FromPyObjectNew(value);
Expand Down Expand Up @@ -1987,19 +1985,25 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
if (kind & CO_FAST_FREE) {
// The cell was set by COPY_FREE_VARS.
assert(value != NULL && PyCell_Check(value));
value = PyCell_GET(value);
value = PyCell_GetRef((PyCellObject *)value);
}
else if (kind & CO_FAST_CELL) {
if (value != NULL) {
if (PyCell_Check(value)) {
assert(!_PyFrame_IsIncomplete(frame));
value = PyCell_GET(value);
value = PyCell_GetRef((PyCellObject *)value);
}
else {
// (likely) Otherwise it is an arg (kind & CO_FAST_LOCAL),
// with the initial value set when the frame was created...
// (unlikely) ...or it was set via the f_locals proxy.
Py_INCREF(value);
}
// (likely) Otherwise it is an arg (kind & CO_FAST_LOCAL),
// with the initial value set when the frame was created...
// (unlikely) ...or it was set via the f_locals proxy.
}
}
else {
Py_XINCREF(value);
}
}
*pvalue = value;
return 1;
Expand Down Expand Up @@ -2076,14 +2080,14 @@ PyFrame_GetVar(PyFrameObject *frame_obj, PyObject *name)
continue;
}

PyObject *value; // borrowed reference
PyObject *value;
if (!frame_get_var(frame, co, i, &value)) {
break;
}
if (value == NULL) {
break;
}
return Py_NewRef(value);
return value;
}

PyErr_Format(PyExc_NameError, "variable %R does not exist", name);
Expand Down
57 changes: 38 additions & 19 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "pycore_typeobject.h" // struct type_cache
#include "pycore_unionobject.h" // _Py_union_type_or
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
#include "pycore_cell.h" // PyCell_GetRef()
#include "opcode.h" // MAKE_CELL

#include <stddef.h> // ptrdiff_t
Expand Down Expand Up @@ -11676,23 +11677,28 @@ super_init_without_args(_PyInterpreterFrame *cframe, PyTypeObject **type_p,

assert(_PyFrame_GetCode(cframe)->co_nlocalsplus > 0);
PyObject *firstarg = PyStackRef_AsPyObjectBorrow(_PyFrame_GetLocalsArray(cframe)[0]);
if (firstarg == NULL) {
PyErr_SetString(PyExc_RuntimeError, "super(): arg[0] deleted");
return -1;
}
// The first argument might be a cell.
if (firstarg != NULL && (_PyLocals_GetKind(co->co_localspluskinds, 0) & CO_FAST_CELL)) {
// "firstarg" is a cell here unless (very unlikely) super()
// was called from the C-API before the first MAKE_CELL op.
if (_PyInterpreterFrame_LASTI(cframe) >= 0) {
// MAKE_CELL and COPY_FREE_VARS have no quickened forms, so no need
// to use _PyOpcode_Deopt here:
assert(_PyCode_CODE(co)[0].op.code == MAKE_CELL ||
_PyCode_CODE(co)[0].op.code == COPY_FREE_VARS);
assert(PyCell_Check(firstarg));
firstarg = PyCell_GET(firstarg);
// "firstarg" is a cell here unless (very unlikely) super()
// was called from the C-API before the first MAKE_CELL op.
if ((_PyLocals_GetKind(co->co_localspluskinds, 0) & CO_FAST_CELL) &&
(_PyInterpreterFrame_LASTI(cframe) >= 0)) {
// MAKE_CELL and COPY_FREE_VARS have no quickened forms, so no need
// to use _PyOpcode_Deopt here:
assert(_PyCode_CODE(co)[0].op.code == MAKE_CELL ||
_PyCode_CODE(co)[0].op.code == COPY_FREE_VARS);
assert(PyCell_Check(firstarg));
firstarg = PyCell_GetRef((PyCellObject *)firstarg);
if (firstarg == NULL) {
PyErr_SetString(PyExc_RuntimeError, "super(): arg[0] deleted");
return -1;
}
}
if (firstarg == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"super(): arg[0] deleted");
return -1;
else {
Py_INCREF(firstarg);
}

// Look for __class__ in the free vars.
Expand All @@ -11707,18 +11713,22 @@ super_init_without_args(_PyInterpreterFrame *cframe, PyTypeObject **type_p,
if (cell == NULL || !PyCell_Check(cell)) {
PyErr_SetString(PyExc_RuntimeError,
"super(): bad __class__ cell");
Py_DECREF(firstarg);
return -1;
}
type = (PyTypeObject *) PyCell_GET(cell);
type = (PyTypeObject *) PyCell_GetRef((PyCellObject *)cell);
if (type == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"super(): empty __class__ cell");
Py_DECREF(firstarg);
return -1;
}
if (!PyType_Check(type)) {
PyErr_Format(PyExc_RuntimeError,
"super(): __class__ is not a type (%s)",
Py_TYPE(type)->tp_name);
Py_DECREF(type);
Py_DECREF(firstarg);
return -1;
}
break;
Expand All @@ -11727,6 +11737,7 @@ super_init_without_args(_PyInterpreterFrame *cframe, PyTypeObject **type_p,
if (type == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"super(): __class__ cell not found");
Py_DECREF(firstarg);
return -1;
}

Expand Down Expand Up @@ -11773,16 +11784,24 @@ super_init_impl(PyObject *self, PyTypeObject *type, PyObject *obj) {
return -1;
}
}
else {
Py_INCREF(type);
Py_XINCREF(obj);
}

if (obj == Py_None)
if (obj == Py_None) {
Py_DECREF(obj);
obj = NULL;
}
if (obj != NULL) {
obj_type = supercheck(type, obj);
if (obj_type == NULL)
if (obj_type == NULL) {
Py_DECREF(type);
Py_DECREF(obj);
return -1;
Py_INCREF(obj);
}
}
Py_XSETREF(su->type, (PyTypeObject*)Py_NewRef(type));
Py_XSETREF(su->type, (PyTypeObject*)type);
Py_XSETREF(su->obj, obj);
Py_XSETREF(su->obj_type, obj_type);
return 0;
Expand Down
7 changes: 6 additions & 1 deletion Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "pycore_pythonrun.h" // _Py_SourceAsString()
#include "pycore_sysmodule.h" // _PySys_GetAttr()
#include "pycore_tuple.h" // _PyTuple_FromArray()
#include "pycore_cell.h" // PyCell_GetRef()

#include "clinic/bltinmodule.c.h"

Expand Down Expand Up @@ -209,7 +210,7 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
PyObject *margs[3] = {name, bases, ns};
cls = PyObject_VectorcallDict(meta, margs, 3, mkw);
if (cls != NULL && PyType_Check(cls) && PyCell_Check(cell)) {
PyObject *cell_cls = PyCell_GET(cell);
PyObject *cell_cls = PyCell_GetRef((PyCellObject *)cell);
if (cell_cls != cls) {
if (cell_cls == NULL) {
const char *msg =
Expand All @@ -221,9 +222,13 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
"__class__ set to %.200R defining %.200R as %.200R";
PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls);
}
Py_XDECREF(cell_cls);
Py_SETREF(cls, NULL);
goto error;
}
else {
Py_DECREF(cell_cls);
}
}
}
error:
Expand Down

0 comments on commit 10ac6e6

Please sign in to comment.