Skip to content

Commit 2ac1b48

Browse files
pitrousamety
andauthored
[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 88cb972) * Remove doc for configure option (leave it hidden in this branch) --------- Co-authored-by: Samet YASLAN <[email protected]>
1 parent 2dbc77e commit 2ac1b48

File tree

7 files changed

+74
-10
lines changed

7 files changed

+74
-10
lines changed

Include/pyport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,11 @@ extern char * _getpty(int *, int, mode_t, int);
748748
# define _Py_ADDRESS_SANITIZER
749749
# endif
750750
# endif
751+
# if __has_feature(thread_sanitizer)
752+
# if !defined(_Py_THREAD_SANITIZER)
753+
# define _Py_THREAD_SANITIZER
754+
# endif
755+
# endif
751756
#elif defined(__GNUC__)
752757
# if defined(__SANITIZE_ADDRESS__)
753758
# define _Py_ADDRESS_SANITIZER

Lib/test/libregrtest/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ def get_build_info():
349349
# --with-undefined-behavior-sanitizer
350350
if support.check_sanitizer(ub=True):
351351
sanitizers.append("UBSAN")
352+
# --with-thread-sanitizer
353+
if support.check_sanitizer(thread=True):
354+
sanitizers.append("TSAN")
352355
if sanitizers:
353356
build.append('+'.join(sanitizers))
354357

@@ -649,19 +652,23 @@ def display_header(use_resources: tuple[str, ...],
649652
asan = support.check_sanitizer(address=True)
650653
msan = support.check_sanitizer(memory=True)
651654
ubsan = support.check_sanitizer(ub=True)
655+
tsan = support.check_sanitizer(thread=True)
652656
sanitizers = []
653657
if asan:
654658
sanitizers.append("address")
655659
if msan:
656660
sanitizers.append("memory")
657661
if ubsan:
658662
sanitizers.append("undefined behavior")
663+
if tsan:
664+
sanitizers.append("thread")
659665
if sanitizers:
660666
print(f"== sanitizers: {', '.join(sanitizers)}")
661667
for sanitizer, env_var in (
662668
(asan, "ASAN_OPTIONS"),
663669
(msan, "MSAN_OPTIONS"),
664670
(ubsan, "UBSAN_OPTIONS"),
671+
(tsan, "TSAN_OPTIONS"),
665672
):
666673
options= os.environ.get(env_var)
667674
if sanitizer and options is not None:

Lib/test/support/__init__.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,10 @@ def skip_if_buildbot(reason=None):
391391
isbuildbot = False
392392
return unittest.skipIf(isbuildbot, reason)
393393

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

399399

400400
cflags = sysconfig.get_config_var('CFLAGS') or ''
@@ -411,18 +411,23 @@ def check_sanitizer(*, address=False, memory=False, ub=False):
411411
'-fsanitize=undefined' in cflags or
412412
'--with-undefined-behavior-sanitizer' in config_args
413413
)
414+
thread_sanitizer = (
415+
'-fsanitize=thread' in cflags or
416+
'--with-thread-sanitizer' in config_args
417+
)
414418
return (
415419
(memory and memory_sanitizer) or
416420
(address and address_sanitizer) or
417-
(ub and ub_sanitizer)
421+
(ub and ub_sanitizer) or
422+
(thread and thread_sanitizer)
418423
)
419424

420425

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

428433
# 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):
431436

432437

433438
def set_sanitizer_env_var(env, option):
434-
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS'):
439+
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS', 'TSAN_OPTIONS'):
435440
if name in env:
436441
env[name] += f':{option}'
437442
else:

Lib/test/test_io.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,7 +1708,8 @@ def test_seek_character_device_file(self):
17081708
class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
17091709
tp = io.BufferedReader
17101710

1711-
@skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing "
1711+
@skip_if_sanitizer(memory=True, address=True, thread=True,
1712+
reason="sanitizer defaults to crashing "
17121713
"instead of returning NULL for malloc failure.")
17131714
def test_constructor(self):
17141715
BufferedReaderTest.test_constructor(self)
@@ -2075,7 +2076,8 @@ def test_slow_close_from_thread(self):
20752076
class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
20762077
tp = io.BufferedWriter
20772078

2078-
@skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing "
2079+
@skip_if_sanitizer(memory=True, address=True, thread=True,
2080+
reason="sanitizer defaults to crashing "
20792081
"instead of returning NULL for malloc failure.")
20802082
def test_constructor(self):
20812083
BufferedWriterTest.test_constructor(self)
@@ -2596,7 +2598,8 @@ def test_interleaved_readline_write(self):
25962598
class CBufferedRandomTest(BufferedRandomTest, SizeofTest):
25972599
tp = io.BufferedRandom
25982600

2599-
@skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing "
2601+
@skip_if_sanitizer(memory=True, address=True, thread=True,
2602+
reason="sanitizer defaults to crashing "
26002603
"instead of returning NULL for malloc failure.")
26012604
def test_constructor(self):
26022605
BufferedRandomTest.test_constructor(self)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for thread sanitizer (TSAN)

configure

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3230,6 +3230,24 @@ AC_MSG_RESULT([no])
32303230
with_ubsan="no"
32313231
])
32323232

3233+
AC_MSG_CHECKING([for --with-thread-sanitizer])
3234+
AC_ARG_WITH(
3235+
[thread_sanitizer],
3236+
[AS_HELP_STRING(
3237+
[--with-thread-sanitizer],
3238+
[enable ThreadSanitizer data race detector, 'tsan' (default is no)]
3239+
)],
3240+
[
3241+
AC_MSG_RESULT([$withval])
3242+
BASECFLAGS="-fsanitize=thread $BASECFLAGS"
3243+
LDFLAGS="-fsanitize=thread $LDFLAGS"
3244+
with_tsan="yes"
3245+
],
3246+
[
3247+
AC_MSG_RESULT([no])
3248+
with_tsan="no"
3249+
])
3250+
32333251
# Set info about shared libraries.
32343252
AC_SUBST([SHLIB_SUFFIX])
32353253
AC_SUBST([LDSHARED])

0 commit comments

Comments
 (0)