Skip to content

Commit 79684cc

Browse files
committed
[lldb-dap] Implement value locations for function pointers
This commit adds `valueLocationReference` to function pointers and function references. Thereby, users can navigate directly to the pointed-to function from within the "variables" pane. In general, it would be useful to also a similar location references also to member function pointers, `std::source_location`, `std::function`, and many more. Doing so would require extending the formatters to provide such a source code location. There were two RFCs about this a while ago: https://discourse.llvm.org/t/rfc-extending-formatters-with-a-source-code-reference/68375 https://discourse.llvm.org/t/rfc-sbvalue-metadata-provider/68377/26 However, both RFCs ended without a conclusion. As such, this commit now solve the lowest-hanging fruit, i.e. function pointers. If people find it useful, I will revive the RFC afterwards.
1 parent 0cc2cd7 commit 79684cc

File tree

7 files changed

+176
-40
lines changed

7 files changed

+176
-40
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
C_SOURCES := main.c
1+
CXX_SOURCES := main.cpp
22

33
include Makefile.rules

lldb/test/API/tools/lldb-dap/locations/TestDAP_locations.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ def test_locations(self):
1919
"""
2020
program = self.getBuildArtifact("a.out")
2121
self.build_and_launch(program)
22-
source = "main.c"
22+
source = "main.cpp"
2323
self.source_path = os.path.join(os.getcwd(), source)
2424
self.set_source_breakpoints(
2525
source,
26-
[line_number(source, "// BREAK HERE")],
26+
[line_number(source, "break here")],
2727
)
2828
self.continue_to_next_stop()
2929

@@ -36,5 +36,46 @@ def test_locations(self):
3636
locals["var1"]["declarationLocationReference"]
3737
)
3838
self.assertTrue(loc_var1["success"])
39-
self.assertTrue(loc_var1["body"]["source"]["path"].endswith("main.c"))
40-
self.assertEqual(loc_var1["body"]["line"], 2)
39+
self.assertTrue(loc_var1["body"]["source"]["path"].endswith("main.cpp"))
40+
self.assertEqual(loc_var1["body"]["line"], 6)
41+
42+
# func_ptr has both a declaration and a valueLocation
43+
self.assertIn("declarationLocationReference", locals["func_ptr"].keys())
44+
self.assertIn("valueLocationReference", locals["func_ptr"].keys())
45+
decl_loc_func_ptr = self.dap_server.request_locations(
46+
locals["func_ptr"]["declarationLocationReference"]
47+
)
48+
self.assertTrue(decl_loc_func_ptr["success"])
49+
self.assertTrue(
50+
decl_loc_func_ptr["body"]["source"]["path"].endswith("main.cpp")
51+
)
52+
self.assertEqual(decl_loc_func_ptr["body"]["line"], 7)
53+
val_loc_func_ptr = self.dap_server.request_locations(
54+
locals["func_ptr"]["valueLocationReference"]
55+
)
56+
self.assertTrue(val_loc_func_ptr["success"])
57+
self.assertTrue(val_loc_func_ptr["body"]["source"]["path"].endswith("main.cpp"))
58+
self.assertEqual(val_loc_func_ptr["body"]["line"], 3)
59+
60+
# func_ref has both a declaration and a valueLocation
61+
self.assertIn("declarationLocationReference", locals["func_ref"].keys())
62+
self.assertIn("valueLocationReference", locals["func_ref"].keys())
63+
decl_loc_func_ref = self.dap_server.request_locations(
64+
locals["func_ref"]["declarationLocationReference"]
65+
)
66+
self.assertTrue(decl_loc_func_ref["success"])
67+
self.assertTrue(
68+
decl_loc_func_ref["body"]["source"]["path"].endswith("main.cpp")
69+
)
70+
self.assertEqual(decl_loc_func_ref["body"]["line"], 8)
71+
val_loc_func_ref = self.dap_server.request_locations(
72+
locals["func_ref"]["valueLocationReference"]
73+
)
74+
self.assertTrue(val_loc_func_ref["success"])
75+
self.assertTrue(val_loc_func_ref["body"]["source"]["path"].endswith("main.cpp"))
76+
self.assertEqual(val_loc_func_ref["body"]["line"], 3)
77+
78+
# `evaluate` responses for function pointers also have locations associated
79+
eval_res = self.dap_server.request_evaluate("greet")
80+
self.assertTrue(eval_res["success"])
81+
self.assertIn("valueLocationReference", eval_res["body"].keys())

lldb/test/API/tools/lldb-dap/locations/main.c

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <cstdio>
2+
3+
void greet() { printf("Hello"); }
4+
5+
int main(void) {
6+
int var1 = 1;
7+
void (*func_ptr)() = &greet;
8+
void (&func_ref)() = greet;
9+
__builtin_printf("break here");
10+
return 0;
11+
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,18 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
11981198
return description.trim().str();
11991199
}
12001200

1201+
bool HasValueLocation(lldb::SBValue v) {
1202+
if (!v.GetType().IsPointerType() && !v.GetType().IsReferenceType()) {
1203+
return false;
1204+
}
1205+
1206+
lldb::addr_t addr = v.GetValueAsAddress();
1207+
lldb::SBLineEntry line_entry =
1208+
g_dap.target.ResolveLoadAddress(addr).GetLineEntry();
1209+
1210+
return line_entry.IsValid();
1211+
}
1212+
12011213
// "Variable": {
12021214
// "type": "object",
12031215
// "description": "A Variable is a name/value pair. Optionally a variable
@@ -1277,6 +1289,18 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
12771289
// Object References' in the Overview section for
12781290
// details."
12791291
// },
1292+
// "valueLocationReference": {
1293+
// "type": "integer",
1294+
// "description": "A reference that allows the client to request the
1295+
// location where the variable's value is declared. For
1296+
// example, if the variable contains a function pointer,
1297+
// the adapter may be able to look up the function's
1298+
// location. This should be present only if the adapter
1299+
// is likely to be able to resolve the location.\n\nThis
1300+
// reference shares the same lifetime as the
1301+
// `variablesReference`. See 'Lifetime of Object
1302+
// References' in the Overview section for details."
1303+
// },
12801304
//
12811305
// "$__lldb_extensions": {
12821306
// "description": "Unofficial extensions to the protocol",
@@ -1390,7 +1414,10 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
13901414
object.try_emplace("variablesReference", 0);
13911415

13921416
if (v.GetDeclaration().IsValid())
1393-
object.try_emplace("declarationLocationReference", var_ref);
1417+
object.try_emplace("declarationLocationReference", var_ref << 1);
1418+
1419+
if (HasValueLocation(v))
1420+
object.try_emplace("valueLocationReference", (var_ref << 1) | 1);
13941421

13951422
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
13961423
object.try_emplace("memoryReference", EncodeMemoryReference(addr));
@@ -1416,8 +1443,8 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
14161443
llvm::StringRef comm_file,
14171444
lldb::pid_t debugger_pid) {
14181445
llvm::json::Object run_in_terminal_args;
1419-
// This indicates the IDE to open an embedded terminal, instead of opening the
1420-
// terminal in a new window.
1446+
// This indicates the IDE to open an embedded terminal, instead of opening
1447+
// the terminal in a new window.
14211448
run_in_terminal_args.try_emplace("kind", "integrated");
14221449

14231450
auto launch_request_arguments = launch_request.getObject("arguments");

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ struct VariableDescription {
458458
std::string GetResult(llvm::StringRef context);
459459
};
460460

461+
/// Does the given variable have an associated value location?
462+
bool HasValueLocation(lldb::SBValue v);
463+
461464
/// Create a "Variable" object for a LLDB thread object.
462465
///
463466
/// This function will fill in the following keys in the returned

lldb/tools/lldb-dap/lldb-dap.cpp

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,19 @@ void request_completions(const llvm::json::Object &request) {
15421542
// client can use this optional information to
15431543
// present the variables in a paged UI and fetch
15441544
// them in chunks."
1545+
// },
1546+
// "valueLocationReference": {
1547+
// "type": "integer",
1548+
// "description": "A reference that allows the client to request
1549+
// the location where the returned value is
1550+
// declared. For example, if a function pointer is
1551+
// returned, the adapter may be able to look up the
1552+
// function's location. This should be present only
1553+
// if the adapter is likely to be able to resolve
1554+
// the location.\n\nThis reference shares the same
1555+
// lifetime as the `variablesReference`. See
1556+
// 'Lifetime of Object References' in the
1557+
// Overview section for details."
15451558
// }
15461559
// "memoryReference": {
15471560
// "type": "string",
@@ -1611,16 +1624,17 @@ void request_evaluate(const llvm::json::Object &request) {
16111624
VariableDescription desc(value);
16121625
EmplaceSafeString(body, "result", desc.GetResult(context));
16131626
EmplaceSafeString(body, "type", desc.display_type_name);
1614-
if (value.MightHaveChildren()) {
1615-
auto variableReference = g_dap.variables.InsertVariable(
1616-
value, /*is_permanent=*/context == "repl");
1617-
body.try_emplace("variablesReference", variableReference);
1618-
} else {
1627+
auto var_ref = g_dap.variables.InsertVariable(
1628+
value, /*is_permanent=*/context == "repl");
1629+
if (value.MightHaveChildren())
1630+
body.try_emplace("variablesReference", var_ref);
1631+
else
16191632
body.try_emplace("variablesReference", (int64_t)0);
1620-
}
16211633
if (lldb::addr_t addr = value.GetLoadAddress();
16221634
addr != LLDB_INVALID_ADDRESS)
16231635
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
1636+
if (HasValueLocation(value))
1637+
body.try_emplace("valueLocationReference", var_ref);
16241638
}
16251639
}
16261640
response.try_emplace("body", std::move(body));
@@ -3735,6 +3749,17 @@ void request_threads(const llvm::json::Object &request) {
37353749
// "description": "The number of indexed child variables. The client
37363750
// can use this optional information to present the variables in a
37373751
// paged UI and fetch them in chunks."
3752+
// },
3753+
// "valueLocationReference": {
3754+
// "type": "integer",
3755+
// "description": "A reference that allows the client to request the
3756+
// location where the new value is declared. For example, if the new
3757+
// value is function pointer, the adapter may be able to look up the
3758+
// function's location. This should be present only if the adapter
3759+
// is likely to be able to resolve the location.\n\nThis reference
3760+
// shares the same lifetime as the `variablesReference`. See
3761+
// 'Lifetime of Object References' in the Overview section for
3762+
// details."
37383763
// }
37393764
// },
37403765
// "required": [ "value" ]
@@ -3759,7 +3784,6 @@ void request_setVariable(const llvm::json::Object &request) {
37593784
response.try_emplace("success", false);
37603785

37613786
lldb::SBValue variable;
3762-
int64_t newVariablesReference = 0;
37633787

37643788
// The "id" is the unique integer ID that is unique within the enclosing
37653789
// variablesReference. It is optionally added to any "interface Variable"
@@ -3789,14 +3813,17 @@ void request_setVariable(const llvm::json::Object &request) {
37893813
// so always insert a new one to get its variablesReference.
37903814
// is_permanent is false because debug console does not support
37913815
// setVariable request.
3816+
int64_t new_var_ref =
3817+
g_dap.variables.InsertVariable(variable, /*is_permanent=*/false);
37923818
if (variable.MightHaveChildren())
3793-
newVariablesReference =
3794-
g_dap.variables.InsertVariable(variable, /*is_permanent=*/false);
3795-
body.try_emplace("variablesReference", newVariablesReference);
3796-
3819+
body.try_emplace("variablesReference", new_var_ref);
3820+
else
3821+
body.try_emplace("variablesReference", 0);
37973822
if (lldb::addr_t addr = variable.GetLoadAddress();
37983823
addr != LLDB_INVALID_ADDRESS)
37993824
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
3825+
if (HasValueLocation(variable))
3826+
body.try_emplace("valueLocationReference", new_var_ref);
38003827
} else {
38013828
EmplaceSafeString(body, "message", std::string(error.GetCString()));
38023829
}
@@ -4087,32 +4114,64 @@ void request_variables(const llvm::json::Object &request) {
40874114
void request_locations(const llvm::json::Object &request) {
40884115
llvm::json::Object response;
40894116
FillResponse(request, response);
4090-
auto arguments = request.getObject("arguments");
4117+
auto *arguments = request.getObject("arguments");
40914118

40924119
uint64_t reference_id = GetUnsigned(arguments, "locationReference", 0);
4093-
lldb::SBValue variable = g_dap.variables.GetVariable(reference_id);
4120+
// We use the lowest bit to distinguish between value location and declaration
4121+
// location
4122+
bool isValueLocation = reference_id & 1;
4123+
lldb::SBValue variable = g_dap.variables.GetVariable(reference_id >> 1);
40944124
if (!variable.IsValid()) {
40954125
response["success"] = false;
40964126
response["message"] = "Invalid variable reference";
40974127
g_dap.SendJSON(llvm::json::Value(std::move(response)));
40984128
return;
40994129
}
41004130

4101-
// Get the declaration location
4102-
lldb::SBDeclaration decl = variable.GetDeclaration();
4103-
if (!decl.IsValid()) {
4104-
response["success"] = false;
4105-
response["message"] = "No declaration location available";
4106-
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4107-
return;
4108-
}
4109-
41104131
llvm::json::Object body;
4111-
body.try_emplace("source", CreateSource(decl.GetFileSpec()));
4112-
if (int line = decl.GetLine())
4113-
body.try_emplace("line", line);
4114-
if (int column = decl.GetColumn())
4115-
body.try_emplace("column", column);
4132+
if (isValueLocation) {
4133+
// Get the value location
4134+
if (!variable.GetType().IsPointerType() &&
4135+
!variable.GetType().IsReferenceType()) {
4136+
response["success"] = false;
4137+
response["message"] =
4138+
"Value locations are only available for pointers and references";
4139+
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4140+
return;
4141+
}
4142+
4143+
lldb::addr_t addr = variable.GetValueAsAddress();
4144+
lldb::SBLineEntry line_entry =
4145+
g_dap.target.ResolveLoadAddress(addr).GetLineEntry();
4146+
4147+
if (!line_entry.IsValid()) {
4148+
response["success"] = false;
4149+
response["message"] = "Failed to resolve line entry for location";
4150+
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4151+
return;
4152+
}
4153+
4154+
body.try_emplace("source", CreateSource(line_entry.GetFileSpec()));
4155+
if (int line = line_entry.GetLine())
4156+
body.try_emplace("line", line);
4157+
if (int column = line_entry.GetColumn())
4158+
body.try_emplace("column", column);
4159+
} else {
4160+
// Get the declaration location
4161+
lldb::SBDeclaration decl = variable.GetDeclaration();
4162+
if (!decl.IsValid()) {
4163+
response["success"] = false;
4164+
response["message"] = "No declaration location available";
4165+
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4166+
return;
4167+
}
4168+
4169+
body.try_emplace("source", CreateSource(decl.GetFileSpec()));
4170+
if (int line = decl.GetLine())
4171+
body.try_emplace("line", line);
4172+
if (int column = decl.GetColumn())
4173+
body.try_emplace("column", column);
4174+
}
41164175

41174176
response.try_emplace("body", std::move(body));
41184177
g_dap.SendJSON(llvm::json::Value(std::move(response)));

0 commit comments

Comments
 (0)