Skip to content

[3.11] GH-94438: Account for NULLs on evaluation stack when jumping lines. (GH-94444) #94484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2264,25 +2264,25 @@ async def test_no_jump_backwards_into_async_for_block(output):
output.append(2)
output.append(3)

@jump_test(1, 3, [], (ValueError, 'depth'))
@jump_test(1, 3, [], (ValueError, 'stack'))
def test_no_jump_forwards_into_with_block(output):
output.append(1)
with tracecontext(output, 2):
output.append(3)

@async_jump_test(1, 3, [], (ValueError, 'depth'))
@async_jump_test(1, 3, [], (ValueError, 'stack'))
async def test_no_jump_forwards_into_async_with_block(output):
output.append(1)
async with asynctracecontext(output, 2):
output.append(3)

@jump_test(3, 2, [1, 2, -1], (ValueError, 'depth'))
@jump_test(3, 2, [1, 2, -1], (ValueError, 'stack'))
def test_no_jump_backwards_into_with_block(output):
with tracecontext(output, 1):
output.append(2)
output.append(3)

@async_jump_test(3, 2, [1, 2, -1], (ValueError, 'depth'))
@async_jump_test(3, 2, [1, 2, -1], (ValueError, 'stack'))
async def test_no_jump_backwards_into_async_with_block(output):
async with asynctracecontext(output, 1):
output.append(2)
Expand Down Expand Up @@ -2584,6 +2584,63 @@ async def test_jump_backward_over_async_listcomp_v2(output):
output.append(7)
output.append(8)

# checking for segfaults.
@jump_test(3, 7, [], error=(ValueError, "stack"))
def test_jump_with_null_on_stack_load_global(output):
a = 1
print(
output.append(3)
)
output.append(5)
(
( # 7
a
+
10
)
+
13
)
output.append(15)

# checking for segfaults.
@jump_test(4, 8, [], error=(ValueError, "stack"))
def test_jump_with_null_on_stack_push_null(output):
a = 1
f = print
f(
output.append(4)
)
output.append(6)
(
( # 8
a
+
11
)
+
14
)
output.append(16)

# checking for segfaults.
@jump_test(3, 7, [], error=(ValueError, "stack"))
def test_jump_with_null_on_stack_load_attr(output):
a = 1
list.append(
output, 3
)
output.append(5)
(
( # 7
a
+
10
)
+
13
)
output.append(15)

class TestExtendedArgs(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Account for instructions that can push NULL to the stack when setting line
number in a frame. Prevents some (unlikely) crashes.
56 changes: 43 additions & 13 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,24 @@ typedef enum kind {
Iterator = 1,
Except = 2,
Object = 3,
Null = 4,
} Kind;

#define BITS_PER_BLOCK 2
static int
compatible_kind(Kind from, Kind to) {
if (to == 0) {
return 0;
}
if (to == Object) {
return from != Null;
}
if (to == Null) {
return 1;
}
return from == to;
}

#define BITS_PER_BLOCK 3

#define UNINITIALIZED -2
#define OVERFLOWED -1
Expand Down Expand Up @@ -298,6 +313,31 @@ mark_stacks(PyCodeObject *code_obj, int len)
case RERAISE:
/* End of block */
break;
case PUSH_NULL:
next_stack = push_value(next_stack, Null);
stacks[i+1] = next_stack;
break;
case LOAD_GLOBAL:
{
int j = get_arg(code, i);
if (j & 1) {
next_stack = push_value(next_stack, Null);
}
next_stack = push_value(next_stack, Object);
stacks[i+1] = next_stack;
break;
}
case LOAD_ATTR:
{
int j = get_arg(code, i);
if (j & 1) {
next_stack = pop_value(next_stack);
next_stack = push_value(next_stack, Null);
next_stack = push_value(next_stack, Object);
}
stacks[i+1] = next_stack;
break;
}
default:
{
int delta = PyCompile_OpcodeStackEffect(opcode, _Py_OPARG(code[i]));
Expand All @@ -318,17 +358,6 @@ mark_stacks(PyCodeObject *code_obj, int len)
return stacks;
}

static int
compatible_kind(Kind from, Kind to) {
if (to == 0) {
return 0;
}
if (to == Object) {
return 1;
}
return from == to;
}

static int
compatible_stack(int64_t from_stack, int64_t to_stack)
{
Expand Down Expand Up @@ -365,7 +394,8 @@ explain_incompatible_stack(int64_t to_stack)
case Except:
return "can't jump into an 'except' block as there's no exception";
case Object:
return "differing stack depth";
case Null:
return "incompatible stacks";
case Iterator:
return "can't jump into the body of a for loop";
default:
Expand Down