Skip to content

Commit 2824ef7

Browse files
vogelsgesangDanielCChen
authored andcommitted
[lldb-dap] Implement value locations for function pointers (llvm#104589)
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 add 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 implements the lowest-hanging fruit, i.e. function pointers. If people find it useful, I will revive the RFC afterwards.
1 parent 6b20086 commit 2824ef7

File tree

7 files changed

+192
-40
lines changed

7 files changed

+192
-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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
return 0; // break here
10+
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,25 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
12231223
return description.trim().str();
12241224
}
12251225

1226+
bool ValuePointsToCode(lldb::SBValue v) {
1227+
if (!v.GetType().GetPointeeType().IsFunctionType())
1228+
return false;
1229+
1230+
lldb::addr_t addr = v.GetValueAsAddress();
1231+
lldb::SBLineEntry line_entry =
1232+
g_dap.target.ResolveLoadAddress(addr).GetLineEntry();
1233+
1234+
return line_entry.IsValid();
1235+
}
1236+
1237+
int64_t PackLocation(int64_t var_ref, bool is_value_location) {
1238+
return var_ref << 1 | is_value_location;
1239+
}
1240+
1241+
std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
1242+
return std::pair{location_id >> 1, location_id & 1};
1243+
}
1244+
12261245
// "Variable": {
12271246
// "type": "object",
12281247
// "description": "A Variable is a name/value pair. Optionally a variable
@@ -1302,6 +1321,18 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
13021321
// Object References' in the Overview section for
13031322
// details."
13041323
// },
1324+
// "valueLocationReference": {
1325+
// "type": "integer",
1326+
// "description": "A reference that allows the client to request the
1327+
// location where the variable's value is declared. For
1328+
// example, if the variable contains a function pointer,
1329+
// the adapter may be able to look up the function's
1330+
// location. This should be present only if the adapter
1331+
// is likely to be able to resolve the location.\n\nThis
1332+
// reference shares the same lifetime as the
1333+
// `variablesReference`. See 'Lifetime of Object
1334+
// References' in the Overview section for details."
1335+
// },
13051336
//
13061337
// "$__lldb_extensions": {
13071338
// "description": "Unofficial extensions to the protocol",
@@ -1415,7 +1446,11 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
14151446
object.try_emplace("variablesReference", 0);
14161447

14171448
if (v.GetDeclaration().IsValid())
1418-
object.try_emplace("declarationLocationReference", var_ref);
1449+
object.try_emplace("declarationLocationReference",
1450+
PackLocation(var_ref, false));
1451+
1452+
if (ValuePointsToCode(v))
1453+
object.try_emplace("valueLocationReference", PackLocation(var_ref, true));
14191454

