From 9f53816d5b99fc0f97e9eafc124afa333f10c837 Mon Sep 17 00:00:00 2001
From: Pavel Nekrasov
Date: Thu, 5 Nov 2026 15:14:27 +0300
Subject: [PATCH] Fix undefined 32-bit shifts in frameobject.c stack_swap()
Widen MASK and top to int64_t before shifting: shift can reach 60.
---
Lib/test/test_frame.py | 25 +++++++++++++++++++++++++
Objects/frameobject.c | 3 ++-
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index a0a11966ebf3c1..2eefb82f60e078 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -5,6 +5,7 @@
import textwrap
import threading
import unittest
+import warnings
import weakref
try:
import _testcapi
@@ -222,6 +223,30 @@ def test_f_lineno_del_segfault(self):
with self.assertRaises(AttributeError):
del f.f_lineno
+ def test_f_lineno_deep_stack_swap(self):
+ fors = " ".join(f"for v{i} in range(1)" for i in range(11))
+ params = ", ".join(f"v{i}=None" for i in range(11))
+ ns = {}
+ exec(f"def f({params}):\n return [0 {fors}]\n", ns)
+ f = ns["f"]
+
+ jumped = []
+
+ def tracer(frame, event, arg):
+ if (event == 'line' and frame.f_code is f.__code__
+ and not jumped):
+ jumped.append(None)
+ frame.f_lineno = frame.f_lineno
+ return tracer
+
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', RuntimeWarning)
+ sys.settrace(tracer)
+ try:
+ self.assertEqual(f(), [0])
+ finally:
+ sys.settrace(None)
+
def test_f_trace(self):
f, _, _ = self.make_frames()
def tracer(*args):
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 61be2beca22d15..38f117e61345df 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -1320,7 +1320,8 @@ stack_swap(int64_t stack, int n)
Kind to_swap = peek(stack, n);
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))
+ | ((int64_t)top << shift);
int64_t replaced_top = (replaced_low & ~MASK) | to_swap;
return replaced_top;
}