diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2021-07-25 17:04:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-25 17:04:06 -0700 |
commit | 01601aa7360ae51e74a64dbe957288096bb364fd (patch) | |
tree | 5b15dacaa20c53e997b0c2cafcd29b0ae3d89ecc | |
parent | 628baf6fef4d633e756024cf813941a4075360c1 (diff) | |
download | cpython-git-01601aa7360ae51e74a64dbe957288096bb364fd.tar.gz |
[3.10] bpo-44600: Fix line numbers for pattern matching cleanup code (GH-27346) (GH-27356)
(cherry picked from commit 4214f470f0cb9b6fef9a90758756fbc00ba95b5a)
Co-authored-by: Charles Burkland <charles.aburkland@gmail.com>
Automerge-Triggered-By: GH:brandtbucher
-rw-r--r-- | Lib/test/test_patma.py | 76 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Security/2021-07-25-20-04-54.bpo-44600.0WMldg.rst | 1 | ||||
-rw-r--r-- | Python/compile.c | 16 |
3 files changed, 89 insertions, 4 deletions
diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 69a648a5a7..96c1726b0f 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -3,6 +3,7 @@ import collections import dataclasses import enum import inspect +import sys import unittest @@ -3056,6 +3057,81 @@ class TestValueErrors(unittest.TestCase): self.assertIs(z, None) +class TestTracing(unittest.TestCase): + + def _test_trace(self, func, expected_linenos, *f_args): + actual_linenos = set() + def trace(frame, event, arg): + if frame.f_code.co_name == func.__name__: + relative_lineno = frame.f_lineno - func.__code__.co_firstlineno + actual_linenos.add(relative_lineno) + return trace + + sys.settrace(trace) + func(*f_args) + sys.settrace(None) + self.assertSetEqual(actual_linenos, expected_linenos) + + def test_default_case_traces_correctly_a(self): + def default_no_assign(command): # 0 + match command.split(): # 1 + case ["go", direction] if direction in "nesw": # 2 + return f"go {direction}" # 3 + case ["go", _]: # 4 + return "no go" # 5 + case _: # 6 + return "default" # 7 + + self._test_trace(default_no_assign, {0, 1, 2, 3}, "go n") + self._test_trace(default_no_assign, {0, 1, 2, 4, 5}, "go x") + self._test_trace(default_no_assign, {0, 1, 2, 4, 6, 7}, "spam") + + def test_default_case_traces_correctly_b(self): + def default_wildcard_assign(command): # 0 + match command.split(): # 1 + case ["go", direction] if direction in "nesw": # 2 + return f"go {direction}" # 3 + case ["go", _]: # 4 + return "no go" # 5 + case x: # 6 + return x # 7 + + self._test_trace(default_wildcard_assign, {0, 1, 2, 3}, "go n") + self._test_trace(default_wildcard_assign, {0, 1, 2, 4, 5}, "go x") + self._test_trace(default_wildcard_assign, {0, 1, 2, 4, 6, 7}, "spam") + + def test_default_case_traces_correctly_c(self): + def no_default(command): # 0 + match command.split(): # 1 + case ["go", direction] if direction in "nesw": # 2 + return f"go {direction}" # 3 + case ["go", _]: # 4 + return "no go" # 5 + + self._test_trace(no_default, {0, 1, 2, 3}, "go n") + self._test_trace(no_default, {0, 1, 2, 4, 5}, "go x") + self._test_trace(no_default, {0, 1, 2, 4}, "spam") + + def test_default_case_traces_correctly_d(self): + def only_default_no_assign(command): # 0 + match command.split(): # 1 + case _: # 2 + return "default" # 3 + + self._test_trace(only_default_no_assign, {0, 1, 2, 3}, "go n") + self._test_trace(only_default_no_assign, {0, 1, 2, 3} , "go x") + self._test_trace(only_default_no_assign, {0, 1, 2, 3}, "spam") + + def test_default_case_traces_correctly_e(self): + def only_default_wildcard_assign(command): # 0 + match command.split(): # 1 + case x: # 2 + return x # 3 + + self._test_trace(only_default_wildcard_assign, {0, 1, 2, 3}, "go n") + self._test_trace(only_default_wildcard_assign, {0, 1, 2, 3} , "go x") + self._test_trace(only_default_wildcard_assign, {0, 1, 2, 3}, "spam") + if __name__ == "__main__": """ # From inside environment using this Python, with pyperf installed: diff --git a/Misc/NEWS.d/next/Security/2021-07-25-20-04-54.bpo-44600.0WMldg.rst b/Misc/NEWS.d/next/Security/2021-07-25-20-04-54.bpo-44600.0WMldg.rst new file mode 100644 index 0000000000..ea4e04f6da --- /dev/null +++ b/Misc/NEWS.d/next/Security/2021-07-25-20-04-54.bpo-44600.0WMldg.rst @@ -0,0 +1 @@ +Fix incorrect line numbers while tracing some failed patterns in :ref:`match <match>` statements. Patch by Charles Burkland.
\ No newline at end of file diff --git a/Python/compile.c b/Python/compile.c index 46660f243a..de2a6bf296 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -6398,17 +6398,25 @@ compiler_match_inner(struct compiler *c, stmt_ty s, pattern_context *pc) } VISIT_SEQ(c, stmt, m->body); ADDOP_JUMP(c, JUMP_FORWARD, end); + // If the pattern fails to match, we want the line number of the + // cleanup to be associated with the failed pattern, not the last line + // of the body + SET_LOC(c, m->pattern); RETURN_IF_FALSE(emit_and_reset_fail_pop(c, pc)); } if (has_default) { - if (cases == 1) { - // No matches. Done with the subject: - ADDOP(c, POP_TOP); - } // A trailing "case _" is common, and lets us save a bit of redundant // pushing and popping in the loop above: m = asdl_seq_GET(s->v.Match.cases, cases - 1); SET_LOC(c, m->pattern); + if (cases == 1) { + // No matches. Done with the subject: + ADDOP(c, POP_TOP); + } + else { + // Show line coverage for default case (it doesn't create bytecode) + ADDOP(c, NOP); + } if (m->guard) { RETURN_IF_FALSE(compiler_jump_if(c, m->guard, end, 0)); } |