mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	bpo-33042: Fix pre-initialization sys module configuration (GH-6157)
- new test case for pre-initialization of sys.warnoptions and sys._xoptions - restored ability to call these APIs prior to Py_Initialize - updated the docs for the affected APIs to make it clear they can be called before Py_Initialize - also enhanced the existing embedding test cases to check for expected settings in the sys module
This commit is contained in:
		
							parent
							
								
									d02ac25ab0
								
							
						
					
					
						commit
						bc77eff8b9
					
				
					 7 changed files with 296 additions and 12 deletions
				
			
		|  | @ -31,6 +31,9 @@ The following functions can be safely called before Python is initialized: | ||||||
|   * :c:func:`Py_SetProgramName` |   * :c:func:`Py_SetProgramName` | ||||||
|   * :c:func:`Py_SetPythonHome` |   * :c:func:`Py_SetPythonHome` | ||||||
|   * :c:func:`Py_SetStandardStreamEncoding` |   * :c:func:`Py_SetStandardStreamEncoding` | ||||||
|  |   * :c:func:`PySys_AddWarnOption` | ||||||
|  |   * :c:func:`PySys_AddXOption` | ||||||
|  |   * :c:func:`PySys_ResetWarnOptions` | ||||||
| 
 | 
 | ||||||
| * Informative functions: | * Informative functions: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -205,16 +205,24 @@ accessible to C code.  They all work with the current interpreter thread's | ||||||
| 
 | 
 | ||||||
| .. c:function:: void PySys_ResetWarnOptions() | .. c:function:: void PySys_ResetWarnOptions() | ||||||
| 
 | 
 | ||||||
|    Reset :data:`sys.warnoptions` to an empty list. |    Reset :data:`sys.warnoptions` to an empty list. This function may be | ||||||
|  |    called prior to :c:func:`Py_Initialize`. | ||||||
| 
 | 
 | ||||||
| .. c:function:: void PySys_AddWarnOption(const wchar_t *s) | .. c:function:: void PySys_AddWarnOption(const wchar_t *s) | ||||||
| 
 | 
 | ||||||
|    Append *s* to :data:`sys.warnoptions`. |    Append *s* to :data:`sys.warnoptions`. This function must be called prior | ||||||
|  |    to :c:func:`Py_Initialize` in order to affect the warnings filter list. | ||||||
| 
 | 
 | ||||||
| .. c:function:: void PySys_AddWarnOptionUnicode(PyObject *unicode) | .. c:function:: void PySys_AddWarnOptionUnicode(PyObject *unicode) | ||||||
| 
 | 
 | ||||||
|    Append *unicode* to :data:`sys.warnoptions`. |    Append *unicode* to :data:`sys.warnoptions`. | ||||||
| 
 | 
 | ||||||
|  |    Note: this function is not currently usable from outside the CPython | ||||||
|  |    implementation, as it must be called prior to the implicit import of | ||||||
|  |    :mod:`warnings` in :c:func:`Py_Initialize` to be effective, but can't be | ||||||
|  |    called until enough of the runtime has been initialized to permit the | ||||||
|  |    creation of Unicode objects. | ||||||
|  | 
 | ||||||
| .. c:function:: void PySys_SetPath(const wchar_t *path) | .. c:function:: void PySys_SetPath(const wchar_t *path) | ||||||
| 
 | 
 | ||||||
|    Set :data:`sys.path` to a list object of paths found in *path* which should |    Set :data:`sys.path` to a list object of paths found in *path* which should | ||||||
|  | @ -260,7 +268,8 @@ accessible to C code.  They all work with the current interpreter thread's | ||||||
| .. c:function:: void PySys_AddXOption(const wchar_t *s) | .. c:function:: void PySys_AddXOption(const wchar_t *s) | ||||||
| 
 | 
 | ||||||
|    Parse *s* as a set of :option:`-X` options and add them to the current |    Parse *s* as a set of :option:`-X` options and add them to the current | ||||||
|    options mapping as returned by :c:func:`PySys_GetXOptions`. |    options mapping as returned by :c:func:`PySys_GetXOptions`. This function | ||||||
|  |    may be called prior to :c:func:`Py_Initialize`. | ||||||
| 
 | 
 | ||||||
|    .. versionadded:: 3.2 |    .. versionadded:: 3.2 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -951,6 +951,14 @@ Build and C API Changes | ||||||
|   second argument is *NULL* and the :c:type:`wchar_t*` string contains null |   second argument is *NULL* and the :c:type:`wchar_t*` string contains null | ||||||
|   characters.  (Contributed by Serhiy Storchaka in :issue:`30708`.) |   characters.  (Contributed by Serhiy Storchaka in :issue:`30708`.) | ||||||
| 
 | 
 | ||||||
|  | - Changes to the startup sequence and the management of dynamic memory | ||||||
|  |   allocators mean that the long documented requirement to call | ||||||
|  |   :c:func:`Py_Initialize` before calling most C API functions is now | ||||||
|  |   relied on more heavily, and failing to abide by it may lead to segfaults in | ||||||
|  |   embedding applications. See the :ref:`porting-to-python-37` section in this | ||||||
|  |   document and the :ref:`pre-init-safe` section in the C API documentation | ||||||
|  |   for more details. | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| Other CPython Implementation Changes | Other CPython Implementation Changes | ||||||
| ==================================== | ==================================== | ||||||
|  | @ -1098,6 +1106,7 @@ API and Feature Removals | ||||||
|   ``asyncio._overlapped``. Replace ``from asyncio import selectors`` with |   ``asyncio._overlapped``. Replace ``from asyncio import selectors`` with | ||||||
|   ``import selectors`` for example. |   ``import selectors`` for example. | ||||||
| 
 | 
 | ||||||
|  | .. _porting-to-python-37: | ||||||
| 
 | 
 | ||||||
| Porting to Python 3.7 | Porting to Python 3.7 | ||||||
| ===================== | ===================== | ||||||
|  | @ -1282,14 +1291,24 @@ Other CPython implementation changes | ||||||
| ------------------------------------ | ------------------------------------ | ||||||
| 
 | 
 | ||||||
| * In preparation for potential future changes to the public CPython runtime | * In preparation for potential future changes to the public CPython runtime | ||||||
|   initialization API (see :pep:`432` for details), CPython's internal startup |   initialization API (see :pep:`432` for an initial, but somewhat outdated, | ||||||
|  |   draft), CPython's internal startup | ||||||
|   and configuration management logic has been significantly refactored. While |   and configuration management logic has been significantly refactored. While | ||||||
|   these updates are intended to be entirely transparent to both embedding |   these updates are intended to be entirely transparent to both embedding | ||||||
|   applications and users of the regular CPython CLI, they're being mentioned |   applications and users of the regular CPython CLI, they're being mentioned | ||||||
|   here as the refactoring changes the internal order of various operations |   here as the refactoring changes the internal order of various operations | ||||||
|   during interpreter startup, and hence may uncover previously latent defects, |   during interpreter startup, and hence may uncover previously latent defects, | ||||||
|   either in embedding applications, or in CPython itself. |   either in embedding applications, or in CPython itself. | ||||||
|   (Contributed by Nick Coghlan and Eric Snow as part of :issue:`22257`.) |   (Initially contributed by Nick Coghlan and Eric Snow as part of | ||||||
|  |   :issue:`22257`, and further updated by Nick, Eric, and Victor Stinner in a | ||||||
|  |   number of other issues). Some known details affected: | ||||||
|  | 
 | ||||||
|  |     * :c:func:`PySys_AddWarnOptionUnicode` is not currently usable by embedding | ||||||
|  |       applications due to the requirement to create a Unicode object prior to | ||||||
|  |       calling `Py_Initialize`. Use :c:func:`PySys_AddWarnOption` instead. | ||||||
|  |     * warnings filters added by an embedding application with | ||||||
|  |       :c:func:`PySys_AddWarnOption` should now more consistently take precedence | ||||||
|  |       over the default filters set by the interpreter | ||||||
| 
 | 
 | ||||||
| * Due to changes in the way the default warnings filters are configured, | * Due to changes in the way the default warnings filters are configured, | ||||||
|   setting :c:data:`Py_BytesWarningFlag` to a value greater than one is no longer |   setting :c:data:`Py_BytesWarningFlag` to a value greater than one is no longer | ||||||
|  |  | ||||||
|  | @ -51,7 +51,7 @@ def run_embedded_interpreter(self, *args, env=None): | ||||||
|         if p.returncode != 0 and support.verbose: |         if p.returncode != 0 and support.verbose: | ||||||
|             print(f"--- {cmd} failed ---") |             print(f"--- {cmd} failed ---") | ||||||
|             print(f"stdout:\n{out}") |             print(f"stdout:\n{out}") | ||||||
|             print(f"stderr:\n{out}") |             print(f"stderr:\n{err}") | ||||||
|             print(f"------") |             print(f"------") | ||||||
| 
 | 
 | ||||||
|         self.assertEqual(p.returncode, 0, |         self.assertEqual(p.returncode, 0, | ||||||
|  | @ -83,7 +83,7 @@ def run_repeated_init_and_subinterpreters(self): | ||||||
|         for line in out.splitlines(): |         for line in out.splitlines(): | ||||||
|             if line == "--- Pass {} ---".format(numloops): |             if line == "--- Pass {} ---".format(numloops): | ||||||
|                 self.assertEqual(len(current_run), 0) |                 self.assertEqual(len(current_run), 0) | ||||||
|                 if support.verbose: |                 if support.verbose > 1: | ||||||
|                     print(line) |                     print(line) | ||||||
|                 numloops += 1 |                 numloops += 1 | ||||||
|                 continue |                 continue | ||||||
|  | @ -96,7 +96,7 @@ def run_repeated_init_and_subinterpreters(self): | ||||||
|             # Parse the line from the loop.  The first line is the main |             # Parse the line from the loop.  The first line is the main | ||||||
|             # interpreter and the 3 afterward are subinterpreters. |             # interpreter and the 3 afterward are subinterpreters. | ||||||
|             interp = Interp(*match.groups()) |             interp = Interp(*match.groups()) | ||||||
|             if support.verbose: |             if support.verbose > 1: | ||||||
|                 print(interp) |                 print(interp) | ||||||
|             self.assertTrue(interp.interp) |             self.assertTrue(interp.interp) | ||||||
|             self.assertTrue(interp.tstate) |             self.assertTrue(interp.tstate) | ||||||
|  | @ -190,12 +190,33 @@ def test_forced_io_encoding(self): | ||||||
| 
 | 
 | ||||||
|     def test_pre_initialization_api(self): |     def test_pre_initialization_api(self): | ||||||
|         """ |         """ | ||||||
|         Checks the few parts of the C-API that work before the runtine |         Checks some key parts of the C-API that need to work before the runtine | ||||||
|         is initialized (via Py_Initialize()). |         is initialized (via Py_Initialize()). | ||||||
|         """ |         """ | ||||||
|         env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path)) |         env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path)) | ||||||
|         out, err = self.run_embedded_interpreter("pre_initialization_api", env=env) |         out, err = self.run_embedded_interpreter("pre_initialization_api", env=env) | ||||||
|         self.assertEqual(out, '') |         if sys.platform == "win32": | ||||||
|  |             expected_path = self.test_exe | ||||||
|  |         else: | ||||||
|  |             expected_path = os.path.join(os.getcwd(), "spam") | ||||||
|  |         expected_output = f"sys.executable: {expected_path}\n" | ||||||
|  |         self.assertIn(expected_output, out) | ||||||
|  |         self.assertEqual(err, '') | ||||||
|  | 
 | ||||||
|  |     def test_pre_initialization_sys_options(self): | ||||||
|  |         """ | ||||||
|  |         Checks that sys.warnoptions and sys._xoptions can be set before the | ||||||
|  |         runtime is initialized (otherwise they won't be effective). | ||||||
|  |         """ | ||||||
|  |         env = dict(PYTHONPATH=os.pathsep.join(sys.path)) | ||||||
|  |         out, err = self.run_embedded_interpreter( | ||||||
|  |                         "pre_initialization_sys_options", env=env) | ||||||
|  |         expected_output = ( | ||||||
|  |             "sys.warnoptions: ['once', 'module', 'default']\n" | ||||||
|  |             "sys._xoptions: {'not_an_option': '1', 'also_not_an_option': '2'}\n" | ||||||
|  |             "warnings.filters[:3]: ['default', 'module', 'once']\n" | ||||||
|  |         ) | ||||||
|  |         self.assertIn(expected_output, out) | ||||||
|         self.assertEqual(err, '') |         self.assertEqual(err, '') | ||||||
| 
 | 
 | ||||||
|     def test_bpo20891(self): |     def test_bpo20891(self): | ||||||
|  |  | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | Embedding applications may once again call PySys_ResetWarnOptions, | ||||||
|  | PySys_AddWarnOption, and PySys_AddXOption prior to calling Py_Initialize. | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| #include "pythread.h" | #include "pythread.h" | ||||||
| #include <inttypes.h> | #include <inttypes.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  | #include <wchar.h> | ||||||
| 
 | 
 | ||||||
| /*********************************************************
 | /*********************************************************
 | ||||||
|  * Embedded interpreter tests that need a custom exe |  * Embedded interpreter tests that need a custom exe | ||||||
|  | @ -130,23 +131,89 @@ static int test_forced_io_encoding(void) | ||||||
|  * Test parts of the C-API that work before initialization |  * Test parts of the C-API that work before initialization | ||||||
|  *********************************************************/ |  *********************************************************/ | ||||||
| 
 | 
 | ||||||
|  | /* The pre-initialization tests tend to break by segfaulting, so explicitly
 | ||||||
|  |  * flushed progress messages make the broken API easier to find when they fail. | ||||||
|  |  */ | ||||||
|  | #define _Py_EMBED_PREINIT_CHECK(msg) \ | ||||||
|  |     do {printf(msg); fflush(stdout);} while (0); | ||||||
|  | 
 | ||||||
