GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [C API] Py_CLEAR(), Py_SETREF() and Py_XSETREF() memcpy() implementation is not safe with strict aliasing · Issue #161185 · python/cpython

[C API] Py_CLEAR(), Py_SETREF() and Py_XSETREF() memcpy() implementation is not safe with strict aliasing #161185

New issue
Copy link
New issue
Copy link
Closed
Closed
[C API] Py_CLEAR(), Py_SETREF() and Py_XSETREF() memcpy() implementation is not safe with strict aliasing#161185
Copy link
Assignees
vstinner
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-C-APItype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@vstinner
vstinner
opened on Nov 2, 2026
Issue body actions

Issue reported by @ngoldbaum on pythoncapi-compat: python/pythoncapi-compat#187 (comment). Copy/paste of his message.


I asked an AI model to look this over and it spotted an issue (CPython's fallback also has this problem):

The memcpy() protects the write, but the preceding read still violates strict aliasing:

PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst));
PyObject *_tmp_old_dst = (*_tmp_dst_ptr);

If dst is a PyTypeObject *, dereferencing _tmp_dst_ptr accesses a PyTypeObject * object through a PyObject * lvalue. GCC can therefore assume that this read does not observe a write made through PyTypeObject **.

Here is a standalone reproducer using the exact Py_XSETREF() implementation from this PR:

#include <Python.h>

#include <cstdio>
#include <cstring>

#ifndef _Py_CAST
#  define _Py_CAST(type, expr) ((type)(expr))
#endif
#ifndef _PyObject_CAST
#  define _PyObject_CAST(obj) _Py_CAST(PyObject *, (obj))
#endif

#define REPRO_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)

__attribute__((noinline))
static void
replace(PyTypeObject **a, PyTypeObject **b,
        PyTypeObject *first, PyTypeObject *second)
{
    *a = first;
    REPRO_XSETREF(*b, second);
}

int
main()
{
    Py_Initialize();

    PyType_Slot slots[] = {{0, NULL}};
    PyType_Spec spec = {
        "repro.T", sizeof(PyObject), 0, Py_TPFLAGS_DEFAULT, slots
    };
    PyObject *first = PyType_FromSpec(&spec);
    PyObject *second = PyType_FromSpec(&spec);
    if (first == NULL || second == NULL) {
        PyErr_Print();
        return 1;
    }

    Py_ssize_t before = Py_REFCNT(first);
    PyTypeObject *slot = NULL;

    // Both pointer arguments alias. The assignment makes first the old value
    // that REPRO_XSETREF() must release before storing second.
    replace(&slot, &slot,
            reinterpret_cast<PyTypeObject *>(Py_NewRef(first)),
            reinterpret_cast<PyTypeObject *>(Py_NewRef(second)));

    std::printf("first refcount: %zd; expected: %zd\n",
                Py_REFCNT(first), before);
    return 0;
}

On CPython 3.10 with GCC 15.2.0:

$ g++ -std=c++11 $(python3.10-config --cflags) \
      -DPy_LIMITED_API=0x030a0000 -O2 -fstrict-aliasing \
      repro.cpp $(python3.10-config --embed --ldflags) -o repro
$ ./repro
first refcount: 3; expected: 2

The reference acquired by Py_NewRef(first) was not released. Compiling the same source with -fno-strict-aliasing produces the expected refcount of 2.

With Py_SETREF() instead of Py_XSETREF(), the stale value can be NULL, causing a segmentation fault when it is passed to Py_DECREF().

Linked PRs

  • gh-161185: Fix strict aliasing issue in Py_SETREF() macros family #161186
  • [3.15] gh-161185: Fix strict aliasing issue in Py_SETREF() macros family (#161186) #161360
  • [3.14] gh-161185: Fix strict aliasing issue in Py_SETREF() macros family (GH-161186) (GH-161360) #161362
  • [3.14] gh-161185: Fix test_cext: replace PyType_FromSlots() with PyType_FromSpec() #161373

Activity

  1. vstinner
    added
    topic-C-API
    on Nov 2, 2026
  2. vstinner
    self-assigned this
    on Nov 2, 2026
  3. picnixz
    added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Nov 2, 2026
  4. vstinner
    added 4 commits that reference this issue on Nov 4, 2026

    gh-161185: Fix strict aliasing issue in Py_SETREF() macros family (#1…

    f8e65ee

    pythongh-161185: Fix strict aliasing issue in Py_SETREF() macros fami…

    d26d759

    [3.15] gh-161185: Fix strict aliasing issue in Py_SETREF() macros fam…

    0dc1a96

    [3.14] gh-161185: Fix strict aliasing issue in Py_SETREF() macros fam…

    adb9185
  5. vstinner
    closed this as completedon Nov 4, 2026
  6. vstinner
    added 2 commits that reference this issue on Nov 5, 2026

    pythongh-161185: Fix test_cext: replace PyType_FromSlots() with PyTyp…

    3af5547

    [3.14] gh-161185: Fix test_cext: replace PyType_FromSlots() with PyTy…

    8cb34fa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @vstinner
    vstinner

Labels

interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-C-APItype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.