xref: /llvm-project/lldb/test/API/functionalities/inline-stepping/TestInlineStepping.py (revision 23a01a413d29f2d5b1f6204d0237e3884ae0231e)
1"""Test stepping over and into inlined functions."""
2
3import lldb
4from lldbsuite.test.decorators import *
5from lldbsuite.test.lldbtest import *
6from lldbsuite.test import lldbutil
7
8
9class TestInlineStepping(TestBase):
10    @add_test_categories(["pyapi"])
11    @skipIf(oslist=["windows"], archs=["aarch64"])  # Flaky on buildbot
12    @expectedFailureAll(
13        compiler="icc",
14        bugnumber="# Not really a bug.  ICC combines two inlined functions.",
15    )
16    def test_with_python_api(self):
17        """Test stepping over and into inlined functions."""
18        self.build()
19        self.inline_stepping()
20
21    @add_test_categories(["pyapi"])
22    def test_step_over_with_python_api(self):
23        """Test stepping over and into inlined functions."""
24        self.build()
25        self.inline_stepping_step_over()
26
27    @add_test_categories(["pyapi"])
28    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr32343")
29    def test_step_in_template_with_python_api(self):
30        """Test stepping in to templated functions."""
31        self.build()
32        self.step_in_template()
33
34    @add_test_categories(["pyapi"])
35    def test_virtual_inline_stepping(self):
36        """Test stepping through a virtual inlined call stack"""
37        self.build()
38        self.virtual_inline_stepping()
39
40    def setUp(self):
41        # Call super's setUp().
42        TestBase.setUp(self)
43        # Find the line numbers that we will step to in main:
44        self.main_source = "calling.cpp"
45        self.source_lines = {}
46        functions = [
47            "caller_ref_1",
48            "caller_ref_2",
49            "inline_ref_1",
50            "inline_ref_2",
51            "called_by_inline_ref",
52            "caller_trivial_1",
53            "caller_trivial_2",
54            "inline_trivial_1",
55            "inline_trivial_2",
56            "called_by_inline_trivial",
57        ]
58        for name in functions:
59            self.source_lines[name] = line_number(
60                self.main_source, "// In " + name + "."
61            )
62        self.main_source_spec = lldb.SBFileSpec(self.main_source)
63
64    def do_step(self, step_type, destination_line_entry, test_stack_depth):
65        expected_stack_depth = self.thread.GetNumFrames()
66        if step_type == "into":
67            expected_stack_depth += 1
68            self.thread.StepInto()
69        elif step_type == "out":
70            expected_stack_depth -= 1
71            self.thread.StepOut()
72        elif step_type == "over":
73            self.thread.StepOver()
74        else:
75            self.fail("Unrecognized step type: " + step_type)
76
77        threads = lldbutil.get_stopped_threads(
78            self.process, lldb.eStopReasonPlanComplete
79        )
80        if len(threads) != 1:
81            destination_description = lldb.SBStream()
82            destination_line_entry.GetDescription(destination_description)
83            self.fail(
84                "Failed to stop due to step "
85                + step_type
86                + " operation stepping to: "
87                + destination_description.GetData()
88            )
89
90        self.thread = threads[0]
91
92        stop_line_entry = self.thread.GetFrameAtIndex(0).GetLineEntry()
93        self.assertTrue(stop_line_entry.IsValid(), "Stop line entry was not valid.")
94
95        # Don't use the line entry equal operator because we don't care about
96        # the column number.
97        stop_at_right_place = (
98            stop_line_entry.GetFileSpec() == destination_line_entry.GetFileSpec()
99            and stop_line_entry.GetLine() == destination_line_entry.GetLine()
100        )
101        if not stop_at_right_place:
102            destination_description = lldb.SBStream()
103            destination_line_entry.GetDescription(destination_description)
104
105            actual_description = lldb.SBStream()
106            stop_line_entry.GetDescription(actual_description)
107
108            self.fail(
109                "Step "
110                + step_type
111                + " stopped at wrong place: expected: "
112                + destination_description.GetData()
113                + " got: "
114                + actual_description.GetData()
115                + "."
116            )
117
118        real_stack_depth = self.thread.GetNumFrames()
119
120        if test_stack_depth and real_stack_depth != expected_stack_depth:
121            destination_description = lldb.SBStream()
122            destination_line_entry.GetDescription(destination_description)
123            self.fail(
124                "Step %s to %s got wrong number of frames, should be: %d was: %d."
125                % (
126                    step_type,
127                    destination_description.GetData(),
128                    expected_stack_depth,
129                    real_stack_depth,
130                )
131            )
132
133    def run_step_sequence(self, step_sequence):
134        """This function takes a list of duples instructing how to run the program.  The first element in each duple is
135        a source pattern for the target location, and the second is the operation that will take you from the current
136        source location to the target location.  It will then run all the steps in the sequence.
137        It will check that you arrived at the expected source location at each step, and that the stack depth changed
138        correctly for the operation in the sequence."""
139
140        target_line_entry = lldb.SBLineEntry()
141        target_line_entry.SetFileSpec(self.main_source_spec)
142
143        test_stack_depth = True
144        # Work around for <rdar://problem/16363195>, the darwin unwinder seems flakey about whether it duplicates the first frame
145        # or not, which makes counting stack depth unreliable.
146        if self.platformIsDarwin():
147            test_stack_depth = False
148
149        for step_pattern in step_sequence:
150            step_stop_line = line_number(self.main_source, step_pattern[0])
151            target_line_entry.SetLine(step_stop_line)
152            self.do_step(step_pattern[1], target_line_entry, test_stack_depth)
153
154    def inline_stepping(self):
155        """Use Python APIs to test stepping over and hitting breakpoints."""
156        exe = self.getBuildArtifact("a.out")
157
158        target = self.dbg.CreateTarget(exe)
159        self.assertTrue(target, VALID_TARGET)
160
161        break_1_in_main = target.BreakpointCreateBySourceRegex(
162            "// Stop here and step over to set up stepping over.", self.main_source_spec
163        )
164        self.assertTrue(break_1_in_main, VALID_BREAKPOINT)
165
166        # Now launch the process, and do not stop at entry point.
167        self.process = target.LaunchSimple(
168            None, None, self.get_process_working_directory()
169        )
170
171        self.assertTrue(self.process, PROCESS_IS_VALID)
172
173        # The stop reason of the thread should be breakpoint.
174        threads = lldbutil.get_threads_stopped_at_breakpoint(
175            self.process, break_1_in_main
176        )
177
178        if len(threads) != 1:
179            self.fail("Failed to stop at first breakpoint in main.")
180
181        self.thread = threads[0]
182
183        # Step over the inline_value = 0 line to get us to inline_trivial_1 called from main.  Doing it this way works
184        # around a bug in lldb where the breakpoint on the containing line of an inlined function with no return value
185        # gets set past the insertion line in the function.
186        # Then test stepping over a simple inlined function.  Note, to test all the parts of the inlined stepping
187        # the calls inline_stepping_1 and inline_stepping_2 should line up at the same address, that way we will test
188        # the "virtual" stepping.
189        # FIXME: Put in a check to see if that is true and warn if it is not.
190
191        step_sequence = [
192            ["// At inline_trivial_1 called from main.", "over"],
193            ["// At first call of caller_trivial_1 in main.", "over"],
194        ]
195        self.run_step_sequence(step_sequence)
196
197        # Now step from caller_ref_1 all the way into called_by_inline_trivial
198
199        step_sequence = [
200            ["// In caller_trivial_1.", "into"],
201            ["// In caller_trivial_2.", "into"],
202            ["// In inline_trivial_1.", "into"],
203            ["// In inline_trivial_2.", "into"],
204            ["// At caller_by_inline_trivial in inline_trivial_2.", "over"],
205            ["// In called_by_inline_trivial.", "into"],
206        ]
207        self.run_step_sequence(step_sequence)
208
209        # Now run to the inline_trivial_1 just before the immediate step into
210        # inline_trivial_2:
211
212        break_2_in_main = target.BreakpointCreateBySourceRegex(
213            "// At second call of caller_trivial_1 in main.", self.main_source_spec
214        )
215        self.assertTrue(break_2_in_main, VALID_BREAKPOINT)
216
217        threads = lldbutil.continue_to_breakpoint(self.process, break_2_in_main)
218        self.assertEqual(
219            len(threads),
220            1,
221            "Successfully ran to call site of second caller_trivial_1 call.",
222        )
223        self.thread = threads[0]
224
225        step_sequence = [
226            ["// In caller_trivial_1.", "into"],
227            ["// In caller_trivial_2.", "into"],
228            ["// In inline_trivial_1.", "into"],
229        ]
230        self.run_step_sequence(step_sequence)
231
232        # Then call some trivial function, and make sure we end up back where
233        # we were in the inlined call stack:
234
235        frame = self.thread.GetFrameAtIndex(0)
236        before_line_entry = frame.GetLineEntry()
237        value = frame.EvaluateExpression("function_to_call()")
238        after_line_entry = frame.GetLineEntry()
239
240        self.assertEqual(
241            before_line_entry.GetLine(),
242            after_line_entry.GetLine(),
243            "Line entry before and after function calls are the same.",
244        )
245
246        # Now make sure stepping OVER in the middle of the stack works, and
247        # then check finish from the inlined frame:
248
249        step_sequence = [
250            ["// At increment in inline_trivial_1.", "over"],
251            ["// At increment in caller_trivial_2.", "out"],
252        ]
253        self.run_step_sequence(step_sequence)
254
255        # Now run to the place in main just before the first call to
256        # caller_ref_1:
257
258        break_3_in_main = target.BreakpointCreateBySourceRegex(
259            "// At first call of caller_ref_1 in main.", self.main_source_spec
260        )
261        self.assertTrue(break_3_in_main, VALID_BREAKPOINT)
262
263        threads = lldbutil.continue_to_breakpoint(self.process, break_3_in_main)
264        self.assertEqual(
265            len(threads), 1, "Successfully ran to call site of first caller_ref_1 call."
266        )
267        self.thread = threads[0]
268
269        step_sequence = [
270            ["// In caller_ref_1.", "into"],
271            ["// In caller_ref_2.", "into"],
272            ["// In inline_ref_1.", "into"],
273            ["// In inline_ref_2.", "into"],
274            ["// In called_by_inline_ref.", "into"],
275            ["// In inline_ref_2.", "out"],
276            ["// In inline_ref_1.", "out"],
277            ["// At increment in inline_ref_1.", "over"],
278            ["// In caller_ref_2.", "out"],
279            ["// At increment in caller_ref_2.", "over"],
280        ]
281        self.run_step_sequence(step_sequence)
282
283    def inline_stepping_step_over(self):
284        """Use Python APIs to test stepping over and hitting breakpoints."""
285        exe = self.getBuildArtifact("a.out")
286
287        target = self.dbg.CreateTarget(exe)
288        self.assertTrue(target, VALID_TARGET)
289
290        break_1_in_main = target.BreakpointCreateBySourceRegex(
291            "// At second call of caller_ref_1 in main.", self.main_source_spec
292        )
293        self.assertGreater(break_1_in_main.GetNumLocations(), 0, VALID_BREAKPOINT)
294
295        # Now launch the process, and do not stop at entry point.
296        self.process = target.LaunchSimple(
297            None, None, self.get_process_working_directory()
298        )
299
300        self.assertTrue(self.process, PROCESS_IS_VALID)
301
302        # The stop reason of the thread should be breakpoint.
303        threads = lldbutil.get_threads_stopped_at_breakpoint(
304            self.process, break_1_in_main
305        )
306
307        if len(threads) != 1:
308            self.fail("Failed to stop at first breakpoint in main.")
309
310        self.thread = threads[0]
311
312        step_sequence = [
313            ["// In caller_ref_1.", "into"],
314            ["// In caller_ref_2.", "into"],
315            ["// At increment in caller_ref_2.", "over"],
316        ]
317        self.run_step_sequence(step_sequence)
318
319        # Now make sure that next to a virtual inlined call stack
320        # gets the call stack depth correct.
321        break_2_in_main = target.BreakpointCreateBySourceRegex(
322            "// Call max_value specialized", self.main_source_spec
323        )
324        self.assertGreater(break_2_in_main.GetNumLocations(), 0, VALID_BREAKPOINT)
325        threads = lldbutil.continue_to_breakpoint(self.process, break_2_in_main)
326        self.assertEqual(len(threads), 1, "Hit our second breakpoint")
327        self.assertEqual(threads[0].id, self.thread.id, "Stopped at right thread")
328        self.thread.StepOver()
329        frame_0 = self.thread.frames[0]
330        line_entry = frame_0.line_entry
331        self.assertEqual(
332            line_entry.file.basename, self.main_source_spec.basename, "File matches"
333        )
334        target_line = line_number("calling.cpp", "// At caller_trivial_inline_1")
335        self.assertEqual(line_entry.line, target_line, "Lines match as well.")
336
337    def step_in_template(self):
338        """Use Python APIs to test stepping in to templated functions."""
339        exe = self.getBuildArtifact("a.out")
340
341        target = self.dbg.CreateTarget(exe)
342        self.assertTrue(target, VALID_TARGET)
343
344        break_1_in_main = target.BreakpointCreateBySourceRegex(
345            "// Call max_value template", self.main_source_spec
346        )
347        self.assertTrue(break_1_in_main, VALID_BREAKPOINT)
348
349        break_2_in_main = target.BreakpointCreateBySourceRegex(
350            "// Call max_value specialized", self.main_source_spec
351        )
352        self.assertTrue(break_2_in_main, VALID_BREAKPOINT)
353
354        # Now launch the process, and do not stop at entry point.
355        self.process = target.LaunchSimple(
356            None, None, self.get_process_working_directory()
357        )
358        self.assertTrue(self.process, PROCESS_IS_VALID)
359
360        # The stop reason of the thread should be breakpoint.
361        threads = lldbutil.get_threads_stopped_at_breakpoint(
362            self.process, break_1_in_main
363        )
364
365        if len(threads) != 1:
366            self.fail("Failed to stop at first breakpoint in main.")
367
368        self.thread = threads[0]
369
370        step_sequence = [["// In max_value template", "into"]]
371        self.run_step_sequence(step_sequence)
372
373        threads = lldbutil.continue_to_breakpoint(self.process, break_2_in_main)
374        self.assertEqual(
375            len(threads),
376            1,
377            "Successfully ran to call site of second caller_trivial_1 call.",
378        )
379        self.thread = threads[0]
380
381        step_sequence = [["// In max_value specialized", "into"]]
382        self.run_step_sequence(step_sequence)
383
384    def run_to_call_site_and_step(
385        self, source_regex, func_name, start_pos, one_more_step_loc=None
386    ):
387        main_spec = lldb.SBFileSpec("calling.cpp")
388        # Set the breakpoint by file and line, not sourced regex because
389        # we want to make sure we can set breakpoints on call sites:
390        call_site_line_num = line_number(self.main_source, source_regex)
391        target, process, thread, bkpt = lldbutil.run_to_line_breakpoint(
392            self, main_spec, call_site_line_num
393        )
394
395        # Make sure that the location is at the call site (run_to_line_breakpoint already asserted
396        # that there's one location.):
397        bkpt_loc = bkpt.location[0]
398        strm = lldb.SBStream()
399        result = bkpt_loc.GetDescription(strm, lldb.eDescriptionLevelFull)
400
401        self.assertTrue(result, "Got a location description")
402        desc = strm.GetData()
403        self.assertIn(f"calling.cpp:{call_site_line_num}", desc, "Right line listed")
404        # We don't get the function name right yet - so we omit it in printing.
405        # Turn on this test when that is working.
406        # self.assertIn(func_name, desc, "Right function listed")
407
408        pc = thread.frame[0].pc
409        for i in range(start_pos, 3):
410            thread.StepInto()
411            frame_0 = thread.frame[0]
412
413            trivial_line_num = line_number(
414                self.main_source, f"In caller_trivial_inline_{i}."
415            )
416            self.assertEqual(
417                frame_0.line_entry.line,
418                trivial_line_num,
419                f"Stepped into the caller_trivial_inline_{i}",
420            )
421            if pc != frame_0.pc:
422                # If we get here, we stepped to the expected line number, but
423                # the compiler on this system has decided to insert an instruction
424                # between the call site of an inlined function with no arguments,
425                # returning void, and its immediate call to another void inlined function
426                # with no arguments.  We aren't going to be testing virtual inline
427                # stepping for this function...
428                break
429
430        if one_more_step_loc:
431            thread.StepInto()
432            frame_0 = thread.frame[0]
433            self.assertEqual(
434                frame_0.line_entry.line,
435                line_number(self.main_source, one_more_step_loc),
436                "Was able to step one more time",
437            )
438        process.Kill()
439        target.Clear()
440
441    def virtual_inline_stepping(self):
442        """Use the Python API's to step through a virtual inlined stack"""
443        self.run_to_call_site_and_step("At caller_trivial_inline_1", "main", 1)
444        self.run_to_call_site_and_step(
445            "In caller_trivial_inline_1", "caller_trivial_inline_1", 2
446        )
447        self.run_to_call_site_and_step(
448            "In caller_trivial_inline_2", "caller_trivial_inline_2", 3
449        )
450        self.run_to_call_site_and_step(
451            "In caller_trivial_inline_3",
452            "caller_trivial_inline_3",
453            4,
454            "After caller_trivial_inline_3",
455        )
456