Skip to content

Commit

Permalink
pythongh-112536: Add support for thread sanitizer (TSAN) (pythongh-11…
Browse files Browse the repository at this point in the history
  • Loading branch information
samety authored Dec 30, 2023
1 parent f46987b commit 88cb972
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 10 deletions.
7 changes: 7 additions & 0 deletions Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,13 @@ Debug options

.. versionadded:: 3.6

.. option:: --with-thread-sanitizer

Enable ThreadSanitizer data race detector, ``tsan``
(default is no).

.. versionadded:: 3.13


Linker options
--------------
Expand Down
5 changes: 5 additions & 0 deletions Include/pyport.h
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ extern "C" {
# 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
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/libregrtest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ def get_build_info():
# --with-undefined-behavior-sanitizer
if support.check_sanitizer(ub=True):
sanitizers.append("UBSAN")
# --with-thread-sanitizer
if support.check_sanitizer(thread=True):
sanitizers.append("TSAN")
if sanitizers:
build.append('+'.join(sanitizers))

Expand Down Expand Up @@ -634,19 +637,23 @@ def display_header(use_resources: tuple[str, ...],
asan = support.check_sanitizer(address=True)
msan = support.check_sanitizer(memory=True)
ubsan = support.check_sanitizer(ub=True)
tsan = support.check_sanitizer(thread=True)
sanitizers = []
if asan:
sanitizers.append("address")
if msan:
sanitizers.append("memory")
if ubsan:
sanitizers.append("undefined behavior")
if tsan:
sanitizers.append("thread")
if sanitizers:
print(f"== sanitizers: {', '.join(sanitizers)}")
for sanitizer, env_var in (
(asan, "ASAN_OPTIONS"),
(msan, "MSAN_OPTIONS"),
(ubsan, "UBSAN_OPTIONS"),
(tsan, "TSAN_OPTIONS"),
):
options= os.environ.get(env_var)
if sanitizer and options is not None:
Expand Down
19 changes: 12 additions & 7 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,10 @@ def skip_if_buildbot(reason=None):
isbuildbot = False
return unittest.skipIf(isbuildbot, reason)

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 ''
Expand All @@ -412,18 +412,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)

# gh-89363: True if fork() can hang if Python is built with Address Sanitizer
Expand All @@ -432,7 +437,7 @@ def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False):


def set_sanitizer_env_var(env, option):
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS'):
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS', 'TSAN_OPTIONS'):
if name in env:
env[name] += f':{option}'
else:
Expand Down
9 changes: 6 additions & 3 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -1654,7 +1654,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)
Expand Down Expand Up @@ -2021,7 +2022,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)
Expand Down Expand Up @@ -2520,7 +2522,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for thread sanitizer (TSAN)
25 changes: 25 additions & 0 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -3067,6 +3067,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])
Expand Down

0 comments on commit 88cb972

Please sign in to comment.