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]