summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_patma.py76
-rw-r--r--Misc/NEWS.d/next/Security/2021-07-25-20-04-54.bpo-44600.0WMldg.rst1
-rw-r--r--Python/compile.c16
3 files changed, 89 insertions, 4 deletions
diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py
index 69a648a..96c1726 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 0000000..ea4e04f
--- /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 3a20f6b..7fb8abf 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -6576,17 +6576,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));
}