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:
Eric Snow 2023-02-15 18:16:00 -07:00 committed by GitHub
parent 3dea4ba6c1
commit 89ac665891
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 557 additions and 19 deletions

View File

@ -248,6 +248,7 @@ typedef struct {
int allow_exec;
int allow_threads;
int allow_daemon_threads;
int check_multi_interp_extensions;
} _PyInterpreterConfig;
#define _PyInterpreterConfig_INIT \
@ -256,6 +257,7 @@ typedef struct {
.allow_exec = 0, \
.allow_threads = 1, \
.allow_daemon_threads = 0, \
.check_multi_interp_extensions = 1, \
}
#define _PyInterpreterConfig_LEGACY_INIT \
@ -264,6 +266,7 @@ typedef struct {
.allow_exec = 1, \
.allow_threads = 1, \
.allow_daemon_threads = 1, \
.check_multi_interp_extensions = 0, \
}
/* --- Helper functions --------------------------------------- */

View File

@ -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).
*/
/* Set if import should check a module for subinterpreter support. */
#define Py_RTFLAGS_MULTI_INTERP_EXTENSIONS (1UL << 8)
/* Set if threads are allowed. */
#define Py_RTFLAGS_THREADS (1UL << 10)

View File

@ -64,6 +64,7 @@ struct _import_state {
/* override for config->use_frozen_modules (for tests)
(-1: "off", 1: "on", 0: no override) */
int override_frozen_modules;
int override_multi_interp_extensions_check;
#ifdef HAVE_DLOPEN
int dlopenflags;
#endif
@ -153,6 +154,10 @@ PyAPI_DATA(const struct _frozen *) _PyImport_FrozenStdlib;
PyAPI_DATA(const struct _frozen *) _PyImport_FrozenTest;
extern const struct _module_alias * _PyImport_FrozenAliases;
PyAPI_FUNC(int) _PyImport_CheckSubinterpIncompatibleExtensionAllowed(
const char *name);
// for testing
PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename);

View File

@ -105,6 +105,24 @@ def frozen_modules(enabled=True):
_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=(), *,
deprecated=False,
usefrozen=False,

View 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__

View File

@ -31,6 +31,10 @@ try:
import _testmultiphase
except ImportError:
_testmultiphase = None
try:
import _testsinglephase
except ImportError:
_testsinglephase = None
# Skip this test if the _testcapi module isn't available.
_testcapi = import_helper.import_module('_testcapi')
@ -1297,17 +1301,20 @@ class SubinterpreterTest(unittest.TestCase):
"""
import json
EXTENSIONS = 1<<8
THREADS = 1<<10
DAEMON_THREADS = 1<<11
FORK = 1<<15
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[-1] = 'check_multi_interp_extensions'
for config, expected in {
(True, True, True, True): FORK | EXEC | THREADS | DAEMON_THREADS,
(False, False, False, False): 0,
(False, False, True, False): THREADS,
(True, True, True, True, True):
FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS,
(False, False, False, False, False): 0,
(False, False, True, False, True): THREADS | EXTENSIONS,
}.items():
kwargs = dict(zip(kwlist, config))
expected = {
@ -1322,12 +1329,93 @@ class SubinterpreterTest(unittest.TestCase):
json.dump(settings, stdin)
''')
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()
settings = json.loads(out)
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):
"""
Exceptions saved in global module state get shared between

View File

