https://github.com/python/cpython/commit/da6730caa5023bd351a3f2a1b64b174c8e609489 commit: da6730caa5023bd351a3f2a1b64b174c8e609489 branch: main author: Sam Gross <colesb...@gmail.com> committer: colesbury <colesb...@gmail.com> date: 2025-03-24T09:49:39-04:00 summary:
gh-128421: Avoid TSAN warnings in `sys._current_frames()` (gh-131548) This tells TSAN not to sanitize `PyUnstable_InterpreterFrame_GetLine()`. There's a possible data race on the access to the frame's `instr_ptr` if the frame is currently executing. We don't really care about the race. In theory, we could use relaxed atomics for every access to `instr_ptr`, but that would create more code churn and current compilers are overly conservative with optimizations around relaxed atomic accesses. We also don't sanitize `_PyFrame_IsIncomplete()` because it accesses `instr_ptr` and is called from assertions within PyFrame_GetCode(). files: M Include/internal/pycore_interpframe.h M Include/pyport.h M Objects/obmalloc.c M Python/frame.c diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h index 41acd877aee873..097da4edee1aaf 100644 --- a/Include/internal/pycore_interpframe.h +++ b/Include/internal/pycore_interpframe.h @@ -189,8 +189,11 @@ _PyFrame_SetStackPointer(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer) * Frames on the frame stack are incomplete until the * first RESUME instruction. * Frames owned by a generator are always complete. + * + * NOTE: We allow racy accesses to the instruction pointer + * from other threads for sys._current_frames() and similar APIs. */ -static inline bool +static inline bool _Py_NO_SANITIZE_THREAD _PyFrame_IsIncomplete(_PyInterpreterFrame *frame) { if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) { diff --git a/Include/pyport.h b/Include/pyport.h index aabd094df54a74..e7162f4d9bf6b0 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -565,27 +565,45 @@ extern "C" { # if __has_feature(memory_sanitizer) # if !defined(_Py_MEMORY_SANITIZER) # define _Py_MEMORY_SANITIZER +# define _Py_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) # endif # endif # if __has_feature(address_sanitizer) # if !defined(_Py_ADDRESS_SANITIZER) # define _Py_ADDRESS_SANITIZER +# define _Py_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) # endif # endif # if __has_feature(thread_sanitizer) # if !defined(_Py_THREAD_SANITIZER) # define _Py_THREAD_SANITIZER +# define _Py_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) # endif # endif #elif defined(__GNUC__) # if defined(__SANITIZE_ADDRESS__) # define _Py_ADDRESS_SANITIZER +# define _Py_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) # endif # if defined(__SANITIZE_THREAD__) # define _Py_THREAD_SANITIZER +# define _Py_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) +# elif __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1) + // TSAN is supported since GCC 5.1, but __SANITIZE_THREAD__ macro + // is provided only since GCC 7. +# define _Py_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) # endif #endif +#ifndef _Py_NO_SANITIZE_ADDRESS +# define _Py_NO_SANITIZE_ADDRESS +#endif +#ifndef _Py_NO_SANITIZE_THREAD +# define _Py_NO_SANITIZE_THREAD +#endif +#ifndef _Py_NO_SANITIZE_MEMORY +# define _Py_NO_SANITIZE_MEMORY +#endif /* AIX has __bool__ redefined in it's system header file. */ #if defined(_AIX) && defined(__bool__) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index be85a20c00dfea..1285398142933a 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -470,40 +470,6 @@ _PyMem_ArenaFree(void *Py_UNUSED(ctx), void *ptr, /*******************************************/ -#if defined(__has_feature) /* Clang */ -# if __has_feature(address_sanitizer) /* is ASAN enabled? */ -# define _Py_NO_SANITIZE_ADDRESS \ - __attribute__((no_sanitize("address"))) -# endif -# if __has_feature(thread_sanitizer) /* is TSAN enabled? */ -# define _Py_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) -# endif -# if __has_feature(memory_sanitizer) /* is MSAN enabled? */ -# define _Py_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) -# endif -#elif defined(__GNUC__) -# if defined(__SANITIZE_ADDRESS__) /* GCC 4.8+, is ASAN enabled? */ -# define _Py_NO_SANITIZE_ADDRESS \ - __attribute__((no_sanitize_address)) -# endif - // TSAN is supported since GCC 5.1, but __SANITIZE_THREAD__ macro - // is provided only since GCC 7. -# if __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1) -# define _Py_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) -# endif -#endif - -#ifndef _Py_NO_SANITIZE_ADDRESS -# define _Py_NO_SANITIZE_ADDRESS -#endif -#ifndef _Py_NO_SANITIZE_THREAD -# define _Py_NO_SANITIZE_THREAD -#endif -#ifndef _Py_NO_SANITIZE_MEMORY -# define _Py_NO_SANITIZE_MEMORY -#endif - - #define ALLOCATORS_MUTEX (_PyRuntime.allocators.mutex) #define _PyMem_Raw (_PyRuntime.allocators.standard.raw) #define _PyMem (_PyRuntime.allocators.standard.mem) diff --git a/Python/frame.c b/Python/frame.c index b59cb4bbb5536e..558f92055bbfc1 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -139,7 +139,9 @@ PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame) return _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT); } -int +// NOTE: We allow racy accesses to the instruction pointer from other threads +// for sys._current_frames() and similar APIs. +int _Py_NO_SANITIZE_THREAD PyUnstable_InterpreterFrame_GetLine(_PyInterpreterFrame *frame) { int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT); _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com