GH-94739: Mark stacks of exception handling blocks for setting frame.f_lineno in the debugger. (GH-94958)

This commit is contained in:
Mark Shannon 2022-07-18 16:06:42 +01:00 committed by GitHub
parent 631160c262
commit 2f8bff6879
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 43 deletions

View file

@ -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);