From 800c126f5ad1b55103f64022dae28da40361d451 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Thu, 13 Nov 2025 16:26:04 -0800 Subject: [PATCH 1/3] [lldb] Require at least Python 3.11 when targeting the Limited C API This is a continuation of my effort [1] to eliminate uses of the private Python API in LLDB. I found 3 more issues that were hidden in the typemap, because those were included before we defined Py_LIMITED_API. The primary motivator for bumping the minimum required Python version is our use of `pybuffer` (i.e. the Buffer Protocol). - For Python versions prior to Python 3.11, your only option was to target the "Old Buffer Protocol" [2], which has been deprecated since Python 3 and removed in Python 3.13, though the symbols still exist for ABI compatibility. - For Python versions 3.13 and later, because of the removal, your only option is to target the "New Buffer Protocol" [3] which was only added to the stable API in Python 3.11. This leads to certain combination of Python versions for building against and targeting with the limited API that are not compatible. For example, you cannot target the 3.8 stable API when building against Python 3.13 because the new version of Python doesn't declare the old APIs and you cannot use the new APIs yet. To make this more complicated, the use of the buffer protocol is coming from SWIG. Furthermore, all released versions of SWIG get this wrong. For unrelated reasons, to work around a bug in SWIG < 4.1, we already reimplement the swig type wrapper for the Buffer Protocol in a way that's compatible with Python 3.11 and later. Instead of waiting for a SWIG release that fixes this issue (it's already fixed on the master branch) we can keep the workaround and bump the minimum Python version when targeting the limited C API. [1] https://github.com/llvm/llvm-project/issues/151617 [2] https://docs.python.org/3.12/c-api/objbuffer.html [3] https://docs.python.org/3/c-api/buffer.html --- lldb/bindings/python/python-typemaps.swig | 19 +++++++++---------- lldb/bindings/python/python-wrapper.swig | 7 +++++-- lldb/bindings/python/python.swig | 5 +++++ lldb/cmake/modules/LLDBConfig.cmake | 6 ++++-- .../ScriptInterpreter/Python/lldb-python.h | 7 ++++--- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lldb/bindings/python/python-typemaps.swig b/lldb/bindings/python/python-typemaps.swig index 3baeaa7770e6c..f73ab768b4c42 100644 --- a/lldb/bindings/python/python-typemaps.swig +++ b/lldb/bindings/python/python-typemaps.swig @@ -220,9 +220,9 @@ AND call SWIG_fail at the same time, because it will result in a double free. } // Disable default type checking for this method to avoid SWIG dispatch issues. -// +// // Problem: SBThread::GetStopDescription has two overloads: -// 1. GetStopDescription(char* dst_or_null, size_t dst_len) +// 1. GetStopDescription(char* dst_or_null, size_t dst_len) // 2. GetStopDescription(lldb::SBStream& stream) // // SWIG generates a dispatch function to select the correct overload based on argument types. @@ -230,9 +230,9 @@ AND call SWIG_fail at the same time, because it will result in a double free. // However, this dispatcher doesn't consider typemaps that transform function signatures. // // In Python, our typemap converts GetStopDescription(char*, size_t) to GetStopDescription(int). -// The dispatcher still checks against the original (char*, size_t) signature instead of +// The dispatcher still checks against the original (char*, size_t) signature instead of // the transformed (int) signature, causing type matching to fail. -// This only affects SBThread::GetStopDescription since the type check also matches +// This only affects SBThread::GetStopDescription since the type check also matches // the argument name, which is unique to this function. %typemap(typecheck, precedence=SWIG_TYPECHECK_POINTER) (char *dst_or_null, size_t dst_len) "" @@ -628,12 +628,11 @@ template <> bool SetNumberFromPyObject(double &number, PyObject *obj) { } } -// These two pybuffer macros are copied out of swig/Lib/python/pybuffer.i, -// and fixed so they will not crash if PyObject_GetBuffer fails. -// https://github.com/swig/swig/issues/1640 -// -// I've also moved the call to PyBuffer_Release to the end of the SWIG wrapper, -// doing it right away is not legal according to the python buffer protocol. +// These two pybuffer macros are copied out of swig/Lib/python/pybuffer.i. +// - Avoids crash if PyObject_GetBuffer fails and delays calling +// PyBuffer_Release (https://github.com/swig/swig/issues/1640) +// - Avoids using deprecated "old buffer protocol" APIs when targeting the +// Stable C API. %inline %{ struct Py_buffer_RAII { Py_buffer buffer = {}; diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 3a0995e84f643..9b9245eac14ce 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -128,8 +128,11 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallTypeScript( PyObject *pfunc_impl = nullptr; - if (pyfunct_wrapper && *pyfunct_wrapper && - PyFunction_Check(*pyfunct_wrapper)) { + if (pyfunct_wrapper && *pyfunct_wrapper +#ifndef Py_LIMITED_API + && PyFunction_Check(*pyfunct_wrapper) +#endif + ) { pfunc_impl = (PyObject *)(*pyfunct_wrapper); if (pfunc_impl->ob_refcnt == 1) { Py_XDECREF(pfunc_impl); diff --git a/lldb/bindings/python/python.swig b/lldb/bindings/python/python.swig index 4a5a39dc4b06d..b2823f98acac8 100644 --- a/lldb/bindings/python/python.swig +++ b/lldb/bindings/python/python.swig @@ -59,6 +59,11 @@ except ImportError: // Parameter types will be used in the autodoc string. %feature("autodoc", "1"); +// Include lldb-python first as it sets Py_LIMITED_API. +%begin %{ +#include "../source/Plugins/ScriptInterpreter/Python/lldb-python.h" +%} + %pythoncode%{ import uuid import re diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake index 4b568d27c4709..0abaa9fb763e7 100644 --- a/lldb/cmake/modules/LLDBConfig.cmake +++ b/lldb/cmake/modules/LLDBConfig.cmake @@ -180,12 +180,14 @@ if (LLDB_ENABLE_PYTHON) "Path to use as PYTHONHOME in lldb. If a relative path is specified, it will be resolved at runtime relative to liblldb directory.") endif() - if (SWIG_VERSION VERSION_GREATER_EQUAL "4.2" AND NOT LLDB_EMBED_PYTHON_HOME) + if (SWIG_VERSION VERSION_GREATER_EQUAL "4.2" + Python3_VERSION VERSION_GREATER_EQUAL "3.11" + AND NOT LLDB_EMBED_PYTHON_HOME) set(default_enable_python_limited_api ON) else() set(default_enable_python_limited_api OFF) endif() - option(LLDB_ENABLE_PYTHON_LIMITED_API "Force LLDB to only use the Python Limited API (requires SWIG 4.2 or later)" + option(LLDB_ENABLE_PYTHON_LIMITED_API "Only use the Python Limited API (requires at least Python 3.11 and SWIG 4.2)" ${default_enable_python_limited_api}) else() # Even if Python scripting is disabled, we still need a Python interpreter to diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h b/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h index 0c281793613a5..98941f504e44c 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h @@ -45,12 +45,13 @@ static llvm::Expected *g_fcxx_modules_workaround [[maybe_unused]]; #include #endif -#define LLDB_MINIMUM_PYTHON_VERSION 0x03080000 - -#if LLDB_ENABLE_PYTHON_LIMITED_API // If defined, LLDB will be ABI-compatible with all Python 3 releases from the // specified one onward, and can use Limited API introduced up to that version. +#if LLDB_ENABLE_PYTHON_LIMITED_API +#define LLDB_MINIMUM_PYTHON_VERSION 0x030b0000 #define Py_LIMITED_API LLDB_MINIMUM_PYTHON_VERSION +#else +#define LLDB_MINIMUM_PYTHON_VERSION 0x03080000 #endif // Include python for non windows machines From a400ac06eb6af925d4d098de1af07443fc3dde3c Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Thu, 13 Nov 2025 16:56:48 -0800 Subject: [PATCH 2/3] Add missing AND --- lldb/cmake/modules/LLDBConfig.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake index 0abaa9fb763e7..1302b1871d066 100644 --- a/lldb/cmake/modules/LLDBConfig.cmake +++ b/lldb/cmake/modules/LLDBConfig.cmake @@ -181,7 +181,7 @@ if (LLDB_ENABLE_PYTHON) endif() if (SWIG_VERSION VERSION_GREATER_EQUAL "4.2" - Python3_VERSION VERSION_GREATER_EQUAL "3.11" + AND Python3_VERSION VERSION_GREATER_EQUAL "3.11" AND NOT LLDB_EMBED_PYTHON_HOME) set(default_enable_python_limited_api ON) else() From 5f935c5d665b099dc02ded2ccc7228e9b13c1d65 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Fri, 14 Nov 2025 10:54:58 -0800 Subject: [PATCH 3/3] Diagnose unsupported configurations --- lldb/cmake/modules/LLDBConfig.cmake | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake index 1302b1871d066..4621642890066 100644 --- a/lldb/cmake/modules/LLDBConfig.cmake +++ b/lldb/cmake/modules/LLDBConfig.cmake @@ -180,8 +180,11 @@ if (LLDB_ENABLE_PYTHON) "Path to use as PYTHONHOME in lldb. If a relative path is specified, it will be resolved at runtime relative to liblldb directory.") endif() - if (SWIG_VERSION VERSION_GREATER_EQUAL "4.2" - AND Python3_VERSION VERSION_GREATER_EQUAL "3.11" + set(python_limited_api_swig_version "4.2") + set(python_limited_api_python_version "3.11") + + if (SWIG_VERSION VERSION_GREATER_EQUAL python_limited_api_swig_version + AND Python3_VERSION VERSION_GREATER_EQUAL python_limited_api_python_version AND NOT LLDB_EMBED_PYTHON_HOME) set(default_enable_python_limited_api ON) else() @@ -189,6 +192,17 @@ if (LLDB_ENABLE_PYTHON) endif() option(LLDB_ENABLE_PYTHON_LIMITED_API "Only use the Python Limited API (requires at least Python 3.11 and SWIG 4.2)" ${default_enable_python_limited_api}) + + # Diagnose unsupported configurations. + if (LLDB_ENABLE_PYTHON_LIMITED_API AND LLDB_EMBED_PYTHON_HOME) + message(SEND_ERROR "LLDB_ENABLE_PYTHON_LIMITED_API is not compatible with LLDB_EMBED_PYTHON_HOME") + endif() + if (LLDB_ENABLE_PYTHON_LIMITED_API AND Python3_VERSION VERSION_LESS python_limited_api_python_version) + message(SEND_ERROR "LLDB_ENABLE_PYTHON_LIMITED_API is not compatible with Python ${Python3_VERSION} (requires Python ${python_limited_api_python_version})") + endif() + if (LLDB_ENABLE_PYTHON_LIMITED_API AND SWIG_VERSION VERSION_LESS python_limited_api_swig_version) + message(SEND_ERROR "LLDB_ENABLE_PYTHON_LIMITED_API is not compatible with SWIG ${SWIG_VERSION} (requires SWIG ${python_limited_api_swig_version})") + endif() else() # Even if Python scripting is disabled, we still need a Python interpreter to # build, for example to generate SBLanguages.h.