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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy