mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-98627: Add an Optional Check for Extension Module Subinterpreter Compatibility (gh-99040)
Enforcing (optionally) the restriction set by PEP 489 makes sense. Furthermore, this sets the stage for a potential restriction related to a per-interpreter GIL. This change includes the following: * add tests for extension module subinterpreter compatibility * add _PyInterpreterConfig.check_multi_interp_extensions * add Py_RTFLAGS_MULTI_INTERP_EXTENSIONS * add _PyImport_CheckSubinterpIncompatibleExtensionAllowed() * fail iff the module does not implement multi-phase init and the current interpreter is configured to check https://github.com/python/cpython/issues/98627
This commit is contained in:
		
							parent
							
								
									3dea4ba6c1
								
							
						
					
					
						commit
						89ac665891
					
				
					 15 changed files with 557 additions and 19 deletions
				
			
		|  | @ -248,6 +248,7 @@ typedef struct { | ||||||
|     int allow_exec; |     int allow_exec; | ||||||
|     int allow_threads; |     int allow_threads; | ||||||
|     int allow_daemon_threads; |     int allow_daemon_threads; | ||||||
|  |     int check_multi_interp_extensions; | ||||||
| } _PyInterpreterConfig; | } _PyInterpreterConfig; | ||||||
| 
 | 
 | ||||||
