xref: /llvm-project/lldb/test/API/functionalities/tsan/basic/TestTsanBasic.py (revision 9c2468821ec51defd09c246fea4a47886fff8c01)
1"""
2Tests basic ThreadSanitizer support (detecting a data race).
3"""
4
5import lldb
6from lldbsuite.test.lldbtest import *
7from lldbsuite.test.decorators import *
8import lldbsuite.test.lldbutil as lldbutil
9import json
10
11
12class TsanBasicTestCase(TestBase):
13    @expectedFailureAll(
14        oslist=["linux"],
15        bugnumber="non-core functionality, need to reenable and fix later (DES 2014.11.07)",
16    )
17    @expectedFailureNetBSD
18    @skipIfFreeBSD  # llvm.org/pr21136 runtimes not yet available by default
19    @skipIfRemote
20    @skipUnlessThreadSanitizer
21    @no_debug_info_test
22    def test(self):
23        self.build()
24        self.tsan_tests()
25
26    def setUp(self):
27        # Call super's setUp().
28        TestBase.setUp(self)
29        self.line_malloc = line_number("main.c", "// malloc line")
30        self.line_thread1 = line_number("main.c", "// thread1 line")
31        self.line_thread2 = line_number("main.c", "// thread2 line")
32
33    def tsan_tests(self):
34        exe = self.getBuildArtifact("a.out")
35        self.expect("file " + exe, patterns=["Current executable set to .*a.out"])
36
37        self.runCmd("run")
38
39        stop_reason = (
40            self.dbg.GetSelectedTarget().process.GetSelectedThread().GetStopReason()
41        )
42        if stop_reason == lldb.eStopReasonExec:
43            # On OS X 10.10 and older, we need to re-exec to enable
44            # interceptors.
45            self.runCmd("continue")
46
47        # the stop reason of the thread should be breakpoint.
48        self.expect(
49            "thread list",
50            "A data race should be detected",
51            substrs=["stopped", "stop reason = Data race detected"],
52        )
53
54        self.assertEqual(
55            self.dbg.GetSelectedTarget().process.GetSelectedThread().GetStopReason(),
56            lldb.eStopReasonInstrumentation,
57        )
58
59        # test that the TSan dylib is present
60        self.expect(
61            "image lookup -n __tsan_get_current_report",
62            "__tsan_get_current_report should be present",
63            substrs=["1 match found"],
64        )
65
66        # We should be stopped in __tsan_on_report
67        process = self.dbg.GetSelectedTarget().process
68        thread = process.GetSelectedThread()
69        frame = thread.GetSelectedFrame()
70        self.assertIn("__tsan_on_report", frame.GetFunctionName())
71
72        # The stopped thread backtrace should contain either line1 or line2
73        # from main.c.
74        found = False
75        for i in range(0, thread.GetNumFrames()):
76            frame = thread.GetFrameAtIndex(i)
77            if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
78                if frame.GetLineEntry().GetLine() == self.line_thread1:
79                    found = True
80                if frame.GetLineEntry().GetLine() == self.line_thread2:
81                    found = True
82        self.assertTrue(found)
83
84        self.expect(
85            "thread info -s",
86            "The extended stop info should contain the TSan provided fields",
87            substrs=["instrumentation_class", "description", "mops"],
88        )
89
90        output_lines = self.res.GetOutput().split("\n")
91        json_line = "\n".join(output_lines[2:])
92        data = json.loads(json_line)
93        self.assertEqual(data["instrumentation_class"], "ThreadSanitizer")
94        self.assertEqual(data["issue_type"], "data-race")
95        self.assertEqual(len(data["mops"]), 2)
96
97        backtraces = thread.GetStopReasonExtendedBacktraces(
98            lldb.eInstrumentationRuntimeTypeAddressSanitizer
99        )
100        self.assertEqual(backtraces.GetSize(), 0)
101
102        backtraces = thread.GetStopReasonExtendedBacktraces(
103            lldb.eInstrumentationRuntimeTypeThreadSanitizer
104        )
105        self.assertGreaterEqual(backtraces.GetSize(), 2)
106
107        # First backtrace is a memory operation
108        thread = backtraces.GetThreadAtIndex(0)
109        found = False
110        for i in range(0, thread.GetNumFrames()):
111            frame = thread.GetFrameAtIndex(i)
112            if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
113                if frame.GetLineEntry().GetLine() == self.line_thread1:
114                    found = True
115                if frame.GetLineEntry().GetLine() == self.line_thread2:
116                    found = True
117        self.assertTrue(found)
118
119        # Second backtrace is a memory operation
120        thread = backtraces.GetThreadAtIndex(1)
121        found = False
122        for i in range(0, thread.GetNumFrames()):
123            frame = thread.GetFrameAtIndex(i)
124            if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
125                if frame.GetLineEntry().GetLine() == self.line_thread1:
126                    found = True
127                if frame.GetLineEntry().GetLine() == self.line_thread2:
128                    found = True
129        self.assertTrue(found)
130
131        self.runCmd("continue")
132
133        # the stop reason of the thread should be a SIGABRT.
134        self.expect(
135            "thread list",
136            "We should be stopped due a SIGABRT",
137            substrs=["stopped", "stop reason = signal SIGABRT"],
138        )
139
140        # test that we're in pthread_kill now (TSan abort the process)
141        self.expect(
142            "thread list",
143            "We should be stopped in pthread_kill",
144            substrs=["pthread_kill"],
145        )
146