diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index ab1685d3e5b8a9..bc022a9f5cbe71 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1181,6 +1181,27 @@ def test_complex_single_line_expression(self): self.assertOpcodeSourcePositionIs(compiled_code, 'BINARY_OP', line=1, end_line=1, column=0, end_column=27, occurrence=4) + def test_multiline_assert_rewritten_as_method_call(self): + # GH-94694: Don't crash if pytest rewrites a multiline assert as a + # method call with the same location information: + tree = ast.parse("assert (\n42\n)") + old_node = tree.body[0] + new_node = ast.Expr( + ast.Call( + ast.Attribute( + ast.Name("spam", ast.Load()), + "eggs", + ast.Load(), + ), + [], + [], + ) + ) + ast.copy_location(new_node, old_node) + ast.fix_missing_locations(new_node) + tree.body[0] = new_node + compile(tree, "", "exec") + class TestExpressionStackSize(unittest.TestCase): # These tests check that the computed stack size for a code object diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 722c265a6a8a51..72d67bf6ef1407 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -702,6 +702,57 @@ class A: pass ) self.assertEqual(result_lines, expected_error.splitlines()) + def test_multiline_method_call_a(self): + def f(): + (None + .method + )() + actual = self.get_exception(f) + expected = [ + f"Traceback (most recent call last):", + f" File \"{__file__}\", line {self.callable_line}, in get_exception", + f" callable()", + f" ^^^^^^^^^^", + f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", + f" .method", + f" ^^^^^^", + ] + self.assertEqual(actual, expected) + + def test_multiline_method_call_b(self): + def f(): + (None. + method + )() + actual = self.get_exception(f) + expected = [ + f"Traceback (most recent call last):", + f" File \"{__file__}\", line {self.callable_line}, in get_exception", + f" callable()", + f" ^^^^^^^^^^", + f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", + f" method", + f" ^^^^^^", + ] + self.assertEqual(actual, expected) + + def test_multiline_method_call_c(self): + def f(): + (None + . method + )() + actual = self.get_exception(f) + expected = [ + f"Traceback (most recent call last):", + f" File \"{__file__}\", line {self.callable_line}, in get_exception", + f" callable()", + f" ^^^^^^^^^^", + f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", + f" . method", + f" ^^^^^^", + ] + self.assertEqual(actual, expected) + @cpython_only @requires_debug_ranges() class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests): diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-08-16-44-11.gh-issue-94694.VkL2CM.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-08-16-44-11.gh-issue-94694.VkL2CM.rst new file mode 100644 index 00000000000000..6434788140f420 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-08-16-44-11.gh-issue-94694.VkL2CM.rst @@ -0,0 +1,4 @@ +Fix an issue that could cause code with multi-line method lookups to have +misleading or incorrect column offset information. In some cases (when +compiling a hand-built AST) this could have resulted in a hard crash of the +interpreter. diff --git a/Python/compile.c b/Python/compile.c index f36a6e85a54c20..427fb2a369481f 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -4736,8 +4736,15 @@ update_location_to_match_attr(struct compiler *c, expr_ty meth) { if (meth->lineno != meth->end_lineno) { // Make start location match attribute - c->u->u_loc.lineno = meth->end_lineno; - c->u->u_loc.col_offset = meth->end_col_offset - (int)PyUnicode_GetLength(meth->v.Attribute.attr)-1; + c->u->u_loc.lineno = c->u->u_loc.end_lineno = meth->end_lineno; + int len = (int)PyUnicode_GET_LENGTH(meth->v.Attribute.attr); + if (len <= meth->end_col_offset) { + c->u->u_loc.col_offset = meth->end_col_offset - len; + } + else { + // GH-94694: Somebody's compiling weird ASTs. Just drop the columns: + c->u->u_loc.col_offset = c->u->u_loc.end_col_offset = -1; + } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy