Skip to content

Commit e5c8ad3

Browse files
authored
[3.11] GH-94694: Fix column offsets for multi-line method lookups (GH-94721)
(cherry picked from commit 264b3dd)
1 parent 7b5737a commit e5c8ad3

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

Lib/test/test_compile.py

+21
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,27 @@ def test_complex_single_line_expression(self):
11451145
self.assertOpcodeSourcePositionIs(compiled_code, 'BINARY_OP',
11461146
line=1, end_line=1, column=0, end_column=27, occurrence=4)
11471147

1148+
def test_multiline_assert_rewritten_as_method_call(self):
1149+
# GH-94694: Don't crash if pytest rewrites a multiline assert as a
1150+
# method call with the same location information:
1151+
tree = ast.parse("assert (\n42\n)")
1152+
old_node = tree.body[0]
1153+
new_node = ast.Expr(
1154+
ast.Call(
1155+
ast.Attribute(
1156+
ast.Name("spam", ast.Load()),
1157+
"eggs",
1158+
ast.Load(),
1159+
),
1160+
[],
1161+
[],
1162+
)
1163+
)
1164+
ast.copy_location(new_node, old_node)
1165+
ast.fix_missing_locations(new_node)
1166+
tree.body[0] = new_node
1167+
compile(tree, "<test>", "exec")
1168+
11481169

11491170
class TestExpressionStackSize(unittest.TestCase):
11501171
# These tests check that the computed stack size for a code object

Lib/test/test_traceback.py

+51
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,57 @@ class A: pass
703703
)
704704
self.assertEqual(result_lines, expected_error.splitlines())
705705

706+
def test_multiline_method_call_a(self):
707+
def f():
708+
(None
709+
.method
710+
)()
711+
actual = self.get_exception(f)
712+
expected = [
713+
f"Traceback (most recent call last):",
714+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
715+
f" callable()",
716+
f" ^^^^^^^^^^",
717+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
718+
f" .method",
719+
f" ^^^^^^",
720+
]
721+
self.assertEqual(actual, expected)
722+
723+
def test_multiline_method_call_b(self):
724+
def f():
725+
(None.
726+
method
727+
)()
728+
actual = self.get_exception(f)
729+
expected = [
730+
f"Traceback (most recent call last):",
731+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
732+
f" callable()",
733+
f" ^^^^^^^^^^",
734+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
735+
f" method",
736+
f" ^^^^^^",
737+
]
738+
self.assertEqual(actual, expected)
739+
740+
def test_multiline_method_call_c(self):
741+
def f():
742+
(None
743+
. method
744+
)()
745+
actual = self.get_exception(f)
746+
expected = [
747+
f"Traceback (most recent call last):",
748+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
749+
f" callable()",
750+
f" ^^^^^^^^^^",
751+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
752+
f" . method",
753+
f" ^^^^^^",
754+
]
755+
self.assertEqual(actual, expected)
756+
706757
@cpython_only
707758
@requires_debug_ranges()
708759
class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix an issue that could cause code with multi-line method lookups to have
2+
misleading or incorrect column offset information. In some cases (when
3+
compiling a hand-built AST) this could have resulted in a hard crash of the
4+
interpreter.

Python/compile.c

+9-2
Original file line numberDiff line numberDiff line change
@@ -4788,8 +4788,15 @@ update_location_to_match_attr(struct compiler *c, expr_ty meth)
47884788
{
47894789
if (meth->lineno != meth->end_lineno) {
47904790
// Make start location match attribute
4791-
c->u->u_lineno = meth->end_lineno;
4792-
c->u->u_col_offset = meth->end_col_offset - (int)PyUnicode_GetLength(meth->v.Attribute.attr)-1;
4791+
c->u->u_lineno = c->u->u_end_lineno = meth->end_lineno;
4792+
int len = (int)PyUnicode_GET_LENGTH(meth->v.Attribute.attr);
4793+
if (len <= meth->end_col_offset) {
4794+
c->u->u_col_offset = meth->end_col_offset - len;
4795+
}
4796+
else {
4797+
// GH-94694: Somebody's compiling weird ASTs. Just drop the columns:
4798+
c->u->u_col_offset = c->u->u_end_col_offset = -1;
4799+
}
47934800
}
47944801
}
47954802

0 commit comments

Comments
 (0)