Skip to content

Commit d431988

Browse files
committed
pythonGH-94739: Mark stacks of exception handling blocks for setting frame.f_lineno in the debugger. (pythonGH-94958)
1 parent a914fa9 commit d431988

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
@@ -438,6 +438,19 @@ read_obj(uint16_t *p)
438438
return (PyObject *)val;
439439
}
440440

441+
/* See Objects/exception_handling_notes.txt for details.
442+
*/
443+
static inline unsigned char *
444+
parse_varint(unsigned char *p, int *result) {
445+
int val = p[0] & 63;
446+
while (p[0] & 64) {
447+
p++;
448+
val = (val << 6) | (p[0] & 63);
449+
}
450+
*result = val;
451+
return p+1;
452+
}
453+
441454
static inline int
442455
write_varint(uint8_t *ptr, unsigned int val)
443456
{

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:
@@ -327,11 +396,22 @@ mark_stacks(PyCodeObject *code_obj, int len)
327396
stacks[i+1] = next_stack;
328397
break;
329398
case LOAD_METHOD:
399+
assert(top_of_stack(next_stack) == Object);
330400
next_stack = pop_value(next_stack);
331401
next_stack = push_value(next_stack, Null);
332402
next_stack = push_value(next_stack, Object);
333403
stacks[i+1] = next_stack;
334404
break;
405+
case CALL:
406+
{
407+
int args = get_arg(code, i);
408+
for (int j = 0; j < args+2; j++) {
409+
next_stack = pop_value(next_stack);
410+
}
411+
next_stack = push_value(next_stack, Object);
412+
stacks[i+1] = next_stack;
413+
break;
414+
}
335415
default:
336416
{
337417
int delta = PyCompile_OpcodeStackEffect(opcode, _Py_OPARG(code[i]));
@@ -347,6 +427,34 @@ mark_stacks(PyCodeObject *code_obj, int len)
347427
}
348428
}
349429
}
430+
/* Scan exception table */
431+
unsigned char *start = (unsigned char *)PyBytes_AS_STRING(code_obj->co_exceptiontable);
432+
unsigned char *end = start + PyBytes_GET_SIZE(code_obj->co_exceptiontable);
433+
unsigned char *scan = start;
434+
while (scan < end) {
435+
int start_offset, size, handler;
436+
scan = parse_varint(scan, &start_offset);
437+
assert(start_offset >= 0 && start_offset < len);
438+
scan = parse_varint(scan, &size);
439+
assert(size >= 0 && start_offset+size <= len);
440+
scan = parse_varint(scan, &handler);
441+
assert(handler >= 0 && handler < len);
442+
int depth_and_lasti;
443+
scan = parse_varint(scan, &depth_and_lasti);
444+
int level = depth_and_lasti >> 1;
445+
int lasti = depth_and_lasti & 1;
446+
if (stacks[start_offset] != UNINITIALIZED) {
447+
if (stacks[handler] == UNINITIALIZED) {
448+
todo = 1;
449+
uint64_t target_stack = pop_to_level(stacks[start_offset], level);
450+
if (lasti) {
451+
target_stack = push_value(target_stack, Lasti);
452+
}
453+
target_stack = push_value(target_stack, Except);
454+
stacks[handler] = target_stack;
455+
}
456+
}
457+
}
350458
}
351459
Py_DECREF(co_code);
352460
return stacks;
@@ -387,6 +495,8 @@ explain_incompatible_stack(int64_t to_stack)
387495
switch(target_kind) {
388496
case Except:
389497
return "can't jump into an 'except' block as there's no exception";
498+
case Lasti:
499+
return "can't jump into a re-raising block as there's no location";
390500
case Object:
391501
case Null:
392502
return "incompatible stacks";
@@ -612,7 +722,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
612722
msg = "stack to deep to analyze";
613723
}
614724
else if (start_stack == UNINITIALIZED) {
615-
msg = "can't jump from within an exception handler";
725+
msg = "can't jump from unreachable code";
616726
}
617727
else {
618728
msg = explain_incompatible_stack(target_stack);

Python/ceval.c

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

60496049
}
60506050

6051-
/* Exception table parsing code.
6052-
* See Objects/exception_table_notes.txt for details.
6053-
*/
6054-
6055-
static inline unsigned char *
6056-
parse_varint(unsigned char *p, int *result) {
6057-
int val = p[0] & 63;
6058-
while (p[0] & 64) {
6059-
p++;
6060-
val = (val << 6) | (p[0] & 63);
6061-
}
6062-
*result = val;
6063-
return p+1;
6064-
}
60656051

60666052
static inline unsigned char *
60676053
scan_back_to_entry_start(unsigned char *p) {

0 commit comments

Comments
 (0)