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
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Include/pyport.h
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions Lib/test/libregrtest/utils.py
Original file line number Diff line number Diff line change
@@ -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))

@@ -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:
19 changes: 12 additions & 7 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
@@ -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 ''
@@ -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
@@ -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:
9 changes: 6 additions & 3 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
@@ -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)
@@ -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)
@@ -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)
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
@@ -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])