8000 gh-135443: Sometimes Fall Back to __main__.__dict__ For Globals (gh-1… · python/cpython@a450a0d · GitHub
[go: up one dir, main page]

Skip to content
  • Commit a450a0d

    Browse files
    gh-135443: Sometimes Fall Back to __main__.__dict__ For Globals (gh-135491)
    For several builtin functions, we now fall back to __main__.__dict__ for the globals when there is no current frame and _PyInterpreterState_IsRunningMain() returns true. This allows those functions to be run with Interpreter.call(). The affected builtins: * exec() * eval() * globals() * locals() * vars() * dir() We take a similar approach with "stateless" functions, which don't use any global variables.
    1 parent 68b7e1a commit a450a0d

    File tree

    7 files changed

    +392
    -66
    lines changed

    7 files changed

    +392
    -66
    lines changed

    Include/internal/pycore_ceval.h

    Lines changed: 10 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -239,6 +239,16 @@ static inline void _Py_LeaveRecursiveCall(void) {
    239239

    240240
    extern _PyInterpreterFrame* _PyEval_GetFrame(void);
    241241

    242+
    extern PyObject * _PyEval_GetGlobalsFromRunningMain(PyThreadState *);
    243+
    extern int _PyEval_EnsureBuiltins(
    244+
    PyThreadState *,
    245+
    PyObject *,
    246+
    PyObject **p_builtins);
    247+
    extern int _PyEval_EnsureBuiltinsWithModule(
    248+
    PyThreadState *,
    249+
    PyObject *,
    250+
    PyObject **p_builtins);
    251+
    242252
    PyAPI_FUNC(PyObject *)_Py_MakeCoro(PyFunctionObject *func);
    243253

    244254
    /* Handle signals, pending calls, GIL drop request

    Lib/test/test_interpreters/test_api.py

    Lines changed: 107 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1414,6 +1414,113 @@ def test_call_invalid(self):
    14141414
    with self.assertRaises(interpreters.NotShareableError):
    14151415
    interp.call(func, op, 'eggs!')
    14161416

    1417+
    def test_callable_requires_frame(self):
    1418+
    # There are various functions that require a current frame.
    1419+
    interp = interpreters.create()
    1420+
    for call, expected in [
    1421+
    ((eval, '[1, 2, 3]'),
    1422+
    [1, 2, 3]),
    1423+
    ((eval, 'sum([1, 2, 3])'),
    1424+
    6),
    1425+
    ((exec, '...'),
    1426+
    None),
    1427+
    ]:
    1428+
    with self.subTest(str(call)):
    1429+
    res = interp.call(*call)
    1430+
    self.assertEqual(res, expected)
    1431+
    1432+
    result_not_pickleable = [
    1433+
    globals,
    1434+
    locals,
    1435+
    vars,
    1436+
    ]
    1437+
    for func, expectedtype in {
    1438+
    globals: dict,
    1439+
    locals: dict,
    1440+
    vars: dict,
    1441+
    dir: list,
    1442+
    }.items():
    1443+
    with self.subTest(str(func)):
    1444+
    if func in result_not_pickleable:
    1445+
    with self.assertRaises(interpreters.NotShareableError):
    1446+
    interp.call(func)
    1447+
    else:
    1448+
    res = interp.call(func)
    1449+
    self.assertIsInstance(res, expectedtype)
    1450+
    self.assertIn('__builtins__', res)
    1451+
    1452+
    def test_globals_from_builtins(self):
    1453+
    # The builtins exec(), eval(), globals(), locals(), vars(),
    1454+
    # and dir() each runs relative to the target interpreter's
    1455+
    # __main__ module, when called directly. However,
    1456+
    # globals(), locals(), and vars() don't work when called
    1457+
    # directly so we don't check them.
    1458+
    from _frozen_importlib import BuiltinImporter
    1459+
    interp = interpreters.create()
    1460+
    1461+
    names = interp.call(dir)
    1462+
    self.assertEqual(names, [
    1463+
    '__builtins__',
    1464+
    '__doc__',
    1465+
    '__loader__',
    1466+
    '__name__',
    1467+
    '__package__',
    1468+
    '__spec__',
    1469+
    ])
    1470+
    1471+
    values = {name: interp.call(eval, name)
    1472+
    for name in names if name != '__builtins__'}
    1473+
    self.assertEqual(values, {
    1474+
    '__name__': '__main__',
    1475+
    '__doc__': None,
    1476+
    '__spec__': None, # It wasn't imported, so no module spec?
    1477+
    '__package__': None,
    1478+
    '__loader__': BuiltinImporter,
    1479+
    })
    1480+
    with self.assertRaises(ExecutionFailed):
    1481+
    interp.call(eval, 'spam'),
    1482+
    1483+
    interp.call(exec, f'assert dir() == {names}')
    1484+
    1485+
    # Update the interpreter's __main__.
    1486+
    interp.prepare_main(spam=42)
    1487+
    expected = names + ['spam']
    1488+
    1489+
    names = interp.call(dir)
    1490+
    self.assertEqual(names, expected)
    1491+
    1492+
    value = interp.call(eval, 'spam')
    1493+
    self.assertEqual(value, 42)
    1494+
    1495+
    interp.call(exec, f'assert dir() == {expected}, dir()')
    1496+
    1497+
    def test_globals_from_stateless_func(self):
    1498+
    # A stateless func, which doesn't depend on any globals,
    1499+
    # doesn't go through pickle, so it runs in __main__.
    1500+
    def set_global(name, value):
    1501+
    globals()[name] = value
    1502+
    1503+
    def get_global(name):
    1504+
    return globals().get(name)
    1505+
    1506+
    interp = interpreters.create()
    1507+
    1508+
    modname = interp.call(get_global, '__name__')
    1509+
    self.assertEqual(modname, '__main__')
    1510+
    1511+
    res = interp.call(get_global, 'spam')
    1512+
    self.assertIsNone(res)
    1513+
    1514+
    interp.exec('spam = True')
    1515+
    res = interp.call(get_global, 'spam')
    1516+
    self.assertTrue(res)
    1517+
    1518+
    interp.call(set_global, 'spam', 42)
    1519+
    res = interp.call(get_global, 'spam')
    1520+
    self.assertEqual(res, 42)
    1521+
    1522+
    interp.exec('assert spam == 42, repr(spam)')
    1523+
    14171524
    def test_call_in_thread(self):
    14181525
    interp = interpreters.create()
    14191526

    Objects/object.c

    Lines changed: 18 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -2084,9 +2084,25 @@ _dir_locals(void)
    20842084
    PyObject *names;
    20852085
    PyObject *locals;
    20862086

    2087-
    locals = _PyEval_GetFrameLocals();
    2088-
    if (locals == NULL)
    2087+
    if (_PyEval_GetFrame() != NULL) {
    2088+
    locals = _PyEval_GetFrameLocals();
    2089+
    }
    2090+
    else {
    2091+
    PyThreadState *tstate = _PyThreadState_GET();
    2092+
    locals = _PyEval_GetGlobalsFromRunningMain(tstate);
    2093+
    if (locals == NULL) {
    2094+
    if (!_PyErr_Occurred(tstate)) {
    2095+
    locals = _PyEval_GetFrameLocals();
    2096+
    assert(_PyErr_Occurred(tstate));
    2097+
    }
    2098+
    }
    2099+
    else {
    2100+
    Py_INCREF(locals);
    2101+
    }
    2102+
    }
    2103+
    if (locals == NULL) {
    20892104
    return NULL;
    2105+
    }
    20902106

    20912107
    names = PyMapping_Keys(locals);
    20922108
    Py_DECREF(locals);

    0 commit comments

    Comments
     (0)
    0