Skip to content

[lldb-dap] Add: show return value on step out #106907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 59 additions & 11 deletions lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,9 @@ def do_test_scopes_variables_setVariable_evaluate(

verify_locals["argc"]["equals"]["value"] = "123"
verify_locals["pt"]["children"]["x"]["equals"]["value"] = "111"
verify_locals["x @ main.cpp:17"] = {"equals": {"type": "int", "value": "89"}}
verify_locals["x @ main.cpp:19"] = {"equals": {"type": "int", "value": "42"}}
verify_locals["x @ main.cpp:21"] = {"equals": {"type": "int", "value": "72"}}
verify_locals["x @ main.cpp:19"] = {"equals": {"type": "int", "value": "89"}}
verify_locals["x @ main.cpp:21"] = {"equals": {"type": "int", "value": "42"}}
verify_locals["x @ main.cpp:23"] = {"equals": {"type": "int", "value": "72"}}

self.verify_variables(verify_locals, self.dap_server.get_local_variables())

Expand All @@ -353,32 +353,32 @@ def do_test_scopes_variables_setVariable_evaluate(
self.dap_server.request_setVariable(1, "x @ main.cpp:0", 9)["success"]
)

self.assertTrue(
self.dap_server.request_setVariable(1, "x @ main.cpp:17", 17)["success"]
)
self.assertTrue(
self.dap_server.request_setVariable(1, "x @ main.cpp:19", 19)["success"]
)
self.assertTrue(
self.dap_server.request_setVariable(1, "x @ main.cpp:21", 21)["success"]
)
self.assertTrue(
self.dap_server.request_setVariable(1, "x @ main.cpp:23", 23)["success"]
)

# The following should have no effect
self.assertFalse(
self.dap_server.request_setVariable(1, "x @ main.cpp:21", "invalid")[
self.dap_server.request_setVariable(1, "x @ main.cpp:23", "invalid")[
"success"
]
)

verify_locals["x @ main.cpp:17"]["equals"]["value"] = "17"
verify_locals["x @ main.cpp:19"]["equals"]["value"] = "19"
verify_locals["x @ main.cpp:21"]["equals"]["value"] = "21"
verify_locals["x @ main.cpp:23"]["equals"]["value"] = "23"

self.verify_variables(verify_locals, self.dap_server.get_local_variables())

# The plain x variable shold refer to the innermost x
self.assertTrue(self.dap_server.request_setVariable(1, "x", 22)["success"])
verify_locals["x @ main.cpp:21"]["equals"]["value"] = "22"
verify_locals["x @ main.cpp:23"]["equals"]["value"] = "22"

self.verify_variables(verify_locals, self.dap_server.get_local_variables())

Expand All @@ -394,10 +394,10 @@ def do_test_scopes_variables_setVariable_evaluate(
locals = self.dap_server.get_local_variables()
names = [var["name"] for var in locals]
# The first shadowed x shouldn't have a suffix anymore
verify_locals["x"] = {"equals": {"type": "int", "value": "17"}}
self.assertNotIn("x @ main.cpp:17", names)
verify_locals["x"] = {"equals": {"type": "int", "value": "19"}}
self.assertNotIn("x @ main.cpp:19", names)
self.assertNotIn("x @ main.cpp:21", names)
self.assertNotIn("x @ main.cpp:23", names)

self.verify_variables(verify_locals, locals)

Expand Down Expand Up @@ -663,6 +663,54 @@ def do_test_indexedVariables(self, enableSyntheticChildDebugging: bool):
]["variables"]
self.verify_variables(verify_children, children)

def test_return_variables(self):
"""
Test the stepping out of a function with return value show the variable correctly.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)

return_name = "(Return Value)"
verify_locals = {
return_name: {"equals": {"type": "int", "value": "300"}},
"argc": {},
"argv": {},
"pt": {},
"x": {},
"return_result": {"equals": {"type": "int"}},
}

function_name = "test_return_variable"
breakpoint_ids = self.set_function_breakpoints([function_name])

self.assertEqual(len(breakpoint_ids), 1)
self.continue_to_breakpoints(breakpoint_ids)

threads = self.dap_server.get_threads()
for thread in threads:
if thread.get("reason") == "breakpoint":
thread_id = thread["id"]

self.stepOut(threadId=thread_id)

local_variables = self.dap_server.get_local_variables()
varref_dict = {}

# `verify_variable` function only checks if the local variables
# are in the `verify_dict` passed this will cause this test to pass
# even if there is no return value.
local_variable_names = [
variable["name"] for variable in local_variables
]
self.assertIn(
return_name,
local_variable_names,
"return variable is not in local variables",
)

self.verify_variables(verify_locals, local_variables, varref_dict)
break

@skipIfWindows
def test_indexedVariables(self):
self.do_test_indexedVariables(enableSyntheticChildDebugging=False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,59 @@ def test_get_num_children(self):
"`script formatter.num_children_calls", context="repl"
)["body"]["result"],
)

@skipIf(archs=["arm64"])
def test_return_variable_with_children(self):
"""
Test the stepping out of a function with return value show the children correctly
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)

