diff --git a/Lib/ast.py b/Lib/ast.py index f445b32040e5fb2..2df029ec005a79d 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), diff --git a/Lib/inspect.py b/Lib/inspect.py index af6aa3eb37a53bb..db99f1d53582f37 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2209,7 +2209,7 @@ def wrap_value(s): if isinstance(value, (str, int, float, bytes, bool, type(None), sentinel)): - return ast.Constant(value) + return ast.parse(s, mode='eval').body raise ValueError class RewriteSymbolics(ast.NodeTransformer): @@ -2230,28 +2230,23 @@ def visit_Name(self, node): raise ValueError() return wrap_value(node.id) - def visit_BinOp(self, node): - # Support constant folding of a couple simple binary operations - # commonly used to define default values in text signatures - left = self.visit(node.left) - right = self.visit(node.right) - if not isinstance(left, ast.Constant) or not isinstance(right, ast.Constant): - raise ValueError - if isinstance(node.op, ast.Add): - return ast.Constant(left.value + right.value) - elif isinstance(node.op, ast.Sub): - return ast.Constant(left.value - right.value) - elif isinstance(node.op, ast.BitOr): - return ast.Constant(left.value | right.value) - raise ValueError - def p(name_node, default_node, default=empty): name = parse_name(name_node) if default_node and default_node is not _empty: try: default_node = RewriteSymbolics().visit(default_node) - default = ast.literal_eval(default_node) - except ValueError: + default_source = ast.unparse(default_node) + try: + default = ast.literal_eval(default_source) + except ValueError: + try: + default = eval(default_source, module_dict) + except NameError: + try: + default = eval(default_source, sys_module_dict) + except NameError: + raise ValueError + except ValueError as exc: raise ValueError("{!r} builtin has invalid signature".format(obj)) from None parameters.append(Parameter(name, kind, default=default, annotation=empty)) diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 0112c9163fd0cdb..c9f8c247a040234 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -1981,6 +1981,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 7351f97fd9a4b5c..33e1b3b5b2cbd26 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6287,9 +6287,9 @@ def test_operator_module_has_signatures(self): self._test_module_has_signatures(operator) def test_os_module_has_signatures(self): - unsupported_signature = {'chmod', 'utime'} + unsupported_signature = {'utime'} unsupported_signature |= {name for name in - ['get_terminal_size', 'link', 'register_at_fork', 'startfile'] + ['get_terminal_size', 'register_at_fork', 'startfile'] if hasattr(os, name)} self._test_module_has_signatures(os, unsupported_signature=unsupported_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 000000000000000..77257f65619a06a --- /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.