summaryrefslogtreecommitdiff
path: root/Lib
diff options
context:
space:
mode:
authorPablo Galindo <Pablogsal@gmail.com>2020-09-15 19:32:56 +0100
committerGitHub <noreply@github.com>2020-09-15 20:32:56 +0200
commit55e0836849c14fb474e1ba7f37851e07660eea3c (patch)
tree95427c11bbfe568191eeb77ab20b9f779d1451e5 /Lib
parent0cc037f8a72c283bf64d1968e34cbdc22b0e3010 (diff)
downloadcpython-git-55e0836849c14fb474e1ba7f37851e07660eea3c.tar.gz
[3.9] bpo-41631: _ast module uses again a global state (GH-21961) (GH-22258)
Partially revert commit ac46eb4ad6662cf6d771b20d8963658b2186c48c: "bpo-38113: Update the Python-ast.c generator to PEP384 (gh-15957)". Using a module state per module instance is causing subtle practical problems. For example, the Mercurial project replaces the __import__() function to implement lazy import, whereas Python expected that "import _ast" always return a fully initialized _ast module. Add _PyAST_Fini() to clear the state at exit. The _ast module has no state (set _astmodule.m_size to 0). Remove astmodule_traverse(), astmodule_clear() and astmodule_free() functions.. (cherry picked from commit e5fbe0cbd4be99ced5f000ad382208ad2a561c90) Co-authored-by: Victor Stinner <vstinner@python.org>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/ast.py37
-rw-r--r--Lib/test/test_ast.py84
2 files changed, 106 insertions, 15 deletions
diff --git a/Lib/ast.py b/Lib/ast.py
index 65ebd0100d..d860917f4d 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -497,18 +497,20 @@ class NodeTransformer(NodeVisitor):
return node
-# The following code is for backward compatibility.
-# It will be removed in future.
+# If the ast module is loaded more than once, only add deprecated methods once
+if not hasattr(Constant, 'n'):
+ # The following code is for backward compatibility.
+ # It will be removed in future.
-def _getter(self):
- """Deprecated. Use value instead."""
- return self.value
+ def _getter(self):
+ """Deprecated. Use value instead."""
+ return self.value
-def _setter(self, value):
- self.value = value
+ def _setter(self, value):
+ self.value = value
-Constant.n = property(_getter, _setter)
-Constant.s = property(_getter, _setter)
+ Constant.n = property(_getter, _setter)
+ Constant.s = property(_getter, _setter)
class _ABC(type):
@@ -600,14 +602,19 @@ class ExtSlice(slice):
def __new__(cls, dims=(), **kwargs):
return Tuple(list(dims), Load(), **kwargs)
-def _dims_getter(self):
- """Deprecated. Use elts instead."""
- return self.elts
+# If the ast module is loaded more than once, only add deprecated methods once
+if not hasattr(Tuple, 'dims'):
+ # The following code is for backward compatibility.
+ # It will be removed in future.
-def _dims_setter(self, value):
- self.elts = value
+ def _dims_getter(self):
+ """Deprecated. Use elts instead."""
+ return self.elts
-Tuple.dims = property(_dims_getter, _dims_setter)
+ def _dims_setter(self, value):
+ self.elts = value
+
+ Tuple.dims = property(_dims_getter, _dims_setter)
class Suite(mod):
"""Deprecated AST node class. Unused in Python 3."""
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index f5aef61ec6..5f57ce8724 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -1,7 +1,9 @@
import ast
+import builtins
import dis
import os
import sys
+import types
import unittest
import warnings
import weakref
@@ -1945,6 +1947,88 @@ class NodeVisitorTests(unittest.TestCase):
])
+@support.cpython_only
+class ModuleStateTests(unittest.TestCase):
+ # bpo-41194, bpo-41261, bpo-41631: The _ast module uses a global state.
+
+ def check_ast_module(self):
+ # Check that the _ast module still works as expected
+ code = 'x + 1'
+ filename = '<string>'
+ mode = 'eval'
+
+ # Create _ast.AST subclasses instances
+ ast_tree = compile(code, filename, mode, flags=ast.PyCF_ONLY_AST)
+
+ # Call PyAST_Check()
+ code = compile(ast_tree, filename, mode)
+ self.assertIsInstance(code, types.CodeType)
+
+ def test_reload_module(self):
+ # bpo-41194: Importing the _ast module twice must not crash.
+ with support.swap_item(sys.modules, '_ast', None):
+ del sys.modules['_ast']
+ import _ast as ast1
+
+ del sys.modules['_ast']
+ import _ast as ast2
+
+ self.check_ast_module()
+
+ # Unloading the two _ast module instances must not crash.
+ del ast1
+ del ast2
+ support.gc_collect()
+
+ self.check_ast_module()
+
+ def test_sys_modules(self):
+ # bpo-41631: Test reproducing a Mercurial crash when PyAST_Check()
+ # imported the _ast module internally.
+ lazy_mod = object()
+
+ def my_import(name, *args, **kw):
+ sys.modules[name] = lazy_mod
+ return lazy_mod
+
+ with support.swap_item(sys.modules, '_ast', None):
+ del sys.modules['_ast']
+
+ with support.swap_attr(builtins, '__import__', my_import):
+ # Test that compile() does not import the _ast module
+ self.check_ast_module()
+ self.assertNotIn('_ast', sys.modules)
+
+ # Sanity check of the test itself
+ import _ast
+ self.assertIs(_ast, lazy_mod)
+
+ def test_subinterpreter(self):
+ # bpo-41631: Importing and using the _ast module in a subinterpreter
+ # must not crash.
+ code = dedent('''
+ import _ast
+ import ast
+ import gc
+ import sys
+ import types
+
+ # Create _ast.AST subclasses instances and call PyAST_Check()
+ ast_tree = compile('x+1', '<string>', 'eval',
+ flags=ast.PyCF_ONLY_AST)
+ code = compile(ast_tree, 'string', 'eval')
+ if not isinstance(code, types.CodeType):
+ raise AssertionError
+
+ # Unloading the _ast module must not crash.
+ del ast, _ast
+ del sys.modules['ast'], sys.modules['_ast']
+ gc.collect()
+ ''')
+ res = support.run_in_subinterp(code)
+ self.assertEqual(res, 0)
+
+
def main():
if __name__ != '__main__':
return