Skip to content

Commit 2f8bff6

Browse files
authored
GH-94739: Mark stacks of exception handling blocks for setting frame.f_lineno in the debugger. (GH-94958)
1 parent 631160c commit 2f8bff6

File tree

5 files changed

+164
-43
lines changed

5 files changed

+164
-43
lines changed

Include/internal/pycore_code.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,19 @@ read_obj(uint16_t *p)
390390
return (PyObject *)val;
391391
}
392392

393+
/* See Objects/exception_handling_notes.txt for details.
394+
*/
395+
static inline unsigned char *
396+
parse_varint(unsigned char *p, int *result) {
397+
int val = p[0] & 63;
398+
while (p[0] & 64) {
399+
p++;
400+
val = (val << 6) | (p[0] & 63);
401+
}
402+
*result = val;
403+
return p+1;
404+
}
405+
393406
static inline int
394407
write_varint(uint8_t *ptr, unsigned int val)
395408
{

Lib/test/test_sys_settrace.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,7 +1879,7 @@ def test_jump_out_of_block_backwards(output):
18791879
output.append(6)
18801880
output.append(7)
18811881

1882-
@async_jump_test(4, 5, [3], (ValueError, 'into'))
1882+
@async_jump_test(4, 5, [3, 5])
18831883
async def test_jump_out_of_async_for_block_forwards(output):
18841884
for i in [1]:
18851885
async for i in asynciter([1, 2]):
@@ -1921,7 +1921,7 @@ def test_jump_in_nested_finally(output):
19211921
output.append(8)
19221922
output.append(9)
19231923

1924-
@jump_test(6, 7, [2], (ValueError, 'within'))
1924+
@jump_test(6, 7, [2, 7], (ZeroDivisionError, ''))
19251925
def test_jump_in_nested_finally_2(output):
19261926
try:
19271927
output.append(2)
@@ -1932,7 +1932,7 @@ def test_jump_in_nested_finally_2(output):
19321932
output.append(7)
19331933
output.append(8)
19341934

1935-
@jump_test(6, 11, [2], (ValueError, 'within'))
1935+
@jump_test(6, 11, [2, 11], (ZeroDivisionError, ''))
19361936
def test_jump_in_nested_finally_3(output):
19371937
try:
19381938
output.append(2)
@@ -2043,8 +2043,8 @@ def test_jump_backwards_out_of_try_except_block(output):
20432043
output.append(5)
20442044
raise
20452045

2046-
@jump_test(5, 7, [4], (ValueError, 'within'))
2047-
def test_no_jump_between_except_blocks(output):
2046+
@jump_test(5, 7, [4, 7, 8])
2047+
def test_jump_between_except_blocks(output):
20482048
try:
20492049
1/0
20502050
except ZeroDivisionError:
@@ -2054,8 +2054,19 @@ def test_no_jump_between_except_blocks(output):
20542054
output.append(7)
20552055
output.append(8)
20562056

2057-
@jump_test(5, 6, [4], (ValueError, 'within'))
2058-
def test_no_jump_within_except_block(output):
2057+
@jump_test(5, 7, [4, 7, 8])
2058+
def test_jump_from_except_to_finally(output):
2059+
try:
2060+
1/0
2061+
except ZeroDivisionError:
2062+
output.append(4)
2063+
output.append(5)
2064+
finally:
2065+
output.append(7)
2066+
output.append(8)
2067+
2068+
@jump_test(5, 6, [4, 6, 7])
2069+
def test_jump_within_except_block(output):
20592070
try:
20602071
1/0
20612072
except:
@@ -2290,7 +2301,7 @@ def test_no_jump_backwards_into_for_block(output):
22902301
output.append(2)
22912302
output.append(3)
22922303

2293-
@async_jump_test(3, 2, [2, 2], (ValueError, 'within'))
2304+
@async_jump_test(3, 2, [2, 2], (ValueError, "can't jump into the body of a for loop"))
22942305
async def test_no_jump_backwards_into_async_for_block(output):
22952306
async for i in asynciter([1, 2]):
22962307
output.append(2)
@@ -2355,8 +2366,8 @@ def test_jump_backwards_into_try_except_block(output):
23552366
output.append(6)
23562367

23572368
# 'except' with a variable creates an implicit finally block
2358-
@jump_test(5, 7, [4], (ValueError, 'within'))
2359-
def test_no_jump_between_except_blocks_2(output):
2369+
@jump_test(5, 7, [4, 7, 8])
2370+
def test_jump_between_except_blocks_2(output):
23602371
try:
23612372
1/0
23622373
except ZeroDivisionError:
@@ -2392,23 +2403,23 @@ def test_jump_out_of_finally_block(output):
23922403
finally:
23932404
output.append(5)
23942405

2395-
@jump_test(1, 5, [], (ValueError, "into an exception"))
2406+
@jump_test(1, 5, [], (ValueError, "can't jump into an 'except' block as there's no exception"))
23962407
def test_no_jump_into_bare_except_block(output):
23972408
output.append(1)
23982409
try:
23992410
output.append(3)
24002411
except:
24012412
output.append(5)
24022413

2403-
@jump_test(1, 5, [], (ValueError, "into an exception"))
2414+
@jump_test(1, 5, [], (ValueError, "can't jump into an 'except' block as there's no exception"))
24042415
def test_no_jump_into_qualified_except_block(output):
24052416
output.append(1)
24062417
try:
24072418
output.append(3)
24082419
except Exception:
24092420
output.append(5)
24102421

2411-
@jump_test(3, 6, [2, 5, 6], (ValueError, "into an exception"))
2422+
@jump_test(3, 6, [2, 5, 6], (ValueError, "can't jump into an 'except' block as there's no exception"))
24122423
def test_no_jump_into_bare_except_block_from_try_block(output):
24132424
try:
24142425
output.append(2)
@@ -2419,7 +2430,7 @@ def test_no_jump_into_bare_except_block_from_try_block(output):
24192430
raise
24202431
output.append(8)
24212432

2422-
@jump_test(3, 6, [2], (ValueError, "into an exception"))
2433+
@jump_test(3, 6, [2], (ValueError, "can't jump into an 'except' block as there's no exception"))
24232434
def test_no_jump_into_qualified_except_block_from_try_block(output):
24242435
try:
24252436
output.append(2)
@@ -2430,8 +2441,8 @@ def test_no_jump_into_qualified_except_block_from_try_block(output):
24302441
raise
24312442
output.append(8)
24322443

2433-
@jump_test(7, 1, [1, 3, 6], (ValueError, "within"))
2434-
def test_no_jump_out_of_bare_except_block(output):
2444+
@jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7])
2445+
def test_jump_out_of_bare_except_block(output):
24352446
output.append(1)
24362447
try:
24372448
output.append(3)
@@ -2440,8 +2451,8 @@ def test_no_jump_out_of_bare_except_block(output):
24402451
output.append(6)
24412452
output.append(7)
24422453

2443-
@jump_test(7, 1, [1, 3, 6], (ValueError, "within"))
2444-
def test_no_jump_out_of_qualified_except_block(output):
2454+
@jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7])
2455+
def test_jump_out_of_qualified_except_block(output):
24452456
output.append(1)
24462457
try:
24472458
output.append(3)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow jumping within, out of, and across exception handlers in the debugger.

Objects/frameobject.c

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ typedef enum kind {
138138
Except = 2,
139139
Object = 3,
140140
Null = 4,
141+
Lasti = 5,
141142
} Kind;
142143

143144
static int
@@ -162,6 +163,8 @@ compatible_kind(Kind from, Kind to) {
162163
#define MAX_STACK_ENTRIES (63/BITS_PER_BLOCK)
163164
#define WILL_OVERFLOW (1ULL<<((MAX_STACK_ENTRIES-1)*BITS_PER_BLOCK))
164165

166+
#define EMPTY_STACK 0
167+
165168
static inline int64_t
166169
push_value(int64_t stack, Kind kind)
167170
{
@@ -185,6 +188,69 @@ top_of_stack(int64_t stack)
185188
return stack & ((1<<BITS_PER_BLOCK)-1);
186189
}
187190

191+
static int64_t
192+
pop_to_level(int64_t stack, int level) {
193+
if (level == 0) {
194+
return EMPTY_STACK;
195+
}
196+
int64_t max_item = (1<<BITS_PER_BLOCK) - 1;
197+
int64_t level_max_stack = max_item << ((level-1) * BITS_PER_BLOCK);
198+
while (stack > level_max_stack) {
199+
stack = pop_value(stack);
200+
}
201+
return stack;
202+
}
203+
204+
#if 0
205+
/* These functions are useful for debugging the stack marking code */
206+
207+
static char
208+
tos_char(int64_t stack) {
209+
switch(top_of_stack(stack)) {
210+
case Iterator:
211+
return 'I';
212+
case Except:
213+
return 'E';
214+
case Object:
215+
return 'O';
216+
case Lasti:
217+
return 'L';
218+
case Null:
219+
return 'N';
220+
}
221+
}
222+
223+
static void
224+
print_stack(int64_t stack) {
225+
if (stack < 0) {
226+
if (stack == UNINITIALIZED) {
227+
printf("---");
228+
}
229+
else if (stack == OVERFLOWED) {
230+
printf("OVERFLOWED");
231+
}
232+
else {
233+
printf("??");
234+
}
235+
return;
236+
}
237+
while (stack) {
238+
printf("%c", tos_char(stack));
239+
stack = pop_value(stack);
240+
}
241+
}
242+
243+
static void
244+
print_stacks(int64_t *stacks, int n) {
245+
for (int i = 0; i < n; i++) {
246+
printf("%d: ", i);
247+
print_stack(stacks[i]);
248+
printf("\n");
249+
}
250+
}
251+
252+
#endif
253+
188254
static int64_t *
189255
mark_stacks(PyCodeObject *code_obj, int len)
190256
{
@@ -204,7 +270,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
204270
for (int i = 1; i <= len; i++) {
205271
stacks[i] = UNINITIALIZED;
206272
}
207-
stacks[0] = 0;
273+
stacks[0] = EMPTY_STACK;
208274
if (code_obj->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
209275
{
210276
// Generators get sent None while starting:
@@ -213,6 +279,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
213279
int todo = 1;
214280
while (todo) {
215281
todo = 0;
282+
/* Scan instructions */
216283
for (i = 0; i < len; i++) {
217284
int64_t next_stack = stacks[i];
218285
if (next_stack == UNINITIALIZED) {
@@ -296,23 +363,25 @@ mark_stacks(PyCodeObject *code_obj, int len)
296363
break;
297364
}
298365
case END_ASYNC_FOR:
299-
next_stack = pop_value(pop_value(pop_value(next_stack)));
366+
next_stack = pop_value(pop_value(next_stack));
300367
stacks[i+1] = next_stack;
301368
break;
302369
case PUSH_EXC_INFO:
370+
next_stack = push_value(next_stack, Except);
371+
stacks[i+1] = next_stack;
372+
break;
303373
case POP_EXCEPT:
304-
/* These instructions only appear in exception handlers, which
305-
* skip this switch ever since the move to zero-cost exceptions
306-
* (their stack remains UNINITIALIZED because nothing sets it).
307-
*
308-
* Note that explain_incompatible_stack interprets an
309-
* UNINITIALIZED stack as belonging to an exception handler.
310-
*/
311-
Py_UNREACHABLE();
374+
next_stack = pop_value(next_stack);
375+
stacks[i+1] = next_stack;
312376
break;
313377
case RETURN_VALUE:
378+
assert(pop_value(next_stack) == EMPTY_STACK);
379+
assert(top_of_stack(next_stack) == Object);
380+
break;
314381
case RAISE_VARARGS:
382+
break;
315383
case RERAISE:
384+
assert(top_of_stack(next_stack) == Except);
316385
/* End of block */
317386
break;
318387
case PUSH_NULL:
@@ -331,6 +400,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
331400
}
332401
case LOAD_ATTR:
333402
{
403+
assert(top_of_stack(next_stack) == Object);
334404
int j = get_arg(code, i);
335405
if (j & 1) {
336406
next_stack = pop_value(next_stack);
@@ -340,6 +410,16 @@ mark_stacks(PyCodeObject *code_obj, int len)
340410
stacks[i+1] = next_stack;
341411
break;
342412
}
413+
case CALL:
414+
{
415+
int args = get_arg(code, i);
416+
for (int j = 0; j < args+2; j++) {
417+
next_stack = pop_value(next_stack);
418+
}
419+
next_stack = push_value(next_stack, Object);
420+
stacks[i+1] = next_stack;
421+
break;
422+
}
343423
default:
344424
{
345425
int delta = PyCompile_OpcodeStackEffect(opcode, _Py_OPARG(code[i]));
@@ -355,6 +435,34 @@ mark_stacks(PyCodeObject *code_obj, int len)
355435
}
356436
}
357437
}
438+
/* Scan exception table */
439+
unsigned char *start = (unsigned char *)PyBytes_AS_STRING(code_obj->co_exceptiontable);
440+
unsigned char *end = start + PyBytes_GET_SIZE(code_obj->co_exceptiontable);
441+
unsigned char *scan = start;
442+
while (scan < end) {
443+
int start_offset, size, handler;
444+
scan = parse_varint(scan, &start_offset);
445+
assert(start_offset >= 0 && start_offset < len);
446+
scan = parse_varint(scan, &size);
447+
assert(size >= 0 && start_offset+size <= len);
448+
scan = parse_varint(scan, &handler);
449+
assert(handler >= 0 && handler < len);
450+
int depth_and_lasti;
451+
scan = parse_varint(scan, &depth_and_lasti);
452+
int level = depth_and_lasti >> 1;
453+
int lasti = depth_and_lasti & 1;
454+
if (stacks[start_offset] != UNINITIALIZED) {
455+
if (stacks[handler] == UNINITIALIZED) {
456+
todo = 1;
457+
uint64_t target_stack = pop_to_level(stacks[start_offset], level);
458+
if (lasti) {
459+
target_stack = push_value(target_stack, Lasti);
460+
}
461+
target_stack = push_value(target_stack, Except);
462+
stacks[handler] = target_stack;
463+
}
464+
}
465+
}
358466
}
359467
Py_DECREF(co_code);
360468
return stacks;
@@ -395,6 +503,8 @@ explain_incompatible_stack(int64_t to_stack)
395503
switch(target_kind) {
396504
case Except:
397505
return "can't jump into an 'except' block as there's no exception";
506+
case Lasti:
507+
return "can't jump into a re-raising block as there's no location";
398508
case Object:
399509
case Null:
400510
return "incompatible stacks";
@@ -650,7 +760,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
650760
msg = "stack to deep to analyze";
651761
}
652762
else if (start_stack == UNINITIALIZED) {
653-
msg = "can't jump from within an exception handler";
763+
msg = "can't jump from unreachable code";
654764
}
655765
else {
656766
msg = explain_incompatible_stack(target_stack);

Python/ceval.c

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6089,20 +6089,6 @@ positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
60896089

60906090
}
60916091

6092-
/* Exception table parsing code.
6093-
* See Objects/exception_table_notes.txt for details.
6094-
*/
6095-
6096-
static inline unsigned char *
6097-
parse_varint(unsigned char *p, int *result) {
6098-
int val = p[0] & 63;
6099-
while (p[0] & 64) {
6100-
p++;
6101-
val = (val << 6) | (p[0] & 63);
6102-
}
6103-
*result = val;
6104-
return p+1;
6105-
}
61066092

61076093
static inline unsigned char *
61086094
scan_back_to_entry_start(unsigned char *p) {

0 commit comments

Comments
 (0)