mirror of
https://github.com/python/cpython.git
synced 2025-07-19 09:15:34 +00:00
GH-94739: Mark stacks of exception handling blocks for setting frame.f_lineno in the debugger. (GH-94958)
This commit is contained in:
parent
631160c262
commit
2f8bff6879
5 changed files with 164 additions and 43 deletions
|
@ -390,6 +390,19 @@ read_obj(uint16_t *p)
|
||||||
return (PyObject *)val;
|
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
|
static inline int
|
||||||
write_varint(uint8_t *ptr, unsigned int val)
|
write_varint(uint8_t *ptr, unsigned int val)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1879,7 +1879,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(6)
|
output.append(6)
|
||||||
output.append(7)
|
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):
|
async def test_jump_out_of_async_for_block_forwards(output):
|
||||||
for i in [1]:
|
for i in [1]:
|
||||||
async for i in asynciter([1, 2]):
|
async for i in asynciter([1, 2]):
|
||||||
|
@ -1921,7 +1921,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(8)
|
output.append(8)
|
||||||
output.append(9)
|
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):
|
def test_jump_in_nested_finally_2(output):
|
||||||
try:
|
try:
|
||||||
output.append(2)
|
output.append(2)
|
||||||
|
@ -1932,7 +1932,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(7)
|
output.append(7)
|
||||||
output.append(8)
|
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):
|
def test_jump_in_nested_finally_3(output):
|
||||||
try:
|
try:
|
||||||
output.append(2)
|
output.append(2)
|
||||||
|
@ -2043,8 +2043,8 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(5)
|
output.append(5)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@jump_test(5, 7, [4], (ValueError, 'within'))
|
@jump_test(5, 7, [4, 7, 8])
|
||||||
def test_no_jump_between_except_blocks(output):
|
def test_jump_between_except_blocks(output):
|
||||||
try:
|
try:
|
||||||
1/0
|
1/0
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
|
@ -2054,8 +2054,19 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(7)
|
output.append(7)
|
||||||
output.append(8)
|
output.append(8)
|
||||||
|
|
||||||
@jump_test(5, 6, [4], (ValueError, 'within'))
|
@jump_test(5, 7, [4, 7, 8])
|
||||||
def test_no_jump_within_except_block(output):
|
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:
|
try:
|
||||||
1/0
|
1/0
|
||||||
except:
|
except:
|
||||||
|
@ -2290,7 +2301,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(2)
|
output.append(2)
|
||||||
output.append(3)
|
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 def test_no_jump_backwards_into_async_for_block(output):
|
||||||
async for i in asynciter([1, 2]):
|
async for i in asynciter([1, 2]):
|
||||||
output.append(2)
|
output.append(2)
|
||||||
|
@ -2355,8 +2366,8 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(6)
|
output.append(6)
|
||||||
|
|
||||||
# 'except' with a variable creates an implicit finally block
|
# 'except' with a variable creates an implicit finally block
|
||||||
@jump_test(5, 7, [4], (ValueError, 'within'))
|
@jump_test(5, 7, [4, 7, 8])
|
||||||
def test_no_jump_between_except_blocks_2(output):
|
def test_jump_between_except_blocks_2(output):
|
||||||
try:
|
try:
|
||||||
1/0
|
1/0
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
|
@ -2392,7 +2403,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
output.append(5)
|
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):
|
def test_no_jump_into_bare_except_block(output):
|
||||||
output.append(1)
|
output.append(1)
|
||||||
try:
|
try:
|
||||||
|
@ -2400,7 +2411,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
except:
|
except:
|
||||||
output.append(5)
|
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):
|
def test_no_jump_into_qualified_except_block(output):
|
||||||
output.append(1)
|
output.append(1)
|
||||||
try:
|
try:
|
||||||
|
@ -2408,7 +2419,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
except Exception:
|
except Exception:
|
||||||
output.append(5)
|
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):
|
def test_no_jump_into_bare_except_block_from_try_block(output):
|
||||||
try:
|
try:
|
||||||
output.append(2)
|
output.append(2)
|
||||||
|
@ -2419,7 +2430,7 @@ class JumpTestCase(unittest.TestCase):
|
||||||
raise
|
raise
|
||||||
output.append(8)
|
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):
|
def test_no_jump_into_qualified_except_block_from_try_block(output):
|
||||||
try:
|
try:
|
||||||
output.append(2)
|
output.append(2)
|
||||||
|
@ -2430,8 +2441,8 @@ class JumpTestCase(unittest.TestCase):
|
||||||
raise
|
raise
|
||||||
output.append(8)
|
output.append(8)
|
||||||
|
|
||||||
@jump_test(7, 1, [1, 3, 6], (ValueError, "within"))
|
@jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7])
|
||||||
def test_no_jump_out_of_bare_except_block(output):
|
def test_jump_out_of_bare_except_block(output):
|
||||||
output.append(1)
|
output.append(1)
|
||||||
try:
|
try:
|
||||||
output.append(3)
|
output.append(3)
|
||||||
|
@ -2440,8 +2451,8 @@ class JumpTestCase(unittest.TestCase):
|
||||||
output.append(6)
|
output.append(6)
|
||||||
output.append(7)
|
output.append(7)
|
||||||
|
|
||||||
@jump_test(7, 1, [1, 3, 6], (ValueError, "within"))
|
@jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7])
|
||||||
def test_no_jump_out_of_qualified_except_block(output):
|
def test_jump_out_of_qualified_except_block(output):
|
||||||
output.append(1)
|
output.append(1)
|
||||||
try:
|
try:
|
||||||
output.append(3)
|
output.append(3)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Allow jumping within, out of, and across exception handlers in the debugger.
|
|
@ -138,6 +138,7 @@ typedef enum kind {
|
||||||
Except = 2,
|
Except = 2,
|
||||||
Object = 3,
|
Object = 3,
|
||||||
Null = 4,
|
Null = 4,
|
||||||
|
Lasti = 5,
|
||||||
} Kind;
|
} Kind;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -162,6 +163,8 @@ compatible_kind(Kind from, Kind to) {
|
||||||
#define MAX_STACK_ENTRIES (63/BITS_PER_BLOCK)
|
#define MAX_STACK_ENTRIES (63/BITS_PER_BLOCK)
|
||||||
#define WILL_OVERFLOW (1ULL<<((MAX_STACK_ENTRIES-1)*BITS_PER_BLOCK))
|
#define WILL_OVERFLOW (1ULL<<((MAX_STACK_ENTRIES-1)*BITS_PER_BLOCK))
|
||||||
|
|
||||||
|
#define EMPTY_STACK 0
|
||||||
|
|
||||||
static inline int64_t
|
static inline int64_t
|
||||||
push_value(int64_t stack, Kind kind)
|
push_value(int64_t stack, Kind kind)
|
||||||
{
|
{
|
||||||
|
@ -185,6 +188,69 @@ top_of_stack(int64_t stack)
|
||||||
return stack & ((1<<BITS_PER_BLOCK)-1);
|
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 *
|
static int64_t *
|
||||||
mark_stacks(PyCodeObject *code_obj, int len)
|
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++) {
|
for (int i = 1; i <= len; i++) {
|
||||||
stacks[i] = UNINITIALIZED;
|
stacks[i] = UNINITIALIZED;
|
||||||
}
|
}
|
||||||
stacks[0] = 0;
|
stacks[0] = EMPTY_STACK;
|
||||||
if (code_obj->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
|
if (code_obj->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
|
||||||
{
|
{
|
||||||
// Generators get sent None while starting:
|
// Generators get sent None while starting:
|
||||||
|
@ -213,6 +279,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
|
||||||
int todo = 1;
|
int todo = 1;
|
||||||
while (todo) {
|
while (todo) {
|
||||||
todo = 0;
|
todo = 0;
|
||||||
|
/* Scan instructions */
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
int64_t next_stack = stacks[i];
|
int64_t next_stack = stacks[i];
|
||||||
if (next_stack == UNINITIALIZED) {
|
if (next_stack == UNINITIALIZED) {
|
||||||
|
@ -296,23 +363,25 @@ mark_stacks(PyCodeObject *code_obj, int len)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case END_ASYNC_FOR:
|
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;
|
stacks[i+1] = next_stack;
|
||||||
break;
|
break;
|
||||||
case PUSH_EXC_INFO:
|
case PUSH_EXC_INFO:
|
||||||
|
next_stack = push_value(next_stack, Except);
|
||||||
|
stacks[i+1] = next_stack;
|
||||||
|
break;
|
||||||
case POP_EXCEPT:
|
case POP_EXCEPT:
|
||||||
/* These instructions only appear in exception handlers, which
|
next_stack = pop_value(next_stack);
|
||||||
* skip this switch ever since the move to zero-cost exceptions
|
stacks[i+1] = next_stack;
|
||||||
* (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();
|
|
||||||
break;
|
break;
|
||||||
case RETURN_VALUE:
|
case RETURN_VALUE:
|
||||||
|
assert(pop_value(next_stack) == EMPTY_STACK);
|
||||||
|
assert(top_of_stack(next_stack) == Object);
|
||||||
|
break;
|
||||||
case RAISE_VARARGS:
|
case RAISE_VARARGS:
|
||||||
|
break;
|
||||||
case RERAISE:
|
case RERAISE:
|
||||||
|
assert(top_of_stack(next_stack) == Except);
|
||||||
/* End of block */
|
/* End of block */
|
||||||
break;
|
break;
|
||||||
case PUSH_NULL:
|
case PUSH_NULL:
|
||||||
|
@ -331,6 +400,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
|
||||||
}
|
}
|
||||||
case LOAD_ATTR:
|
case LOAD_ATTR:
|
||||||
{
|
{
|
||||||
|
assert(top_of_stack(next_stack) == Object);
|
||||||
int j = get_arg(code, i);
|
int j = get_arg(code, i);
|
||||||
if (j & 1) {
|
if (j & 1) {
|
||||||
next_stack = pop_value(next_stack);
|
next_stack = pop_value(next_stack);
|
||||||
|
@ -340,6 +410,16 @@ mark_stacks(PyCodeObject *code_obj, int len)
|
||||||
stacks[i+1] = next_stack;
|
stacks[i+1] = next_stack;
|
||||||
break;
|
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:
|
default:
|
||||||
{
|
{
|
||||||
int delta = PyCompile_OpcodeStackEffect(opcode, _Py_OPARG(code[i]));
|
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);
|
Py_DECREF(co_code);
|
||||||
return stacks;
|
return stacks;
|
||||||
|
@ -395,6 +503,8 @@ explain_incompatible_stack(int64_t to_stack)
|
||||||
switch(target_kind) {
|
switch(target_kind) {
|
||||||
case Except:
|
case Except:
|
||||||
return "can't jump into an 'except' block as there's no exception";
|
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 Object:
|
||||||
case Null:
|
case Null:
|
||||||
return "incompatible stacks";
|
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";
|
msg = "stack to deep to analyze";
|
||||||
}
|
}
|
||||||
else if (start_stack == UNINITIALIZED) {
|
else if (start_stack == UNINITIALIZED) {
|
||||||
msg = "can't jump from within an exception handler";
|
msg = "can't jump from unreachable code";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg = explain_incompatible_stack(target_stack);
|
msg = explain_incompatible_stack(target_stack);
|
||||||
|
|
|
@ -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 *
|
static inline unsigned char *
|
||||||
scan_back_to_entry_start(unsigned char *p) {
|
scan_back_to_entry_start(unsigned char *p) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue