[lldb-dap] Prevent using an implicit step-in. (#143644)

When there is a function that is inlined at the current program counter.
If you get the current `line_entry` using the program counter's address
it will point to the location of the inline function that may be in
another file. (this is in implicit step-in and should not happen what
step over is called).

Use the current frame to get the `line_entry`
This commit is contained in:
Ebuka Ezike 2025-07-03 13:12:30 +01:00 committed by GitHub
parent 8763ac3252
commit c5f47c6fd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 88 additions and 7 deletions

View File

@ -344,7 +344,12 @@ class DAPTestCaseBase(TestBase):
granularity="statement",
timeout=DEFAULT_TIMEOUT,
):
self.dap_server.request_next(threadId=threadId, granularity=granularity)
response = self.dap_server.request_next(
threadId=threadId, granularity=granularity
)
self.assertTrue(
response["success"], f"next request failed: response {response}"
)
if waitForStop:
return self.dap_server.wait_for_stopped(timeout)
return None

View File

@ -83,3 +83,41 @@ class TestDAP_step(lldbdap_testcase.DAPTestCaseBase):
# only step one thread that is at the breakpoint and stop
break
def test_step_over_inlined_function(self):
"""
Test stepping over when the program counter is in another file.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.cpp"
breakpoint_lines = [line_number(source, "// breakpoint 2")]
step_over_pos = line_number(source, "// position_after_step_over")
breakpoint_ids = self.set_source_breakpoints(source, breakpoint_lines)
self.assertEqual(
len(breakpoint_ids),
len(breakpoint_lines),
"expect correct number of breakpoints.",
)
self.continue_to_breakpoints(breakpoint_ids)
thread_id = self.dap_server.get_thread_id()
self.stepOver(thread_id)
levels = 1
frames = self.get_stackFrames(thread_id, 0, levels)
self.assertEqual(len(frames), levels, "expect current number of frame levels.")
top_frame = frames[0]
self.assertEqual(
top_frame["source"]["name"], source, "expect we are in the same file."
)
self.assertTrue(
top_frame["source"]["path"].endswith(source),
f"expect path ending with '{source}'.",
)
self.assertEqual(
top_frame["line"],
step_over_pos,
f"expect step_over on line {step_over_pos}",
)
self.continue_to_exit()

View File

@ -1,3 +1,5 @@
#include "other.h"
int function(int x) {
if ((x % 2) == 0)
return function(x - 1) + x; // breakpoint 1
@ -5,4 +7,14 @@ int function(int x) {
return x;
}
int main(int argc, char const *argv[]) { return function(2); }
int function2() {
int volatile value = 3; // breakpoint 2
inlined_fn(); // position_after_step_over
return value;
}
int main(int argc, char const *argv[]) {
int func_result = function2();
return function(2) - func_result; // returns 0
}

View File

@ -0,0 +1,7 @@
#ifndef OTHER_H
#define OTHER_H
__attribute__((noinline)) void not_inlined_fn() {};
__attribute__((always_inline)) inline void inlined_fn() { not_inlined_fn(); }
#endif // OTHER_H

View File

@ -623,6 +623,17 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
llvm_unreachable("enum cases exhausted.");
}
std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) {
if (!frame.IsValid())
return std::nullopt;
const lldb::SBAddress frame_pc = frame.GetPCAddress();
if (DisplayAssemblySource(debugger, frame_pc))
return ResolveAssemblySource(frame_pc);
return CreateSource(frame.GetLineEntry().GetFileSpec());
}
std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) {
if (DisplayAssemblySource(debugger, address))
return ResolveAssemblySource(address);

View File

@ -254,6 +254,17 @@ struct DAP {
ReplMode DetectReplMode(lldb::SBFrame frame, std::string &expression,
bool partial_expression);
/// Create a `protocol::Source` object as described in the debug adapter
/// definition.
///
/// \param[in] frame
/// The frame to use when populating the "Source" object.
///
/// \return
/// A `protocol::Source` object that follows the formal JSON
/// definition outlined by Microsoft.
std::optional<protocol::Source> ResolveSource(const lldb::SBFrame &frame);
/// Create a "Source" JSON object as described in the debug adapter
/// definition.
///

View File

@ -572,9 +572,7 @@ llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame,
EmplaceSafeString(object, "name", frame_name);
auto target = frame.GetThread().GetProcess().GetTarget();
std::optional<protocol::Source> source =
dap.ResolveSource(frame.GetPCAddress());
std::optional<protocol::Source> source = dap.ResolveSource(frame);
if (source && !IsAssemblySource(*source)) {
// This is a normal source with a valid line entry.
@ -586,8 +584,7 @@ llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame,
// This is a source where the disassembly is used, but there is a valid
// symbol. Calculate the line of the current PC from the start of the
// current symbol.
lldb::SBTarget target = frame.GetThread().GetProcess().GetTarget();
lldb::SBInstructionList inst_list = target.ReadInstructions(
lldb::SBInstructionList inst_list = dap.target.ReadInstructions(
frame.GetSymbol().GetStartAddress(), frame.GetPCAddress(), nullptr);
size_t inst_line = inst_list.GetSize();