Skip to content

Commit 5dac437

Browse files
committed
gh-98724: Fix the Py_CLEAR() macro in the limited C API
If _Py_TYPEOF() is not available, the limited C API now implements the Py_CLEAR() macro as a function call which hides implementation details. The function uses memcpy() of <string.h> which is not included by <Python.h>. * Add private _Py_Clear() function. * Add _Py_Clear() to Misc/stable_abi.toml.
1 parent cda9f02 commit 5dac437

File tree

6 files changed

+61
-4
lines changed

6 files changed

+61
-4
lines changed

Include/object.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,13 @@ static inline void Py_DECREF(PyObject *op)
612612
* and so avoid type punning. Otherwise, use memcpy() which causes type erasure
613613
* and so prevents the compiler to reuse an old cached 'op' value after
614614
* Py_CLEAR().
615+
*
616+
* If _Py_TYPEOF() is not available, the limited C API implementation uses a
617+
* function call to hide implementation details. The function uses memcpy() of
618+
* <string.h> which is not included by <Python.h>.
615619
*/
620+
PyAPI_FUNC(void) _Py_Clear(PyObject **pobj);
621+
616622
#ifdef _Py_TYPEOF
617623
#define Py_CLEAR(op) \
618624
do { \
@@ -623,6 +629,8 @@ static inline void Py_DECREF(PyObject *op)
623629
Py_DECREF(_tmp_old_op); \
624630
} \
625631
} while (0)
632+
#elif defined(Py_LIMITED_API)
633+
#define Py_CLEAR(op) _Py_Clear(_Py_CAST(PyObject**, &(op)))
626634
#else
627635
#define Py_CLEAR(op) \
628636
do { \

Lib/test/test_stable_abi_ctypes.py

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Misc/stable_abi.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,3 +2386,7 @@
23862386
added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix
23872387
[const.Py_AUDIT_READ]
23882388
added = '3.12' # Before 3.12, available in "structmember.h"
2389+
2390+
[function._Py_Clear]
2391+
added = '3.12'
2392+
abi_only = true

Modules/_testcapimodule.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,27 +2593,53 @@ test_set_type_size(PyObject *self, PyObject *Py_UNUSED(ignored))
25932593
static PyObject*
25942594
test_py_clear(PyObject *self, PyObject *Py_UNUSED(ignored))
25952595
{
2596+
PyObject *list = PyList_New(0);
2597+
if (list == NULL) {
2598+
return NULL;
2599+
}
2600+
assert(Py_REFCNT(list) == 1);
2601+
25962602
// simple case with a variable
2597-
PyObject *obj = PyList_New(0);
2603+
PyObject *obj = Py_NewRef(list);
25982604
if (obj == NULL) {
2599-
return NULL;
2605+
goto error;
26002606
}
2607+
assert(Py_REFCNT(list) == 2);
26012608
Py_CLEAR(obj);
2609+
assert(Py_REFCNT(list) == 1);
26022610
assert(obj == NULL);
26032611

2612+
// test _Py_Clear() used by the limited C API
2613+
PyObject *obj2 = Py_NewRef(list);
2614+
if (obj2 == NULL) {
2615+
goto error;
2616+
}
2617+
assert(Py_REFCNT(list) == 2);
2618+
_Py_Clear(&obj2);
2619+
assert(Py_REFCNT(list) == 1);
2620+
assert(obj2 == NULL);
2621+
26042622
// gh-98724: complex case, Py_CLEAR() argument has a side effect
26052623
PyObject* array[1];
2606-
array[0] = PyList_New(0);
2624+
array[0] = Py_NewRef(list);
26072625
if (array[0] == NULL) {
2608-
return NULL;
2626+
goto error;
26092627
}
26102628

26112629
PyObject **p = array;
2630+
assert(Py_REFCNT(list) == 2);
26122631
Py_CLEAR(*p++);
2632+
assert(Py_REFCNT(list) == 1);
26132633
assert(array[0] == NULL);
26142634
assert(p == array + 1);
26152635

2636+
assert(Py_REFCNT(list) == 1);
2637+
Py_DECREF(list);
26162638
Py_RETURN_NONE;
2639+
2640+
error:
2641+
Py_DECREF(list);
2642+
return NULL;
26172643
}
26182644

26192645

Objects/object.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,23 @@ int Py_IsFalse(PyObject *x)
24462446
return Py_Is(x, Py_False);
24472447
}
24482448

2449+
void _Py_Clear(PyObject **pobj)
2450+
{
2451+
PyObject *old_obj = *pobj;
2452+
if (old_obj == NULL) {
2453+
return;
2454+
}
2455+
2456+
// gh-99701: In the limited C API, the Py_CLEAR(obj) macro has a type
2457+
// punning issue, it casts '&obj' to PyObject** to call _Py_Clear(). Use
2458+
// memcpy() instead of a simple assignment to cause type erasure and so
2459+
// prevent the compiler to reuse an old cached 'obj' value after
2460+
// Py_CLEAR().
2461+
PyObject *null_ptr = NULL;
2462+
memcpy(pobj, &null_ptr, sizeof(PyObject*));
2463+
Py_DECREF(old_obj);
2464+
}
2465+
24492466
#ifdef __cplusplus
24502467
}
24512468
#endif

PC/python3dll.c

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)