From ace3f9a0ce7b9fe8ae757fdd614f1e7a171f92b0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 10 Nov 2020 21:10:22 +0100 Subject: [PATCH] bpo-42260: Fix _PyConfig_Read() if compute_path_config=0 (GH-23220) Fix _PyConfig_Read() if compute_path_config=0: use values set by Py_SetPath(), Py_SetPythonHome() and Py_SetProgramName(). Add compute_path_config parameter to _PyConfig_InitPathConfig(). The following functions now return NULL if called before Py_Initialize(): * Py_GetExecPrefix() * Py_GetPath() * Py_GetPrefix() * Py_GetProgramFullPath() * Py_GetProgramName() * Py_GetPythonHome() These functions no longer automatically computes the Python Path Configuration. Moreover, Py_SetPath() no longer computes program_full_path. --- Doc/c-api/init.rst | 36 ++++++++ Doc/c-api/init_config.rst | 38 ++++---- Doc/whatsnew/3.10.rst | 8 ++ Include/internal/pycore_initconfig.h | 4 +- .../2020-11-10-14-27-39.bpo-42260.-Br3Co.rst | 7 ++ Python/initconfig.c | 4 +- Python/pathconfig.c | 89 ++++--------------- Python/pylifecycle.c | 2 +- 8 files changed, 93 insertions(+), 95 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2020-11-10-14-27-39.bpo-42260.-Br3Co.rst diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 3d18bb3f0b9..5736b83f211 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -376,6 +376,12 @@ Process-wide parameters The returned string points into static storage; the caller should not modify its value. + This function should not be called before :c:func:`Py_Initialize`, otherwise + it returns ``NULL``. + + .. versionchanged:: 3.10 + It now returns ``NULL`` if called before :c:func:`Py_Initialize`. + .. c:function:: wchar_t* Py_GetPrefix() @@ -389,6 +395,12 @@ Process-wide parameters script at build time. The value is available to Python code as ``sys.prefix``. It is only useful on Unix. See also the next function. + This function should not be called before :c:func:`Py_Initialize`, otherwise + it returns ``NULL``. + + .. versionchanged:: 3.10 + It now returns ``NULL`` if called before :c:func:`Py_Initialize`. + .. c:function:: wchar_t* Py_GetExecPrefix() @@ -424,6 +436,12 @@ Process-wide parameters while having :file:`/usr/local/plat` be a different filesystem for each platform. + This function should not be called before :c:func:`Py_Initialize`, otherwise + it returns ``NULL``. + + .. versionchanged:: 3.10 + It now returns ``NULL`` if called before :c:func:`Py_Initialize`. + .. c:function:: wchar_t* Py_GetProgramFullPath() @@ -437,6 +455,12 @@ Process-wide parameters static storage; the caller should not modify its value. The value is available to Python code as ``sys.executable``. + This function should not be called before :c:func:`Py_Initialize`, otherwise + it returns ``NULL``. + + .. versionchanged:: 3.10 + It now returns ``NULL`` if called before :c:func:`Py_Initialize`. + .. c:function:: wchar_t* Py_GetPath() @@ -455,8 +479,14 @@ Process-wide parameters can be (and usually is) modified later to change the search path for loading modules. + This function should not be called before :c:func:`Py_Initialize`, otherwise + it returns ``NULL``. + .. XXX should give the exact rules + .. versionchanged:: 3.10 + It now returns ``NULL`` if called before :c:func:`Py_Initialize`. + .. c:function:: void Py_SetPath(const wchar_t *) @@ -638,6 +668,12 @@ Process-wide parameters :c:func:`Py_SetPythonHome`, or the value of the :envvar:`PYTHONHOME` environment variable if it is set. + This function should not be called before :c:func:`Py_Initialize`, otherwise + it returns ``NULL``. + + .. versionchanged:: 3.10 + It now returns ``NULL`` if called before :c:func:`Py_Initialize`. + .. _threads: diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index edfeba5db7d..db7c1f43765 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -24,11 +24,15 @@ There are two kinds of configuration: environments variables are ignored, the LC_CTYPE locale is left unchanged and no signal handler is registred. +The :c:func:`Py_RunMain` function can be used to write a customized Python +program. + See also :ref:`Initialization, Finalization, and Threads `. .. seealso:: :pep:`587` "Python Initialization Configuration". + Example ======= @@ -532,7 +536,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: wchar_t* base_executable @@ -544,7 +548,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: wchar_t* base_prefix @@ -552,7 +556,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: int buffered_stdio @@ -634,7 +638,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: wchar_t* executable @@ -643,7 +647,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: int faulthandler @@ -726,7 +730,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` input. + Part of the :ref:`Python Path Configuration ` input. .. c:member:: int import_time @@ -817,7 +821,7 @@ PyConfig Default: value of the ``PLATLIBDIR`` macro which is set at configure time by ``--with-platlibdir`` (default: ``"lib"``). - Part of the :ref:`Path Configuration ` input. + Part of the :ref:`Python Path Configuration ` input. .. versionadded:: 3.9 @@ -830,7 +834,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` input. + Part of the :ref:`Python Path Configuration ` input. .. c:member:: PyWideStringList module_search_paths .. c:member:: int module_search_paths_set @@ -838,14 +842,14 @@ PyConfig Module search paths: :data:`sys.path`. If :c:member:`~PyConfig.module_search_paths_set` is equal to 0, the - function calculating the :ref:`Path Configuration ` + function calculating the :ref:`Python Path Configuration ` overrides the :c:member:`~PyConfig.module_search_paths` and sets :c:member:`~PyConfig.module_search_paths_set` to ``1``. Default: empty list (``module_search_paths``) and ``0`` (``module_search_paths_set``). - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: int optimization_level @@ -911,7 +915,7 @@ PyConfig .. c:member:: int pathconfig_warnings - On Unix, if non-zero, calculating the :ref:`Path Configuration + On Unix, if non-zero, calculating the :ref:`Python Path Configuration ` can log warnings into ``stderr``. If equals to 0, suppress these warnings. @@ -919,7 +923,7 @@ PyConfig Default: ``1`` in Python mode, ``0`` in isolated mode. - Part of the :ref:`Path Configuration ` input. + Part of the :ref:`Python Path Configuration ` input. .. c:member:: wchar_t* prefix @@ -928,7 +932,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` output. + Part of the :ref:`Python Path Configuration ` output. .. c:member:: wchar_t* program_name @@ -946,7 +950,7 @@ PyConfig Default: ``NULL``. - Part of the :ref:`Path Configuration ` input. + Part of the :ref:`Python Path Configuration ` input. .. c:member:: wchar_t* pycache_prefix @@ -1262,7 +1266,7 @@ and user site directory. The C standard streams (ex: ``stdout``) and the LC_CTYPE locale are left unchanged. Signal handlers are not installed. Configuration files are still used with this configuration. Set the -:ref:`Path Configuration ` ("output fields") to ignore these +:ref:`Python Path Configuration ` ("output fields") to ignore these configuration files and avoid the function computing the default path configuration. @@ -1287,8 +1291,8 @@ and :ref:`Python UTF-8 Mode ` .. _init-path-config: -Path Configuration -================== +Python Path Configuration +========================= :c:type:`PyConfig` contains multiple fields for the path configuration: diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 95cdb06f997..76e11f0ddf0 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -493,6 +493,14 @@ Porting to Python 3.10 ``unicodedata.ucnhash_CAPI`` has been moved to the internal C API. (Contributed by Victor Stinner in :issue:`42157`.) +* :c:func:`Py_GetPath`, :c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`, + :c:func:`Py_GetProgramFullPath`, :c:func:`Py_GetPythonHome` and + :c:func:`Py_GetProgramName` functions now return ``NULL`` if called before + :c:func:`Py_Initialize` (before Python is initialized). Use the new + :ref:`Python Initialization Configuration API ` to get the + :ref:`Python Path Configuration. `. + (Contributed by Victor Stinner in :issue:`42260`.) + Deprecated ---------- diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index d8400b1c42e..28cd57030e2 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -151,7 +151,9 @@ PyAPI_FUNC(void) _PyConfig_InitCompatConfig(PyConfig *config); extern PyStatus _PyConfig_Copy( PyConfig *config, const PyConfig *config2); -extern PyStatus _PyConfig_InitPathConfig(PyConfig *config); +extern PyStatus _PyConfig_InitPathConfig( + PyConfig *config, + int compute_path_config); extern PyStatus _PyConfig_Read(PyConfig *config, int compute_path_config); extern PyStatus _PyConfig_Write(const PyConfig *config, struct pyruntimestate *runtime); diff --git a/Misc/NEWS.d/next/C API/2020-11-10-14-27-39.bpo-42260.-Br3Co.rst b/Misc/NEWS.d/next/C API/2020-11-10-14-27-39.bpo-42260.-Br3Co.rst new file mode 100644 index 00000000000..e7b5a558fd4 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2020-11-10-14-27-39.bpo-42260.-Br3Co.rst @@ -0,0 +1,7 @@ +:c:func:`Py_GetPath`, :c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`, +:c:func:`Py_GetProgramFullPath`, :c:func:`Py_GetPythonHome` and +:c:func:`Py_GetProgramName` functions now return ``NULL`` if called before +:c:func:`Py_Initialize` (before Python is initialized). Use the new +:ref:`Python Initialization Configuration API ` to get the +:ref:`Python Path Configuration. `. Patch by Victor +Stinner. diff --git a/Python/initconfig.c b/Python/initconfig.c index 11db4a3ef59..1fcc3600aa4 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -2069,8 +2069,8 @@ config_read(PyConfig *config, int compute_path_config) } } - if (compute_path_config && config->_install_importlib) { - status = _PyConfig_InitPathConfig(config); + if (config->_install_importlib) { + status = _PyConfig_InitPathConfig(config, compute_path_config); if (_PyStatus_EXCEPTION(status)) { return status; } diff --git a/Python/pathconfig.c b/Python/pathconfig.c index 12a684a66b7..470aba75bea 100644 --- a/Python/pathconfig.c +++ b/Python/pathconfig.c @@ -332,7 +332,8 @@ config_init_module_search_paths(PyConfig *config, _PyPathConfig *pathconfig) - _PyPathConfig_Calculate() */ static PyStatus -pathconfig_calculate(_PyPathConfig *pathconfig, const PyConfig *config) +pathconfig_init(_PyPathConfig *pathconfig, const PyConfig *config, + int compute_path_config) { PyStatus status; @@ -349,12 +350,9 @@ pathconfig_calculate(_PyPathConfig *pathconfig, const PyConfig *config) goto done; } - if (_Py_path_config.module_search_path == NULL) { + if (compute_path_config) { status = _PyPathConfig_Calculate(pathconfig, config); } - else { - /* Py_SetPath() has been called: avoid _PyPathConfig_Calculate() */ - } done: PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); @@ -363,17 +361,19 @@ done: static PyStatus -config_calculate_pathconfig(PyConfig *config) +config_init_pathconfig(PyConfig *config, int compute_path_config) { _PyPathConfig pathconfig = _PyPathConfig_INIT; PyStatus status; - status = pathconfig_calculate(&pathconfig, config); + status = pathconfig_init(&pathconfig, config, compute_path_config); if (_PyStatus_EXCEPTION(status)) { goto done; } - if (!config->module_search_paths_set) { + if (!config->module_search_paths_set + && pathconfig.module_search_path != NULL) + { status = config_init_module_search_paths(config, &pathconfig); if (_PyStatus_EXCEPTION(status)) { goto done; @@ -381,7 +381,7 @@ config_calculate_pathconfig(PyConfig *config) } #define COPY_ATTR(PATH_ATTR, CONFIG_ATTR) \ - if (config->CONFIG_ATTR == NULL) { \ + if (config->CONFIG_ATTR == NULL && pathconfig.PATH_ATTR != NULL) { \ if (copy_wstr(&config->CONFIG_ATTR, pathconfig.PATH_ATTR) < 0) { \ goto no_memory; \ } \ @@ -427,7 +427,7 @@ done: PyStatus -_PyConfig_InitPathConfig(PyConfig *config) +_PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) { /* Do we need to calculate the path? */ if (!config->module_search_paths_set @@ -435,26 +435,26 @@ _PyConfig_InitPathConfig(PyConfig *config) || config->prefix == NULL || config->exec_prefix == NULL) { - PyStatus status = config_calculate_pathconfig(config); + PyStatus status = config_init_pathconfig(config, compute_path_config); if (_PyStatus_EXCEPTION(status)) { return status; } } - if (config->base_prefix == NULL) { + if (config->base_prefix == NULL && config->prefix != NULL) { if (copy_wstr(&config->base_prefix, config->prefix) < 0) { return _PyStatus_NO_MEMORY(); } } - if (config->base_exec_prefix == NULL) { + if (config->base_exec_prefix == NULL && config->exec_prefix != NULL) { if (copy_wstr(&config->base_exec_prefix, config->exec_prefix) < 0) { return _PyStatus_NO_MEMORY(); } } - if (config->base_executable == NULL) { + if (config->base_executable == NULL && config->executable != NULL) { if (copy_wstr(&config->base_executable, config->executable) < 0) { return _PyStatus_NO_MEMORY(); @@ -465,53 +465,6 @@ _PyConfig_InitPathConfig(PyConfig *config) } -static PyStatus -pathconfig_global_read(_PyPathConfig *pathconfig) -{ - PyConfig config; - _PyConfig_InitCompatConfig(&config); - - /* Call _PyConfig_InitPathConfig() */ - PyStatus status = PyConfig_Read(&config); - if (_PyStatus_EXCEPTION(status)) { - goto done; - } - - status = pathconfig_set_from_config(pathconfig, &config); - -done: - PyConfig_Clear(&config); - return status; -} - - -static void -pathconfig_global_init(void) -{ - PyStatus status; - - if (_Py_path_config.module_search_path == NULL) { - status = pathconfig_global_read(&_Py_path_config); - if (_PyStatus_EXCEPTION(status)) { - Py_ExitStatusException(status); - } - } - else { - /* Global configuration already initialized */ - } - - assert(_Py_path_config.program_full_path != NULL); - assert(_Py_path_config.prefix != NULL); - assert(_Py_path_config.exec_prefix != NULL); - assert(_Py_path_config.module_search_path != NULL); - assert(_Py_path_config.program_name != NULL); - /* home can be NULL */ -#ifdef MS_WINDOWS - assert(_Py_path_config.base_executable != NULL); -#endif -} - - /* External interface */ static void _Py_NO_RETURN @@ -531,23 +484,17 @@ Py_SetPath(const wchar_t *path) PyMemAllocatorEx old_alloc; _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - /* Getting the program full path calls pathconfig_global_init() */ - wchar_t *program_full_path = _PyMem_RawWcsdup(Py_GetProgramFullPath()); - - PyMem_RawFree(_Py_path_config.program_full_path); PyMem_RawFree(_Py_path_config.prefix); PyMem_RawFree(_Py_path_config.exec_prefix); PyMem_RawFree(_Py_path_config.module_search_path); - _Py_path_config.program_full_path = program_full_path; _Py_path_config.prefix = _PyMem_RawWcsdup(L""); _Py_path_config.exec_prefix = _PyMem_RawWcsdup(L""); _Py_path_config.module_search_path = _PyMem_RawWcsdup(path); PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - if (_Py_path_config.program_full_path == NULL - || _Py_path_config.prefix == NULL + if (_Py_path_config.prefix == NULL || _Py_path_config.exec_prefix == NULL || _Py_path_config.module_search_path == NULL) { @@ -621,7 +568,6 @@ _Py_SetProgramFullPath(const wchar_t *program_full_path) wchar_t * Py_GetPath(void) { - pathconfig_global_init(); return _Py_path_config.module_search_path; } @@ -629,7 +575,6 @@ Py_GetPath(void) wchar_t * Py_GetPrefix(void) { - pathconfig_global_init(); return _Py_path_config.prefix; } @@ -637,7 +582,6 @@ Py_GetPrefix(void) wchar_t * Py_GetExecPrefix(void) { - pathconfig_global_init(); return _Py_path_config.exec_prefix; } @@ -645,7 +589,6 @@ Py_GetExecPrefix(void) wchar_t * Py_GetProgramFullPath(void) { - pathconfig_global_init(); return _Py_path_config.program_full_path; } @@ -653,7 +596,6 @@ Py_GetProgramFullPath(void) wchar_t* Py_GetPythonHome(void) { - pathconfig_global_init(); return _Py_path_config.home; } @@ -661,7 +603,6 @@ Py_GetPythonHome(void) wchar_t * Py_GetProgramName(void) { - pathconfig_global_init(); return _Py_path_config.program_name; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 609e0a42e4d..93bce49b63d 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1039,7 +1039,7 @@ init_interp_main(PyThreadState *tstate) } // Compute the path configuration - status = _PyConfig_InitPathConfig(&interp->config); + status = _PyConfig_InitPathConfig(&interp->config, 1); if (_PyStatus_EXCEPTION(status)) { return status; }