From c937f76068b8ee8434cdf97eeab9af3b31444dbf Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Oct 2026 18:02:24 +0200 Subject: [PATCH 1/2] gh-158445: Allocate memory in the heap in Py_GetVersion() Py_GetVersion() now allocates memory on the heap, instead of using a static buffer, to no longer truncate the version if it's longer than 299 bytes. Update Py_GetCompiler() and Py_GetBuildInfo() tests: they are now always a part of sys.version. --- Doc/c-api/interp-lifecycle.rst | 2 +- Include/internal/pycore_pylifecycle.h | 3 +- Lib/test/test_capi/test_build.py | 8 +--- Modules/main.c | 1 + Python/getcompiler.c | 9 ++-- Python/getversion.c | 67 ++++++++++++++++++++++----- Python/pylifecycle.c | 6 ++- 7 files changed, 72 insertions(+), 24 deletions(-) diff --git a/Doc/c-api/interp-lifecycle.rst b/Doc/c-api/interp-lifecycle.rst index efd208263bdf33..226fb8fb4b14b3 100644 --- a/Doc/c-api/interp-lifecycle.rst +++ b/Doc/c-api/interp-lifecycle.rst @@ -514,7 +514,7 @@ Process-wide parameters Return the version of this Python interpreter. This is a string that looks something like :: - "3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]" + "3.15.0rc2 (3.15.0~rc2-1.fc44.x86_64, Sep 3 2026, 00:00:00) [GCC 16.2.1 20260819 (Red Hat 16.2.1-2)]" .. index:: single: version (in module sys) diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index bfc94e3e8529b7..99d09c5e0d4e11 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -23,7 +23,7 @@ extern int _Py_IsLocaleCoercionTarget(const char *ctype_loc); /* Various one-time initializers */ -extern void _Py_InitVersion(void); +extern PyStatus _Py_GetVersion_Init(void); extern PyStatus _PyFaulthandler_Init(int enable); extern PyObject * _PyBuiltin_Init(PyInterpreterState *interp); extern int _PyBuiltin_InitPythonFunctions(PyObject *dict); @@ -43,6 +43,7 @@ extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp); /* Various internal finalizers */ +extern void _Py_GetVersion_Fini(void); extern int _PySignal_Init(int install_signal_handlers); extern void _PySignal_Fini(void); diff --git a/Lib/test/test_capi/test_build.py b/Lib/test/test_capi/test_build.py index 8319ad1ec77da8..d45c9dbc1c5882 100644 --- a/Lib/test/test_capi/test_build.py +++ b/Lib/test/test_capi/test_build.py @@ -20,15 +20,11 @@ def test_getcopyright(self): # Test Py_GetCompiler() def test_getcompiler(self): - compiler = _testlimitedcapi.py_getcompiler() - self.assertIsInstance(compiler, str) - self.assertNotEqual(compiler, '') + self.assertIn(_testlimitedcapi.py_getcompiler(), sys.version) # Test Py_GetBuildInfo() def test_getbuildinfo(self): - buildinfo = _testlimitedcapi.py_getbuildinfo() - self.assertIsInstance(buildinfo, str) - self.assertNotEqual(buildinfo, '') + self.assertIn(_testlimitedcapi.py_getbuildinfo(), sys.version) # Test internal _Py_GetBuiltWithAssert() def test__getbuiltwithassert(self): diff --git a/Modules/main.c b/Modules/main.c index 44a025d500e35e..097c10504d69a3 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -847,6 +847,7 @@ pymain_free(void) _PyPathConfig_ClearGlobal(); _Py_ClearArgcArgv(); _PyRuntime_Finalize(); + _Py_GetVersion_Fini(); } diff --git a/Python/getcompiler.c b/Python/getcompiler.c index cc56ad8c895551..baa03679d79fb8 100644 --- a/Python/getcompiler.c +++ b/Python/getcompiler.c @@ -3,6 +3,7 @@ #include "Python.h" +// PC/pyconfig.h defines _Py_COMPILER #ifdef _Py_COMPILER # define COMPILER _Py_COMPILER #endif @@ -12,14 +13,14 @@ // Note the __clang__ conditional has to come before the __GNUC__ one because // clang pretends to be GCC. #if defined(__clang__) -#define COMPILER "[Clang " __clang_version__ "]" +# define COMPILER "[Clang " __clang_version__ "]" #elif defined(__GNUC__) -#define COMPILER "[GCC " __VERSION__ "]" +# define COMPILER "[GCC " __VERSION__ "]" // Generic fallbacks. #elif defined(__cplusplus) -#define COMPILER "[C++]" +# define COMPILER "[C++]" #else -#define COMPILER "[C]" +# define COMPILER "[C]" #endif #endif /* !COMPILER */ diff --git a/Python/getversion.c b/Python/getversion.c index 8d8bc6ea70048c..5c05b841de6fc8 100644 --- a/Python/getversion.c +++ b/Python/getversion.c @@ -2,32 +2,77 @@ /* Return the full version string. */ #include "Python.h" +#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY() -#include "patchlevel.h" static int initialized = 0; -static char version[300]; +// Use the smallest possible buffer: it's only needed if malloc() fails *and* +// Py_GetVersion() is called before Py_Initialize(). The buffer should be big +// enough to store "3.16.0a0 " string. +static char static_version[20]; +static char *heap_version = NULL; -void _Py_InitVersion(void) + +PyStatus +_Py_GetVersion_Init(void) { if (initialized) { - return; + return _PyStatus_OK(); } - initialized = 1; + #ifdef Py_GIL_DISABLED - const char *buildinfo_format = "%.80s free-threading build (%.80s) %.80s"; + const char *format = "%s free-threading build (%s) %s"; + size_t format_len = strlen(" free-threading build () "); #else - const char *buildinfo_format = "%.80s (%.80s) %.80s"; + const char *format = "%s (%s) %s"; + size_t format_len = strlen(" () "); #endif - PyOS_snprintf(version, sizeof(version), buildinfo_format, - PY_VERSION, Py_GetBuildInfo(), Py_GetCompiler()); + const char *version_str = PY_VERSION; + const char *buildinfo = Py_GetBuildInfo(); + const char *compiler = Py_GetCompiler(); + // +1 for the trailing NUL byte + size_t len = (format_len + strlen(version_str) + strlen(buildinfo) + + strlen(compiler) + 1); + + // Always format the static version + PyOS_snprintf(static_version, sizeof(static_version), format, + version_str, buildinfo, compiler); + + heap_version = malloc(len); + if (heap_version == NULL) { + // If malloc() failed, don't set initialized to 1, so next + // Py_GetVersion() will try again to allocate memory. + return _PyStatus_NO_MEMORY(); + } + + PyOS_snprintf(heap_version, len, format, + version_str, buildinfo, compiler); + initialized = 1; + return _PyStatus_OK(); +} + +void +_Py_GetVersion_Fini(void) +{ + if (heap_version) { + free(heap_version); + heap_version = NULL; + } } const char * Py_GetVersion(void) { - _Py_InitVersion(); - return version; + PyStatus status = _Py_GetVersion_Init(); + // Ignore error: Py_GetVersion() API cannot report error + (void)status; + + if (heap_version) { + return heap_version; + } + else { + return static_version; + } } // Export the Python hex version as a constant. diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 8888f523559123..ba1b94c47984ab 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -559,7 +559,11 @@ pycore_init_runtime(_PyRuntimeState *runtime, */ _PyRuntimeState_SetFinalizing(runtime, NULL); - _Py_InitVersion(); + status = _Py_GetVersion_Init(); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + _Py_DumpTraceback_Init(); status = _Py_HashRandomization_Init(config); From ef0a101daa1da6ef5d5bb3b4f86e2c37bf388d86 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Oct 2026 22:53:34 +0200 Subject: [PATCH 2/2] Fix make check-c-globals --- Tools/c-analyzer/cpython/ignored.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 2ad801c671855c..220e84ef1f60c2 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -48,7 +48,8 @@ Objects/unicodeobject.c _init_global_state initialized - Modules/getbuildinfo.c - buildinfo - Modules/getbuildinfo.c - initialized - Python/getversion.c - initialized - -Python/getversion.c - version - +Python/getversion.c - static_version - +Python/getversion.c - heap_version - ## public C-API - set during first init Python/bootstrap_hash.c - _Py_HashSecret_Initialized -