diff --git a/Lib/ast.py b/Lib/ast.py index f445b32040e5fb..42f4dc04804d06 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -59,17 +59,25 @@ def literal_eval(node_or_string): """ if isinstance(node_or_string, str): node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval').body + return _convert_literal(node_or_string, True) elif isinstance(node_or_string, Expression): node_or_string = node_or_string.body return _convert_literal(node_or_string) -def _convert_literal(node): +_permitted_literal_types = (str, bytes, int, float, complex, + bool, type(None), type(...)) + + +def _convert_literal(node, omit_validation=False): """ Used by `literal_eval` to convert an AST node into a value. """ if isinstance(node, Constant): - return node.value + if omit_validation: + return node.value + if type(value := node.value) in _permitted_literal_types: + return value if isinstance(node, Dict) and len(node.keys) == len(node.values): return dict(zip( map(_convert_literal, node.keys), @@ -83,9 +91,16 @@ def _convert_literal(node): return set(map(_convert_literal, node.elts)) if ( isinstance(node, Call) and isinstance(node.func, Name) - and node.func.id == 'set' and node.args == node.keywords == [] ): - return set() + if node.func.id == 'set' and node.args == node.keywords == []: + return set() + elif ( + node.func.id == 'sentinel' and len(node.args) == 1 + and node.keywords == [] + and isinstance(arg := node.args[0], Constant) + and isinstance(name := arg.value, str) + ): + return sentinel(name) if ( isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)) diff --git a/Lib/inspect.py b/Lib/inspect.py index 3f8991c79652d3..e3ff105d248021 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2215,9 +2215,11 @@ def wrap_value(s): except NameError: raise ValueError - if isinstance(value, (str, int, float, bytes, bool, type(None), - sentinel)): + if isinstance(value, (str, int, float, bytes, bool, type(None))): return ast.Constant(value) + elif isinstance(value, sentinel): + return ast.Call(func=ast.Name(id='sentinel'), + args=[ast.Constant(value.__name__)]) raise ValueError class RewriteSymbolics(ast.NodeTransformer): diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 7d35fc4ef7c364..f14ac29d15b864 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -2032,6 +2032,9 @@ def test_literal_eval(self): self.assertEqual(ast.literal_eval('{1, 2, 3}'), {1, 2, 3}) self.assertEqual(ast.literal_eval('b"hi"'), b"hi") self.assertEqual(ast.literal_eval('set()'), set()) + val = ast.literal_eval('sentinel("xyz")') + self.assertTrue(isinstance(val, sentinel)) + self.assertEqual(val.__name__, "xyz") self.assertRaises(ValueError, ast.literal_eval, 'foo()') self.assertEqual(ast.literal_eval('6'), 6) self.assertEqual(ast.literal_eval('+6'), 6) @@ -2043,6 +2046,10 @@ def test_literal_eval(self): self.assertRaises(ValueError, ast.literal_eval, '++6') self.assertRaises(ValueError, ast.literal_eval, '+True') self.assertRaises(ValueError, ast.literal_eval, '2+3') + # gh-141778: reject values of invalid types + node = ast.Expression(body=ast.Constant(object())) + ast.fix_missing_locations(node) + self.assertRaises(ValueError, ast.literal_eval, node) def test_literal_eval_str_int_limit(self): with support.adjust_int_max_str_digits(4000): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index ff7475447e95a0..3ebaf0128d6bc9 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6375,7 +6375,9 @@ def test_threading_module_has_signatures(self): def test_thread_module_has_signatures(self): import _thread no_signature = {'RLock'} - self._test_module_has_signatures(_thread, no_signature) + unsupported_signature = {'interrupt_main'} + self._test_module_has_signatures(_thread, no_signature, + unsupported_signature=unsupported_signature) def test_time_module_has_signatures(self): no_signature = { diff --git a/Misc/NEWS.d/next/Library/2025-12-19-07-09-02.gh-issue-141778.VdSWcy.rst b/Misc/NEWS.d/next/Library/2025-12-19-07-09-02.gh-issue-141778.VdSWcy.rst new file mode 100644 index 00000000000000..77257f65619a06 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-19-07-09-02.gh-issue-141778.VdSWcy.rst @@ -0,0 +1,2 @@ +Validate value types of :class:`ast.Constant` nodes in the +:func:`ast.literal_eval`. Patch by Sergey B Kirpichev.