function_name = "test_return_variable_with_children"
breakpoint_ids = self.set_function_breakpoints([function_name])

self.assertEqual(len(breakpoint_ids), 1)
self.continue_to_breakpoints(breakpoint_ids)

threads = self.dap_server.get_threads()
for thread in threads:
if thread.get("reason") == "breakpoint":
thread_id = thread.get("id")
self.assertIsNot(thread_id, None)

self.stepOut(threadId=thread_id)

local_variables = self.dap_server.get_local_variables()

# verify has return variable as local
result_variable = list(
filter(
lambda val: val.get("name") == "(Return Value)", local_variables
)
)
self.assertEqual(len(result_variable), 1)
result_variable = result_variable[0]

result_var_ref = result_variable.get("variablesReference")
self.assertIsNot(result_var_ref, None, "There is no result value")

result_value = self.dap_server.request_variables(result_var_ref)
result_children = result_value["body"]["variables"]
self.assertNotEqual(
result_children, None, "The result does not have children"
)

verify_children = {"buffer": '"hello world!"', "x": "10", "y": "20"}
for child in result_children:
actual_name = child["name"]
actual_value = child["value"]
verify_value = verify_children.get(actual_name)
self.assertNotEqual(verify_value, None)
self.assertEqual(
actual_value,
verify_value,
"Expected child value does not match",
)

break
12 changes: 12 additions & 0 deletions lldb/test/API/tools/lldb-dap/variables/children/main.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
struct Indexed {};
struct NotIndexed {};

#define BUFFER_SIZE 16
struct NonPrimitive {
char buffer[BUFFER_SIZE];
int x;
long y;
};

NonPrimitive test_return_variable_with_children() {
return NonPrimitive{"hello world!", 10, 20};
}

int main() {
Indexed indexed;
NotIndexed not_indexed;
NonPrimitive non_primitive_result = test_return_variable_with_children();
return 0; // break here
}
9 changes: 9 additions & 0 deletions lldb/test/API/tools/lldb-dap/variables/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ struct PointType {
int g_global = 123;
static int s_global = 234;
int test_indexedVariables();
int test_return_variable();

int main(int argc, char const *argv[]) {
static float s_local = 2.25;
PointType pt = {11, 22, {0}};
Expand All @@ -22,6 +24,9 @@ int main(int argc, char const *argv[]) {
s_global = x; // breakpoint 2
}
}
{
int return_result = test_return_variable();
}
return test_indexedVariables(); // breakpoint 3
}

Expand All @@ -34,3 +39,7 @@ int test_indexedVariables() {
large_vector.assign(200, 0);
return 0; // breakpoint 4
}

int test_return_variable() {
return 300; // breakpoint 5
}
23 changes: 23 additions & 0 deletions lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,29 @@ void VariablesRequestHandler::operator()(
variable_name_counts[GetNonNullVariableName(variable)]++;
}

// Show return value if there is any ( in the local top frame )
if (variablesReference == VARREF_LOCALS) {
auto process = dap.target.GetProcess();
auto selected_thread = process.GetSelectedThread();
lldb::SBValue stop_return_value = selected_thread.GetStopReturnValue();

if (stop_return_value.IsValid() &&
(selected_thread.GetSelectedFrame().GetFrameID() == 0)) {
auto renamed_return_value = stop_return_value.Clone("(Return Value)");
int64_t return_var_ref = 0;

if (stop_return_value.MightHaveChildren() ||
stop_return_value.IsSynthetic()) {
return_var_ref = dap.variables.InsertVariable(stop_return_value,
/*is_permanent=*/false);
}
variables.emplace_back(
CreateVariable(renamed_return_value, return_var_ref, hex,
dap.enable_auto_variable_summaries,
dap.enable_synthetic_child_debugging, false));
}
}

// Now we construct the result with unique display variable names
for (auto i = start_idx; i < end_idx; ++i) {
lldb::SBValue variable = top_scope->GetValueAtIndex(i);
Expand Down
1 change: 1 addition & 0 deletions llvm/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Changes to LLDB
### Changes to lldb-dap

* Breakpoints can now be set for specific columns within a line.
* Function return value is now displayed on step-out.

Changes to BOLT
---------------------------------
Expand Down