https://github.com/python/cpython/commit/f5e913c7f710794421e48437f45a65f190830cbf
commit: f5e913c7f710794421e48437f45a65f190830cbf
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-03T15:15:08+02:00
summary:

gh-158445: Allocate memory in the heap in Py_GetVersion() (#158608)

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.

files:
M Doc/c-api/interp-lifecycle.rst
M Include/internal/pycore_pylifecycle.h
M Lib/test/test_capi/test_build.py
M Modules/main.c
M Python/getcompiler.c
M Python/getversion.c
M Python/pylifecycle.c
M Tools/c-analyzer/cpython/ignored.tsv

diff --git a/Doc/c-api/interp-lifecycle.rst b/Doc/c-api/interp-lifecycle.rst
index efd208263bdf339..226fb8fb4b14b3e 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 bfc94e3e8529b75..99d09c5e0d4e113 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 8319ad1ec77da81..d45c9dbc1c58825 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 44a025d500e35e2..097c10504d69a36 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 cc56ad8c8955513..baa03679d79fb84 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 8d8bc6ea70048c9..5c05b841de6fc84 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 8888f523559123a..ba1b94c47984ab6 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);
diff --git a/Tools/c-analyzer/cpython/ignored.tsv 
b/Tools/c-analyzer/cpython/ignored.tsv
index 2ad801c671855cf..220e84ef1f60c2c 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      -

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to