Skip to content

Commit 5e5db03

Browse files
committed
Probe all pages when extending stack limits
1 parent 7b36f59 commit 5e5db03

File tree

5 files changed

+63
-41
lines changed

5 files changed

+63
-41
lines changed

Include/internal/pycore_ceval.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) {
222222
(void)tstate;
223223
}
224224

225-
PyAPI_FUNC(void) _Py_InitializeRecursionCheck(PyThreadState *tstate);
225+
PyAPI_FUNC(void) _Py_UpdateRecursionLimits(PyThreadState *tstate);
226226

227227
static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate, int margin_count) {
228228
char here;
@@ -231,7 +231,7 @@ static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate, int margin_co
231231
return 0;
232232
}
233233
if (tstate->c_stack_hard_limit == 0) {
234-
_Py_InitializeRecursionCheck(tstate);
234+
_Py_UpdateRecursionLimits(tstate);
235235
}
236236
return here_addr <= tstate->c_stack_soft_limit + margin_count * PYOS_STACK_MARGIN_BYTES;
237237
}
@@ -327,7 +327,7 @@ void _Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit);
327327

328328
PyAPI_FUNC(PyObject *) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyStackRef right, double value);
329329

330-
extern int _PyOS_CheckStack(int words);
330+
extern void _Py_StackProbe(uintptr_t from, int bytes, int *probed);
331331

332332
#ifdef __cplusplus
333333
}

Include/pythonrun.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ PyAPI_FUNC(void) PyErr_DisplayException(PyObject *);
2121
/* Stuff with no proper home (yet) */
2222
PyAPI_DATA(int) (*PyOS_InputHook)(void);
2323

24-
/* Stack size, in "pointers" (so we get extra safety margins
25-
on 64-bit platforms). On a 32-bit platform, this translates
26-
to an 16k margin. */
27-
#define PYOS_STACK_MARGIN 4096
24+
/* Stack size, in "pointers". This must be large enough, so
25+
* no two calls to check recursion depth are more than this far
26+
* apart. In practice, that means it must be larger than the C
27+
* stack consumption of PyEval_EvalDefault */
28+
#if defined(Py_DEBUG) && defined(WIN32)
29+
# define PYOS_STACK_MARGIN 3072
30+
#else
31+
# define PYOS_STACK_MARGIN 2048
32+
#endif
2833
#define PYOS_STACK_MARGIN_BYTES (PYOS_STACK_MARGIN * sizeof(void *))
2934

30-
#if defined(WIN32) && !defined(MS_WIN64) && !defined(_M_ARM) && defined(_MSC_VER) && _MSC_VER >= 1300
35+
#if defined(WIN32)
3136
/* Enable stack checking under Microsoft C */
3237
// When changing the platforms, ensure PyOS_CheckStack() docs are still correct
3338
#define USE_STACKCHECK

Python/ceval.c

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,8 @@ _Py_EnterRecursiveCallUnchecked(PyThreadState *tstate)
322322

323323
#if defined(__s390x__)
324324
# define Py_C_STACK_SIZE 320000
325-
#elif defined(_WIN32) && defined(_M_ARM64)
326-
# define Py_C_STACK_SIZE 400000
327325
#elif defined(_WIN32)
328-
# define Py_C_STACK_SIZE 1200000
326+
// Don't define Py_C_STACK_SIZE, use probing instead
329327
#elif defined(__ANDROID__)
330328
# define Py_C_STACK_SIZE 1200000
331329
#elif defined(__sparc__)
@@ -342,32 +340,30 @@ _Py_EnterRecursiveCallUnchecked(PyThreadState *tstate)
342340
#endif
343341

