summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2022-07-18 15:06:42 (GMT)
committerGitHub <noreply@github.com>2022-07-18 15:06:42 (GMT)
commit2f8bff6879c5d76d143068e8bc867196a7d28afc (patch)
treed6d031a15146ed75eb67e735979a80a9715f635a
parent631160c262b40bf4ce3da6cd7bbb972ae2e9fc91 (diff)
downloadcpython-2f8bff6879c5d76d143068e8bc867196a7d28afc.zip
cpython-2f8bff6879c5d76d143068e8bc867196a7d28afc.tar.gz
cpython-2f8bff6879c5d76d143068e8bc867196a7d28afc.tar.bz2
GH-94739: Mark stacks of exception handling blocks for setting frame.f_lineno in the debugger. (GH-94958)
-rw-r--r--Include/internal/pycore_code.h13
-rw-r--r--Lib/test/test_sys_settrace.py47
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-07-18-14-19-21.gh-issue-94739.NQJQi7.rst1
-rw-r--r--Objects/frameobject.c132
-rw-r--r--Python/ceval.c14
5 files changed, 164 insertions, 43 deletions
diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index c975f1c..61e40eb 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -390,6 +390,19 @@ read_obj(uint16_t *p)
return (PyObject *)val;
}
+/* See Objects/exception_handling_notes.txt for details.
+ */
+static inline unsigned char *
+parse_varint(unsigned char *p, int *result) {
+ int val = p[0] & 63;
+ while (p[0] & 64) {
+ p++;
+ val = (val << 6) | (p[0] & 63);
+ }
+ *result = val;
+ return p+1;
+}
+
static inline int
write_varint(uint8_t *ptr, unsigned int val)
{
diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
index 4f2dd4f..8d0c317 100644
--- a/Lib/test/test_sys_settrace.py
+++ b/Lib/test/test_sys_settrace.py
@@ -1879,7 +1879,7 @@ class JumpTestCase(unittest.TestCase):
output.append(6)
output.append(7)
- @async_jump_test(4, 5, [3], (ValueError, 'into'))
+ @async_jump_test(4, 5, [3, 5])
async def test_jump_out_of_async_for_block_forwards(output):
for i in [1]:
async for i in asynciter([1, 2]):
@@ -1921,7 +1921,7 @@ class JumpTestCase(unittest.TestCase):
output.append(8)
output.append(9)
- @jump_test(6, 7, [2], (ValueError, 'within'))
+ @jump_test(6, 7, [2, 7], (ZeroDivisionError, ''))
def test_jump_in_nested_finally_2(output):
try:
output.append(2)
@@ -1932,7 +1932,7 @@ class JumpTestCase(unittest.TestCase):
output.append(7)
output.append(8)
- @jump_test(6, 11, [2], (ValueError, 'within'))
+ @jump_test(6, 11, [2, 11], (ZeroDivisionError, ''))
def test_jump_in_nested_finally_3(output):
try:
output.append(2)
@@ -2043,8 +2043,8 @@ class JumpTestCase(unittest.TestCase):
output.append(5)
raise
- @jump_test(5, 7, [4], (ValueError, 'within'))
- def test_no_jump_between_except_blocks(output):
+ @jump_test(5, 7, [4, 7, 8])
+ def test_jump_between_except_blocks(output):
try:
1/0
except ZeroDivisionError:
@@ -2054,8 +2054,19 @@ class JumpTestCase(unittest.TestCase):
output.append(7)
output.append(8)
- @jump_test(5, 6, [4], (ValueError, 'within'))
- def test_no_jump_within_except_block(output):
+ @jump_test(5, 7, [4, 7, 8])
+ def test_jump_from_except_to_finally(output):
+ try:
+ 1/0
+ except ZeroDivisionError:
+ output.append(4)
+ output.append(5)
+ finally:
+ output.append(7)
+ output.append(8)
+
+ @jump_test(5, 6, [4, 6, 7])
+ def test_jump_within_except_block(output):
try:
1/0
except:
@@ -2290,7 +2301,7 @@ class JumpTestCase(unittest.TestCase):
output.append(2)
output.append(3)
- @async_jump_test(3, 2, [2, 2], (ValueError, 'within'))
+ @async_jump_test(3, 2, [2, 2], (ValueError, "can't jump into the body of a for loop"))
async def test_no_jump_backwards_into_async_for_block(output):
async for i in asynciter([1, 2]):
output.append(2)
@@ -2355,8 +2366,8 @@ class JumpTestCase(unittest.TestCase):
output.append(6)
# 'except' with a variable creates an implicit finally block
- @jump_test(5, 7, [4], (ValueError, 'within'))
- def test_no_jump_between_except_blocks_2(output):
+ @jump_test(5, 7, [4, 7, 8])
+ def test_jump_between_except_blocks_2(output):
try:
1/0
except ZeroDivisionError:
@@ -2392,7 +2403,7 @@ class JumpTestCase(unittest.TestCase):
finally:
output.append(5)
- @jump_test(1, 5, [], (ValueError, "into an exception"))
+ @jump_test(1, 5, [], (ValueError, "can't jump into an 'except' block as there's no exception"))
def test_no_jump_into_bare_except_block(output):
output.append(1)
try:
@@ -2400,7 +2411,7 @@ class JumpTestCase(unittest.TestCase):
except:
output.append(5)
- @jump_test(1, 5, [], (ValueError, "into an exception"))
+ @jump_test(1, 5, [], (ValueError, "can't jump into an 'except' block as there's no exception"))
def test_no_jump_into_qualified_except_block(output):
output.append(1)
try:
@@ -2408,7 +2419,7 @@ class JumpTestCase(unittest.TestCase):
except Exception:
output.append(5)
- @jump_test(3, 6, [2, 5, 6], (ValueError, "into an exception"))
+ @jump_test(3, 6, [2, 5, 6], (ValueError, "can't jump into an 'except' block as there's no exception"))
def test_no_jump_into_bare_except_block_from_try_block(output):
try:
output.append(2)
@@ -2419,7 +2430,7 @@ class JumpTestCase(unittest.TestCase):
raise
output.append(8)
- @jump_test(3, 6, [2], (ValueError, "into an exception"))
+ @jump_test(3, 6, [2], (ValueError, "can't jump into an 'except' block as there's no exception"))
def test_no_jump_into_qualified_except_block_from_try_block(output):
try:
output.append(2)
@@ -2430,8 +2441,8 @@ class JumpTestCase(unittest.TestCase):
raise
output.append(8)
- @jump_test(7, 1, [1, 3, 6], (ValueError, "within"))
- def test_no_jump_out_of_bare_except_block(output):
+ @jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7])
+ def test_jump_out_of_bare_except_block(output):
output.append(1)
try:
output.append(3)
@@ -2440,8 +2451,8 @@ class JumpTestCase(unittest.TestCase):
output.append(6)
output.append(7)
- @jump_test(7, 1, [1, 3, 6], (ValueError, "within"))
- def test_no_jump_out_of_qualified_except_block(output):
+ @jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7])
+ def test_jump_out_of_qualified_except_block(output):
output.append(1)
try:
output.append(3)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-18-14-19-21.gh-issue-94739.NQJQi7.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-18-14-19-21.gh-issue-94739.NQJQi7.rst
new file mode 100644
index 0000000..7476892
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-18-14-19-21.gh-issue-94739.NQJQi7.rst
@@ -0,0 +1 @@
+Allow jumping within, out of, and across exception handlers in the debugger.
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 6f4de65..3e97b75 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -138,6 +138,7 @@ typedef enum kind {
Except = 2,
Object = 3,
Null = 4,
+ Lasti = 5,
} Kind;
static int
@@ -162,6 +163,8 @@ compatible_kind(Kind from, Kind to) {
#define MAX_STACK_ENTRIES (63/BITS_PER_BLOCK)
#define WILL_OVERFLOW (1ULL<<((MAX_STACK_ENTRIES-1)*BITS_PER_BLOCK))
+#define EMPTY_STACK 0
+
static inline int64_t
push_value(int64_t stack, Kind kind)
{
@@ -185,6 +188,69 @@ top_of_stack(int64_t stack)
return stack & ((1<<BITS_PER_BLOCK)-1);
}
+static int64_t
+pop_to_level(int64_t stack, int level) {
+ if (level == 0) {
+ return EMPTY_STACK;
+ }
+ int64_t max_item = (1<<BITS_PER_BLOCK) - 1;
+ int64_t level_max_stack = max_item << ((level-1) * BITS_PER_BLOCK);
+ while (stack > level_max_stack) {
+ stack = pop_value(stack);
+ }
+ return stack;
+}
+
+#if 0
+/* These functions are useful for debugging the stack marking code */
+
+static char
+tos_char(int64_t stack) {
+ switch(top_of_stack(stack)) {
+ case Iterator:
+ return 'I';
+ case Except:
+ return 'E';
+ case Object:
+ return 'O';
+ case Lasti:
+ return 'L';
+ case Null:
+ return 'N';
+ }
+}
+
+static void
+print_stack(int64_t stack) {
+ if (stack < 0) {
+ if (stack == UNINITIALIZED) {
+ printf("---");
+ }
+ else if (stack == OVERFLOWED) {
+ printf("OVERFLOWED");
+ }
+ else {
+ printf("??");
+ }
+ return;
+ }
+ while (stack) {
+ printf("%c", tos_char(stack));
+ stack = pop_value(stack);
+ }
+}
+
+static void
+print_stacks(int64_t *stacks, int n) {
+ for (int i = 0; i < n; i++) {
+ printf("%d: ", i);
+ print_stack(stacks[i]);
+ printf("\n");
+ }
+}
+
+#endif
+
static int64_t *
mark_stacks(PyCodeObject *code_obj, int len)
{
@@ -204,7 +270,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
for (int i = 1; i <= len; i++) {
stacks[i] = UNINITIALIZED;
}
- stacks[0] = 0;
+ stacks[0] = EMPTY_STACK;
if (code_obj->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
{
// Generators get sent None while starting:
@@ -213,6 +279,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
int todo = 1;
while (todo) {
todo = 0;
+ /* Scan instructions */
for (i = 0; i < len; i++) {
int64_t next_stack = stacks[i];
if (next_stack == UNINITIALIZED) {
@@ -296,23 +363,25 @@ mark_stacks(PyCodeObject *code_obj, int len)
break;
}
case END_ASYNC_FOR:
- next_stack = pop_value(pop_value(pop_value(next_stack)));
+ next_stack = pop_value(pop_value(next_stack));
stacks[i+1] = next_stack;
break;
case PUSH_EXC_INFO:
+ next_stack = push_value(next_stack, Except);
+ stacks[i+1] = next_stack;
+ break;
case POP_EXCEPT:
- /* These instructions only appear in exception handlers, which
- * skip this switch ever since the move to zero-cost exceptions
- * (their stack remains UNINITIALIZED because nothing sets it).
- *
- * Note that explain_incompatible_stack interprets an
- * UNINITIALIZED stack as belonging to an exception handler.
- */
- Py_UNREACHABLE();
+ next_stack = pop_value(next_stack);
+ stacks[i+1] = next_stack;
break;
case RETURN_VALUE:
+ assert(pop_value(next_stack) == EMPTY_STACK);
+ assert(top_of_stack(next_stack) == Object);
+ break;
case RAISE_VARARGS:
+ break;
case RERAISE:
+ assert(top_of_stack(next_stack) == Except);
/* End of block */
break;
case PUSH_NULL:
@@ -331,6 +400,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
}
case LOAD_ATTR:
{
+ assert(top_of_stack(next_stack) == Object);
int j = get_arg(code, i);
if (j & 1) {
next_stack = pop_value(next_stack);
@@ -340,6 +410,16 @@ mark_stacks(PyCodeObject *code_obj, int len)
stacks[i+1] = next_stack;
break;
}
+ case CALL:
+ {
+ int args = get_arg(code, i);
+ for (int j = 0; j < args+2; j++) {
+ next_stack = pop_value(next_stack);
+ }
+ next_stack = push_value(next_stack, Object);
+ stacks[i+1] = next_stack;
+ break;
+ }
default:
{
int delta = PyCompile_OpcodeStackEffect(opcode, _Py_OPARG(code[i]));
@@ -355,6 +435,34 @@ mark_stacks(PyCodeObject *code_obj, int len)
}
}
}
+ /* Scan exception table */
+ unsigned char *start = (unsigned char *)PyBytes_AS_STRING(code_obj->co_exceptiontable);
+ unsigned char *end = start + PyBytes_GET_SIZE(code_obj->co_exceptiontable);
+ unsigned char *scan = start;
+ while (scan < end) {
+ int start_offset, size, handler;
+ scan = parse_varint(scan, &start_offset);
+ assert(start_offset >= 0 && start_offset < len);
+ scan = parse_varint(scan, &size);
+ assert(size >= 0 && start_offset+size <= len);
+ scan = parse_varint(scan, &handler);
+ assert(handler >= 0 && handler < len);
+ int depth_and_lasti;
+ scan = parse_varint(scan, &depth_and_lasti);
+ int level = depth_and_lasti >> 1;
+ int lasti = depth_and_lasti & 1;
+ if (stacks[start_offset] != UNINITIALIZED) {
+ if (stacks[handler] == UNINITIALIZED) {
+ todo = 1;
+ uint64_t target_stack = pop_to_level(stacks[start_offset], level);
+ if (lasti) {
+ target_stack = push_value(target_stack, Lasti);
+ }
+ target_stack = push_value(target_stack, Except);
+ stacks[handler] = target_stack;
+ }
+ }
+ }
}
Py_DECREF(co_code);
return stacks;
@@ -395,6 +503,8 @@ explain_incompatible_stack(int64_t to_stack)
switch(target_kind) {
case Except:
return "can't jump into an 'except' block as there's no exception";
+ case Lasti:
+ return "can't jump into a re-raising block as there's no location";
case Object:
case Null:
return "incompatible stacks";
@@ -650,7 +760,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
msg = "stack to deep to analyze";
}
else if (start_stack == UNINITIALIZED) {
- msg = "can't jump from within an exception handler";
+ msg = "can't jump from unreachable code";
}
else {
msg = explain_incompatible_stack(target_stack);
diff --git a/Python/ceval.c b/Python/ceval.c
index 2a0ce63..88d13c8 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -6089,20 +6089,6 @@ fail:
}
-/* Exception table parsing code.
- * See Objects/exception_table_notes.txt for details.
- */
-
-static inline unsigned char *
-parse_varint(unsigned char *p, int *result) {
- int val = p[0] & 63;
- while (p[0] & 64) {
- p++;
- val = (val << 6) | (p[0] & 63);
- }
- *result = val;
- return p+1;
-}
static inline unsigned char *
scan_back_to_entry_start(unsigned char *p) {