From 26ba98e95625c91d24b6d780afa5fe233bf9ec0f Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Tue, 28 Feb 2017 01:13:17 +1100 Subject: [PATCH 1/4] Improve exception message in ast.literal_eval When a non-literal is given to literal_eval, attempt to be more helpful with the message, rather than calling it 'malformed'. --- Lib/ast.py | 2 ++ Lib/test/test_fstring.py | 2 +- .../next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst diff --git a/Lib/ast.py b/Lib/ast.py index 2ecb03f38bc0d0..4320de016ae7c3 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -52,6 +52,8 @@ def _convert_num(node): return node.value elif isinstance(node, Num): return node.n + elif isinstance(node, AST): + raise ValueError('%s not allowed in literal' % type(node).__name__) raise ValueError('malformed node or string: ' + repr(node)) def _convert_signed_num(node): if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)): diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 5e7efe25e39ccc..a8709bb31d65f8 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -327,7 +327,7 @@ def g(): self.assertIsNone(g.__doc__) def test_literal_eval(self): - with self.assertRaisesRegex(ValueError, 'malformed node or string'): + with self.assertRaisesRegex(ValueError, 'JoinedStr not allowed in literal'): ast.literal_eval("f'x'") def test_ast_compile_time_concat(self): diff --git a/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst b/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst new file mode 100644 index 00000000000000..25c5a8252b50ec --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst @@ -0,0 +1,2 @@ +Improve wording of error messages from ast.literal_eval, distinguishing +valid-but-unacceptable nodes from those that are actually malformed. From 08ffc0358f493fe212d3a2a1848f11b6f97daec5 Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Wed, 21 Feb 2018 05:10:04 +1100 Subject: [PATCH 2/4] Include patch author name in NEWS --- .../NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst b/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst index 25c5a8252b50ec..41dab03fe14a4c 100644 --- a/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst +++ b/Misc/NEWS.d/next/Library/2018-02-21-04-39-49.bpo-32888.G5i41Q.rst @@ -1,2 +1,3 @@ Improve wording of error messages from ast.literal_eval, distinguishing valid-but-unacceptable nodes from those that are actually malformed. +Patch by Chris Angelico. From fb83598264639d890914ec65b7b6fdb45c8c2091 Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Wed, 21 Feb 2018 05:10:15 +1100 Subject: [PATCH 3/4] Distinguish different causes of error --- Lib/ast.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 4320de016ae7c3..7b52ad45823307 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -46,23 +46,23 @@ def literal_eval(node_or_string): node_or_string = parse(node_or_string, mode='eval') if isinstance(node_or_string, Expression): node_or_string = node_or_string.body - def _convert_num(node): + def _convert_num(node, ctx): if isinstance(node, Constant): if isinstance(node.value, (int, float, complex)): return node.value elif isinstance(node, Num): return node.n elif isinstance(node, AST): - raise ValueError('%s not allowed in literal' % type(node).__name__) + raise ValueError('%s not allowed in %s' % (type(node).__name__, ctx)) raise ValueError('malformed node or string: ' + repr(node)) - def _convert_signed_num(node): + def _convert_signed_num(node, ctx): if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)): - operand = _convert_num(node.operand) + operand = _convert_num(node.operand, 'unary +/-') if isinstance(node.op, UAdd): return + operand else: return - operand - return _convert_num(node) + return _convert_num(node, ctx) def _convert(node): if isinstance(node, Constant): return node.value @@ -82,14 +82,14 @@ def _convert(node): elif isinstance(node, NameConstant): return node.value elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)): - left = _convert_signed_num(node.left) - right = _convert_num(node.right) + left = _convert_signed_num(node.left, 'binary +/-') + right = _convert_num(node.right, 'binary +/-') if isinstance(left, (int, float)) and isinstance(right, complex): if isinstance(node.op, Add): return left + right else: return left - right - return _convert_signed_num(node) + return _convert_signed_num(node, 'literal') return _convert(node_or_string) From cce0c6c5935c4bc122eb37f104ec76f473598253 Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Wed, 21 Feb 2018 05:30:10 +1100 Subject: [PATCH 4/4] Add test of different forms of exception message --- Lib/test/test_ast.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 67f363ad31f39a..67ed76de819e11 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -583,6 +583,19 @@ def test_literal_eval_complex(self): self.assertRaises(ValueError, ast.literal_eval, '3+(0+6j)') self.assertRaises(ValueError, ast.literal_eval, '-(3+6j)') + def test_literal_eval_message(self): + # Issue #32888 + tests = { + "2 * 5": "BinOp not allowed in literal", + "[] + []": "List not allowed in binary +/-", + "+''": "Str not allowed in unary +/-", + ast.BinOp(ast.Num(1), ast.Add(), 'oops'): "malformed node or string: 'oops'", + } + for test, message in tests.items(): + with self.assertRaises(ValueError) as cm: + ast.literal_eval(test) + self.assertIn(message, str(cm.exception)) + def test_bad_integer(self): # issue13436: Bad error message with invalid numeric values body = [ast.ImportFrom(module='time',