Skip to content

Commit 001cf08

Browse files
sametyaisk
authored andcommitted
pythongh-112536: Add support for thread sanitizer (TSAN) (pythongh-112648)
1 parent 9bc74be commit 001cf08

File tree

8 files changed

+81
-10
lines changed

8 files changed

+81
-10
lines changed

Diff for: Doc/using/configure.rst

+7
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,13 @@ Debug options
745745

746746
.. versionadded:: 3.6
747747

748+
.. option:: --with-thread-sanitizer
749+
750+
Enable ThreadSanitizer data race detector, ``tsan``
751+
(default is no).
752+
753+
.. versionadded:: 3.13
754+
748755

749756
Linker options
750757
--------------

Diff for: Include/pyport.h

+5
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,11 @@ extern "C" {
563563
# define _Py_ADDRESS_SANITIZER
564564
# endif
565565
# endif
566+
# if __has_feature(thread_sanitizer)
567+
# if !defined(_Py_THREAD_SANITIZER)
568+
# define _Py_THREAD_SANITIZER
569+
# endif
570+
# endif
566571
#elif defined(__GNUC__)
567572
# if defined(__SANITIZE_ADDRESS__)
568573
# define _Py_ADDRESS_SANITIZER

Diff for: Lib/test/libregrtest/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,9 @@ def get_build_info():
340340
# --with-undefined-behavior-sanitizer
341341
if support.check_sanitizer(ub=True):
342342
sanitizers.append("UBSAN")
343+
# --with-thread-sanitizer
344+
if support.check_sanitizer(thread=True):
345+
sanitizers.append("TSAN")
343346
if sanitizers:
344347
build.append('+'.join(sanitizers))
345348

@@ -634,19 +637,23 @@ def display_header(use_resources: tuple[str, ...],
634637
asan = support.check_sanitizer(address=True)
635638
msan = support.check_sanitizer(memory=True)
636639
ubsan = support.check_sanitizer(ub=True)
640+
tsan = support.check_sanitizer(thread=True)
637641
sanitizers = []
638642
if asan:
639643
sanitizers.append("address")
640644
if msan:
641645
sanitizers.append("memory")
642646
if ubsan:
643647
sanitizers.append("undefined behavior")
648+
if tsan:
649+
sanitizers.append("thread")
644650
if sanitizers:
645651
print(f"== sanitizers: {', '.join(sanitizers)}")
646652
for sanitizer, env_var in (
647653
(asan, "ASAN_OPTIONS"),
648654
(msan, "MSAN_OPTIONS"),
649655
(ubsan, "UBSAN_OPTIONS"),
656+
(tsan, "TSAN_OPTIONS"),
650657
):
651658
options= os.environ.get(env_var)
652659
if sanitizer and options is not None:

Diff for: Lib/test/support/__init__.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,10 @@ def skip_if_buildbot(reason=None):
392392
isbuildbot = False
393393
return unittest.skipIf(isbuildbot, reason)
394394

395-
def check_sanitizer(*, address=False, memory=False, ub=False):
395+
def check_sanitizer(*, address=False, memory=False, ub=False, thread=False):
396396
"""Returns True if Python is compiled with sanitizer support"""
397-
if not (address or memory or ub):
398-
raise ValueError('At least one of address, memory, or ub must be True')
397+
if not (address or memory or ub or thread):
398+
raise ValueError('At least one of address, memory, ub or thread must be True')
399399

400400

401401
cflags = sysconfig.get_config_var('CFLAGS') or ''
@@ -412,18 +412,23 @@ def check_sanitizer(*, address=False, memory=False, ub=False):
412412
'-fsanitize=undefined' in cflags or
413413
'--with-undefined-behavior-sanitizer' in config_args
414414
)
415+
thread_sanitizer = (
416+
'-fsanitize=thread' in cflags or
417+
'--with-thread-sanitizer' in config_args
418+
)
415419
return (
416420
(memory and memory_sanitizer) or
417421
(address and address_sanitizer) or
418-
(ub and ub_sanitizer)
422+
(ub and ub_sanitizer) or
423+
(thread and thread_sanitizer)
419424
)
420425

421426

422-
def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False):
427+
def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False, thread=False):
423428
"""Decorator raising SkipTest if running with a sanitizer active."""
424429
if not reason:
425430
reason = 'not working with sanitizers active'
426-
skip = check_sanitizer(address=address, memory=memory, ub=ub)
431+
skip = check_sanitizer(address=address, memory=memory, ub=ub, thread=thread)
427432
return unittest.skipIf(skip, reason)
428433

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

433438

434439
def set_sanitizer_env_var(env, option):
435-
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS'):
440+
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS', 'TSAN_OPTIONS'):
436441
if name in env:
437442
env[name] += f':{option}'
438443
else:

Diff for: Lib/test/test_io.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,8 @@ def test_truncate_on_read_only(self):
16541654
class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
16551655
tp = io.BufferedReader
16561656

1657-
@skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing "
1657+
@skip_if_sanitizer(memory=True, address=True, thread=True,
1658+
reason="sanitizer defaults to crashing "
16581659
"instead of returning NULL for malloc failure.")
16591660
def test_constructor(self):
16601661
BufferedReaderTest.test_constructor(self)
@@ -2021,7 +2022,8 @@ def test_slow_close_from_thread(self):
20212022
class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
20222023
tp = io.BufferedWriter
20232024

2024-
@skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing "
2025+
@skip_if_sanitizer(memory=True, address=True, thread=True,
2026+
reason="sanitizer defaults to crashing "
20252027
"instead of returning NULL for malloc failure.")
20262028
def test_constructor(self):
20272029
BufferedWriterTest.test_constructor(self)
@@ -2520,7 +2522,8 @@ def test_interleaved_readline_write(self):
25202522
class CBufferedRandomTest(BufferedRandomTest, SizeofTest):
25212523
tp = io.BufferedRandom
25222524

2523-
@skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing "
2525+
@skip_if_sanitizer(memory=True, address=True, thread=True,
2526+
reason="sanitizer defaults to crashing "
25242527
"instead of returning NULL for malloc failure.")
25252528
def test_constructor(self):
25262529
BufferedRandomTest.test_constructor(self)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for thread sanitizer (TSAN)

Diff for: configure

+25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: configure.ac

+18
Original file line numberDiff line numberDiff line change
@@ -3067,6 +3067,24 @@ AC_MSG_RESULT([no])
30673067
with_ubsan="no"
30683068
])
30693069

3070+
AC_MSG_CHECKING([for --with-thread-sanitizer])
3071+
AC_ARG_WITH(
3072+
[thread_sanitizer],
3073+
[AS_HELP_STRING(
3074+
[--with-thread-sanitizer],
3075+
[enable ThreadSanitizer data race detector, 'tsan' (default is no)]
3076+
)],
3077+
[
3078+
AC_MSG_RESULT([$withval])
3079+
BASECFLAGS="-fsanitize=thread $BASECFLAGS"
3080+
LDFLAGS="-fsanitize=thread $LDFLAGS"
3081+
with_tsan="yes"
3082+
],
3083+
[
3084+
AC_MSG_RESULT([no])
3085+
with_tsan="no"
3086+
])
3087+
30703088
# Set info about shared libraries.
30713089
AC_SUBST([SHLIB_SUFFIX])
30723090
AC_SUBST([LDSHARED])

0 commit comments

Comments
 (0)