14201455
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
14211456
object.try_emplace("memoryReference", EncodeMemoryReference(addr));
@@ -1441,8 +1476,8 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
14411476
llvm::StringRef comm_file,
14421477
lldb::pid_t debugger_pid) {
14431478
llvm::json::Object run_in_terminal_args;
1444-
// This indicates the IDE to open an embedded terminal, instead of opening the
1445-
// terminal in a new window.
1479+
// This indicates the IDE to open an embedded terminal, instead of opening
1480+
// the terminal in a new window.
14461481
run_in_terminal_args.try_emplace("kind", "integrated");
14471482

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

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,16 @@ struct VariableDescription {
480480
std::string GetResult(llvm::StringRef context);
481481
};
482482

483+
/// Does the given variable have an associated value location?
484+
bool ValuePointsToCode(lldb::SBValue v);
485+
486+
/// Pack a location into a single integer which we can send via
487+
/// the debug adapter protocol.
488+
int64_t PackLocation(int64_t var_ref, bool is_value_location);
489+
490+
/// Reverse of `PackLocation`
491+
std::pair<int64_t, bool> UnpackLocation(int64_t location_id);
492+
483493
/// Create a "Variable" object for a LLDB thread object.
484494
///
485495
/// This function will fill in the following keys in the returned

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

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,19 @@ void request_completions(const llvm::json::Object &request) {
15611561
// client can use this optional information to
15621562
// present the variables in a paged UI and fetch
15631563
// them in chunks."
1564+
// },
1565+
// "valueLocationReference": {
1566+
// "type": "integer",
1567+
// "description": "A reference that allows the client to request
1568+
// the location where the returned value is
1569+
// declared. For example, if a function pointer is
1570+
// returned, the adapter may be able to look up the
1571+
// function's location. This should be present only
1572+
// if the adapter is likely to be able to resolve
1573+
// the location.\n\nThis reference shares the same
1574+
// lifetime as the `variablesReference`. See
1575+
// 'Lifetime of Object References' in the
1576+
// Overview section for details."
15641577
// }
15651578
// "memoryReference": {
15661579
// "type": "string",
@@ -1647,16 +1660,19 @@ void request_evaluate(const llvm::json::Object &request) {
16471660
VariableDescription desc(value);
16481661
EmplaceSafeString(body, "result", desc.GetResult(context));
16491662
EmplaceSafeString(body, "type", desc.display_type_name);
1650-
if (value.MightHaveChildren()) {
1651-
auto variableReference = g_dap.variables.InsertVariable(
1663+
int64_t var_ref = 0;
1664+
if (value.MightHaveChildren() || ValuePointsToCode(value))
1665+
var_ref = g_dap.variables.InsertVariable(
16521666
value, /*is_permanent=*/context == "repl");
1653-
body.try_emplace("variablesReference", variableReference);
1654-
} else {
1667+
if (value.MightHaveChildren())
1668+
body.try_emplace("variablesReference", var_ref);
1669+
else
16551670
body.try_emplace("variablesReference", (int64_t)0);
1656-
}
16571671
if (lldb::addr_t addr = value.GetLoadAddress();
16581672
addr != LLDB_INVALID_ADDRESS)
16591673
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
1674+
if (ValuePointsToCode(value))
1675+
body.try_emplace("valueLocationReference", var_ref);
16601676
}
16611677
}
16621678
response.try_emplace("body", std::move(body));
@@ -3770,6 +3786,17 @@ void request_threads(const llvm::json::Object &request) {
37703786
// "description": "The number of indexed child variables. The client
37713787
// can use this optional information to present the variables in a
37723788
// paged UI and fetch them in chunks."
3789+
// },
3790+
// "valueLocationReference": {
3791+
// "type": "integer",
3792+
// "description": "A reference that allows the client to request the
3793+
// location where the new value is declared. For example, if the new
3794+
// value is function pointer, the adapter may be able to look up the
3795+
// function's location. This should be present only if the adapter
3796+
// is likely to be able to resolve the location.\n\nThis reference
3797+
// shares the same lifetime as the `variablesReference`. See
3798+
// 'Lifetime of Object References' in the Overview section for
3799+
// details."
37733800
// }
37743801
// },
37753802
// "required": [ "value" ]
@@ -3794,7 +3821,6 @@ void request_setVariable(const llvm::json::Object &request) {
37943821
response.try_emplace("success", false);
37953822

37963823
lldb::SBValue variable;
3797-
int64_t newVariablesReference = 0;
37983824

37993825
// The "id" is the unique integer ID that is unique within the enclosing
38003826
// variablesReference. It is optionally added to any "interface Variable"
@@ -3824,14 +3850,17 @@ void request_setVariable(const llvm::json::Object &request) {
38243850
// so always insert a new one to get its variablesReference.
38253851
// is_permanent is false because debug console does not support
38263852
// setVariable request.
3853+
int64_t new_var_ref =
3854+
g_dap.variables.InsertVariable(variable, /*is_permanent=*/false);
38273855
if (variable.MightHaveChildren())
3828-
newVariablesReference =
3829-
g_dap.variables.InsertVariable(variable, /*is_permanent=*/false);
3830-
body.try_emplace("variablesReference", newVariablesReference);
3831-
3856+
body.try_emplace("variablesReference", new_var_ref);
3857+
else
3858+
body.try_emplace("variablesReference", 0);
38323859
if (lldb::addr_t addr = variable.GetLoadAddress();
38333860
addr != LLDB_INVALID_ADDRESS)
38343861
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
3862+
if (ValuePointsToCode(variable))
3863+
body.try_emplace("valueLocationReference", new_var_ref);
38353864
} else {
38363865
EmplaceSafeString(body, "message", std::string(error.GetCString()));
38373866
}
@@ -4122,32 +4151,64 @@ void request_variables(const llvm::json::Object &request) {
41224151
void request_locations(const llvm::json::Object &request) {
41234152
llvm::json::Object response;
41244153
FillResponse(request, response);
4125-
auto arguments = request.getObject("arguments");
4154+
auto *arguments = request.getObject("arguments");
41264155

4127-
uint64_t reference_id = GetUnsigned(arguments, "locationReference", 0);
4128-
lldb::SBValue variable = g_dap.variables.GetVariable(reference_id);
4156+
uint64_t location_id = GetUnsigned(arguments, "locationReference", 0);
4157+
// We use the lowest bit to distinguish between value location and declaration
4158+
// location
4159+
auto [var_ref, is_value_location] = UnpackLocation(location_id);
4160+
lldb::SBValue variable = g_dap.variables.GetVariable(var_ref);
41294161
if (!variable.IsValid()) {
41304162
response["success"] = false;
41314163
response["message"] = "Invalid variable reference";
41324164
g_dap.SendJSON(llvm::json::Value(std::move(response)));
41334165
return;
41344166
}
41354167

4136-
// Get the declaration location
4137-
lldb::SBDeclaration decl = variable.GetDeclaration();
4138-
if (!decl.IsValid()) {
4139-
response["success"] = false;
4140-
response["message"] = "No declaration location available";
4141-
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4142-
return;
4143-
}
4144-
41454168
llvm::json::Object body;
4146-
body.try_emplace("source", CreateSource(decl.GetFileSpec()));
4147-
if (int line = decl.GetLine())
4148-
body.try_emplace("line", line);
4149-
if (int column = decl.GetColumn())
4150-
body.try_emplace("column", column);
4169+
if (is_value_location) {
4170+
// Get the value location
4171+
if (!variable.GetType().IsPointerType() &&
4172+
!variable.GetType().IsReferenceType()) {
4173+
response["success"] = false;
4174+
response["message"] =
4175+
"Value locations are only available for pointers and references";
4176+
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4177+
return;
4178+
}
4179+
4180+
lldb::addr_t addr = variable.GetValueAsAddress();
4181+
lldb::SBLineEntry line_entry =
4182+
g_dap.target.ResolveLoadAddress(addr).GetLineEntry();
4183+
4184+
if (!line_entry.IsValid()) {
4185+
response["success"] = false;
4186+
response["message"] = "Failed to resolve line entry for location";
4187+
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4188+
return;
4189+
}
4190+
4191+
body.try_emplace("source", CreateSource(line_entry.GetFileSpec()));
4192+
if (int line = line_entry.GetLine())
4193+
body.try_emplace("line", line);
4194+
if (int column = line_entry.GetColumn())
4195+
body.try_emplace("column", column);
4196+
} else {
4197+
// Get the declaration location
4198+
lldb::SBDeclaration decl = variable.GetDeclaration();
4199+
if (!decl.IsValid()) {
4200+
response["success"] = false;
4201+
response["message"] = "No declaration location available";
4202+
g_dap.SendJSON(llvm::json::Value(std::move(response)));
4203+
return;
4204+
}
4205+
4206+
body.try_emplace("source", CreateSource(decl.GetFileSpec()));
4207+
if (int line = decl.GetLine())
4208+
body.try_emplace("line", line);
4209+
if (int column = decl.GetColumn())
4210+
body.try_emplace("column", column);
4211+
}
41514212

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

0 commit comments

Comments
 (0)