From 493649f965ef8ba613a6c60d3881adeae1052518 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Wed, 14 Oct 2020 20:11:09 +0200 Subject: [PATCH] fix: valgrind-detected after-freeing access of PyMethodDef (macOS Python 3.9.0 segfaults) (#2576) * Check if valgrind-detected after-freeing access of PyMethodDef causes macOS Python 3.9 segfaults * fix: only apply leak on 3.9.0 * fix: faster check * fix: better naming thanks to @bstaletic Co-authored-by: Henry Schreiner --- .github/workflows/ci.yml | 6 ------ include/pybind11/pybind11.h | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59466c09..d8a33d4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,12 +70,6 @@ jobs: python: pypy3 arch: x64 - # TODO: renable - # Currently segfaults on macOS Python 3.9 - - runs-on: macos-latest - python: 3.9 - arch: x64 - name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }} ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index da682ea7..8698390e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -452,6 +452,12 @@ protected: /// When a cpp_function is GCed, release any memory allocated by pybind11 static void destruct(detail::function_record *rec) { + // If on Python 3.9, check the interpreter "MICRO" (patch) version. + // If this is running on 3.9.0, we have to work around a bug. + #if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 9 + static bool is_zero = Py_GetVersion()[4] == '0'; + #endif + while (rec) { detail::function_record *next = rec->next; if (rec->free_data) @@ -466,7 +472,15 @@ protected: } if (rec->def) { std::free(const_cast(rec->def->ml_doc)); - delete rec->def; + // Python 3.9.0 decref's these in the wrong order; rec->def + // If loaded on 3.9.0, let these leak (use Python 3.9.1 at runtime to fix) + // See https://github.com/python/cpython/pull/22670 + #if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 9 + if (!is_zero) + delete rec->def; + #else + delete rec->def; + #endif } delete rec; rec = next;