Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.12] gh-112536: Add support for thread sanitizer (TSAN) (gh-112648) #116924

Merged
merged 2 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions Include/pyport.h
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,11 @@ extern char * _getpty(int *, int, mode_t, int);
# 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 @@ -349,6 +349,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 @@ -649,19 +652,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 @@ -391,10 +391,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 @@ -411,18 +411,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 @@ -431,7 +436,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 @@ -1708,7 +1708,8 @@ def test_seek_character_device_file(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 @@ -2075,7 +2076,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 @@ -2596,7 +2598,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 @@ -3230,6 +3230,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