1"""
2Test breakpoint conditions with 'breakpoint modify -c <expr> id'.
3"""
4
5import lldb
6from lldbsuite.test.decorators import *
7from lldbsuite.test.lldbtest import *
8from lldbsuite.test import lldbutil
9
10
11class BreakpointConditionsTestCase(TestBase):
12    def test_breakpoint_condition_and_run_command(self):
13        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
14        self.build()
15        self.breakpoint_conditions()
16
17    def test_breakpoint_condition_inline_and_run_command(self):
18        """Exercise breakpoint condition inline with 'breakpoint set'."""
19        self.build()
20        self.breakpoint_conditions(inline=True)
21
22    @add_test_categories(["pyapi"])
23    def test_breakpoint_condition_and_python_api(self):
24        """Use Python APIs to set breakpoint conditions."""
25        self.build()
26        self.breakpoint_conditions_python()
27
28    @add_test_categories(["pyapi"])
29    def test_breakpoint_invalid_condition_and_python_api(self):
30        """Use Python APIs to set breakpoint conditions."""
31        self.build()
32        self.breakpoint_invalid_conditions_python()
33
34    def setUp(self):
35        # Call super's setUp().
36        TestBase.setUp(self)
37        # Find the line number to of function 'c'.
38        self.line1 = line_number(
39            "main.c", '// Find the line number of function "c" here.'
40        )
41        self.line2 = line_number(
42            "main.c", "// Find the line number of c's parent call here."
43        )
44
45    def breakpoint_conditions(self, inline=False):
46        """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'."""
47        exe = self.getBuildArtifact("a.out")
48        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
49
50        if inline:
51            # Create a breakpoint by function name 'c' and set the condition.
52            lldbutil.run_break_set_by_symbol(
53                self,
54                "c",
55                extra_options="-c 'val == 3'",
56                num_expected_locations=1,
57                sym_exact=True,
58            )
59        else:
60            # Create a breakpoint by function name 'c'.
61            lldbutil.run_break_set_by_symbol(
62                self, "c", num_expected_locations=1, sym_exact=True
63            )
64
65            # And set a condition on the breakpoint to stop on when 'val == 3'.
66            self.runCmd("breakpoint modify -c 'val == 3' 1")
67
68        # Now run the program.
69        self.runCmd("run", RUN_SUCCEEDED)
70
71        # The process should be stopped at this point.
72        self.expect("process status", PROCESS_STOPPED, patterns=["Process .* stopped"])
73
74        # 'frame variable --show-types val' should return 3 due to breakpoint condition.
75        self.expect(
76            "frame variable --show-types val",
77            VARIABLES_DISPLAYED_CORRECTLY,
78            startstr="(int) val = 3",
79        )
80
81        # Also check the hit count, which should be 3, by design.
82        self.expect(
83            "breakpoint list -f",
84            BREAKPOINT_HIT_ONCE,
85            substrs=["resolved = 1", "Condition: val == 3", "hit count = 1"],
86        )
87
88        # The frame #0 should correspond to main.c:36, the executable statement
89        # in function name 'c'.  And the parent frame should point to
90        # main.c:24.
91        self.expect(
92            "thread backtrace",
93            STOPPED_DUE_TO_BREAKPOINT_CONDITION,
94            # substrs = ["stop reason = breakpoint"],
95            patterns=[
96                "frame #0.*main.c:%d" % self.line1,
97                "frame #1.*main.c:%d" % self.line2,
98            ],
99        )
100
101        # Test that "breakpoint modify -c ''" clears the condition for the last
102        # created breakpoint, so that when the breakpoint hits, val == 1.
103        self.runCmd("process kill")
104        self.runCmd("breakpoint modify -c ''")
105        self.expect(
106            "breakpoint list -f",
107            BREAKPOINT_STATE_CORRECT,
108            matching=False,
109            substrs=["Condition:"],
110        )
111
112        # Now run the program again.
113        self.runCmd("run", RUN_SUCCEEDED)
114
115        # The process should be stopped at this point.
116        self.expect("process status", PROCESS_STOPPED, patterns=["Process .* stopped"])
117
118        # 'frame variable --show-types val' should return 1 since it is the first breakpoint hit.
119        self.expect(
120            "frame variable --show-types val",
121            VARIABLES_DISPLAYED_CORRECTLY,
122            startstr="(int) val = 1",
123        )
124
125        self.runCmd("process kill")
126
127    def breakpoint_conditions_python(self):
128        """Use Python APIs to set breakpoint conditions."""
129        target = self.createTestTarget()
130
131        # Now create a breakpoint on main.c by name 'c'.
132        breakpoint = target.BreakpointCreateByName("c", "a.out")
133        self.trace("breakpoint:", breakpoint)
134        self.assertTrue(
135            breakpoint and breakpoint.GetNumLocations() == 1, VALID_BREAKPOINT
136        )
137
138        # We didn't associate a thread index with the breakpoint, so it should
139        # be invalid.
140        self.assertEqual(
141            breakpoint.GetThreadIndex(),
142            lldb.UINT32_MAX,
143            "The thread index should be invalid",
144        )
145        # The thread name should be invalid, too.
146        self.assertIsNone(
147            breakpoint.GetThreadName(), "The thread name should be invalid"
148        )
149
150        # Let's set the thread index for this breakpoint and verify that it is,
151        # indeed, being set correctly.
152        # There's only one thread for the process.
153        breakpoint.SetThreadIndex(1)
154        self.assertEqual(
155            breakpoint.GetThreadIndex(), 1, "The thread index has been set correctly"
156        )
157
158        # Get the breakpoint location from breakpoint after we verified that,
159        # indeed, it has one location.
160        location = breakpoint.GetLocationAtIndex(0)
161        self.assertTrue(location and location.IsEnabled(), VALID_BREAKPOINT_LOCATION)
162
163        # Set the condition on the breakpoint location.
164        location.SetCondition("val == 3")
165        self.expect(location.GetCondition(), exe=False, startstr="val == 3")
166
167        # Now launch the process, and do not stop at entry point.
168        process = target.LaunchSimple(None, None, self.get_process_working_directory())
169        self.assertTrue(process, PROCESS_IS_VALID)
170
171        # Frame #0 should be on self.line1 and the break condition should hold.
172        from lldbsuite.test.lldbutil import get_stopped_thread
173
174        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
175        self.assertTrue(
176            thread.IsValid(),
177            "There should be a thread stopped due to breakpoint condition",
178        )
179
180        frame0 = thread.GetFrameAtIndex(0)
181        var = frame0.FindValue("val", lldb.eValueTypeVariableArgument)
182        self.assertEqual(
183            frame0.GetLineEntry().GetLine(),
184            self.line1,
185            "The debugger stopped on the correct line",
186        )
187        self.assertEqual(var.GetValue(), "3")
188
189        # The hit count for the breakpoint should be 1.
190        self.assertEqual(breakpoint.GetHitCount(), 1)
191
192        # Test that the condition expression didn't create a result variable:
193        options = lldb.SBExpressionOptions()
194        value = frame0.EvaluateExpression("$0", options)
195        self.assertTrue(
196            value.GetError().Fail(), "Conditions should not make result variables."
197        )
198        process.Continue()
199
200    def breakpoint_invalid_conditions_python(self):
201        """Use Python APIs to set breakpoint conditions."""
202        exe = self.getBuildArtifact("a.out")
203
204        # Create a target by the debugger.
205        target = self.dbg.CreateTarget(exe)
206        self.assertTrue(target, VALID_TARGET)
207
208        # Now create a breakpoint on main.c by name 'c'.
209        breakpoint = target.BreakpointCreateByName("c", "a.out")
210        self.trace("breakpoint:", breakpoint)
211        self.assertTrue(
212            breakpoint and breakpoint.GetNumLocations() == 1, VALID_BREAKPOINT
213        )
214
215        # Set the condition on the breakpoint.
216        breakpoint.SetCondition("no_such_variable == not_this_one_either")
217        self.expect(
218            breakpoint.GetCondition(),
219            exe=False,
220            startstr="no_such_variable == not_this_one_either",
221        )
222
223        # Now launch the process, and do not stop at entry point.
224        process = target.LaunchSimple(None, None, self.get_process_working_directory())
225        self.assertTrue(process, PROCESS_IS_VALID)
226
227        # Frame #0 should be on self.line1 and the break condition should hold.
228        from lldbsuite.test.lldbutil import get_stopped_thread
229
230        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
231        self.assertTrue(
232            thread.IsValid(),
233            "There should be a thread stopped due to breakpoint condition",
234        )
235        frame0 = thread.GetFrameAtIndex(0)
236        var = frame0.FindValue("val", lldb.eValueTypeVariableArgument)
237        self.assertEqual(frame0.GetLineEntry().GetLine(), self.line1)
238
239        # The hit count for the breakpoint should be 1.
240        self.assertEqual(breakpoint.GetHitCount(), 1)
241