From 76898abba6043a80803fcf67971355bf4693a2fa Mon Sep 17 00:00:00 2001 From: "xinyu.wang" Date: Sun, 8 Sep 2024 01:16:43 +0200 Subject: [PATCH] backport thread sanitizer to 3.10 --- Doc/using/configure.rst | 7 ++++++ Include/Python.h | 8 ++++++ Lib/test/support/__init__.py | 17 ++++++++----- Lib/test/test_io.py | 9 ++++--- ...-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst | 1 + configure | 25 +++++++++++++++++++ configure.ac | 18 +++++++++++++ 7 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index ab33e0a06f5145..99033289d1398d 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -336,6 +336,13 @@ Debug options .. versionadded:: 3.6 +.. option:: --with-thread-sanitizer + + Enable ThreadSanitizer data race detector, ``tsan`` + (default is no). + + .. versionadded:: 3.13 + Linker options -------------- diff --git a/Include/Python.h b/Include/Python.h index d3186c32e35500..035c75f1df3700 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -62,10 +62,18 @@ # define _Py_ADDRESS_SANITIZER # endif # endif +# if __has_feature(thread_sanitizer) +# if !defined(_Py_THREAD_SANITIZER) +# define _Py_THREAD_SANITIZER +# endif +# endif #elif defined(__GNUC__) # if defined(__SANITIZE_ADDRESS__) # define _Py_ADDRESS_SANITIZER # endif +# if defined(__SANITIZE_THREAD__) +# define _Py_THREAD_SANITIZER +# endif #endif #include "pymath.h" diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index b7cf1e28581ed9..f38ecac3e4dda2 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -367,10 +367,10 @@ def wrapper(*args, **kw): return decorator -def check_sanitizer(*, address=False, memory=False, ub=False): +def check_sanitizer(*, address=False, memory=False, ub=False, thread=False): """Returns True if Python is compiled with sanitizer support""" - if not (address or memory or ub): - raise ValueError('At least one of address, memory, or ub must be True') + if not (address or memory or ub or thread): + raise ValueError('At least one of address, memory, ub or thread must be True') _cflags = sysconfig.get_config_var('CFLAGS') or '' @@ -387,18 +387,23 @@ def check_sanitizer(*, address=False, memory=False, ub=False): '-fsanitize=undefined' in _cflags or '--with-undefined-behavior-sanitizer' in _config_args ) + thread_sanitizer = ( + '-fsanitize=thread' in _cflags or + '--with-thread-sanitizer' in _config_args + ) return ( (memory and memory_sanitizer) or (address and address_sanitizer) or - (ub and ub_sanitizer) + (ub and ub_sanitizer) or + (thread and thread_sanitizer) ) -def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False): +def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False, thread=False): """Decorator raising SkipTest if running with a sanitizer active.""" if not reason: reason = 'not working with sanitizers active' - skip = check_sanitizer(address=address, memory=memory, ub=ub) + skip = check_sanitizer(address=address, memory=memory, ub=ub, thread=thread) return unittest.skipIf(skip, reason) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 8dae85ac4f54c5..a9235b09545045 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1547,7 +1547,8 @@ def test_truncate_on_read_only(self): class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader - @skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing " + @skip_if_sanitizer(memory=True, address=True, thread=True, + reason="sanitizer defaults to crashing " "instead of returning NULL for malloc failure.") def test_constructor(self): BufferedReaderTest.test_constructor(self) @@ -1912,7 +1913,8 @@ def test_slow_close_from_thread(self): class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter - @skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing " + @skip_if_sanitizer(memory=True, address=True, thread=True, + reason="sanitizer defaults to crashing " "instead of returning NULL for malloc failure.") def test_constructor(self): BufferedWriterTest.test_constructor(self) @@ -2411,7 +2413,8 @@ def test_interleaved_readline_write(self): class CBufferedRandomTest(BufferedRandomTest, SizeofTest): tp = io.BufferedRandom - @skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing " + @skip_if_sanitizer(memory=True, address=True, thread=True, + reason="sanitizer defaults to crashing " "instead of returning NULL for malloc failure.") def test_constructor(self): BufferedRandomTest.test_constructor(self) diff --git a/Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst b/Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst new file mode 100644 index 00000000000000..a136eb47584993 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst @@ -0,0 +1 @@ +Add support for thread sanitizer (TSAN) diff --git a/configure b/configure index 4b71c4e00f8919..f74f50731aa886 100755 --- a/configure +++ b/configure @@ -832,6 +832,7 @@ with_lto with_address_sanitizer with_memory_sanitizer with_undefined_behavior_sanitizer +with_thread_sanitizer with_hash_algorithm with_tzpath with_libs @@ -1560,6 +1561,8 @@ Optional Packages: --with-undefined-behavior-sanitizer enable UndefinedBehaviorSanitizer undefined behaviour detector, 'ubsan' (default is no) + --with-thread-sanitizer enable ThreadSanitizer data race detector, 'tsan' + (default is no) --with-hash-algorithm=[fnv|siphash24] select hash algorithm for use in Python/pyhash.c (default is SipHash24) @@ -9684,6 +9687,28 @@ with_ubsan="no" fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-thread-sanitizer" >&5 +printf %s "checking for --with-thread-sanitizer... " >&6; } + +# Check whether --with-thread_sanitizer was given. +if test ${with_thread_sanitizer+y} +then : + withval=$with_thread_sanitizer; +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $withval" >&5 +printf "%s\n" "$withval" >&6; } +BASECFLAGS="-fsanitize=thread $BASECFLAGS" +LDFLAGS="-fsanitize=thread $LDFLAGS" +with_tsan="yes" + +else $as_nop + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +with_tsan="no" + +fi + + # Set info about shared libraries. diff --git a/configure.ac b/configure.ac index ac3be3850a9bbc..6b74e837e864e5 100644 --- a/configure.ac +++ b/configure.ac @@ -2612,6 +2612,24 @@ AC_MSG_RESULT(no) with_ubsan="no" ]) +AC_MSG_CHECKING([for --with-thread-sanitizer]) +AC_ARG_WITH( + [thread_sanitizer], + [AS_HELP_STRING( + [--with-thread-sanitizer], + [enable ThreadSanitizer data race detector, 'tsan' (default is no)] + )], +[ +AC_MSG_RESULT([$withval]) +BASECFLAGS="-fsanitize=thread $BASECFLAGS" +LDFLAGS="-fsanitize=thread $LDFLAGS" +with_tsan="yes" +], +[ +AC_MSG_RESULT([no]) +with_tsan="no" +]) + # Set info about shared libraries. AC_SUBST(SHLIB_SUFFIX) AC_SUBST(LDSHARED)