| #define _PyInterpreterConfig_INIT \ | #define _PyInterpreterConfig_INIT \ | ||||||
|  | @ -256,6 +257,7 @@ typedef struct { | ||||||
|         .allow_exec = 0, \ |         .allow_exec = 0, \ | ||||||
|         .allow_threads = 1, \ |         .allow_threads = 1, \ | ||||||
|         .allow_daemon_threads = 0, \ |         .allow_daemon_threads = 0, \ | ||||||
|  |         .check_multi_interp_extensions = 1, \ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| #define _PyInterpreterConfig_LEGACY_INIT \ | #define _PyInterpreterConfig_LEGACY_INIT \ | ||||||
|  | @ -264,6 +266,7 @@ typedef struct { | ||||||
|         .allow_exec = 1, \ |         .allow_exec = 1, \ | ||||||
|         .allow_threads = 1, \ |         .allow_threads = 1, \ | ||||||
|         .allow_daemon_threads = 1, \ |         .allow_daemon_threads = 1, \ | ||||||
|  |         .check_multi_interp_extensions = 0, \ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| /* --- Helper functions --------------------------------------- */ | /* --- Helper functions --------------------------------------- */ | ||||||
|  |  | ||||||
|  | @ -11,6 +11,9 @@ is available in a given context.  For example, forking the process | ||||||
| might not be allowed in the current interpreter (i.e. os.fork() would fail). | might not be allowed in the current interpreter (i.e. os.fork() would fail). | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
|  | /* Set if import should check a module for subinterpreter support. */ | ||||||
|  | #define Py_RTFLAGS_MULTI_INTERP_EXTENSIONS (1UL << 8) | ||||||
|  | 
 | ||||||
| /* Set if threads are allowed. */ | /* Set if threads are allowed. */ | ||||||
| #define Py_RTFLAGS_THREADS (1UL << 10) | #define Py_RTFLAGS_THREADS (1UL << 10) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -64,6 +64,7 @@ struct _import_state { | ||||||
|     /* override for config->use_frozen_modules (for tests)
 |     /* override for config->use_frozen_modules (for tests)
 | ||||||
|        (-1: "off", 1: "on", 0: no override) */ |        (-1: "off", 1: "on", 0: no override) */ | ||||||
|     int override_frozen_modules; |     int override_frozen_modules; | ||||||
|  |     int override_multi_interp_extensions_check; | ||||||
| #ifdef HAVE_DLOPEN | #ifdef HAVE_DLOPEN | ||||||
|     int dlopenflags; |     int dlopenflags; | ||||||
| #endif | #endif | ||||||
|  | @ -153,6 +154,10 @@ PyAPI_DATA(const struct _frozen *) _PyImport_FrozenStdlib; | ||||||
| PyAPI_DATA(const struct _frozen *) _PyImport_FrozenTest; | PyAPI_DATA(const struct _frozen *) _PyImport_FrozenTest; | ||||||
| extern const struct _module_alias * _PyImport_FrozenAliases; | extern const struct _module_alias * _PyImport_FrozenAliases; | ||||||
| 
 | 
 | ||||||
|  | PyAPI_FUNC(int) _PyImport_CheckSubinterpIncompatibleExtensionAllowed( | ||||||
|  |     const char *name); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| // for testing
 | // for testing
 | ||||||
| PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename); | PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -105,6 +105,24 @@ def frozen_modules(enabled=True): | ||||||
|         _imp._override_frozen_modules_for_tests(0) |         _imp._override_frozen_modules_for_tests(0) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @contextlib.contextmanager | ||||||
|  | def multi_interp_extensions_check(enabled=True): | ||||||
|  |     """Force legacy modules to be allowed in subinterpreters (or not). | ||||||
|  | 
 | ||||||
|  |     ("legacy" == single-phase init) | ||||||
|  | 
 | ||||||
|  |     This only applies to modules that haven't been imported yet. | ||||||
|  |     It overrides the PyInterpreterConfig.check_multi_interp_extensions | ||||||
|  |     setting (see support.run_in_subinterp_with_config() and | ||||||
|  |     _xxsubinterpreters.create()). | ||||||
|  |     """ | ||||||
|  |     old = _imp._override_multi_interp_extensions_check(1 if enabled else -1) | ||||||
|  |     try: | ||||||
|  |         yield | ||||||
|  |     finally: | ||||||
|  |         _imp._override_multi_interp_extensions_check(old) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def import_fresh_module(name, fresh=(), blocked=(), *, | def import_fresh_module(name, fresh=(), blocked=(), *, | ||||||
|                         deprecated=False, |                         deprecated=False, | ||||||
|                         usefrozen=False, |                         usefrozen=False, | ||||||
|  |  | ||||||
							
								
								
									
										77
									
								
								Lib/test/test_capi/check_config.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Lib/test/test_capi/check_config.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | ||||||
|  | # This script is used by test_misc. | ||||||
|  | 
 | ||||||
|  | import _imp | ||||||
|  | import _testinternalcapi | ||||||
|  | import json | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def import_singlephase(): | ||||||
|  |     assert '_testsinglephase' not in sys.modules | ||||||
|  |     try: | ||||||
|  |         import _testsinglephase | ||||||
|  |     except ImportError: | ||||||
|  |         sys.modules.pop('_testsinglephase') | ||||||
|  |         return False | ||||||
|  |     else: | ||||||
|  |         del sys.modules['_testsinglephase'] | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def check_singlephase(override): | ||||||
|  |     # Check using the default setting. | ||||||
|  |     settings_initial = _testinternalcapi.get_interp_settings() | ||||||
|  |     allowed_initial = import_singlephase() | ||||||
|  |     assert(_testinternalcapi.get_interp_settings() == settings_initial) | ||||||
|  | 
 | ||||||
|  |     # Apply the override and check. | ||||||
|  |     override_initial = _imp._override_multi_interp_extensions_check(override) | ||||||
|  |     settings_after = _testinternalcapi.get_interp_settings() | ||||||
|  |     allowed_after = import_singlephase() | ||||||
|  | 
 | ||||||
|  |     # Apply the override again and check. | ||||||
|  |     noop = {} | ||||||
|  |     override_after = _imp._override_multi_interp_extensions_check(override) | ||||||
|  |     settings_noop = _testinternalcapi.get_interp_settings() | ||||||
|  |     if settings_noop != settings_after: | ||||||
|  |         noop['settings_noop'] = settings_noop | ||||||
|  |     allowed_noop = import_singlephase() | ||||||
|  |     if allowed_noop != allowed_after: | ||||||
|  |         noop['allowed_noop'] = allowed_noop | ||||||
|  | 
 | ||||||
|  |     # Restore the original setting and check. | ||||||
|  |     override_noop = _imp._override_multi_interp_extensions_check(override_initial) | ||||||
|  |     if override_noop != override_after: | ||||||
|  |         noop['override_noop'] = override_noop | ||||||
|  |     settings_restored = _testinternalcapi.get_interp_settings() | ||||||
|  |     allowed_restored = import_singlephase() | ||||||
|  | 
 | ||||||
|  |     # Restore the original setting again. | ||||||
|  |     override_restored = _imp._override_multi_interp_extensions_check(override_initial) | ||||||
|  |     assert(_testinternalcapi.get_interp_settings() == settings_restored) | ||||||
|  | 
 | ||||||
|  |     return dict({ | ||||||
|  |         'requested': override, | ||||||
|  |         'override__initial': override_initial, | ||||||
|  |         'override_after': override_after, | ||||||
|  |         'override_restored': override_restored, | ||||||
|  |         'settings__initial': settings_initial, | ||||||
|  |         'settings_after': settings_after, | ||||||
|  |         'settings_restored': settings_restored, | ||||||
|  |         'allowed__initial': allowed_initial, | ||||||
|  |         'allowed_after': allowed_after, | ||||||
|  |         'allowed_restored': allowed_restored, | ||||||
|  |     }, **noop) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def run_singlephase_check(override, outfd): | ||||||
|  |     with os.fdopen(outfd, 'w') as outfile: | ||||||
|  |         sys.stdout = outfile | ||||||
|  |         sys.stderr = outfile | ||||||
|  |         try: | ||||||
|  |             results = check_singlephase(override) | ||||||
|  |             json.dump(results, outfile) | ||||||
|  |         finally: | ||||||
|  |             sys.stdout = sys.__stdout__ | ||||||
|  |             sys.stderr = sys.__stderr__ | ||||||
|  | @ -31,6 +31,10 @@ | ||||||
|     import _testmultiphase |     import _testmultiphase | ||||||
| except ImportError: | except ImportError: | ||||||
|     _testmultiphase = None |     _testmultiphase = None | ||||||
|  | try: | ||||||
|  |     import _testsinglephase | ||||||
|  | except ImportError: | ||||||
|  |     _testsinglephase = None | ||||||
| 
 | 
 | ||||||
| # Skip this test if the _testcapi module isn't available. | # Skip this test if the _testcapi module isn't available. | ||||||
| _testcapi = import_helper.import_module('_testcapi') | _testcapi = import_helper.import_module('_testcapi') | ||||||
|  | @ -1297,17 +1301,20 @@ def test_configured_settings(self): | ||||||
|         """ |         """ | ||||||
|         import json |         import json | ||||||
| 
 | 
 | ||||||
|  |         EXTENSIONS = 1<<8 | ||||||
|         THREADS = 1<<10 |         THREADS = 1<<10 | ||||||
|         DAEMON_THREADS = 1<<11 |         DAEMON_THREADS = 1<<11 | ||||||
|         FORK = 1<<15 |         FORK = 1<<15 | ||||||
|         EXEC = 1<<16 |         EXEC = 1<<16 | ||||||
| 
 | 
 | ||||||
|         features = ['fork', 'exec', 'threads', 'daemon_threads'] |         features = ['fork', 'exec', 'threads', 'daemon_threads', 'extensions'] | ||||||
|         kwlist = [f'allow_{n}' for n in features] |         kwlist = [f'allow_{n}' for n in features] | ||||||
|  |         kwlist[-1] = 'check_multi_interp_extensions' | ||||||
|         for config, expected in { |         for config, expected in { | ||||||
|             (True, True, True, True): FORK | EXEC | THREADS | DAEMON_THREADS, |             (True, True, True, True, True): | ||||||
|             (False, False, False, False): 0, |                 FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS, | ||||||
|             (False, False, True, False): THREADS, |             (False, False, False, False, False): 0, | ||||||
|  |             (False, False, True, False, True): THREADS | EXTENSIONS, | ||||||
|         }.items(): |         }.items(): | ||||||
|             kwargs = dict(zip(kwlist, config)) |             kwargs = dict(zip(kwlist, config)) | ||||||
|             expected = { |             expected = { | ||||||
|  | @ -1322,12 +1329,93 @@ def test_configured_settings(self): | ||||||
|                         json.dump(settings, stdin) |                         json.dump(settings, stdin) | ||||||
|                     ''') |                     ''') | ||||||
|                 with os.fdopen(r) as stdout: |                 with os.fdopen(r) as stdout: | ||||||
|                     support.run_in_subinterp_with_config(script, **kwargs) |                     ret = support.run_in_subinterp_with_config(script, **kwargs) | ||||||
|  |                     self.assertEqual(ret, 0) | ||||||
|                     out = stdout.read() |                     out = stdout.read() | ||||||
|                 settings = json.loads(out) |                 settings = json.loads(out) | ||||||
| 
 | 
 | ||||||
|                 self.assertEqual(settings, expected) |                 self.assertEqual(settings, expected) | ||||||
| 
 | 
 | ||||||
|  |     @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") | ||||||
|  |     @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") | ||||||
|  |     def test_overridden_setting_extensions_subinterp_check(self): | ||||||
|  |         """ | ||||||
|  |         PyInterpreterConfig.check_multi_interp_extensions can be overridden | ||||||
|  |         with PyInterpreterState.override_multi_interp_extensions_check. | ||||||
|  |         This verifies that the override works but does not modify | ||||||
|  |         the underlying setting. | ||||||
|  |         """ | ||||||
|  |         import json | ||||||
|  | 
 | ||||||
|  |         EXTENSIONS = 1<<8 | ||||||
|  |         THREADS = 1<<10 | ||||||
|  |         DAEMON_THREADS = 1<<11 | ||||||
|  |         FORK = 1<<15 | ||||||
|  |         EXEC = 1<<16 | ||||||
|  |         BASE_FLAGS = FORK | EXEC | THREADS | DAEMON_THREADS | ||||||
|  |         base_kwargs = { | ||||||
|  |             'allow_fork': True, | ||||||
|  |             'allow_exec': True, | ||||||
|  |             'allow_threads': True, | ||||||
|  |             'allow_daemon_threads': True, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         def check(enabled, override): | ||||||
|  |             kwargs = dict( | ||||||
|  |                 base_kwargs, | ||||||
|  |                 check_multi_interp_extensions=enabled, | ||||||
|  |             ) | ||||||
|  |             flags = BASE_FLAGS | EXTENSIONS if enabled else BASE_FLAGS | ||||||
|  |             settings = { | ||||||
|  |                 'feature_flags': flags, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             expected = { | ||||||
|  |                 'requested': override, | ||||||
|  |                 'override__initial': 0, | ||||||
|  |                 'override_after': override, | ||||||
|  |                 'override_restored': 0, | ||||||
|  |                 # The override should not affect the config or settings. | ||||||
|  |                 'settings__initial': settings, | ||||||
|  |                 'settings_after': settings, | ||||||
|  |                 'settings_restored': settings, | ||||||
|  |                 # These are the most likely values to be wrong. | ||||||
|  |                 'allowed__initial': not enabled, | ||||||
|  |                 'allowed_after': not ((override > 0) if override else enabled), | ||||||
|  |                 'allowed_restored': not enabled, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             r, w = os.pipe() | ||||||
|  |             script = textwrap.dedent(f''' | ||||||
|  |                 from test.test_capi.check_config import run_singlephase_check | ||||||
|  |                 run_singlephase_check({override}, {w}) | ||||||
|  |                 ''') | ||||||
|  |             with os.fdopen(r) as stdout: | ||||||
|  |                 ret = support.run_in_subinterp_with_config(script, **kwargs) | ||||||
|  |                 self.assertEqual(ret, 0) | ||||||
|  |                 out = stdout.read() | ||||||
|  |             results = json.loads(out) | ||||||
|  | 
 | ||||||
|  |             self.assertEqual(results, expected) | ||||||
|  | 
 | ||||||
|  |         self.maxDiff = None | ||||||
|  | 
 | ||||||
|  |         # setting: check disabled | ||||||
|  |         with self.subTest('config: check disabled; override: disabled'): | ||||||
|  |             check(False, -1) | ||||||
|  |         with self.subTest('config: check disabled; override: use config'): | ||||||
|  |             check(False, 0) | ||||||
|  |         with self.subTest('config: check disabled; override: enabled'): | ||||||
|  |             check(False, 1) | ||||||
|  | 
 | ||||||
|  |         # setting: check enabled | ||||||
|  |         with self.subTest('config: check enabled; override: disabled'): | ||||||
|  |             check(True, -1) | ||||||
|  |         with self.subTest('config: check enabled; override: use config'): | ||||||
|  |             check(True, 0) | ||||||
|  |         with self.subTest('config: check enabled; override: enabled'): | ||||||
|  |             check(True, 1) | ||||||
|  | 
 | ||||||
|     def test_mutate_exception(self): |     def test_mutate_exception(self): | ||||||
|         """ |         """ | ||||||
|         Exceptions saved in global module state get shared between |         Exceptions saved in global module state get shared between | ||||||
|  |  | ||||||
|  | @ -1656,13 +1656,15 @@ def test_init_use_frozen_modules(self): | ||||||
|                                        api=API_PYTHON, env=env) |                                        api=API_PYTHON, env=env) | ||||||
| 
 | 
 | ||||||
|     def test_init_main_interpreter_settings(self): |     def test_init_main_interpreter_settings(self): | ||||||
|  |         EXTENSIONS = 1<<8 | ||||||
|         THREADS = 1<<10 |         THREADS = 1<<10 | ||||||
|         DAEMON_THREADS = 1<<11 |         DAEMON_THREADS = 1<<11 | ||||||
|         FORK = 1<<15 |         FORK = 1<<15 | ||||||
|         EXEC = 1<<16 |         EXEC = 1<<16 | ||||||
|         expected = { |         expected = { | ||||||
|             # All optional features should be enabled. |             # All optional features should be enabled. | ||||||
|             'feature_flags': FORK | EXEC | THREADS | DAEMON_THREADS, |             'feature_flags': | ||||||
|  |                 FORK | EXEC | THREADS | DAEMON_THREADS, | ||||||
|         } |         } | ||||||
|         out, err = self.run_embedded_interpreter( |         out, err = self.run_embedded_interpreter( | ||||||
|             'test_init_main_interpreter_settings', |             'test_init_main_interpreter_settings', | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ | ||||||
| from test.support import os_helper | from test.support import os_helper | ||||||
| from test.support import ( | from test.support import ( | ||||||
|     STDLIB_DIR, swap_attr, swap_item, cpython_only, is_emscripten, |     STDLIB_DIR, swap_attr, swap_item, cpython_only, is_emscripten, | ||||||
|     is_wasi) |     is_wasi, run_in_subinterp_with_config) | ||||||
| from test.support.import_helper import ( | from test.support.import_helper import ( | ||||||
|     forget, make_legacy_pyc, unlink, unload, DirsOnSysPath, CleanImport) |     forget, make_legacy_pyc, unlink, unload, DirsOnSysPath, CleanImport) | ||||||
| from test.support.os_helper import ( | from test.support.os_helper import ( | ||||||
|  | @ -30,6 +30,14 @@ | ||||||
| from test.support import threading_helper | from test.support import threading_helper | ||||||
| from test.test_importlib.util import uncache | from test.test_importlib.util import uncache | ||||||
| from types import ModuleType | from types import ModuleType | ||||||
|  | try: | ||||||
|  |     import _testsinglephase | ||||||
|  | except ImportError: | ||||||
|  |     _testsinglephase = None | ||||||
|  | try: | ||||||
|  |     import _testmultiphase | ||||||
|  | except ImportError: | ||||||
|  |     _testmultiphase = None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| skip_if_dont_write_bytecode = unittest.skipIf( | skip_if_dont_write_bytecode = unittest.skipIf( | ||||||
|  | @ -1392,6 +1400,216 @@ def test_unwritable_module(self): | ||||||
|             unwritable.x = 42 |             unwritable.x = 42 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class SubinterpImportTests(unittest.TestCase): | ||||||
|  | 
 | ||||||
|  |     RUN_KWARGS = dict( | ||||||
|  |         allow_fork=False, | ||||||
|  |         allow_exec=False, | ||||||
|  |         allow_threads=True, | ||||||
|  |         allow_daemon_threads=False, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") | ||||||
|  |     def pipe(self): | ||||||
|  |         r, w = os.pipe() | ||||||
|  |         self.addCleanup(os.close, r) | ||||||
|  |         self.addCleanup(os.close, w) | ||||||
|  |         if hasattr(os, 'set_blocking'): | ||||||
|  |             os.set_blocking(r, False) | ||||||
|  |         return (r, w) | ||||||
|  | 
 | ||||||
|  |     def import_script(self, name, fd, check_override=None): | ||||||
|  |         override_text = '' | ||||||
|  |         if check_override is not None: | ||||||
|  |             override_text = f''' | ||||||
|  |             import _imp | ||||||
|  |             _imp._override_multi_interp_extensions_check({check_override}) | ||||||
|  |             ''' | ||||||
|  |         return textwrap.dedent(f''' | ||||||
|  |             import os, sys | ||||||
|  |             {override_text} | ||||||
|  |             try: | ||||||
|  |                 import {name} | ||||||
|  |             except ImportError as exc: | ||||||
|  |                 text = 'ImportError: ' + str(exc) | ||||||
|  |             else: | ||||||
|  |                 text = 'okay' | ||||||
|  |             os.write({fd}, text.encode('utf-8')) | ||||||
|  |             ''') | ||||||
|  | 
 | ||||||
|  |     def run_shared(self, name, *, | ||||||
|  |                    check_singlephase_setting=False, | ||||||
|  |                    check_singlephase_override=None, | ||||||
|  |                    ): | ||||||
|  |         """ | ||||||
|  |         Try importing the named module in a subinterpreter. | ||||||
|  | 
 | ||||||
|  |         The subinterpreter will be in the current process. | ||||||
|  |         The module will have already been imported in the main interpreter. | ||||||
|  |         Thus, for extension/builtin modules, the module definition will | ||||||
|  |         have been loaded already and cached globally. | ||||||
|  | 
 | ||||||
|  |         "check_singlephase_setting" determines whether or not | ||||||
|  |         the interpreter will be configured to check for modules | ||||||
|  |         that are not compatible with use in multiple interpreters. | ||||||
|  | 
 | ||||||
|  |         This should always return "okay" for all modules if the | ||||||
|  |         setting is False (with no override). | ||||||
|  |         """ | ||||||
|  |         __import__(name) | ||||||
|  | 
 | ||||||
|  |         kwargs = dict( | ||||||
|  |             **self.RUN_KWARGS, | ||||||
|  |             check_multi_interp_extensions=check_singlephase_setting, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         r, w = self.pipe() | ||||||
|  |         script = self.import_script(name, w, check_singlephase_override) | ||||||
|  | 
 | ||||||
|  |         ret = run_in_subinterp_with_config(script, **kwargs) | ||||||
|  |         self.assertEqual(ret, 0) | ||||||
|  |         return os.read(r, 100) | ||||||
|  | 
 | ||||||
|  |     def check_compatible_shared(self, name, *, strict=False): | ||||||
|  |         # Verify that the named module may be imported in a subinterpreter. | ||||||
|  |         # (See run_shared() for more info.) | ||||||
|  |         out = self.run_shared(name, check_singlephase_setting=strict) | ||||||
|  |         self.assertEqual(out, b'okay') | ||||||
|  | 
 | ||||||
|  |     def check_incompatible_shared(self, name): | ||||||
|  |         # Differences from check_compatible_shared(): | ||||||
|  |         #  * verify that import fails | ||||||
|  |         #  * "strict" is always True | ||||||
|  |         out = self.run_shared(name, check_singlephase_setting=True) | ||||||
|  |         self.assertEqual( | ||||||
|  |             out.decode('utf-8'), | ||||||
|  |             f'ImportError: module {name} does not support loading in subinterpreters', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def check_compatible_isolated(self, name, *, strict=False): | ||||||
|  |         # Differences from check_compatible_shared(): | ||||||
|  |         #  * subinterpreter in a new process | ||||||
|  |         #  * module has never been imported before in that process | ||||||
|  |         #  * this tests importing the module for the first time | ||||||
|  |         _, out, err = script_helper.assert_python_ok('-c', textwrap.dedent(f''' | ||||||
|  |             import _testcapi, sys | ||||||
|  |             assert ( | ||||||
|  |                 {name!r} in sys.builtin_module_names or | ||||||
|  |                 {name!r} not in sys.modules | ||||||
|  |             ), repr({name!r}) | ||||||
|  |             ret = _testcapi.run_in_subinterp_with_config( | ||||||
|  |                 {self.import_script(name, "sys.stdout.fileno()")!r}, | ||||||
|  |                 **{self.RUN_KWARGS}, | ||||||
|  |                 check_multi_interp_extensions={strict}, | ||||||
|  |             ) | ||||||
|  |             assert ret == 0, ret | ||||||
|  |             ''')) | ||||||
|  |         self.assertEqual(err, b'') | ||||||
|  |         self.assertEqual(out, b'okay') | ||||||
|  | 
 | ||||||
|  |     def check_incompatible_isolated(self, name): | ||||||
|  |         # Differences from check_compatible_isolated(): | ||||||
|  |         #  * verify that import fails | ||||||
|  |         #  * "strict" is always True | ||||||
|  |         _, out, err = script_helper.assert_python_ok('-c', textwrap.dedent(f''' | ||||||
|  |             import _testcapi, sys | ||||||
|  |             assert {name!r} not in sys.modules, {name!r} | ||||||
|  |             ret = _testcapi.run_in_subinterp_with_config( | ||||||
|  |                 {self.import_script(name, "sys.stdout.fileno()")!r}, | ||||||
|  |                 **{self.RUN_KWARGS}, | ||||||
|  |                 check_multi_interp_extensions=True, | ||||||
|  |             ) | ||||||
|  |             assert ret == 0, ret | ||||||
|  |             ''')) | ||||||
|  |         self.assertEqual(err, b'') | ||||||
|  |         self.assertEqual( | ||||||
|  |             out.decode('utf-8'), | ||||||
|  |             f'ImportError: module {name} does not support loading in subinterpreters', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def test_builtin_compat(self): | ||||||
|  |         module = 'sys' | ||||||
|  |         with self.subTest(f'{module}: not strict'): | ||||||
|  |             self.check_compatible_shared(module, strict=False) | ||||||
|  |         with self.subTest(f'{module}: strict, shared'): | ||||||
|  |             self.check_compatible_shared(module, strict=True) | ||||||
|  | 
 | ||||||
|  |     @cpython_only | ||||||
|  |     def test_frozen_compat(self): | ||||||
|  |         module = '_frozen_importlib' | ||||||
|  |         if __import__(module).__spec__.origin != 'frozen': | ||||||
|  |             raise unittest.SkipTest(f'{module} is unexpectedly not frozen') | ||||||
|  |         with self.subTest(f'{module}: not strict'): | ||||||
|  |             self.check_compatible_shared(module, strict=False) | ||||||
|  |         with self.subTest(f'{module}: strict, shared'): | ||||||
|  |             self.check_compatible_shared(module, strict=True) | ||||||
|  | 
 | ||||||
|  |     @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") | ||||||
|  |     def test_single_init_extension_compat(self): | ||||||
|  |         module = '_testsinglephase' | ||||||
|  |         with self.subTest(f'{module}: not strict'): | ||||||
|  |             self.check_compatible_shared(module, strict=False) | ||||||
|  |         with self.subTest(f'{module}: strict, shared'): | ||||||
|  |             self.check_incompatible_shared(module) | ||||||
|  |         with self.subTest(f'{module}: strict, isolated'): | ||||||
|  |             self.check_incompatible_isolated(module) | ||||||
|  | 
 | ||||||
|  |     @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module") | ||||||
|  |     def test_multi_init_extension_compat(self): | ||||||
|  |         module = '_testmultiphase' | ||||||
|  |         with self.subTest(f'{module}: not strict'): | ||||||
|  |             self.check_compatible_shared(module, strict=False) | ||||||
|  |         with self.subTest(f'{module}: strict, shared'): | ||||||
|  |             self.check_compatible_shared(module, strict=True) | ||||||
|  |         with self.subTest(f'{module}: strict, isolated'): | ||||||
|  |             self.check_compatible_isolated(module, strict=True) | ||||||
|  | 
 | ||||||
|  |     def test_python_compat(self): | ||||||
|  |         module = 'threading' | ||||||
|  |         if __import__(module).__spec__.origin == 'frozen': | ||||||
|  |             raise unittest.SkipTest(f'{module} is unexpectedly frozen') | ||||||
|  |         with self.subTest(f'{module}: not strict'): | ||||||
|  |             self.check_compatible_shared(module, strict=False) | ||||||
|  |         with self.subTest(f'{module}: strict, shared'): | ||||||
|  |             self.check_compatible_shared(module, strict=True) | ||||||
|  |         with self.subTest(f'{module}: strict, isolated'): | ||||||
|  |             self.check_compatible_isolated(module, strict=True) | ||||||
|  | 
 | ||||||
|  |     @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") | ||||||
|  |     def test_singlephase_check_with_setting_and_override(self): | ||||||
|  |         module = '_testsinglephase' | ||||||
|  | 
 | ||||||
|  |         def check_compatible(setting, override): | ||||||
|  |             out = self.run_shared( | ||||||
|  |                 module, | ||||||
|  |                 check_singlephase_setting=setting, | ||||||
|  |                 check_singlephase_override=override, | ||||||
|  |             ) | ||||||
|  |             self.assertEqual(out, b'okay') | ||||||
|  | 
 | ||||||
|  |         def check_incompatible(setting, override): | ||||||
|  |             out = self.run_shared( | ||||||
|  |                 module, | ||||||
|  |                 check_singlephase_setting=setting, | ||||||
|  |                 check_singlephase_override=override, | ||||||
|  |             ) | ||||||
|  |             self.assertNotEqual(out, b'okay') | ||||||
|  | 
 | ||||||
|  |         with self.subTest('config: check enabled; override: enabled'): | ||||||
|  |             check_incompatible(True, 1) | ||||||
|  |         with self.subTest('config: check enabled; override: use config'): | ||||||
|  |             check_incompatible(True, 0) | ||||||
|  |         with self.subTest('config: check enabled; override: disabled'): | ||||||
|  |             check_compatible(True, -1) | ||||||
|  | 
 | ||||||
|  |         with self.subTest('config: check disabled; override: enabled'): | ||||||
|  |             check_incompatible(False, 1) | ||||||
|  |         with self.subTest('config: check disabled; override: use config'): | ||||||
|  |             check_compatible(False, 0) | ||||||
|  |         with self.subTest('config: check disabled; override: disabled'): | ||||||
|  |             check_compatible(False, -1) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     # Test needs to be a package, so we can do relative imports. |     # Test needs to be a package, so we can do relative imports. | ||||||
|     unittest.main() |     unittest.main() | ||||||
|  |  | ||||||
|  | @ -1347,6 +1347,7 @@ def func(): | ||||||
|                 allow_exec=True, |                 allow_exec=True, | ||||||
|                 allow_threads={allowed}, |                 allow_threads={allowed}, | ||||||
|                 allow_daemon_threads={daemon_allowed}, |                 allow_daemon_threads={daemon_allowed}, | ||||||
|  |                 check_multi_interp_extensions=False, | ||||||
|             ) |             ) | ||||||
|             """) |             """) | ||||||
|         with test.support.SuppressCrashReport(): |         with test.support.SuppressCrashReport(): | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | When an interpreter is configured to check (and only then), importing an | ||||||
|  | extension module will now fail when the extension does not support multiple | ||||||
|  | interpreters (i.e. doesn't implement PEP 489 multi-phase init). This does | ||||||
|  | not apply to the main interpreter, nor to subinterpreters created with | ||||||
|  | ``Py_NewInterpreter()``. | ||||||
|  | @ -1618,6 +1618,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | ||||||
|     int allow_exec = -1; |     int allow_exec = -1; | ||||||
|     int allow_threads = -1; |     int allow_threads = -1; | ||||||
|     int allow_daemon_threads = -1; |     int allow_daemon_threads = -1; | ||||||
|  |     int check_multi_interp_extensions = -1; | ||||||
|     int r; |     int r; | ||||||
|     PyThreadState *substate, *mainstate; |     PyThreadState *substate, *mainstate; | ||||||
|     /* only initialise 'cflags.cf_flags' to test backwards compatibility */ |     /* only initialise 'cflags.cf_flags' to test backwards compatibility */ | ||||||
|  | @ -1628,11 +1629,13 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | ||||||
|                              "allow_exec", |                              "allow_exec", | ||||||
|                              "allow_threads", |                              "allow_threads", | ||||||
|                              "allow_daemon_threads", |                              "allow_daemon_threads", | ||||||
|  |                              "check_multi_interp_extensions", | ||||||
|                              NULL}; |                              NULL}; | ||||||
|     if (!PyArg_ParseTupleAndKeywords(args, kwargs, |     if (!PyArg_ParseTupleAndKeywords(args, kwargs, | ||||||
|                     "s$pppp:run_in_subinterp_with_config", kwlist, |                     "s$ppppp:run_in_subinterp_with_config", kwlist, | ||||||
|                     &code, &allow_fork, &allow_exec, |                     &code, &allow_fork, &allow_exec, | ||||||
|                     &allow_threads, &allow_daemon_threads)) { |                     &allow_threads, &allow_daemon_threads, | ||||||
|  |                     &check_multi_interp_extensions)) { | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|     if (allow_fork < 0) { |     if (allow_fork < 0) { | ||||||
|  | @ -1651,6 +1654,10 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | ||||||
|         PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads"); |         PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads"); | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|  |     if (check_multi_interp_extensions < 0) { | ||||||
|  |         PyErr_SetString(PyExc_ValueError, "missing check_multi_interp_extensions"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     mainstate = PyThreadState_Get(); |     mainstate = PyThreadState_Get(); | ||||||
| 
 | 
 | ||||||
|  | @ -1661,6 +1668,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) | ||||||
|         .allow_exec = allow_exec, |         .allow_exec = allow_exec, | ||||||
|         .allow_threads = allow_threads, |         .allow_threads = allow_threads, | ||||||
|         .allow_daemon_threads = allow_daemon_threads, |         .allow_daemon_threads = allow_daemon_threads, | ||||||
|  |         .check_multi_interp_extensions = check_multi_interp_extensions, | ||||||
|     }; |     }; | ||||||
|     substate = _Py_NewInterpreterFromConfig(&config); |     substate = _Py_NewInterpreterFromConfig(&config); | ||||||
|     if (substate == NULL) { |     if (substate == NULL) { | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								Python/clinic/import.c.h
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										33
									
								
								Python/clinic/import.c.h
									
										
									
										generated
									
									
									
								
							|  | @ -442,6 +442,37 @@ exit: | ||||||
|     return return_value; |     return return_value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | PyDoc_STRVAR(_imp__override_multi_interp_extensions_check__doc__, | ||||||
|  | "_override_multi_interp_extensions_check($module, override, /)\n" | ||||||
|  | "--\n" | ||||||
|  | "\n" | ||||||
|  | "(internal-only) Override PyInterpreterConfig.check_multi_interp_extensions.\n" | ||||||
|  | "\n" | ||||||
|  | "(-1: \"never\", 1: \"always\", 0: no override)"); | ||||||
|  | 
 | ||||||
|  | #define _IMP__OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK_METHODDEF    \ | ||||||
|  |     {"_override_multi_interp_extensions_check", (PyCFunction)_imp__override_multi_interp_extensions_check, METH_O, _imp__override_multi_interp_extensions_check__doc__}, | ||||||
|  | 
 | ||||||
|  | static PyObject * | ||||||
|  | _imp__override_multi_interp_extensions_check_impl(PyObject *module, | ||||||
|  |                                                   int override); | ||||||
|  | 
 | ||||||
|  | static PyObject * | ||||||
|  | _imp__override_multi_interp_extensions_check(PyObject *module, PyObject *arg) | ||||||
|  | { | ||||||
|  |     PyObject *return_value = NULL; | ||||||
|  |     int override; | ||||||
|  | 
 | ||||||
|  |     override = _PyLong_AsInt(arg); | ||||||
|  |     if (override == -1 && PyErr_Occurred()) { | ||||||
|  |         goto exit; | ||||||
|  |     } | ||||||
|  |     return_value = _imp__override_multi_interp_extensions_check_impl(module, override); | ||||||
|  | 
 | ||||||
|  | exit: | ||||||
|  |     return return_value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #if defined(HAVE_DYNAMIC_LOADING) | #if defined(HAVE_DYNAMIC_LOADING) | ||||||
| 
 | 
 | ||||||
| PyDoc_STRVAR(_imp_create_dynamic__doc__, | PyDoc_STRVAR(_imp_create_dynamic__doc__, | ||||||
|  | @ -617,4 +648,4 @@ exit: | ||||||
| #ifndef _IMP_EXEC_DYNAMIC_METHODDEF | #ifndef _IMP_EXEC_DYNAMIC_METHODDEF | ||||||
|     #define _IMP_EXEC_DYNAMIC_METHODDEF |     #define _IMP_EXEC_DYNAMIC_METHODDEF | ||||||
| #endif /* !defined(_IMP_EXEC_DYNAMIC_METHODDEF) */ | #endif /* !defined(_IMP_EXEC_DYNAMIC_METHODDEF) */ | ||||||
| /*[clinic end generated code: output=806352838c3f7008 input=a9049054013a1b77]*/ | /*[clinic end generated code: output=b18d46e0036eff49 input=a9049054013a1b77]*/ | ||||||
|  |  | ||||||
|  | @ -74,6 +74,8 @@ static struct _inittab *inittab_copy = NULL; | ||||||
|     (interp)->imports.modules_by_index |     (interp)->imports.modules_by_index | ||||||
| #define IMPORTLIB(interp) \ | #define IMPORTLIB(interp) \ | ||||||
|     (interp)->imports.importlib |     (interp)->imports.importlib | ||||||
|  | #define OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp) \ | ||||||
|  |     (interp)->imports.override_multi_interp_extensions_check | ||||||
| #define OVERRIDE_FROZEN_MODULES(interp) \ | #define OVERRIDE_FROZEN_MODULES(interp) \ | ||||||
|     (interp)->imports.override_frozen_modules |     (interp)->imports.override_frozen_modules | ||||||
| #ifdef HAVE_DLOPEN | #ifdef HAVE_DLOPEN | ||||||
|  | @ -816,6 +818,38 @@ _extensions_cache_clear_all(void) | ||||||
|     Py_CLEAR(EXTENSIONS); |     Py_CLEAR(EXTENSIONS); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  | check_multi_interp_extensions(PyInterpreterState *interp) | ||||||
|  | { | ||||||
|  |     int override = OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp); | ||||||
|  |     if (override < 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     else if (override > 0) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     else if (_PyInterpreterState_HasFeature( | ||||||
|  |                 interp, Py_RTFLAGS_MULTI_INTERP_EXTENSIONS)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int | ||||||
|  | _PyImport_CheckSubinterpIncompatibleExtensionAllowed(const char *name) | ||||||
|  | { | ||||||
|  |     PyInterpreterState *interp = _PyInterpreterState_Get(); | ||||||
|  |     if (check_multi_interp_extensions(interp)) { | ||||||
|  |         assert(!_Py_IsMainInterpreter(interp)); | ||||||
|  |         PyErr_Format(PyExc_ImportError, | ||||||
|  |                      "module %s does not support loading in subinterpreters", | ||||||
|  |                      name); | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int | static int | ||||||
| fix_up_extension(PyObject *mod, PyObject *name, PyObject *filename) | fix_up_extension(PyObject *mod, PyObject *name, PyObject *filename) | ||||||
| { | { | ||||||
|  | @ -3297,6 +3331,34 @@ _imp__override_frozen_modules_for_tests_impl(PyObject *module, int override) | ||||||
|     Py_RETURN_NONE; |     Py_RETURN_NONE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /*[clinic input]
 | ||||||
|  | _imp._override_multi_interp_extensions_check | ||||||
|  | 
 | ||||||
|  |     override: int | ||||||
|  |     / | ||||||
|  | 
 | ||||||
|  | (internal-only) Override PyInterpreterConfig.check_multi_interp_extensions. | ||||||
|  | 
 | ||||||
|  | (-1: "never", 1: "always", 0: no override) | ||||||
|  | [clinic start generated code]*/ | ||||||
|  | 
 | ||||||
|  | static PyObject * | ||||||
|  | _imp__override_multi_interp_extensions_check_impl(PyObject *module, | ||||||
|  |                                                   int override) | ||||||
|  | /*[clinic end generated code: output=3ff043af52bbf280 input=e086a2ea181f92ae]*/ | ||||||
|  | { | ||||||
|  |     PyInterpreterState *interp = _PyInterpreterState_GET(); | ||||||
|  |     if (_Py_IsMainInterpreter(interp)) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, | ||||||
|  |                         "_imp._override_multi_interp_extensions_check() " | ||||||
|  |                         "cannot be used in the main interpreter"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |     int oldvalue = OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp); | ||||||
|  |     OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp) = override; | ||||||
|  |     return PyLong_FromLong(oldvalue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #ifdef HAVE_DYNAMIC_LOADING | #ifdef HAVE_DYNAMIC_LOADING | ||||||
| 
 | 
 | ||||||
| /*[clinic input]
 | /*[clinic input]
 | ||||||
|  | @ -3329,18 +3391,23 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file) | ||||||
| 
 | 
 | ||||||
|     PyThreadState *tstate = _PyThreadState_GET(); |     PyThreadState *tstate = _PyThreadState_GET(); | ||||||
|     mod = import_find_extension(tstate, name, path); |     mod = import_find_extension(tstate, name, path); | ||||||
|     if (mod != NULL || PyErr_Occurred()) { |     if (mod != NULL) { | ||||||
|         Py_DECREF(name); |         const char *name_buf = PyUnicode_AsUTF8(name); | ||||||
|         Py_DECREF(path); |         assert(name_buf != NULL); | ||||||
|         return mod; |         if (_PyImport_CheckSubinterpIncompatibleExtensionAllowed(name_buf) < 0) { | ||||||
|  |             Py_DECREF(mod); | ||||||
|  |             mod = NULL; | ||||||
|  |         } | ||||||
|  |         goto finally; | ||||||
|  |     } | ||||||
|  |     else if (PyErr_Occurred()) { | ||||||
|  |         goto finally; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (file != NULL) { |     if (file != NULL) { | ||||||
|         fp = _Py_fopen_obj(path, "r"); |         fp = _Py_fopen_obj(path, "r"); | ||||||
|         if (fp == NULL) { |         if (fp == NULL) { | ||||||
|             Py_DECREF(name); |             goto finally; | ||||||
|             Py_DECREF(path); |  | ||||||
|             return NULL; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|  | @ -3348,10 +3415,12 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file) | ||||||
| 
 | 
 | ||||||
|     mod = _PyImport_LoadDynamicModuleWithSpec(spec, fp); |     mod = _PyImport_LoadDynamicModuleWithSpec(spec, fp); | ||||||
| 
 | 
 | ||||||
|     Py_DECREF(name); |  | ||||||
|     Py_DECREF(path); |  | ||||||
|     if (fp) |     if (fp) | ||||||
|         fclose(fp); |         fclose(fp); | ||||||
|  | 
 | ||||||
|  | finally: | ||||||
|  |     Py_DECREF(name); | ||||||
|  |     Py_DECREF(path); | ||||||
|     return mod; |     return mod; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -3436,6 +3505,7 @@ static PyMethodDef imp_methods[] = { | ||||||
|     _IMP_IS_FROZEN_METHODDEF |     _IMP_IS_FROZEN_METHODDEF | ||||||
|     _IMP__FROZEN_MODULE_NAMES_METHODDEF |     _IMP__FROZEN_MODULE_NAMES_METHODDEF | ||||||
|     _IMP__OVERRIDE_FROZEN_MODULES_FOR_TESTS_METHODDEF |     _IMP__OVERRIDE_FROZEN_MODULES_FOR_TESTS_METHODDEF | ||||||
|  |     _IMP__OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK_METHODDEF | ||||||
|     _IMP_CREATE_DYNAMIC_METHODDEF |     _IMP_CREATE_DYNAMIC_METHODDEF | ||||||
|     _IMP_EXEC_DYNAMIC_METHODDEF |     _IMP_EXEC_DYNAMIC_METHODDEF | ||||||
|     _IMP_EXEC_BUILTIN_METHODDEF |     _IMP_EXEC_BUILTIN_METHODDEF | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "Python.h" | #include "Python.h" | ||||||
| #include "pycore_call.h" | #include "pycore_call.h" | ||||||
|  | #include "pycore_import.h" | ||||||
| #include "pycore_pystate.h" | #include "pycore_pystate.h" | ||||||
| #include "pycore_runtime.h" | #include "pycore_runtime.h" | ||||||
| 
 | 
 | ||||||
|  | @ -203,6 +204,10 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp) | ||||||
| 
 | 
 | ||||||
|     /* Fall back to single-phase init mechanism */ |     /* Fall back to single-phase init mechanism */ | ||||||
| 
 | 
 | ||||||
|  |     if (_PyImport_CheckSubinterpIncompatibleExtensionAllowed(name_buf) < 0) { | ||||||
|  |         goto error; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (hook_prefix == nonascii_prefix) { |     if (hook_prefix == nonascii_prefix) { | ||||||
|         /* don't allow legacy init for non-ASCII module names */ |         /* don't allow legacy init for non-ASCII module names */ | ||||||
|         PyErr_Format( |         PyErr_Format( | ||||||
|  |  | ||||||
|  | @ -565,6 +565,10 @@ init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *con | ||||||
|     if (config->allow_daemon_threads) { |     if (config->allow_daemon_threads) { | ||||||
|         interp->feature_flags |= Py_RTFLAGS_DAEMON_THREADS; |         interp->feature_flags |= Py_RTFLAGS_DAEMON_THREADS; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (config->check_multi_interp_extensions) { | ||||||
|  |         interp->feature_flags |= Py_RTFLAGS_MULTI_INTERP_EXTENSIONS; | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Eric Snow
						Eric Snow