Skip to content

Commit c7b0fec

Browse files
authored
[3.9] bpo-43710: Rollback the 3.9 bpo-42500 fix, it broke the ABI in 3.9.3 (#25179)
This reverts commit 8b795ab. It changed the PyThreadState structure size, breaking the ABI in 3.9.3.
1 parent de0b2b1 commit c7b0fec

File tree

9 files changed

+79
-74
lines changed

9 files changed

+79
-74
lines changed

Include/cpython/pystate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ struct _ts {
5858
/* Borrowed reference to the current frame (it can be NULL) */
5959
PyFrameObject *frame;
6060
int recursion_depth;
61-
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
61+
char overflowed; /* The stack has overflowed. Allow 50 more calls
62+
to handle the runtime error. */
6263
char recursion_critical; /* The current calls must not cause
6364
a stack overflow. */
6465
int stackcheck_counter;

Include/internal/pycore_ceval.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,24 @@ static inline int _Py_EnterRecursiveCall_inline(const char *where) {
9090

9191
#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_inline(where)
9292

93+
/* Compute the "lower-water mark" for a recursion limit. When
94+
* Py_LeaveRecursiveCall() is called with a recursion depth below this mark,
95+
* the overflowed flag is reset to 0. */
96+
static inline int _Py_RecursionLimitLowerWaterMark(int limit) {
97+
if (limit > 200) {
98+
return (limit - 50);
99+
}
100+
else {
101+
return (3 * (limit >> 2));
102+
}
103+
}
104+
93105
static inline void _Py_LeaveRecursiveCall(PyThreadState *tstate) {
94106
tstate->recursion_depth--;
107+
int limit = tstate->interp->ceval.recursion_limit;
108+
if (tstate->recursion_depth < _Py_RecursionLimitLowerWaterMark(limit)) {
109+
tstate->overflowed = 0;
110+
}
95111
}
96112

97113
static inline void _Py_LeaveRecursiveCall_inline(void) {

Lib/test/test_exceptions.py

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ def gen():
10431043
# tstate->recursion_depth is equal to (recursion_limit - 1)
10441044
# and is equal to recursion_limit when _gen_throw() calls
10451045
# PyErr_NormalizeException().
1046-
recurse(setrecursionlimit(depth + 2) - depth)
1046+
recurse(setrecursionlimit(depth + 2) - depth - 1)
10471047
finally:
10481048
sys.setrecursionlimit(recursionlimit)
10491049
print('Done.')
@@ -1073,54 +1073,6 @@ def test_recursion_normalizing_infinite_exception(self):
10731073
b'while normalizing an exception', err)
10741074
self.assertIn(b'Done.', out)
10751075

1076-
1077-
def test_recursion_in_except_handler(self):
1078-
1079-
def set_relative_recursion_limit(n):
1080-
depth = 1
1081-
while True:
1082-
try:
1083-
sys.setrecursionlimit(depth)
1084-
except RecursionError:
1085-
depth += 1
1086-
else:
1087-
break
1088-
sys.setrecursionlimit(depth+n)
1089-
1090-
def recurse_in_except():
1091-
try:
1092-
1/0
1093-
except:
1094-
recurse_in_except()
1095-
1096-
def recurse_after_except():
1097-
try:
1098-
1/0
1099-
except:
1100-
pass
1101-
recurse_after_except()
1102-
1103-
def recurse_in_body_and_except():
1104-
try:
1105-
recurse_in_body_and_except()
1106-
except:
1107-
recurse_in_body_and_except()
1108-
1109-
recursionlimit = sys.getrecursionlimit()
1110-
try:
1111-
set_relative_recursion_limit(10)
1112-
for func in (recurse_in_except, recurse_after_except, recurse_in_body_and_except):
1113-
with self.subTest(func=func):
1114-
try:
1115-
func()
1116-
except RecursionError:
1117-
pass
1118-
else:
1119-
self.fail("Should have raised a RecursionError")
1120-
finally:
1121-
sys.setrecursionlimit(recursionlimit)
1122-
1123-
11241076
@cpython_only
11251077
def test_recursion_normalizing_with_no_memory(self):
11261078
# Issue #30697. Test that in the abort that occurs when there is no
@@ -1157,7 +1109,7 @@ def raiseMemError():
11571109
except MemoryError as e:
11581110
tb = e.__traceback__
11591111
else:
1160-
self.fail("Should have raised a MemoryError")
1112+
self.fail("Should have raises a MemoryError")
11611113
return traceback.format_tb(tb)
11621114

11631115
tb1 = raiseMemError()

Lib/test/test_sys.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ def test_recursionlimit_recovery(self):
219219
def f():
220220
f()
221221
try:
222-
for depth in (50, 75, 100, 250, 1000):
222+
for depth in (10, 25, 50, 75, 100, 250, 1000):
223223
try:
224224
sys.setrecursionlimit(depth)
225225
except RecursionError:
@@ -229,17 +229,17 @@ def f():
229229

230230
# Issue #5392: test stack overflow after hitting recursion
231231
# limit twice
232-
with self.assertRaises(RecursionError):
233-
f()
234-
with self.assertRaises(RecursionError):
235-
f()
232+
self.assertRaises(RecursionError, f)
233+
self.assertRaises(RecursionError, f)
236234
finally:
237235
sys.setrecursionlimit(oldlimit)
238236

239237
@test.support.cpython_only
240238
def test_setrecursionlimit_recursion_depth(self):
241239
# Issue #25274: Setting a low recursion limit must be blocked if the
242-
# current recursion depth is already higher than limit.
240+
# current recursion depth is already higher than the "lower-water
241+
# mark". Otherwise, it may not be possible anymore to
242+
# reset the overflowed flag to 0.
243243

244244
from _testinternalcapi import get_recursion_depth
245245

@@ -260,10 +260,42 @@ def set_recursion_limit_at_depth(depth, limit):
260260
sys.setrecursionlimit(1000)
261261

262262
for limit in (10, 25, 50, 75, 100, 150, 200):
263-
set_recursion_limit_at_depth(limit, limit)
263+
# formula extracted from _Py_RecursionLimitLowerWaterMark()
264+
if limit > 200:
265+
depth = limit - 50
266+
else:
267+
depth = limit * 3 // 4
268+
set_recursion_limit_at_depth(depth, limit)
264269
finally:
265270
sys.setrecursionlimit(oldlimit)
266271

272+
# The error message is specific to CPython
273+
@test.support.cpython_only
274+
def test_recursionlimit_fatalerror(self):
275+
# A fatal error occurs if a second recursion limit is hit when recovering
276+
# from a first one.
277+
code = textwrap.dedent("""
278+
import sys
279+
280+
def f():
281+
try:
282+
f()
283+
except RecursionError:
284+
f()
285+
286+
sys.setrecursionlimit(%d)
287+
f()""")
288+
with test.support.SuppressCrashReport():
289+
for i in (50, 1000):
290+
sub = subprocess.Popen([sys.executable, '-c', code % i],
291+
stderr=subprocess.PIPE)
292+
err = sub.communicate()[1]
293+
self.assertTrue(sub.returncode, sub.returncode)
294+
self.assertIn(
295+
b"Fatal Python error: _Py_CheckRecursiveCall: "
296+
b"Cannot recover from stack overflow",
297+
err)
298+
267299
def test_getwindowsversion(self):
268300
# Raise SkipTest if sys doesn't have getwindowsversion attribute
269301
test.support.get_attribute(sys, "getwindowsversion")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Reverted the fix for https://bugs.python.org/issue42500 as it changed the
2+
PyThreadState struct size and broke the 3.9.x ABI in the 3.9.3 release
3+
(visible on 32-bit platforms using binaries compiled using an earlier
4+
version of Python 3.9.x headers).

Python/ceval.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -793,22 +793,23 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
793793
_Py_CheckRecursionLimit = recursion_limit;
794794
}
795795
#endif
796-
if (tstate->recursion_headroom) {
796+
if (tstate->recursion_critical)
797+
/* Somebody asked that we don't check for recursion. */
798+
return 0;
799+
if (tstate->overflowed) {
797800
if (tstate->recursion_depth > recursion_limit + 50) {
798801
/* Overflowing while handling an overflow. Give up. */
799802
Py_FatalError("Cannot recover from stack overflow.");
800803
}
804+
return 0;
801805
}
802-
else {
803-
if (tstate->recursion_depth > recursion_limit) {
804-
tstate->recursion_headroom++;
805-
_PyErr_Format(tstate, PyExc_RecursionError,
806-
"maximum recursion depth exceeded%s",
807-
where);
808-
tstate->recursion_headroom--;
809-
--tstate->recursion_depth;
810-
return -1;
811-
}
806+
if (tstate->recursion_depth > recursion_limit) {
807+
--tstate->recursion_depth;
808+
tstate->overflowed = 1;
809+
_PyErr_Format(tstate, PyExc_RecursionError,
810+
"maximum recursion depth exceeded%s",
811+
where);
812+
return -1;
812813
}
813814
return 0;
814815
}

Python/errors.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,12 @@ _PyErr_NormalizeException(PyThreadState *tstate, PyObject **exc,
290290
PyObject **val, PyObject **tb)
291291
{
292292
int recursion_depth = 0;
293-
tstate->recursion_headroom++;
294293
PyObject *type, *value, *initial_tb;
295294

296295
restart:
297296
type = *exc;
298297
if (type == NULL) {
299298
/* There was no exception, so nothing to do. */
300-
tstate->recursion_headroom--;
301299
return;
302300
}
303301

@@ -349,7 +347,6 @@ _PyErr_NormalizeException(PyThreadState *tstate, PyObject **exc,
349347
}
350348
*exc = type;
351349
*val = value;
352-
tstate->recursion_headroom--;
353350
return;
354351

355352
error:

Python/pystate.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ new_threadstate(PyInterpreterState *interp, int init)
576576

577577
tstate->frame = NULL;
578578
tstate->recursion_depth = 0;
579-
tstate->recursion_headroom = 0;
579+
tstate->overflowed = 0;
580580
tstate->recursion_critical = 0;
581581
tstate->stackcheck_counter = 0;
582582
tstate->tracing = 0;

Python/sysmodule.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,7 @@ static PyObject *
11601160
sys_setrecursionlimit_impl(PyObject *module, int new_limit)
11611161
/*[clinic end generated code: output=35e1c64754800ace input=b0f7a23393924af3]*/
11621162
{
1163+
int mark;
11631164
PyThreadState *tstate = _PyThreadState_GET();
11641165

11651166
if (new_limit < 1) {
@@ -1177,7 +1178,8 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit)
11771178
Reject too low new limit if the current recursion depth is higher than
11781179
the new low-water mark. Otherwise it may not be possible anymore to
11791180
reset the overflowed flag to 0. */
1180-
if (tstate->recursion_depth >= new_limit) {
1181+
mark = _Py_RecursionLimitLowerWaterMark(new_limit);
1182+
if (tstate->recursion_depth >= mark) {
11811183
_PyErr_Format(tstate, PyExc_RecursionError,
11821184
"cannot set the recursion limit to %i at "
11831185
"the recursion depth %i: the limit is too low",

0 commit comments

Comments
 (0)