344342
void
345-
_Py_InitializeRecursionCheck(PyThreadState *tstate)
343+
_Py_UpdateRecursionLimits(PyThreadState *tstate)
346344
{
347345
char here;
348346
uintptr_t here_addr = (uintptr_t)&here;
349-
tstate->c_stack_top = here_addr;
347+
int to_probe = PYOS_STACK_MARGIN_BYTES * 2;
350348
#ifdef USE_STACKCHECK
351-
if (_PyOS_CheckStack(PYOS_STACK_MARGIN * 2) == 0) {
352-
tstate->c_stack_soft_limit = here_addr - PYOS_STACK_MARGIN_BYTES;
353-
return;
354-
}
355-
int margin = PYOS_STACK_MARGIN;
356-
assert(tstate->c_stack_soft_limit != UINTPTR_MAX);
357-
if (_PyOS_CheckStack(margin)) {
358-
margin = PYOS_STACK_MARGIN/2;
359-
}
360-
else {
361-
if (_PyOS_CheckStack(PYOS_STACK_MARGIN*3/2) == 0) {
362-
margin = PYOS_STACK_MARGIN*3/2;
363-
}
349+
if (tstate->c_stack_top == 0) {
350+
assert(tstate->c_stack_soft_limit == UINTPTR_MAX);
351+
tstate->c_stack_top = _Py_SIZE_ROUND_UP(here_addr, 4096);
352+
tstate->c_stack_soft_limit = tstate->c_stack_top;
353+
to_probe = PYOS_STACK_MARGIN_BYTES * 4;
354+
}
355+
int depth;
356+
uintptr_t implicit_hard_limit = tstate->c_stack_soft_limit - PYOS_STACK_MARGIN_BYTES;
357+
_Py_StackProbe(implicit_hard_limit, to_probe, &depth);
358+
tstate->c_stack_soft_limit = implicit_hard_limit - depth + PYOS_STACK_MARGIN_BYTES * 2;
359+
if (depth != to_probe) {
360+
tstate->c_stack_hard_limit = tstate->c_stack_soft_limit - PYOS_STACK_MARGIN_BYTES;
364361
}
365-
tstate->c_stack_hard_limit = here_addr - margin * sizeof(void *);
366-
tstate->c_stack_soft_limit = tstate->c_stack_hard_limit + PYOS_STACK_MARGIN_BYTES;
367362
#else
368-
assert(tstate->c_stack_soft_limit == UINTPTR_MAX);
369-
tstate->c_stack_soft_limit = here_addr - Py_C_STACK_SIZE;
370-
tstate->c_stack_hard_limit = here_addr - (Py_C_STACK_SIZE + PYOS_STACK_MARGIN_BYTES);
363+
assert(tstate->c_stack_top == 0);
364+
tstate->c_stack_top = _Py_SIZE_ROUND_UP(here_addr, 4096);
365+
tstate->c_stack_soft_limit = tstate->c_stack_top - Py_C_STACK_SIZE;
366+
tstate->c_stack_hard_limit = tstate->c_stack_top - (Py_C_STACK_SIZE + PYOS_STACK_MARGIN_BYTES);
371367
#endif
372368
}
373369

@@ -380,15 +376,15 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
380376
uintptr_t here_addr = (uintptr_t)&here;
381377
assert(tstate->c_stack_soft_limit != 0);
382378
if (tstate->c_stack_hard_limit == 0) {
383-
_Py_InitializeRecursionCheck(tstate);
379+
_Py_UpdateRecursionLimits(tstate);
384380
}
385381
if (here_addr >= tstate->c_stack_soft_limit) {
386382
return 0;
387383
}
388384
assert(tstate->c_stack_hard_limit != 0);
389385
if (here_addr < tstate->c_stack_hard_limit) {
390386
/* Overflowing while handling an overflow. Give up. */
391-
int kbytes_used = (tstate->c_stack_top - here_addr)/1024;
387+
int kbytes_used = (int)(tstate->c_stack_top - here_addr)/1024;
392388
char buffer[80];
393389
snprintf(buffer, 80, "Unrecoverable stack overflow (used %d kB)%s", kbytes_used, where);
394390
Py_FatalError(buffer);
@@ -397,7 +393,7 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
397393
return 0;
398394
}
399395
else {
400-
int kbytes_used = (tstate->c_stack_top - here_addr)/1024;
396+
int kbytes_used = (int)(tstate->c_stack_top - here_addr)/1024;
401397
tstate->recursion_headroom++;
402398
_PyErr_Format(tstate, PyExc_RecursionError,
403399
"Stack overflow (used %d kB)%s",

Python/pystate.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
14931493
tstate->py_recursion_limit = interp->ceval.recursion_limit;
14941494
tstate->py_recursion_remaining = interp->ceval.recursion_limit;
14951495
tstate->c_stack_soft_limit = UINTPTR_MAX;
1496+
tstate->c_stack_top = 0;
14961497
tstate->c_stack_hard_limit = 0;
14971498

14981499
tstate->exc_info = &tstate->exc_state;

Python/pythonrun.c

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,14 +1552,24 @@ _Py_SourceAsString(PyObject *cmd, const char *funcname, const char *what, PyComp
15521552
#include <malloc.h>
15531553
#include <excpt.h>
15541554

1555-
int _PyOS_CheckStack(int words)
1555+
void _Py_StackProbe(uintptr_t from, int bytes, int *probed)
15561556
{
1557+
assert((bytes & 4095) == 0);
1558+
int depth = 4096;
1559+
char here;
1560+
uintptr_t here_addr = (uintptr_t)&here;
15571561
__try
15581562
{
1559-
/* alloca throws a stack overflow exception if there's
1560-
not enough space left on the stack */
1561-
alloca(words * sizeof(void *));
1562-
return 0;
1563+
while (depth <= bytes) {
1564+
uintptr_t probe_point = from - depth + 64;
1565+
if (probe_point < here_addr) {
1566+
/* alloca throws a stack overflow exception if there's
1567+
* not enough space left on the stack */
1568+
alloca(here_addr - probe_point);
1569+
}
1570+
*probed = depth;
1571+
depth += 4096;
1572+
}
15631573
}
15641574
__except (GetExceptionCode() == STATUS_STACK_OVERFLOW ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
15651575
{
@@ -1569,15 +1579,25 @@ int _PyOS_CheckStack(int words)
15691579
Py_FatalError("Could not reset the stack!");
15701580
}
15711581
}
1572-
return 1;
15731582
}
15741583

15751584
/*
15761585
* Return non-zero when we run out of memory on the stack; zero otherwise.
15771586
*/
15781587
int PyOS_CheckStack(void)
15791588
{
1580-
return _PyOS_CheckStack(PYOS_STACK_MARGIN);
1589+
char here;
1590+
uintptr_t here_addr = (uintptr_t)&here;
1591+
uintptr_t from = _Py_SIZE_ROUND_UP(here_addr, 4096);
1592+
int depth;
1593+
_Py_StackProbe(from, PYOS_STACK_MARGIN_BYTES*2, &depth);
1594+
assert(depth <= PYOS_STACK_MARGIN_BYTES*2);
1595+
if (depth == PYOS_STACK_MARGIN_BYTES*2) {
1596+
return 0;
1597+
}
1598+
else {
1599+
return -1;
1600+
}
15811601
}
15821602

15831603
#endif /* WIN32 && _MSC_VER */

0 commit comments

Comments
 (0)