Skip to content

gh-161666: Fix undefined left shifts in stack_swap() - #161667

Open
PavlNekrasov wants to merge 3 commits into
python:mainfrom
PavlNekrasov:fix-frame-stack-swap-shift
Open

PavlNekrasov wants to merge 3 commits into
python:mainfrom
PavlNekrasov:fix-frame-stack-swap-shift

Conversation

@PavlNekrasov

@PavlNekrasov PavlNekrasov commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Widen MASK and top to int64_t before shifting

Widen MASK and top to int64_t before shifting: shift can reach 60.
@picnixz picnixz changed the title Fix undefined left shifts in stack_swap() (Objects/frameobject.c) gh-161666: Fix undefined left shifts in stack_swap() Nov 9, 2026
@picnixz

picnixz commented Nov 9, 2026

Copy link
Copy Markdown
Member

Please follow the devguide when you create a PR (gh-NNNNNN: in the title, NEWS entries).

Comment thread Objects/frameobject.c
Kind top = top_of_stack(stack);
int shift = BITS_PER_BLOCK*(n-1);
int64_t replaced_low = (stack & ~(MASK << shift)) | (top << shift);
int64_t replaced_low = (stack & ~((int64_t)MASK << shift))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stack_swap() still returns Kind (32-bit), so the result gets truncated once the stack has 11+ entries, even with the casts. The return type needs to be int64_t:

-static Kind
+static int64_t
 stack_swap(int64_t stack, int n)

It's visible from Python too: this jump is wrongly rejected with can't jump into the body of a for loop (passes without the swap line). Could go into JumpTestCase in Lib/test/test_sys_settrace.py:

    def testbackwardjumpT(self):
        src = ["def f(output):"]
        src += [" " * (i + 1) + f"for a{i} in [0]:" for i in range(10)]
        src += [" " * 11 + "output.append(1)",
                " " * 11 + "output[0], a9 = a9, output[0]",
                " " * 11 + "output.append(2)"]
        ns = {}
        exec("\n".join(src), ns)
        self.run_test(ns["f"], 13, 11, [1, 1, 2])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn the test I suggested is too deep. 10 loops overflows the stack model and hits the top_of_stack(next_stack) == Object assert in mark_stacks on debug builds, which is what's breaking CI (it happens on main too, with or without the swap line).

6 loops is enough: it still fails on main and passes with this PR.

    def test_backward_jump_deep_stack(self):
        src = ["def f(output):"]
        src += [" " * (i + 1) + f"for a{i} in [0]:" for i in range(6)]
        src += [" " * 7 + "output.append(1)",
                " " * 7 + "output[0], a5 = a5, output[0]",
                " " * 7 + "output.append(2)"]
        ns = {}
        exec("\n".join(src), ns)
        self.run_test(ns["f"], 9, 7, [1, 1, 2])

@PavlNekrasov
PavlNekrasov force-pushed the fix-frame-stack-swap-shift branch from 23042e8 to a05ecf2 Compare November 10, 2026 13:04
The 63-bit stack model was truncated to Kind (32-bit) on return;
return int64_t and add a regression test.
@PavlNekrasov
PavlNekrasov force-pushed the fix-frame-stack-swap-shift branch from a05ecf2 to f45c3cf Compare November 11, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants