Skip to content

Commit

Permalink
[lldb-dap] Implement value locations for function pointers (llvm#104589)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vogelsgesang authored Oct 11, 2024
1 parent cbfcea1 commit 9f8ae78
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 40 deletions.
2 changes: 1 addition & 1 deletion lldb/test/API/tools/lldb-dap/locations/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
C_SOURCES := main.c
CXX_SOURCES := main.cpp

include Makefile.rules
49 changes: 45 additions & 4 deletions lldb/test/API/tools/lldb-dap/locations/TestDAP_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ def test_locations(self):
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.c"
source = "main.cpp"
self.source_path = os.path.join(os.getcwd(), source)
self.set_source_breakpoints(
source,
[line_number(source, "// BREAK HERE")],
[line_number(source, "break here")],
)
self.continue_to_next_stop()

Expand All @@ -36,5 +36,46 @@ def test_locations(self):
locals["var1"]["declarationLocationReference"]
)
self.assertTrue(loc_var1["success"])
self.assertTrue(loc_var1["body"]["source"]["path"].endswith("main.c"))
self.assertEqual(loc_var1["body"]["line"], 2)
self.assertTrue(loc_var1["body"]["source"]["path"].endswith("main.cpp"))
self.assertEqual(loc_var1["body"]["line"], 6)

# func_ptr has both a declaration and a valueLocation
self.assertIn("declarationLocationReference", locals["func_ptr"].keys())
self.assertIn("valueLocationReference", locals["func_ptr"].keys())
decl_loc_func_ptr = self.dap_server.request_locations(
locals["func_ptr"]["declarationLocationReference"]
)
self.assertTrue(decl_loc_func_ptr["success"])
self.assertTrue(
decl_loc_func_ptr["body"]["source"]["path"].endswith("main.cpp")
)
self.assertEqual(decl_loc_func_ptr["body"]["line"], 7)
val_loc_func_ptr = self.dap_server.request_locations(
locals["func_ptr"]["valueLocationReference"]
)
self.assertTrue(val_loc_func_ptr["success"])
self.assertTrue(val_loc_func_ptr["body"]["source"]["path"].endswith("main.cpp"))
self.assertEqual(val_loc_func_ptr["body"]["line"], 3)

# func_ref has both a declaration and a valueLocation
self.assertIn("declarationLocationReference", locals["func_ref"].keys())
self.assertIn("valueLocationReference", locals["func_ref"].keys())
decl_loc_func_ref = self.dap_server.request_locations(
locals["func_ref"]["declarationLocationReference"]
)
self.assertTrue(decl_loc_func_ref["success"])
self.assertTrue(
decl_loc_func_ref["body"]["source"]["path"].endswith("main.cpp")
)
self.assertEqual(decl_loc_func_ref["body"]["line"], 8)
val_loc_func_ref = self.dap_server.request_locations(
locals["func_ref"]["valueLocationReference"]
)
self.assertTrue(val_loc_func_ref["success"])
self.assertTrue(val_loc_func_ref["body"]["source"]["path"].endswith("main.cpp"))
self.assertEqual(val_loc_func_ref["body"]["line"], 3)

# `evaluate` responses for function pointers also have locations associated
eval_res = self.dap_server.request_evaluate("greet")
self.assertTrue(eval_res["success"])
self.assertIn("valueLocationReference", eval_res["body"].keys())
5 changes: 0 additions & 5 deletions lldb/test/API/tools/lldb-dap/locations/main.c

This file was deleted.

10 changes: 10 additions & 0 deletions lldb/test/API/tools/lldb-dap/locations/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <cstdio>

void greet() { printf("Hello"); }

int main(void) {
int var1 = 1;
void (*func_ptr)() = &greet;
void (&func_ref)() = greet;
return 0; // break here
}
41 changes: 38 additions & 3 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,25 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
return description.trim().str();
}

bool ValuePointsToCode(lldb::SBValue v) {
if (!v.GetType().GetPointeeType().IsFunctionType())
return false;

lldb::addr_t addr = v.GetValueAsAddress();
lldb::SBLineEntry line_entry =
g_dap.target.ResolveLoadAddress(addr).GetLineEntry();

return line_entry.IsValid();
}

int64_t PackLocation(int64_t var_ref, bool is_value_location) {
return var_ref << 1 | is_value_location;
}

std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
return std::pair{location_id >> 1, location_id & 1};
}

// "Variable": {
// "type": "object",
// "description": "A Variable is a name/value pair. Optionally a variable
Expand Down Expand Up @@ -1302,6 +1321,18 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
// Object References' in the Overview section for
// details."
// },
// "valueLocationReference": {
// "type": "integer",
// "description": "A reference that allows the client to request the
// location where the variable's value is declared. For
// example, if the variable contains a function pointer,
// the adapter may be able to look up the function's
// location. This should be present only if the adapter
// is likely to be able to resolve the location.\n\nThis
// reference shares the same lifetime as the
// `variablesReference`. See 'Lifetime of Object
// References' in the Overview section for details."
// },
//
// "$__lldb_extensions": {
// "description": "Unofficial extensions to the protocol",
Expand Down Expand Up @@ -1415,7 +1446,11 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
object.try_emplace("variablesReference", 0);

if (v.GetDeclaration().IsValid())
object.try_emplace("declarationLocationReference", var_ref);
object.try_emplace("declarationLocationReference",
PackLocation(var_ref, false));

if (ValuePointsToCode(v))
object.try_emplace("valueLocationReference", PackLocation(var_ref, true));

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

auto launch_request_arguments = launch_request.getObject("arguments");
Expand Down
10 changes: 10 additions & 0 deletions lldb/tools/lldb-dap/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,16 @@ struct VariableDescription {
std::string GetResult(llvm::StringRef context);
};

/// Does the given variable have an associated value location?
bool ValuePointsToCode(lldb::SBValue v);

/// Pack a location into a single integer which we can send via
/// the debug adapter protocol.
int64_t PackLocation(int64_t var_ref, bool is_value_location);

/// Reverse of `PackLocation`
std::pair<int64_t, bool> UnpackLocation(int64_t location_id);

/// Create a "Variable" object for a LLDB thread object.
///
/// This function will fill in the following keys in the returned
Expand Down
115 changes: 88 additions & 27 deletions lldb/tools/lldb-dap/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,19 @@ void request_completions(const llvm::json::Object &request) {
// client can use this optional information to
// present the variables in a paged UI and fetch
// them in chunks."
// },
// "valueLocationReference": {
// "type": "integer",
// "description": "A reference that allows the client to request
// the location where the returned value is
// declared. For example, if a function pointer is
// returned, the adapter may be able to look up the
// function's location. This should be present only
// if the adapter is likely to be able to resolve
// the location.\n\nThis reference shares the same
// lifetime as the `variablesReference`. See
// 'Lifetime of Object References' in the
// Overview section for details."
// }
// "memoryReference": {
// "type": "string",
Expand Down Expand Up @@ -1647,16 +1660,19 @@ void request_evaluate(const llvm::json::Object &request) {
VariableDescription desc(value);
EmplaceSafeString(body, "result", desc.GetResult(context));
EmplaceSafeString(body, "type", desc.display_type_name);
if (value.MightHaveChildren()) {
auto variableReference = g_dap.variables.InsertVariable(
int64_t var_ref = 0;
if (value.MightHaveChildren() || ValuePointsToCode(value))
var_ref = g_dap.variables.InsertVariable(
value, /*is_permanent=*/context == "repl");
body.try_emplace("variablesReference", variableReference);
} else {
if (value.MightHaveChildren())
body.try_emplace("variablesReference", var_ref);
else
body.try_emplace("variablesReference", (int64_t)0);
}
if (lldb::addr_t addr = value.GetLoadAddress();
addr != LLDB_INVALID_ADDRESS)
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
if (ValuePointsToCode(value))
body.try_emplace("valueLocationReference", var_ref);
}
}
response.try_emplace("body", std::move(body));
Expand Down Expand Up @@ -3770,6 +3786,17 @@ void request_threads(const llvm::json::Object &request) {
// "description": "The number of indexed child variables. The client
// can use this optional information to present the variables in a
// paged UI and fetch them in chunks."
// },
// "valueLocationReference": {
// "type": "integer",
// "description": "A reference that allows the client to request the
// location where the new value is declared. For example, if the new
// value is function pointer, the adapter may be able to look up the
// function's location. This should be present only if the adapter
// is likely to be able to resolve the location.\n\nThis reference
// shares the same lifetime as the `variablesReference`. See
// 'Lifetime of Object References' in the Overview section for
// details."
// }
// },
// "required": [ "value" ]
Expand All @@ -3794,7 +3821,6 @@ void request_setVariable(const llvm::json::Object &request) {
response.try_emplace("success", false);

lldb::SBValue variable;
int64_t newVariablesReference = 0;

// The "id" is the unique integer ID that is unique within the enclosing
// variablesReference. It is optionally added to any "interface Variable"
Expand Down Expand Up @@ -3824,14 +3850,17 @@ void request_setVariable(const llvm::json::Object &request) {
// so always insert a new one to get its variablesReference.
// is_permanent is false because debug console does not support
// setVariable request.
int64_t new_var_ref =
g_dap.variables.InsertVariable(variable, /*is_permanent=*/false);
if (variable.MightHaveChildren())
newVariablesReference =
g_dap.variables.InsertVariable(variable, /*is_permanent=*/false);
body.try_emplace("variablesReference", newVariablesReference);

body.try_emplace("variablesReference", new_var_ref);
else
body.try_emplace("variablesReference", 0);
if (lldb::addr_t addr = variable.GetLoadAddress();
addr != LLDB_INVALID_ADDRESS)
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
if (ValuePointsToCode(variable))
body.try_emplace("valueLocationReference", new_var_ref);
} else {
EmplaceSafeString(body, "message", std::string(error.GetCString()));
}
Expand Down Expand Up @@ -4122,32 +4151,64 @@ void request_variables(const llvm::json::Object &request) {
void request_locations(const llvm::json::Object &request) {
llvm::json::Object response;
FillResponse(request, response);
auto arguments = request.getObject("arguments");
auto *arguments = request.getObject("arguments");

uint64_t reference_id = GetUnsigned(arguments, "locationReference", 0);
lldb::SBValue variable = g_dap.variables.GetVariable(reference_id);
uint64_t location_id = GetUnsigned(arguments, "locationReference", 0);
// We use the lowest bit to distinguish between value location and declaration
// location
auto [var_ref, is_value_location] = UnpackLocation(location_id);
lldb::SBValue variable = g_dap.variables.GetVariable(var_ref);
if (!variable.IsValid()) {
response["success"] = false;
response["message"] = "Invalid variable reference";
g_dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

// Get the declaration location
lldb::SBDeclaration decl = variable.GetDeclaration();
if (!decl.IsValid()) {
response["success"] = false;
response["message"] = "No declaration location available";
g_dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

llvm::json::Object body;
body.try_emplace("source", CreateSource(decl.GetFileSpec()));
if (int line = decl.GetLine())
body.try_emplace("line", line);
if (int column = decl.GetColumn())
body.try_emplace("column", column);
if (is_value_location) {
// Get the value location
if (!variable.GetType().IsPointerType() &&
!variable.GetType().IsReferenceType()) {
response["success"] = false;
response["message"] =
"Value locations are only available for pointers and references";
g_dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

lldb::addr_t addr = variable.GetValueAsAddress();
lldb::SBLineEntry line_entry =
g_dap.target.ResolveLoadAddress(addr).GetLineEntry();

if (!line_entry.IsValid()) {
response["success"] = false;
response["message"] = "Failed to resolve line entry for location";
g_dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

body.try_emplace("source", CreateSource(line_entry.GetFileSpec()));
if (int line = line_entry.GetLine())
body.try_emplace("line", line);
if (int column = line_entry.GetColumn())
body.try_emplace("column", column);
} else {
// Get the declaration location
lldb::SBDeclaration decl = variable.GetDeclaration();
if (!decl.IsValid()) {
response["success"] = false;
response["message"] = "No declaration location available";
g_dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

body.try_emplace("source", CreateSource(decl.GetFileSpec()));
if (int line = decl.GetLine())
body.try_emplace("line", line);
if (int column = decl.GetColumn())
body.try_emplace("column", column);
}

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

0 comments on commit 9f8ae78

Please sign in to comment.