mirror of
https://github.com/python/cpython.git
synced 2025-08-10 11:58:39 +00:00
[3.12] gh-112536: Add support for thread sanitizer (TSAN) (gh-112648) (#116924)
* [3.12] gh-112536: Add support for thread sanitizer (TSAN) (gh-112648)
(cherry picked from commit 88cb972000
)
* Remove doc for configure option (leave it hidden in this branch)
---------
Co-authored-by: Samet YASLAN <sametyaslan@gmail.com>
This commit is contained in:
parent
2dbc77e1ec
commit
2ac1b48a04
7 changed files with 74 additions and 10 deletions
|
@ -748,6 +748,11 @@ extern char * _getpty(int *, int, mode_t, int);
|
||||||
# define _Py_ADDRESS_SANITIZER
|
# define _Py_ADDRESS_SANITIZER
|
||||||
# endif
|
# endif
|
||||||
# endif
|
# endif
|
||||||
|
# if __has_feature(thread_sanitizer)
|
||||||
|
# if !defined(_Py_THREAD_SANITIZER)
|
||||||
|
# define _Py_THREAD_SANITIZER
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
#elif defined(__GNUC__)
|
#elif defined(__GNUC__)
|
||||||
# if defined(__SANITIZE_ADDRESS__)
|
# if defined(__SANITIZE_ADDRESS__)
|
||||||
# define _Py_ADDRESS_SANITIZER
|
# define _Py_ADDRESS_SANITIZER
|
||||||
|
|
|
@ -349,6 +349,9 @@ def get_build_info():
|
||||||
# --with-undefined-behavior-sanitizer
|
# --with-undefined-behavior-sanitizer
|
||||||
if support.check_sanitizer(ub=True):
|
if support.check_sanitizer(ub=True):
|
||||||
sanitizers.append("UBSAN")
|
sanitizers.append("UBSAN")
|
||||||
|
# --with-thread-sanitizer
|
||||||
|
if support.check_sanitizer(thread=True):
|
||||||
|
sanitizers.append("TSAN")
|
||||||
if sanitizers:
|
if sanitizers:
|
||||||
build.append('+'.join(sanitizers))
|
build.append('+'.join(sanitizers))
|
||||||
|
|
||||||
|
@ -649,6 +652,7 @@ def display_header(use_resources: tuple[str, ...],
|
||||||
asan = support.check_sanitizer(address=True)
|
asan = support.check_sanitizer(address=True)
|
||||||
msan = support.check_sanitizer(memory=True)
|
msan = support.check_sanitizer(memory=True)
|
||||||
ubsan = support.check_sanitizer(ub=True)
|
ubsan = support.check_sanitizer(ub=True)
|
||||||
|
tsan = support.check_sanitizer(thread=True)
|
||||||
sanitizers = []
|
sanitizers = []
|
||||||
if asan:
|
if asan:
|
||||||
sanitizers.append("address")
|
sanitizers.append("address")
|
||||||
|
@ -656,12 +660,15 @@ def display_header(use_resources: tuple[str, ...],
|
||||||
sanitizers.append("memory")
|
sanitizers.append("memory")
|
||||||
if ubsan:
|
if ubsan:
|
||||||
sanitizers.append("undefined behavior")
|
sanitizers.append("undefined behavior")
|
||||||
|
if tsan:
|
||||||
|
sanitizers.append("thread")
|
||||||
if sanitizers:
|
if sanitizers:
|
||||||
print(f"== sanitizers: {', '.join(sanitizers)}")
|
print(f"== sanitizers: {', '.join(sanitizers)}")
|
||||||
for sanitizer, env_var in (
|
for sanitizer, env_var in (
|
||||||
(asan, "ASAN_OPTIONS"),
|
(asan, "ASAN_OPTIONS"),
|
||||||
(msan, "MSAN_OPTIONS"),
|
(msan, "MSAN_OPTIONS"),
|
||||||
(ubsan, "UBSAN_OPTIONS"),
|
(ubsan, "UBSAN_OPTIONS"),
|
||||||
|
(tsan, "TSAN_OPTIONS"),
|
||||||
):
|
):
|
||||||
options= os.environ.get(env_var)
|
options= os.environ.get(env_var)
|
||||||
if sanitizer and options is not None:
|
if sanitizer and options is not None:
|
||||||
|
|
|
@ -391,10 +391,10 @@ def skip_if_buildbot(reason=None):
|
||||||
isbuildbot = False
|
isbuildbot = False
|
||||||
return unittest.skipIf(isbuildbot, reason)
|
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"""
|
"""Returns True if Python is compiled with sanitizer support"""
|
||||||
if not (address or memory or ub):
|
if not (address or memory or ub or thread):
|
||||||
raise ValueError('At least one of address, memory, or ub must be True')
|
raise ValueError('At least one of address, memory, ub or thread must be True')
|
||||||
|
|
||||||
|
|
||||||
cflags = sysconfig.get_config_var('CFLAGS') or ''
|
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
|
'-fsanitize=undefined' in cflags or
|
||||||
'--with-undefined-behavior-sanitizer' in config_args
|
'--with-undefined-behavior-sanitizer' in config_args
|
||||||
)
|
)
|
||||||
|
thread_sanitizer = (
|
||||||
|
'-fsanitize=thread' in cflags or
|
||||||
|
'--with-thread-sanitizer' in config_args
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
(memory and memory_sanitizer) or
|
(memory and memory_sanitizer) or
|
||||||
(address and address_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."""
|
"""Decorator raising SkipTest if running with a sanitizer active."""
|
||||||
if not reason:
|
if not reason:
|
||||||
reason = 'not working with sanitizers active'
|
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)
|
return unittest.skipIf(skip, reason)
|
||||||
|
|
||||||
# gh-89363: True if fork() can hang if Python is built with Address Sanitizer
|
# gh-89363: True if fork() can hang if Python is built with Address Sanitizer
|
||||||
|
@ -431,7 +436,7 @@ HAVE_ASAN_FORK_BUG = check_sanitizer(address=True)
|
||||||
|
|
||||||
|
|
||||||
def set_sanitizer_env_var(env, option):
|
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:
|
if name in env:
|
||||||
env[name] += f':{option}'
|
env[name] += f':{option}'
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1708,7 +1708,8 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
|
||||||
class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
|
class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
|
||||||
tp = io.BufferedReader
|
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.")
|
"instead of returning NULL for malloc failure.")
|
||||||
def test_constructor(self):
|
def test_constructor(self):
|
||||||
BufferedReaderTest.test_constructor(self)
|
BufferedReaderTest.test_constructor(self)
|
||||||
|
@ -2075,7 +2076,8 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
|
||||||
class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
|
class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
|
||||||
tp = io.BufferedWriter
|
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.")
|
"instead of returning NULL for malloc failure.")
|
||||||
def test_constructor(self):
|
def test_constructor(self):
|
||||||
BufferedWriterTest.test_constructor(self)
|
BufferedWriterTest.test_constructor(self)
|
||||||
|
@ -2596,7 +2598,8 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
|
||||||
class CBufferedRandomTest(BufferedRandomTest, SizeofTest):
|
class CBufferedRandomTest(BufferedRandomTest, SizeofTest):
|
||||||
tp = io.BufferedRandom
|
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.")
|
"instead of returning NULL for malloc failure.")
|
||||||
def test_constructor(self):
|
def test_constructor(self):
|
||||||
BufferedRandomTest.test_constructor(self)
|
BufferedRandomTest.test_constructor(self)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for thread sanitizer (TSAN)
|
25
configure
generated
vendored
25
configure
generated
vendored
|
@ -1089,6 +1089,7 @@ with_dsymutil
|
||||||
with_address_sanitizer
|
with_address_sanitizer
|
||||||
with_memory_sanitizer
|
with_memory_sanitizer
|
||||||
with_undefined_behavior_sanitizer
|
with_undefined_behavior_sanitizer
|
||||||
|
with_thread_sanitizer
|
||||||
with_hash_algorithm
|
with_hash_algorithm
|
||||||
with_tzpath
|
with_tzpath
|
||||||
with_libs
|
with_libs
|
||||||
|
@ -1868,6 +1869,8 @@ Optional Packages:
|
||||||
--with-undefined-behavior-sanitizer
|
--with-undefined-behavior-sanitizer
|
||||||
enable UndefinedBehaviorSanitizer undefined
|
enable UndefinedBehaviorSanitizer undefined
|
||||||
behaviour detector, 'ubsan' (default is no)
|
behaviour detector, 'ubsan' (default is no)
|
||||||
|
--with-thread-sanitizer enable ThreadSanitizer data race detector, 'tsan'
|
||||||
|
(default is no)
|
||||||
--with-hash-algorithm=[fnv|siphash13|siphash24]
|
--with-hash-algorithm=[fnv|siphash13|siphash24]
|
||||||
select hash algorithm for use in Python/pyhash.c
|
select hash algorithm for use in Python/pyhash.c
|
||||||
(default is SipHash13)
|
(default is SipHash13)
|
||||||
|
@ -12661,6 +12664,28 @@ with_ubsan="no"
|
||||||
fi
|
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.
|
# Set info about shared libraries.
|
||||||
|
|
||||||
|
|
||||||
|
|
18
configure.ac
18
configure.ac
|
@ -3230,6 +3230,24 @@ AC_MSG_RESULT([no])
|
||||||
with_ubsan="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.
|
# Set info about shared libraries.
|
||||||
AC_SUBST([SHLIB_SUFFIX])
|
AC_SUBST([SHLIB_SUFFIX])
|
||||||
AC_SUBST([LDSHARED])
|
AC_SUBST([LDSHARED])
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue