diff --git a/Doc/c-api/apiabiversion.rst b/Doc/c-api/apiabiversion.rst index b8a8f2ff886219..b5910bbb44d50e 100644 --- a/Doc/c-api/apiabiversion.rst +++ b/Doc/c-api/apiabiversion.rst @@ -6,9 +6,11 @@ API and ABI Versioning *********************** -``PY_VERSION_HEX`` is the Python version number encoded in a single integer. +.. c:macro:: PY_VERSION_HEX -For example if the ``PY_VERSION_HEX`` is set to ``0x030401a2``, the underlying + ``PY_VERSION_HEX`` is the Python version number encoded in a single integer. + +For example if ``PY_VERSION_HEX`` is set to ``0x030401a2``, the underlying version information can be found by treating it as a 32 bit number in the following manner: diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 4c77d5d1e0e827..69b9c0fae8c5fb 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -194,8 +194,8 @@ PyPreConfig * Configure the LC_CTYPE locale * Set the UTF-8 mode - The :c:member:`struct_size` field must be explicitly initialized to - ``sizeof(PyPreConfig)``. + The :c:member:`header_version` field must be explicitly initialized to + :c:macro:`PY_VERSION_HEX`. Function to initialize a preconfiguration: @@ -274,10 +274,10 @@ PyPreConfig same way the regular Python parses command line arguments: see :ref:`Command Line Arguments `. - .. c:member:: size_t struct_size + .. c:member:: size_t header_version - Size of the structure in bytes: must be initialized to - ``sizeof(PyPreConfig)``. + The version of the CPython headers used to build the embedding + application. Must be initialized to :c:macro:`PY_VERSION_HEX`. Field used for API and ABI compatibility. @@ -332,7 +332,7 @@ Example using the preinitialization to enable the UTF-8 Mode:: PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitPythonConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -360,8 +360,8 @@ PyConfig Structure containing most parameters to configure Python. - The :c:member:`struct_size` field must be explicitly initialized to - ``sizeof(PyConfig)``. + The :c:member:`header_version` field must be explicitly initialized to + :c:macro:`PY_VERSION_HEX`. Structure methods: @@ -679,10 +679,10 @@ PyConfig Encoding and encoding errors of :data:`sys.stdin`, :data:`sys.stdout` and :data:`sys.stderr`. - .. c:member:: size_t struct_size + .. c:member:: size_t header_version - Size of the structure in bytes: must be initialized to - ``sizeof(PyConfig)``. + The version of the CPython headers used to build the embedding + application. Must be initialized to :c:macro:`PY_VERSION_HEX`. Field used for API and ABI compatibility. @@ -754,7 +754,7 @@ Example setting the program name:: { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -787,7 +787,7 @@ configuration, and then override some parameters:: { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -875,7 +875,7 @@ Example of customized Python always running in isolated mode:: { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1067,7 +1067,7 @@ phases:: { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 8ce5622c5c4039..ce0e736aeb9740 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -45,9 +45,10 @@ PyAPI_FUNC(PyStatus) PyWideStringList_Insert(PyWideStringList *list, /* --- PyPreConfig ----------------------------------------------- */ typedef struct { - /* Size of the structure in bytes: must be initialized to - sizeof(PyPreConfig). Field used for API and ABI compatibility. */ - size_t struct_size; + /* Version of the CPython header files used to compile the embedding + application. Expected to be set to PY_VERSION_HEX. + Field is used to check for API and ABI compatibility. */ + uint32_t header_version; int _config_init; /* _PyConfigInitEnum value */ @@ -131,9 +132,10 @@ PyAPI_FUNC(PyStatus) PyPreConfig_InitIsolatedConfig(PyPreConfig *config); /* --- PyConfig ---------------------------------------------- */ typedef struct { - /* Size of the structure in bytes: must be initialized to - sizeof(PyConfig). Field used for API and ABI compatibility. */ - size_t struct_size; + /* Version of the CPython header files used to compile the embedding + application. Expected to be set to PY_VERSION_HEX. + Field is used to check for API and ABI compatibility. */ + uint32_t header_version; int _config_init; /* _PyConfigInitEnum value */ diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index eb6490f5a87f06..7cd712fdf794f3 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -87,6 +87,8 @@ PyAPI_FUNC(void) _Py_get_env_flag( int *flag, const char *name); +PyAPI_FUNC(PyStatus) _Py_CheckVersionCompat(uint32_t header_version); + /* Py_GetArgcArgv() helper */ PyAPI_FUNC(void) _Py_ClearArgcArgv(void); diff --git a/Misc/NEWS.d/next/C API/2019-09-30-03-43-27.bpo-38326.RqHAwd.rst b/Misc/NEWS.d/next/C API/2019-09-30-03-43-27.bpo-38326.RqHAwd.rst new file mode 100644 index 00000000000000..6ca80dde03ee1e --- /dev/null +++ b/Misc/NEWS.d/next/C API/2019-09-30-03-43-27.bpo-38326.RqHAwd.rst @@ -0,0 +1,5 @@ +Replaced the ``struct_size`` field in :c:type:`PyPreConfig` and +:c:type:`PyConfig` with a ``header_version`` field (accepting +``PY_VERSION_HEX``). This allows the config functions to detect all violations +of the full API/ABI compatibility expectations by embedding applications, not +just changes to the size of the configuration structures themselves. diff --git a/Modules/main.c b/Modules/main.c index ef6c66a8dbe57d..4927a9ffbcbd90 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -53,7 +53,7 @@ pymain_init(const _PyArgv *args) #endif PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitPythonConfig(&preconfig); if (_PyStatus_EXCEPTION(status)) { @@ -66,7 +66,7 @@ pymain_init(const _PyArgv *args) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (_PyStatus_EXCEPTION(status)) { goto done; diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp index 2656d188c250a8..adccb40439117f 100644 --- a/PC/python_uwp.cpp +++ b/PC/python_uwp.cpp @@ -167,10 +167,10 @@ wmain(int argc, wchar_t **argv) PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; const wchar_t *moduleName = NULL; const wchar_t *p = wcsrchr(argv[0], L'\\'); diff --git a/Programs/_freeze_importlib.c b/Programs/_freeze_importlib.c index 7c494c2786c058..a3c01d49157510 100644 --- a/Programs/_freeze_importlib.c +++ b/Programs/_freeze_importlib.c @@ -78,7 +78,7 @@ main(int argc, char *argv[]) PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitIsolatedConfig(&config); if (PyStatus_Exception(status)) { diff --git a/Programs/_testembed.c b/Programs/_testembed.c index c8600d58f0b4e0..e50a34a045e8a8 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -385,7 +385,7 @@ static int check_init_compat_config(int preinit) if (preinit) { PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = _PyPreConfig_InitCompatConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -399,7 +399,7 @@ static int check_init_compat_config(int preinit) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = _PyConfig_InitCompatConfig(&config); if (PyStatus_Exception(status)) { @@ -426,6 +426,7 @@ static int test_init_compat_config(void) return check_init_compat_config(0); } +// TODO: Add checks that setting a header version of 0x03070000 fails to start static int test_init_global_config(void) { @@ -481,7 +482,7 @@ static int test_init_from_config(void) PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = _PyPreConfig_InitCompatConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -501,7 +502,7 @@ static int test_init_from_config(void) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = _PyConfig_InitCompatConfig(&config); if (PyStatus_Exception(status)) { @@ -638,7 +639,7 @@ static int check_init_parse_argv(int parse_argv) PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -725,7 +726,7 @@ static int test_init_python_env(void) set_all_env_vars(); PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -780,7 +781,7 @@ static int test_init_isolated_flag(void) /* Test PyConfig.isolated=1 */ PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -806,7 +807,7 @@ static int test_preinit_isolated1(void) PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = _PyPreConfig_InitCompatConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -821,7 +822,7 @@ static int test_preinit_isolated1(void) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = _PyConfig_InitCompatConfig(&config); if (PyStatus_Exception(status)) { @@ -843,7 +844,7 @@ static int test_preinit_isolated2(void) PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = _PyPreConfig_InitCompatConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -859,7 +860,7 @@ static int test_preinit_isolated2(void) /* Test PyConfig.isolated=1 */ PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = _PyConfig_InitCompatConfig(&config); if (PyStatus_Exception(status)) { Py_ExitStatusException(status); @@ -883,7 +884,7 @@ static int test_preinit_dont_parse_argv(void) PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitIsolatedConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -905,7 +906,7 @@ static int test_preinit_dont_parse_argv(void) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitIsolatedConfig(&config); if (PyStatus_Exception(status)) { @@ -931,7 +932,7 @@ static int test_preinit_parse_argv(void) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -989,7 +990,7 @@ static int check_preinit_isolated_config(int preinit) if (preinit) { PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitIsolatedConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -1007,7 +1008,7 @@ static int check_preinit_isolated_config(int preinit) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitIsolatedConfig(&config); if (PyStatus_Exception(status)) { @@ -1058,7 +1059,7 @@ static int check_init_python_config(int preinit) if (preinit) { PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitPythonConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -1072,7 +1073,7 @@ static int check_init_python_config(int preinit) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1104,7 +1105,7 @@ static int test_init_dont_configure_locale(void) PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitPythonConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -1121,7 +1122,7 @@ static int test_init_dont_configure_locale(void) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1140,7 +1141,7 @@ static int test_init_dev_mode(void) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1365,7 +1366,7 @@ static int run_audit_run_test(int argc, wchar_t **argv, void *test) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1415,7 +1416,7 @@ static int test_init_read_set(void) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1465,7 +1466,7 @@ static int test_init_sys_add(void) PySys_AddWarnOption(L"ignore:::sysadd_warnoption"); PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; PyStatus status; status = PyConfig_InitPythonConfig(&config); @@ -1535,7 +1536,7 @@ static int test_init_setpath_config(void) { PyStatus status; PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = PY_VERSION_HEX; status = PyPreConfig_InitPythonConfig(&preconfig); if (PyStatus_Exception(status)) { @@ -1564,7 +1565,7 @@ static int test_init_setpath_config(void) putenv("TESTPATH="); PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1612,7 +1613,7 @@ static int test_init_warnoptions(void) PySys_AddWarnOption(L"ignore:::PySys_AddWarnOption2"); PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1680,7 +1681,7 @@ static int test_init_run_main(void) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1697,7 +1698,7 @@ static int test_init_main(void) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { @@ -1729,7 +1730,7 @@ static int test_run_main(void) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { diff --git a/Python/frozenmain.c b/Python/frozenmain.c index 76309e9e5da292..90ce1702d4697e 100644 --- a/Python/frozenmain.c +++ b/Python/frozenmain.c @@ -40,7 +40,7 @@ Py_FrozenMain(int argc, char **argv) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = PyConfig_InitPythonConfig(&config); if (PyStatus_Exception(status)) { PyConfig_Clear(&config); diff --git a/Python/initconfig.c b/Python/initconfig.c index dec4bf2e4d5778..38ddf48707c01c 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -529,17 +529,6 @@ Py_GetArgcArgv(int *argc, wchar_t ***argv) : _PyStatus_NO_MEMORY()) -static PyStatus -config_check_struct_size(const PyConfig *config) -{ - if (config->struct_size != sizeof(PyConfig)) { - return _PyStatus_ERR("unsupported PyConfig structure size " - "(Python version mismatch?)"); - } - return _PyStatus_OK(); -} - - /* Free memory allocated in config, but don't clear all attributes */ void PyConfig_Clear(PyConfig *config) @@ -579,20 +568,75 @@ PyConfig_Clear(PyConfig *config) #undef CLEAR } +// Future enhancement: consider making these public in patchlevel.h +#define _Py_GET_MAJOR_MINOR(hexversion) ((hexversion & 0xFFFF0000) >> 16) +#define _Py_GET_RELEASE_LEVEL(hexversion) ((hexversion & 0x000000F0) >> 4) +#define _Py_HAS_NOMINALLY_FROZEN_ABI(level) ((level == 0xC) || (level == 0xF)) PyStatus -_PyConfig_InitCompatConfig(PyConfig *config) +_Py_CheckVersionCompat(uint32_t header_version) { - size_t struct_size = config->struct_size; - memset(config, 0, sizeof(*config)); - config->struct_size = struct_size; + uint32_t runtime_version = PY_VERSION_HEX; + + // Exact matches are always fine + if (header_version == runtime_version) { + return _PyStatus_OK(); + } + + // Possible future enhancement: support string formatting for init errors - PyStatus status = config_check_struct_size(config); + // Only allow inexact matches for release candidates and final releases + uint8_t header_level = _Py_GET_RELEASE_LEVEL(header_version); + if (!_Py_HAS_NOMINALLY_FROZEN_ABI(header_level)) { + if (header_level != 0xA && header_level != 0xB) { + return _PyStatus_ERR( + "Embedding application failed to specify a valid header " + "release level" + ); + } + return _PyStatus_ERR( + "Embedding applications built against a pre-release API version " + "must be linked to the exact corresponding library version" + ); + } + uint8_t runtime_level = _Py_GET_RELEASE_LEVEL(runtime_version); + if (!_Py_HAS_NOMINALLY_FROZEN_ABI(header_level)) { + return _PyStatus_ERR( + "Embedding applications linked against a pre-release library " + "must be built against the exact corresponding header version" + ); + } + + // Otherwise require that the X.Y part of the version match + if (_Py_GET_MAJOR_MINOR(header_level) != _Py_GET_MAJOR_MINOR(runtime_level)) { + return _PyStatus_ERR( + "Embedding applications built against the headers for Python X.Y.z " + "must be linked against a library in the same Python X.Y series" + ); + } + + return _PyStatus_OK(); +} +#undef _Py_GET_MAJOR_MINOR +#undef _Py_GET_RELEASE_LEVEL +#undef _Py_HAS_FROZEN_RUNTIME_API + +PyStatus +_PyConfig_InitCompatConfig(PyConfig *config) +{ + uint32_t header_version = config->header_version; + PyStatus status = _Py_CheckVersionCompat(header_version); if (_PyStatus_EXCEPTION(status)) { _PyStatus_UPDATE_FUNC(status); return status; } + // Future enhancement: allow old config struct layouts if an old + // header_version is specified + size_t struct_size = sizeof(*config); + memset(config, 0, struct_size); + config->header_version = header_version; + config->_config_init = (int)_PyConfig_INIT_COMPAT; config->isolated = -1; config->use_environment = -1; @@ -775,13 +819,13 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) { PyStatus status; - status = config_check_struct_size(config); + status = _Py_CheckVersionCompat(config->header_version); if (_PyStatus_EXCEPTION(status)) { _PyStatus_UPDATE_FUNC(status); return status; } - status = config_check_struct_size(config2); + status = _Py_CheckVersionCompat(config2->header_version); if (_PyStatus_EXCEPTION(status)) { _PyStatus_UPDATE_FUNC(status); return status; @@ -2280,7 +2324,7 @@ core_read_precmdline(PyConfig *config, _PyPreCmdline *precmdline) } PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = config->header_version; status = _PyPreConfig_InitFromPreConfig(&preconfig, &_PyRuntime.preconfig); if (_PyStatus_EXCEPTION(status)) { @@ -2475,7 +2519,7 @@ PyConfig_Read(PyConfig *config) PyStatus status; PyWideStringList orig_argv = _PyWideStringList_INIT; - status = config_check_struct_size(config); + status = _Py_CheckVersionCompat(config->header_version); if (_PyStatus_EXCEPTION(status)) { _PyStatus_UPDATE_FUNC(status); return status; diff --git a/Python/pathconfig.c b/Python/pathconfig.c index 6886ab7c42d6d7..613877ec589749 100644 --- a/Python/pathconfig.c +++ b/Python/pathconfig.c @@ -434,7 +434,7 @@ pathconfig_global_read(_PyPathConfig *pathconfig) { PyStatus status; PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = _PyConfig_InitCompatConfig(&config); if (_PyStatus_EXCEPTION(status)) { diff --git a/Python/preconfig.c b/Python/preconfig.c index 01c72f5d6bad3b..6b5a0c3504b20e 100644 --- a/Python/preconfig.c +++ b/Python/preconfig.c @@ -268,31 +268,22 @@ _PyPreCmdline_Read(_PyPreCmdline *cmdline, const PyPreConfig *preconfig) /* --- PyPreConfig ----------------------------------------------- */ - -static PyStatus -preconfig_check_struct_size(PyPreConfig *config) -{ - if (config->struct_size != sizeof(PyPreConfig)) { - return _PyStatus_ERR("unsupported PyPreConfig structure size " - "(Python version mismatch?)"); - } - return _PyStatus_OK(); -} - - PyStatus _PyPreConfig_InitCompatConfig(PyPreConfig *config) { - size_t struct_size = config->struct_size; - memset(config, 0, sizeof(*config)); - config->struct_size = struct_size; - - PyStatus status = preconfig_check_struct_size(config); + uint32_t header_version = config->header_version; + PyStatus status = _Py_CheckVersionCompat(header_version); if (_PyStatus_EXCEPTION(status)) { _PyStatus_UPDATE_FUNC(status); return status; } + // Future enhancement: allow old config struct layouts if an old + // header_version is specified + size_t struct_size = sizeof(*config); + memset(config, 0, struct_size); + config->header_version = header_version; + config->_config_init = (int)_PyConfig_INIT_COMPAT; config->parse_argv = 0; config->isolated = -1; @@ -408,7 +399,13 @@ _PyPreConfig_InitFromConfig(PyPreConfig *preconfig, const PyConfig *config) static void preconfig_copy(PyPreConfig *config, const PyPreConfig *config2) { - assert(config->struct_size == sizeof(PyPreConfig)); + + assert( + !_PyStatus_EXCEPTION(_Py_CheckVersionCompat(config->header_version)) + ); + assert( + !_PyStatus_EXCEPTION(_Py_CheckVersionCompat(config2->header_version)) + ); #define COPY_ATTR(ATTR) config->ATTR = config2->ATTR @@ -829,7 +826,7 @@ _PyPreConfig_Read(PyPreConfig *config, const _PyArgv *args) return status; } - status = preconfig_check_struct_size(config); + status = _Py_CheckVersionCompat(config->header_version); if (_PyStatus_EXCEPTION(status)) { _PyStatus_UPDATE_FUNC(status); return status; @@ -849,7 +846,7 @@ _PyPreConfig_Read(PyPreConfig *config, const _PyArgv *args) /* Save the config to be able to restore it if encodings change */ PyPreConfig save_config; - save_config.struct_size = sizeof(PyPreConfig); + save_config.header_version = config->header_version; status = _PyPreConfig_InitFromPreConfig(&save_config, config); if (_PyStatus_EXCEPTION(status)) { @@ -976,7 +973,7 @@ PyStatus _PyPreConfig_Write(const PyPreConfig *src_config) { PyPreConfig config; - config.struct_size = sizeof(PyPreConfig); + config.header_version = PY_VERSION_HEX; PyStatus status = _PyPreConfig_InitFromPreConfig(&config, src_config); if (_PyStatus_EXCEPTION(status)) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index ea0d7a5ee2b943..0a71c166b315d8 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -735,7 +735,7 @@ _Py_PreInitializeFromPyArgv(const PyPreConfig *src_config, const _PyArgv *args) runtime->preinitializing = 1; PyPreConfig config; - config.struct_size = sizeof(PyPreConfig); + config.header_version = src_config->header_version; status = _PyPreConfig_InitFromPreConfig(&config, src_config); if (_PyStatus_EXCEPTION(status)) { @@ -799,7 +799,7 @@ _Py_PreInitializeFromConfig(const PyConfig *config, } PyPreConfig preconfig; - preconfig.struct_size = sizeof(PyPreConfig); + preconfig.header_version = config->header_version; status = _PyPreConfig_InitFromConfig(&preconfig, config); if (_PyStatus_EXCEPTION(status)) { @@ -852,7 +852,7 @@ pyinit_core(_PyRuntimeState *runtime, } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = src_config->header_version; status = _PyConfig_InitCompatConfig(&config); if (_PyStatus_EXCEPTION(status)) { @@ -1079,7 +1079,7 @@ Py_InitializeEx(int install_sigs) } PyConfig config; - config.struct_size = sizeof(PyConfig); + config.header_version = PY_VERSION_HEX; status = _PyConfig_InitCompatConfig(&config); if (_PyStatus_EXCEPTION(status)) { diff --git a/Python/pystate.c b/Python/pystate.c index 0f0cb229955778..f58b87e352bf0d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -61,7 +61,7 @@ _PyRuntimeState_Init_impl(_PyRuntimeState *runtime) _PyGC_Initialize(&runtime->gc); _PyEval_Initialize(&runtime->ceval); - runtime->preconfig.struct_size = sizeof(PyPreConfig); + runtime->preconfig.header_version = PY_VERSION_HEX; PyStatus status = PyPreConfig_InitPythonConfig(&runtime->preconfig); if (_PyStatus_EXCEPTION(status)) { return status; @@ -209,7 +209,10 @@ PyInterpreterState_New(void) memset(interp, 0, sizeof(*interp)); interp->id_refcount = -1; - interp->config.struct_size = sizeof(PyConfig); + // Note: If "header_version" is ever used for more than just an initial + // interpreter config time compatibility check, then the API/ABI version + // from the parent interpreter should passed down here + interp->config.header_version = PY_VERSION_HEX; PyStatus status = PyConfig_InitPythonConfig(&interp->config); if (_PyStatus_EXCEPTION(status)) { /* Don't report status to caller: PyConfig_InitPythonConfig()