diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index c851ff66487d5c8..3f3124400dfef73 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -96,9 +96,9 @@ Operating System Utilities Return true when the interpreter runs out of stack space. This is a reliable check, but is only available when :const:`USE_STACKCHECK` is defined (currently - on Windows using the Microsoft Visual C++ compiler). :const:`USE_STACKCHECK` - will be defined automatically; you should never change the definition in your - own code. + on macOS and Windows using the Microsoft Visual C++ compiler). + :const:`USE_STACKCHECK` will be defined automatically; you should never + change the definition in your own code. .. c:function:: PyOS_sighandler_t PyOS_getsig(int i) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index a686d640ae94bff..a9577bdd899f1a3 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -70,6 +70,9 @@ Summary -- Release highlights New Features ============ +* An interpreter on macOS system now supports ``USE_STACKCHECK``. The interpreter + detects running out of stack space and raise :class:`MemoryError`. + (Contributed by Dong-hee Na in :issue:`33955`.) Other Language Changes diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index d1792575c973789..2b1c85fac868d76 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -135,6 +135,10 @@ struct _ts { /* Unique thread state id. */ uint64_t id; +#if defined(__APPLE__) + size_t stack_space; + size_t last_stack_remain; +#endif /* XXX signal handlers should also be here */ }; diff --git a/Include/pythonrun.h b/Include/pythonrun.h index 46091e09216330b..3a0403ed7f7ff33 100644 --- a/Include/pythonrun.h +++ b/Include/pythonrun.h @@ -199,6 +199,11 @@ PyAPI_DATA(PyThreadState*) _PyOS_ReadlineTState; #define USE_STACKCHECK #endif +#if defined(__APPLE__) +/* Enable stack checking under macOS */ +#define USE_STACKCHECK +#endif + #ifdef USE_STACKCHECK /* Check that we aren't overflowing our stack */ PyAPI_FUNC(int) PyOS_CheckStack(void); diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 947c935f34728ad..08347f74c7c34f2 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -211,6 +211,18 @@ def test_recursionlimit(self): self.assertEqual(sys.getrecursionlimit(), 10000) sys.setrecursionlimit(oldlimit) + @unittest.skipUnless(sys.platform == 'darwin', 'macOS only') + def test_setrecursion_with_memory_error(self): + oldlimit = sys.getrecursionlimit() + def f(): + f() + try: + sys.setrecursionlimit(1 << 30) + with self.assertRaisesRegex(MemoryError, r'Stack overflow'): + f() + finally: + sys.setrecursionlimit(oldlimit) + def test_recursionlimit_recovery(self): if hasattr(sys, 'gettrace') and sys.gettrace(): self.skipTest('fatal error if run with a trace function') diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-07-02-18-53-31.bpo-33955.2FKRM2.rst b/Misc/NEWS.d/next/Core and Builtins/2018-07-02-18-53-31.bpo-33955.2FKRM2.rst new file mode 100644 index 000000000000000..ec46f0ce93a9629 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-07-02-18-53-31.bpo-33955.2FKRM2.rst @@ -0,0 +1,2 @@ +A interpreter on macOS system now supports USE_STACKCHECK. +The interpreter detects running out of stack space and raise MemoryError. diff --git a/Python/pystate.c b/Python/pystate.c index d792380de464989..7bbb5ebfd91cd1a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -607,7 +607,10 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->context_ver = 1; tstate->id = ++interp->tstate_next_unique_id; - +#if defined(__APPLE__) + tstate->stack_space = 0; + tstate->last_stack_remain = 0; +#endif if (init) { _PyThreadState_Init(tstate); } diff --git a/Python/pythonrun.c b/Python/pythonrun.c index b68a0b5572a1b11..03d395225c03756 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1649,6 +1649,76 @@ PyOS_CheckStack(void) #endif /* WIN32 && _MSC_VER */ +#if defined(__APPLE__) + +#define MAC_OS_10_9 13 +#define MAC_OS_10_10 14 +#define MAC_OS_10_11 15 + +#include + +static size_t +Get_Stack_Check_Size(const pthread_t *thread_self) +{ + size_t stack_space; + if (pthread_main_np() == 1) { + // On macOS 10.09 - 10.11 pthread_get_stacksize_np + // returns wrong stack size on main thread. + // ref: https://github.com/rust-lang/rust/issues/43347#issuecomment-316783599 + // ref: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8020753 + struct utsname sysinfo; + uname(&sysinfo); + const int major_version = atoi(sysinfo.release); + if (major_version == MAC_OS_10_9 || + major_version == MAC_OS_10_10 || + major_version == MAC_OS_10_11) { + struct rlimit limit; + getrlimit(RLIMIT_STACK, &limit); + stack_space = limit.rlim_cur; + } + else { + stack_space = pthread_get_stacksize_np(*thread_self); + } + } + else { + stack_space = pthread_get_stacksize_np(*thread_self); + } + return stack_space; +} + +/* + * Return non-zero when we run out of memory on the stack; zero otherwise. + */ +int +PyOS_CheckStack(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + if (tstate->stack_space == 0) { + const pthread_t thread_self = pthread_self(); + size_t stack_space = Get_Stack_Check_Size(&thread_self); + tstate->stack_space = stack_space; + tstate->last_stack_remain = stack_space; + } + const uintptr_t end = (uintptr_t)pthread_get_stackaddr_np(pthread_self()); + const uintptr_t frame = (uintptr_t)__builtin_frame_address(0); + const size_t remains = tstate->stack_space - (end - frame); + size_t required_stack_space; + + if (remains > tstate->last_stack_remain) { + required_stack_space = remains - tstate->last_stack_remain; + } + else { + required_stack_space = tstate->last_stack_remain - remains; + } + + tstate->last_stack_remain = remains; + if (remains >= required_stack_space) { + return 0; + } + return 1; +} +#endif /* __APPLE__ */ + /* Alternate implementations can be added here... */ #endif /* USE_STACKCHECK */