| static int test_pre_initialization_api(void) | static int test_pre_initialization_api(void) | ||||||
| { | { | ||||||
|     /* Leading "./" ensures getpath.c can still find the standard library */ |     /* Leading "./" ensures getpath.c can still find the standard library */ | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Checking Py_DecodeLocale\n"); | ||||||
|     wchar_t *program = Py_DecodeLocale("./spam", NULL); |     wchar_t *program = Py_DecodeLocale("./spam", NULL); | ||||||
|     if (program == NULL) { |     if (program == NULL) { | ||||||
|         fprintf(stderr, "Fatal error: cannot decode program name\n"); |         fprintf(stderr, "Fatal error: cannot decode program name\n"); | ||||||
|         return 1; |         return 1; | ||||||
|     } |     } | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Checking Py_SetProgramName\n"); | ||||||
|     Py_SetProgramName(program); |     Py_SetProgramName(program); | ||||||
| 
 | 
 | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Initializing interpreter\n"); | ||||||
|     Py_Initialize(); |     Py_Initialize(); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Check sys module contents\n"); | ||||||
|  |     PyRun_SimpleString("import sys; " | ||||||
|  |                        "print('sys.executable:', sys.executable)"); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Finalizing interpreter\n"); | ||||||
|     Py_Finalize(); |     Py_Finalize(); | ||||||
| 
 | 
 | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Freeing memory allocated by Py_DecodeLocale\n"); | ||||||
|     PyMem_RawFree(program); |     PyMem_RawFree(program); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | /* bpo-33042: Ensure embedding apps can predefine sys module options */ | ||||||
|  | static int test_pre_initialization_sys_options(void) | ||||||
|  | { | ||||||
|  |     /* We allocate a couple of the option dynamically, and then delete
 | ||||||
|  |      * them before calling Py_Initialize. This ensures the interpreter isn't | ||||||
|  |      * relying on the caller to keep the passed in strings alive. | ||||||
|  |      */ | ||||||
|  |     wchar_t *static_warnoption = L"once"; | ||||||
|  |     wchar_t *static_xoption = L"also_not_an_option=2"; | ||||||
|  |     size_t warnoption_len = wcslen(static_warnoption); | ||||||
|  |     size_t xoption_len = wcslen(static_xoption); | ||||||
|  |     wchar_t *dynamic_once_warnoption = calloc(warnoption_len+1, sizeof(wchar_t)); | ||||||
|  |     wchar_t *dynamic_xoption = calloc(xoption_len+1, sizeof(wchar_t)); | ||||||
|  |     wcsncpy(dynamic_once_warnoption, static_warnoption, warnoption_len+1); | ||||||
|  |     wcsncpy(dynamic_xoption, static_xoption, xoption_len+1); | ||||||
|  | 
 | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Checking PySys_AddWarnOption\n"); | ||||||
|  |     PySys_AddWarnOption(L"default"); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Checking PySys_ResetWarnOptions\n"); | ||||||
|  |     PySys_ResetWarnOptions(); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Checking PySys_AddWarnOption linked list\n"); | ||||||
|  |     PySys_AddWarnOption(dynamic_once_warnoption); | ||||||
|  |     PySys_AddWarnOption(L"module"); | ||||||
|  |     PySys_AddWarnOption(L"default"); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Checking PySys_AddXOption\n"); | ||||||
|  |     PySys_AddXOption(L"not_an_option=1"); | ||||||
|  |     PySys_AddXOption(dynamic_xoption); | ||||||
|  | 
 | ||||||
|  |     /* Delete the dynamic options early */ | ||||||
|  |     free(dynamic_once_warnoption); | ||||||
|  |     dynamic_once_warnoption = NULL; | ||||||
|  |     free(dynamic_xoption); | ||||||
|  |     dynamic_xoption = NULL; | ||||||
|  | 
 | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Initializing interpreter\n"); | ||||||
|  |     _testembed_Py_Initialize(); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Check sys module contents\n"); | ||||||
|  |     PyRun_SimpleString("import sys; " | ||||||
|  |                        "print('sys.warnoptions:', sys.warnoptions); " | ||||||
|  |                        "print('sys._xoptions:', sys._xoptions); " | ||||||
|  |                        "warnings = sys.modules['warnings']; " | ||||||
|  |                        "latest_filters = [f[0] for f in warnings.filters[:3]]; " | ||||||
|  |                        "print('warnings.filters[:3]:', latest_filters)"); | ||||||
|  |     _Py_EMBED_PREINIT_CHECK("Finalizing interpreter\n"); | ||||||
|  |     Py_Finalize(); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* bpo-20891: Avoid race condition when initialising the GIL */ | ||||||
| static void bpo20891_thread(void *lockp) | static void bpo20891_thread(void *lockp) | ||||||
| { | { | ||||||
|     PyThread_type_lock lock = *((PyThread_type_lock*)lockp); |     PyThread_type_lock lock = *((PyThread_type_lock*)lockp); | ||||||
|  | @ -217,6 +284,7 @@ static struct TestCase TestCases[] = { | ||||||
|     { "forced_io_encoding", test_forced_io_encoding }, |     { "forced_io_encoding", test_forced_io_encoding }, | ||||||
|     { "repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters }, |     { "repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters }, | ||||||
|     { "pre_initialization_api", test_pre_initialization_api }, |     { "pre_initialization_api", test_pre_initialization_api }, | ||||||
|  |     { "pre_initialization_sys_options", test_pre_initialization_sys_options }, | ||||||
|     { "bpo20891", test_bpo20891 }, |     { "bpo20891", test_bpo20891 }, | ||||||
|     { NULL, NULL } |     { NULL, NULL } | ||||||
| }; | }; | ||||||
|  | @ -232,13 +300,13 @@ int main(int argc, char *argv[]) | ||||||
| 
 | 
 | ||||||
|     /* No match found, or no test name provided, so display usage */ |     /* No match found, or no test name provided, so display usage */ | ||||||
|     printf("Python " PY_VERSION " _testembed executable for embedded interpreter tests\n" |     printf("Python " PY_VERSION " _testembed executable for embedded interpreter tests\n" | ||||||
|            "Normally executed via 'EmbeddingTests' in Lib/test/test_capi.py\n\n" |            "Normally executed via 'EmbeddingTests' in Lib/test/test_embed.py\n\n" | ||||||
|            "Usage: %s TESTNAME\n\nAll available tests:\n", argv[0]); |            "Usage: %s TESTNAME\n\nAll available tests:\n", argv[0]); | ||||||
|     for (struct TestCase *tc = TestCases; tc && tc->name; tc++) { |     for (struct TestCase *tc = TestCases; tc && tc->name; tc++) { | ||||||
|         printf("  %s\n", tc->name); |         printf("  %s\n", tc->name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Non-zero exit code will cause test_capi.py tests to fail.
 |     /* Non-zero exit code will cause test_embed.py tests to fail.
 | ||||||
|        This is intentional. */ |        This is intentional. */ | ||||||
|     return -1; |     return -1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1609,11 +1609,141 @@ list_builtin_module_names(void) | ||||||
|     return list; |     return list; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Pre-initialization support for sys.warnoptions and sys._xoptions
 | ||||||
|  |  * | ||||||
|  |  * Modern internal code paths: | ||||||
|  |  *   These APIs get called after _Py_InitializeCore and get to use the | ||||||
|  |  *   regular CPython list, dict, and unicode APIs. | ||||||
|  |  * | ||||||
|  |  * Legacy embedding code paths: | ||||||
|  |  *   The multi-phase initialization API isn't public yet, so embedding | ||||||
|  |  *   apps still need to be able configure sys.warnoptions and sys._xoptions | ||||||
|  |  *   before they call Py_Initialize. To support this, we stash copies of | ||||||
|  |  *   the supplied wchar * sequences in linked lists, and then migrate the | ||||||
|  |  *   contents of those lists to the sys module in _PyInitializeCore. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | struct _preinit_entry { | ||||||
|  |     wchar_t *value; | ||||||
|  |     struct _preinit_entry *next; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct _preinit_entry *_Py_PreInitEntry; | ||||||
|  | 
 | ||||||
|  | static _Py_PreInitEntry _preinit_warnoptions = NULL; | ||||||
|  | static _Py_PreInitEntry _preinit_xoptions = NULL; | ||||||
|  | 
 | ||||||
|  | static _Py_PreInitEntry | ||||||
|  | _alloc_preinit_entry(const wchar_t *value) | ||||||
|  | { | ||||||
|  |     /* To get this to work, we have to initialize the runtime implicitly */ | ||||||
|  |     _PyRuntime_Initialize(); | ||||||
|  | 
 | ||||||
|  |     /* Force default allocator, so we can ensure that it also gets used to
 | ||||||
|  |      * destroy the linked list in _clear_preinit_entries. | ||||||
|  |      */ | ||||||
|  |     PyMemAllocatorEx old_alloc; | ||||||
|  |     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); | ||||||
|  | 
 | ||||||
|  |     _Py_PreInitEntry node = PyMem_RawCalloc(1, sizeof(*node)); | ||||||
|  |     if (node != NULL) { | ||||||
|  |         node->value = _PyMem_RawWcsdup(value); | ||||||
|  |         if (node->value == NULL) { | ||||||
|  |             PyMem_RawFree(node); | ||||||
|  |             node = NULL; | ||||||
|  |         }; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); | ||||||
|  |     return node; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int | ||||||
|  | _append_preinit_entry(_Py_PreInitEntry *optionlist, const wchar_t *value) | ||||||
|  | { | ||||||
|  |     _Py_PreInitEntry new_entry = _alloc_preinit_entry(value); | ||||||
|  |     if (new_entry == NULL) { | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  |     /* We maintain the linked list in this order so it's easy to play back
 | ||||||
|  |      * the add commands in the same order later on in _Py_InitializeCore | ||||||
|  |      */ | ||||||
|  |     _Py_PreInitEntry last_entry = *optionlist; | ||||||
|  |     if (last_entry == NULL) { | ||||||
|  |         *optionlist = new_entry; | ||||||
|  |     } else { | ||||||
|  |         while (last_entry->next != NULL) { | ||||||
|  |             last_entry = last_entry->next; | ||||||
|  |         } | ||||||
|  |         last_entry->next = new_entry; | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | _clear_preinit_entries(_Py_PreInitEntry *optionlist) | ||||||
|  | { | ||||||
|  |     _Py_PreInitEntry current = *optionlist; | ||||||
|  |     *optionlist = NULL; | ||||||
|  |     /* Deallocate the nodes and their contents using the default allocator */ | ||||||
|  |     PyMemAllocatorEx old_alloc; | ||||||
|  |     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); | ||||||
|  |     while (current != NULL) { | ||||||
|  |         _Py_PreInitEntry next = current->next; | ||||||
|  |         PyMem_RawFree(current->value); | ||||||
|  |         PyMem_RawFree(current); | ||||||
|  |         current = next; | ||||||
|  |     } | ||||||
|  |     PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | _clear_all_preinit_options(void) | ||||||
|  | { | ||||||
|  |     _clear_preinit_entries(&_preinit_warnoptions); | ||||||
|  |     _clear_preinit_entries(&_preinit_xoptions); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int | ||||||
|  | _PySys_ReadPreInitOptions(void) | ||||||
|  | { | ||||||
|  |     /* Rerun the add commands with the actual sys module available */ | ||||||
|  |     PyThreadState *tstate = PyThreadState_GET(); | ||||||
|  |     if (tstate == NULL) { | ||||||
|  |         /* Still don't have a thread state, so something is wrong! */ | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  |     _Py_PreInitEntry entry = _preinit_warnoptions; | ||||||
|  |     while (entry != NULL) { | ||||||
|  |         PySys_AddWarnOption(entry->value); | ||||||
|  |         entry = entry->next; | ||||||
|  |     } | ||||||
|  |     entry = _preinit_xoptions; | ||||||
|  |     while (entry != NULL) { | ||||||
|  |         PySys_AddXOption(entry->value); | ||||||
|  |         entry = entry->next; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     _clear_all_preinit_options(); | ||||||
|  |     return 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| static PyObject * | static PyObject * | ||||||
| get_warnoptions(void) | get_warnoptions(void) | ||||||
| { | { | ||||||
|     PyObject *warnoptions = _PySys_GetObjectId(&PyId_warnoptions); |     PyObject *warnoptions = _PySys_GetObjectId(&PyId_warnoptions); | ||||||
|     if (warnoptions == NULL || !PyList_Check(warnoptions)) { |     if (warnoptions == NULL || !PyList_Check(warnoptions)) { | ||||||
|  |         /* PEP432 TODO: we can reach this if warnoptions is NULL in the main
 | ||||||
|  |         *  interpreter config. When that happens, we need to properly set | ||||||
|  |          * the `warnoptions` reference in the main interpreter config as well. | ||||||
|  |          * | ||||||
|  |          * For Python 3.7, we shouldn't be able to get here due to the | ||||||
|  |          * combination of how _PyMainInterpreter_ReadConfig and _PySys_EndInit | ||||||
|  |          * work, but we expect 3.8+ to make the _PyMainInterpreter_ReadConfig | ||||||
|  |          * call optional for embedding applications, thus making this | ||||||
|  |          * reachable again. | ||||||
|  |          */ | ||||||
|         Py_XDECREF(warnoptions); |         Py_XDECREF(warnoptions); | ||||||
|         warnoptions = PyList_New(0); |         warnoptions = PyList_New(0); | ||||||
|         if (warnoptions == NULL) |         if (warnoptions == NULL) | ||||||
|  | @ -1630,6 +1760,12 @@ get_warnoptions(void) | ||||||
| void | void | ||||||
| PySys_ResetWarnOptions(void) | PySys_ResetWarnOptions(void) | ||||||
| { | { | ||||||
|  |     PyThreadState *tstate = PyThreadState_GET(); | ||||||
|  |     if (tstate == NULL) { | ||||||
|  |         _clear_preinit_entries(&_preinit_warnoptions); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     PyObject *warnoptions = _PySys_GetObjectId(&PyId_warnoptions); |     PyObject *warnoptions = _PySys_GetObjectId(&PyId_warnoptions); | ||||||
|     if (warnoptions == NULL || !PyList_Check(warnoptions)) |     if (warnoptions == NULL || !PyList_Check(warnoptions)) | ||||||
|         return; |         return; | ||||||
|  | @ -1658,6 +1794,11 @@ PySys_AddWarnOptionUnicode(PyObject *option) | ||||||
| void | void | ||||||
| PySys_AddWarnOption(const wchar_t *s) | PySys_AddWarnOption(const wchar_t *s) | ||||||
| { | { | ||||||
|  |     PyThreadState *tstate = PyThreadState_GET(); | ||||||
|  |     if (tstate == NULL) { | ||||||
|  |         _append_preinit_entry(&_preinit_warnoptions, s); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|     PyObject *unicode; |     PyObject *unicode; | ||||||
|     unicode = PyUnicode_FromWideChar(s, -1); |     unicode = PyUnicode_FromWideChar(s, -1); | ||||||
|     if (unicode == NULL) |     if (unicode == NULL) | ||||||
|  | @ -1678,6 +1819,16 @@ get_xoptions(void) | ||||||
| { | { | ||||||
|     PyObject *xoptions = _PySys_GetObjectId(&PyId__xoptions); |     PyObject *xoptions = _PySys_GetObjectId(&PyId__xoptions); | ||||||
|     if (xoptions == NULL || !PyDict_Check(xoptions)) { |     if (xoptions == NULL || !PyDict_Check(xoptions)) { | ||||||
|  |         /* PEP432 TODO: we can reach this if xoptions is NULL in the main
 | ||||||
|  |         *  interpreter config. When that happens, we need to properly set | ||||||
|  |          * the `xoptions` reference in the main interpreter config as well. | ||||||
|  |          * | ||||||
|  |          * For Python 3.7, we shouldn't be able to get here due to the | ||||||
|  |          * combination of how _PyMainInterpreter_ReadConfig and _PySys_EndInit | ||||||
|  |          * work, but we expect 3.8+ to make the _PyMainInterpreter_ReadConfig | ||||||
|  |          * call optional for embedding applications, thus making this | ||||||
|  |          * reachable again. | ||||||
|  |          */ | ||||||
|         Py_XDECREF(xoptions); |         Py_XDECREF(xoptions); | ||||||
|         xoptions = PyDict_New(); |         xoptions = PyDict_New(); | ||||||
|         if (xoptions == NULL) |         if (xoptions == NULL) | ||||||
|  | @ -1730,6 +1881,11 @@ _PySys_AddXOptionWithError(const wchar_t *s) | ||||||
| void | void | ||||||
| PySys_AddXOption(const wchar_t *s) | PySys_AddXOption(const wchar_t *s) | ||||||
| { | { | ||||||
|  |     PyThreadState *tstate = PyThreadState_GET(); | ||||||
|  |     if (tstate == NULL) { | ||||||
|  |         _append_preinit_entry(&_preinit_xoptions, s); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|     if (_PySys_AddXOptionWithError(s) < 0) { |     if (_PySys_AddXOptionWithError(s) < 0) { | ||||||
|         /* No return value, therefore clear error state if possible */ |         /* No return value, therefore clear error state if possible */ | ||||||
|         if (_PyThreadState_UncheckedGet()) { |         if (_PyThreadState_UncheckedGet()) { | ||||||
|  | @ -2257,6 +2413,7 @@ _PySys_BeginInit(PyObject **sysmod) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     *sysmod = m; |     *sysmod = m; | ||||||
|  | 
 | ||||||
|     return _Py_INIT_OK(); |     return _Py_INIT_OK(); | ||||||
| 
 | 
 | ||||||
| type_init_failed: | type_init_failed: | ||||||
|  | @ -2333,6 +2490,11 @@ _PySys_EndInit(PyObject *sysdict, _PyMainInterpreterConfig *config) | ||||||
|     if (get_xoptions() == NULL) |     if (get_xoptions() == NULL) | ||||||
|         return -1; |         return -1; | ||||||
| 
 | 
 | ||||||
|  |     /* Transfer any sys.warnoptions and sys._xoptions set directly
 | ||||||
|  |      * by an embedding application from the linked list to the module. */ | ||||||
|  |     if (_PySys_ReadPreInitOptions() != 0) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|     if (PyErr_Occurred()) |     if (PyErr_Occurred()) | ||||||
|         return -1; |         return -1; | ||||||
|     return 0; |     return 0; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Nick Coghlan
						Nick Coghlan