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
14    mydir = TestBase.compute_mydir(__file__)
15
16    @expectedFailureAll(
17        oslist=["linux"],
18        bugnumber="non-core functionality, need to reenable and fix later (DES 2014.11.07)")
19    @expectedFailureNetBSD
20    @skipIfFreeBSD  # llvm.org/pr21136 runtimes not yet available by default
21    @skipIfRemote
22    @skipUnlessThreadSanitizer
23    @expectedFailureDarwin # FIXME: https://reviews.llvm.org/D112603
24    @no_debug_info_test
25    def test(self):
26        self.build()
27        self.tsan_tests()
28
29    def setUp(self):
30        # Call super's setUp().
31        TestBase.setUp(self)
32        self.line_malloc = line_number('main.c', '// malloc line')
33        self.line_thread1 = line_number('main.c', '// thread1 line')
34        self.line_thread2 = line_number('main.c', '// thread2 line')
35
36    def tsan_tests(self):
37        exe = self.getBuildArtifact("a.out")
38        self.expect(
39            "file " + exe,
40            patterns=["Current executable set to .*a.out"])
41
42        self.runCmd("run")
43
44        stop_reason = self.dbg.GetSelectedTarget().process.GetSelectedThread().GetStopReason()
45        if stop_reason == lldb.eStopReasonExec:
46            # On OS X 10.10 and older, we need to re-exec to enable
47            # interceptors.
48            self.runCmd("continue")
49
50        # the stop reason of the thread should be breakpoint.
51        self.expect("thread list", "A data race should be detected",
52                    substrs=['stopped', 'stop reason = Data race detected'])
53
54        self.assertEqual(
55            self.dbg.GetSelectedTarget().process.GetSelectedThread().GetStopReason(),
56            lldb.eStopReasonInstrumentation)
57
58        # test that the TSan dylib is present
59        self.expect(
60            "image lookup -n __tsan_get_current_report",
61            "__tsan_get_current_report should be present",
62            substrs=['1 match found'])
63
64        # We should be stopped in __tsan_on_report
65        process = self.dbg.GetSelectedTarget().process
66        thread = process.GetSelectedThread()
67        frame = thread.GetSelectedFrame()
68        self.assertIn("__tsan_on_report", frame.GetFunctionName())
69
70        # The stopped thread backtrace should contain either line1 or line2
71        # from main.c.
72        found = False
73        for i in range(0, thread.GetNumFrames()):
74            frame = thread.GetFrameAtIndex(i)
75            if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
76                if frame.GetLineEntry().GetLine() == self.line_thread1:
77                    found = True
78                if frame.GetLineEntry().GetLine() == self.line_thread2:
79                    found = True
80        self.assertTrue(found)
81
82        self.expect(
83            "thread info -s",
84            "The extended stop info should contain the TSan provided fields",
85            substrs=[
86                "instrumentation_class",
87                "description",
88                "mops"])
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        self.assertEqual(backtraces.GetSize(), 0)
100
101        backtraces = thread.GetStopReasonExtendedBacktraces(
102            lldb.eInstrumentationRuntimeTypeThreadSanitizer)
103        self.assertTrue(backtraces.GetSize() >= 2)
104
105        # First backtrace is a memory operation
106        thread = backtraces.GetThreadAtIndex(0)
107        found = False
108        for i in range(0, thread.GetNumFrames()):
109            frame = thread.GetFrameAtIndex(i)
110            if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
111                if frame.GetLineEntry().GetLine() == self.line_thread1:
112                    found = True
113                if frame.GetLineEntry().GetLine() == self.line_thread2:
114                    found = True
115        self.assertTrue(found)
116
117        # Second backtrace is a memory operation
118        thread = backtraces.GetThreadAtIndex(1)
119        found = False
120        for i in range(0, thread.GetNumFrames()):
121            frame = thread.GetFrameAtIndex(i)
122            if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
123                if frame.GetLineEntry().GetLine() == self.line_thread1:
124                    found = True
125                if frame.GetLineEntry().GetLine() == self.line_thread2:
126                    found = True
127        self.assertTrue(found)
128
129        self.runCmd("continue")
130
131        # the stop reason of the thread should be a SIGABRT.
132        self.expect("thread list", "We should be stopped due a SIGABRT",
133                    substrs=['stopped', 'stop reason = signal SIGABRT'])
134
135        # test that we're in pthread_kill now (TSan abort the process)
136        self.expect("thread list", "We should be stopped in pthread_kill",
137                    substrs=['pthread_kill'])
138