From 6e8ff7554931b0cfd4ae421f99ae3aa71e0c516a Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 1 Oct 2019 02:35:56 +1000 Subject: [PATCH 1/2] bpo-38326: Use Python API version as config version This allows the config APIs to detect attempts to embed all potentially ABI incompatible runtime versions, rather than only being able to detect incompatibilities in the size of the PyPreConfig and PyConfig structures. --- Doc/c-api/apiabiversion.rst | 6 +- Doc/c-api/init_config.rst | 30 +++---- Include/cpython/initconfig.h | 14 ++-- Include/internal/pycore_initconfig.h | 2 + .../2019-09-30-03-43-27.bpo-38326.RqHAwd.rst | 5 ++ Modules/main.c | 4 +- PC/python_uwp.cpp | 4 +- Programs/_freeze_importlib.c | 2 +- Programs/_testembed.c | 61 +++++++------- Python/frozenmain.c | 2 +- Python/initconfig.c | 84 ++++++++++++++----- Python/pathconfig.c | 2 +- Python/preconfig.c | 39 ++++----- Python/pylifecycle.c | 8 +- Python/pystate.c | 5 +- 15 files changed, 161 insertions(+), 107 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2019-09-30-03-43-27.bpo-38326.RqHAwd.rst 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..93eacc005c266d 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,8 @@ PyInterpreterState_New(void) memset(interp, 0, sizeof(*interp)); interp->id_refcount = -1; - interp->config.struct_size = sizeof(PyConfig); + // TODO: Should this pass the API/ABI version from the parent interpreter? + 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() From 0f791bd0b2e2dc6e2e9b3e6d72d083341a3f1e98 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 1 Oct 2019 03:11:02 +1000 Subject: [PATCH 2/2] Clarify a subinterpreter related comment --- Python/pystate.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 93eacc005c266d..f58b87e352bf0d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -209,7 +209,9 @@ PyInterpreterState_New(void) memset(interp, 0, sizeof(*interp)); interp->id_refcount = -1; - // TODO: Should this pass the API/ABI version from the parent interpreter? + // 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)) {