@ -1656,13 +1656,15 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
api=API_PYTHON, env=env)
def test_init_main_interpreter_settings(self):
EXTENSIONS = 1<<8
THREADS = 1<<10
DAEMON_THREADS = 1<<11
FORK = 1<<15
EXEC = 1<<16
expected = {
# 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(
'test_init_main_interpreter_settings',

View File

@ -21,7 +21,7 @@ from unittest import mock
from test.support import os_helper
from test.support import (
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 (
forget, make_legacy_pyc, unlink, unload, DirsOnSysPath, CleanImport)
from test.support.os_helper import (
@ -30,6 +30,14 @@ from test.support import script_helper
from test.support import threading_helper
from test.test_importlib.util import uncache
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(
@ -1392,6 +1400,216 @@ class CircularImportTests(unittest.TestCase):
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__':
# Test needs to be a package, so we can do relative imports.
unittest.main()

View File

@ -1347,6 +1347,7 @@ class SubinterpThreadingTests(BaseTestCase):
allow_exec=True,
allow_threads={allowed},
allow_daemon_threads={daemon_allowed},
check_multi_interp_extensions=False,
)
""")
with test.support.SuppressCrashReport():

View File

@ -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()``.

View File

@ -1618,6 +1618,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
int allow_exec = -1;
int allow_threads = -1;
int allow_daemon_threads = -1;
int check_multi_interp_extensions = -1;
int r;
PyThreadState *substate, *mainstate;
/* 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_threads",
"allow_daemon_threads",
"check_multi_interp_extensions",
NULL};
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,
&allow_threads, &allow_daemon_threads)) {
&allow_threads, &allow_daemon_threads,
&check_multi_interp_extensions)) {
return NULL;
}
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");
return NULL;
}
if (check_multi_interp_extensions < 0) {
PyErr_SetString(PyExc_ValueError, "missing check_multi_interp_extensions");
return NULL;
}
mainstate = PyThreadState_Get();
@ -1661,6 +1668,7 @@ 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 = check_multi_interp_extensions,
};
substate = _Py_NewInterpreterFromConfig(&config);
if (substate == NULL) {

View File

@ -442,6 +442,37 @@ exit:
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)
PyDoc_STRVAR(_imp_create_dynamic__doc__,
@ -617,4 +648,4 @@ exit:
#ifndef _IMP_EXEC_DYNAMIC_METHODDEF
#define _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]*/

View File

@ -74,6 +74,8 @@ static struct _inittab *inittab_copy = NULL;
(interp)->imports.modules_by_index
#define IMPORTLIB(interp) \
(interp)->imports.importlib
#define OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp) \
(interp)->imports.override_multi_interp_extensions_check
#define OVERRIDE_FROZEN_MODULES(interp) \
(interp)->imports.override_frozen_modules
#ifdef HAVE_DLOPEN
@ -816,6 +818,38 @@ _extensions_cache_clear_all(void)
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
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;
}
/*[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
/*[clinic input]
@ -3329,18 +3391,23 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
PyThreadState *tstate = _PyThreadState_GET();
mod = import_find_extension(tstate, name, path);
if (mod != NULL || PyErr_Occurred()) {
Py_DECREF(name);
Py_DECREF(path);
return mod;
if (mod != NULL) {
const char *name_buf = PyUnicode_AsUTF8(name);
assert(name_buf != NULL);
if (_PyImport_CheckSubinterpIncompatibleExtensionAllowed(name_buf) < 0) {
Py_DECREF(mod);
mod = NULL;
}
goto finally;
}
else if (PyErr_Occurred()) {
goto finally;
}
if (file != NULL) {
fp = _Py_fopen_obj(path, "r");
if (fp == NULL) {
Py_DECREF(name);
Py_DECREF(path);
return NULL;
goto finally;
}
}
else
@ -3348,10 +3415,12 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
mod = _PyImport_LoadDynamicModuleWithSpec(spec, fp);
Py_DECREF(name);
Py_DECREF(path);
if (fp)
fclose(fp);
finally:
Py_DECREF(name);
Py_DECREF(path);
return mod;
}
@ -3436,6 +3505,7 @@ static PyMethodDef imp_methods[] = {
_IMP_IS_FROZEN_METHODDEF
_IMP__FROZEN_MODULE_NAMES_METHODDEF
_IMP__OVERRIDE_FROZEN_MODULES_FOR_TESTS_METHODDEF
_IMP__OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK_METHODDEF
_IMP_CREATE_DYNAMIC_METHODDEF
_IMP_EXEC_DYNAMIC_METHODDEF
_IMP_EXEC_BUILTIN_METHODDEF

View File

@ -3,6 +3,7 @@
#include "Python.h"
#include "pycore_call.h"
#include "pycore_import.h"
#include "pycore_pystate.h"
#include "pycore_runtime.h"
@ -203,6 +204,10 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
/* Fall back to single-phase init mechanism */
if (_PyImport_CheckSubinterpIncompatibleExtensionAllowed(name_buf) < 0) {
goto error;
}
if (hook_prefix == nonascii_prefix) {
/* don't allow legacy init for non-ASCII module names */
PyErr_Format(

View File

@ -565,6 +565,10 @@ init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *con
if (config->allow_daemon_threads) {
interp->feature_flags |= Py_RTFLAGS_DAEMON_THREADS;
}
if (config->check_multi_interp_extensions) {
interp->feature_flags |= Py_RTFLAGS_MULTI_INTERP_EXTENSIONS;
}
}