Skip to content

Commit

Permalink
[3.11] GH-94694: Fix column offsets for multi-line method lookups (GH…
Browse files Browse the repository at this point in the history
…-94721)

(cherry picked from commit 264b3dd)
  • Loading branch information
brandtbucher authored Jul 10, 2022
1 parent 7b5737a commit e5c8ad3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 2 deletions.
21 changes: 21 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,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, "<test>", "exec")


class TestExpressionStackSize(unittest.TestCase):
# These tests check that the computed stack size for a code object
Expand Down
51 changes: 51 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 9 additions & 2 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -4788,8 +4788,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_lineno = meth->end_lineno;
c->u->u_col_offset = meth->end_col_offset - (int)PyUnicode_GetLength(meth->v.Attribute.attr)-1;
c->u->u_lineno = c->u->u_end_lineno = meth->end_lineno;
int len = (int)PyUnicode_GET_LENGTH(meth->v.Attribute.attr);
if (len <= meth->end_col_offset) {
c->u->u_col_offset = meth->end_col_offset - len;
}
else {
// GH-94694: Somebody's compiling weird ASTs. Just drop the columns:
c->u->u_col_offset = c->u->u_end_col_offset = -1;
}
}
}

Expand Down

0 comments on commit e5c8ad3

Please sign in to comment.