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

https://github.com/user-attachments/assets/cff48c6f-37ae-4f72-b881-3eff4178fb3c
This commit is contained in:
Da-Viper
2025-03-02 00:21:21 +00:00
committed by GitHub
parent 5e6c0853fd
commit 8ec0d60e28
6 changed files with 160 additions and 11 deletions

View File

@@ -341,9 +341,9 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
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())
@@ -353,32 +353,32 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
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())
@@ -394,10 +394,10 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
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)
@@ -663,6 +663,54 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
]["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)

View File

@@ -40,3 +40,59 @@ class TestDAP_variables_children(lldbdap_testcase.DAPTestCaseBase):
"`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

View File

@@ -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
}

View File

@@ -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}};
@@ -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
}
@@ -34,3 +39,7 @@ int test_indexedVariables() {
large_vector.assign(200, 0);
return 0; // breakpoint 4
}
int test_return_variable() {
return 300; // breakpoint 5
}

View File

@@ -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);

View File

@@ -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
---------------------------------