Skip to content

Commit c937f76

Browse files
committed
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.
1 parent b5aa195 commit c937f76

7 files changed

Lines changed: 72 additions & 24 deletions

File tree

‎Doc/c-api/interp-lifecycle.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ Process-wide parameters
514514
Return the version of this Python interpreter. This is a string that looks
515515
something like ::
516516
517-
"3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]"
517+
"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)]"
518518
519519
.. index:: single: version (in module sys)
520520

‎Include/internal/pycore_pylifecycle.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extern int _Py_IsLocaleCoercionTarget(const char *ctype_loc);
2323

2424
/* Various one-time initializers */
2525

26-
extern void _Py_InitVersion(void);
26+
extern PyStatus _Py_GetVersion_Init(void);
2727
extern PyStatus _PyFaulthandler_Init(int enable);
2828
extern PyObject * _PyBuiltin_Init(PyInterpreterState *interp);
2929
extern int _PyBuiltin_InitPythonFunctions(PyObject *dict);
@@ -43,6 +43,7 @@ extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp);
4343

4444
/* Various internal finalizers */
4545

46+
extern void _Py_GetVersion_Fini(void);
4647
extern int _PySignal_Init(int install_signal_handlers);
4748
extern void _PySignal_Fini(void);
4849

‎Lib/test/test_capi/test_build.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,11 @@ def test_getcopyright(self):
2020

2121
# Test Py_GetCompiler()
2222
def test_getcompiler(self):
23-
compiler = _testlimitedcapi.py_getcompiler()
24-
self.assertIsInstance(compiler, str)
25-
self.assertNotEqual(compiler, '')
23+
self.assertIn(_testlimitedcapi.py_getcompiler(), sys.version)
2624

2725
# Test Py_GetBuildInfo()
2826
def test_getbuildinfo(self):
29-
buildinfo = _testlimitedcapi.py_getbuildinfo()
30-
self.assertIsInstance(buildinfo, str)
31-
self.assertNotEqual(buildinfo, '')
27+
self.assertIn(_testlimitedcapi.py_getbuildinfo(), sys.version)
3228

3329
# Test internal _Py_GetBuiltWithAssert()
3430
def test__getbuiltwithassert(self):

‎Modules/main.c‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,7 @@ pymain_free(void)
847847
_PyPathConfig_ClearGlobal();
848848
_Py_ClearArgcArgv();
849849
_PyRuntime_Finalize();
850+
_Py_GetVersion_Fini();
850851
}
851852

852853

‎Python/getcompiler.c‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "Python.h"
55

6+
// PC/pyconfig.h defines _Py_COMPILER
67
#ifdef _Py_COMPILER
78
# define COMPILER _Py_COMPILER
89
#endif
@@ -12,14 +13,14 @@
1213
// Note the __clang__ conditional has to come before the __GNUC__ one because
1314
// clang pretends to be GCC.
1415
#if defined(__clang__)
15-
#define COMPILER "[Clang " __clang_version__ "]"
16+
# define COMPILER "[Clang " __clang_version__ "]"
1617
#elif defined(__GNUC__)
17-
#define COMPILER "[GCC " __VERSION__ "]"
18+
# define COMPILER "[GCC " __VERSION__ "]"
1819
// Generic fallbacks.
1920
#elif defined(__cplusplus)
20-
#define COMPILER "[C++]"
21+
# define COMPILER "[C++]"
2122
#else
22-
#define COMPILER "[C]"
23+
# define COMPILER "[C]"
2324
#endif
2425

2526
#endif /* !COMPILER */

‎Python/getversion.c‎

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,77 @@
22
/* Return the full version string. */
33

44
#include "Python.h"
5+
#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY()
56

6-
#include "patchlevel.h"
77

88
static int initialized = 0;
9-
static char version[300];
9+
// Use the smallest possible buffer: it's only needed if malloc() fails *and*
10+
// Py_GetVersion() is called before Py_Initialize(). The buffer should be big
11+
// enough to store "3.16.0a0 " string.
12+
static char static_version[20];
13+
static char *heap_version = NULL;
1014

11-
void _Py_InitVersion(void)
15+
16+
PyStatus
17+
_Py_GetVersion_Init(void)
1218
{
1319
if (initialized) {
14-
return;
20+
return _PyStatus_OK();
1521
}
16-
initialized = 1;
22+
1723
#ifdef Py_GIL_DISABLED
18-
const char *buildinfo_format = "%.80s free-threading build (%.80s) %.80s";
24+
const char *format = "%s free-threading build (%s) %s";
25+
size_t format_len = strlen(" free-threading build () ");
1926
#else
20-
const char *buildinfo_format = "%.80s (%.80s) %.80s";
27+
const char *format = "%s (%s) %s";
28+
size_t format_len = strlen(" () ");
2129
#endif
22-
PyOS_snprintf(version, sizeof(version), buildinfo_format,
23-
PY_VERSION, Py_GetBuildInfo(), Py_GetCompiler());
30+
const char *version_str = PY_VERSION;
31+
const char *buildinfo = Py_GetBuildInfo();
32+
const char *compiler = Py_GetCompiler();
33+
// +1 for the trailing NUL byte
34+
size_t len = (format_len + strlen(version_str) + strlen(buildinfo)
35+
+ strlen(compiler) + 1);
36+
37+
// Always format the static version
38+
PyOS_snprintf(static_version, sizeof(static_version), format,
39+
version_str, buildinfo, compiler);
40+
41+
heap_version = malloc(len);
42+
if (heap_version == NULL) {
43+
// If malloc() failed, don't set initialized to 1, so next
44+
// Py_GetVersion() will try again to allocate memory.
45+
return _PyStatus_NO_MEMORY();
46+
}
47+
48+
PyOS_snprintf(heap_version, len, format,
49+
version_str, buildinfo, compiler);
50+
initialized = 1;
51+
return _PyStatus_OK();
52+
}
53+
54+
void
55+
_Py_GetVersion_Fini(void)
56+
{
57+
if (heap_version) {
58+
free(heap_version);
59+
heap_version = NULL;
60+
}
2461
}
2562

2663
const char *
2764
Py_GetVersion(void)
2865
{
29-
_Py_InitVersion();
30-
return version;
66+
PyStatus status = _Py_GetVersion_Init();
67+
// Ignore error: Py_GetVersion() API cannot report error
68+
(void)status;
69+
70+
if (heap_version) {
71+
return heap_version;
72+
}
73+
else {
74+
return static_version;
75+
}
3176
}
3277

3378
// Export the Python hex version as a constant.

‎Python/pylifecycle.c‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,11 @@ pycore_init_runtime(_PyRuntimeState *runtime,
559559
*/
560560
_PyRuntimeState_SetFinalizing(runtime, NULL);
561561

562-
_Py_InitVersion();
562+
status = _Py_GetVersion_Init();
563+
if (_PyStatus_EXCEPTION(status)) {
564+
return status;
565+
}
566+
563567
_Py_DumpTraceback_Init();
564568

565569
status = _Py_HashRandomization_Init(config);

0 commit comments

Comments
 (0)