FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-157649: Restore safe C++ refcount macro temporaries by sankalpsthakur · Pull Request #157718 · python/cpython · GitHub

Repository navigation

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .cpp  (1) .h  (1) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
23 changes: 12 additions & 11 deletions Include/pyport.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -538,18 +538,19 @@ extern "C" {
//
// Example: _Py_TYPEOF(x) x_copy = (x);
//
// On C23, use typeof(). On C++11, use decltype(). Otherwise, use __typeof__()
// if on GCC, clang or MSVC 17.9 and newer.
// On C23, use typeof(). Otherwise, use __typeof__() if on GCC, clang or
// MSVC 17.9 and newer.
//
// On MSVC, check also _MSVC_LANG since __cplusplus is 199711L unless
// the /Zc:__cplusplus flag is used.
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define _Py_TYPEOF(expr) typeof(expr)
#elif defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L)
# define _Py_TYPEOF(expr) decltype(expr)
#elif defined(__GNUC__) || defined(__clang__) || \
(defined(_MSC_VER) && _MSC_VER >= 1939)
# define _Py_TYPEOF(expr) __typeof__(expr)
// In C++, decltype() can produce a reference type for an lvalue expression,
// making a refcount macro's temporary alias its destination (gh-157649).
// Leave _Py_TYPEOF undefined to use the existing memcpy() implementations.
#if !defined(__cplusplus)
# if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define _Py_TYPEOF(expr) typeof(expr)
# elif defined(__GNUC__) || defined(__clang__) || \
(defined(_MSC_VER) && _MSC_VER >= 1939)
# define _Py_TYPEOF(expr) __typeof__(expr)
# endif
#endif


Expand Down
83 changes: 83 additions & 0 deletions Lib/test/test_cppext/extension.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,85 @@ test_datetime(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
Py_RETURN_NONE;
}

// gh-157649: Py_SETREF(), Py_XSETREF(), and Py_CLEAR() must copy the old
// pointer, not bind to an array item or reference the macro overwrites.
static int
test_refcount_lvalues(void)
{
PyObject *old_obj = PyList_New(0);
if (old_obj == _Py_NULL) {
return -1;
}
PyObject *new_obj = PyList_New(0);
if (new_obj == _Py_NULL) {
Py_DECREF(old_obj);
return -1;
}
PyObject *slots[2] = {Py_NewRef(old_obj), _Py_NULL};

#ifndef Py_LIMITED_API
Py_SETREF(slots[0], Py_NewRef(new_obj));
assert(slots[0] == new_obj);
assert(Py_REFCNT(old_obj) == 1);
assert(Py_REFCNT(new_obj) == 2);

Py_XSETREF(slots[0], Py_NewRef(old_obj));
assert(slots[0] == old_obj);
assert(Py_REFCNT(old_obj) == 2);
assert(Py_REFCNT(new_obj) == 1);
#endif

int index = 0;
Py_CLEAR(slots[index++]);
assert(index == 1);
assert(slots[0] == _Py_NULL);
assert(Py_REFCNT(old_obj) == 1);
Py_CLEAR(slots[0]);

slots[0] = Py_NewRef(old_obj);
PyObject *&slot = slots[0];
Py_CLEAR(slot);
assert(slots[0] == _Py_NULL);
assert(Py_REFCNT(old_obj) == 1);

#ifndef Py_LIMITED_API
slot = Py_NewRef(old_obj);
Py_SETREF(slot, Py_NewRef(new_obj));
assert(slots[0] == new_obj);
assert(Py_REFCNT(old_obj) == 1);
assert(Py_REFCNT(new_obj) == 2);

Py_XSETREF((slot), Py_NewRef(old_obj));
assert(slots[0] == old_obj);
assert(Py_REFCNT(old_obj) == 2);
assert(Py_REFCNT(new_obj) == 1);
Py_CLEAR(slot);

// Both arguments must be evaluated once, including a NULL destination.
index = 0;
Py_XSETREF(slots[index++], Py_NewRef(new_obj));
assert(index == 1);
assert(slots[0] == new_obj);
assert(Py_REFCNT(new_obj) == 2);
Py_CLEAR(slots[0]);

// Preserve support for pointers to concrete object types.
PyListObject *lists[1] = {(PyListObject *)Py_NewRef(old_obj)};
Py_SETREF(lists[0], (PyListObject *)Py_NewRef(new_obj));
assert((PyObject *)lists[0] == new_obj);
assert(Py_REFCNT(old_obj) == 1);
assert(Py_REFCNT(new_obj) == 2);
Py_CLEAR(lists[0]);
assert(lists[0] == _Py_NULL);
#endif

assert(Py_REFCNT(old_obj) == 1);
assert(Py_REFCNT(new_obj) == 1);
Py_DECREF(old_obj);
Py_DECREF(new_obj);
return 0;
}

static PyMethodDef _testcppext_methods[] = {
{"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
Expand Down Expand Up @@ -298,6 +377,10 @@ _testcppext_exec(PyObject *module)
if (!result) return -1;
Py_DECREF(result);

if (test_refcount_lvalues() < 0) {
return -1;
}

// test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a C++ regression in :c:macro:`Py_CLEAR`, :c:macro:`Py_SETREF` and
:c:macro:`Py_XSETREF` when the destination is an array item or a reference.
The macros could decrement the wrong object's reference count or crash.
Loading

Back | FazBrowse Home | New Git URL