[lldb-dap] Migrate variables request protocol types. (#147611)
This adds new protocol types for the 'variables' request. While implementing this, I removed the '$__lldb_extension' field we returned on the 'variables' request, since I think all the data can be retrieved from other DAP requests. --------- Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
This commit is contained in:
parent
8d3f497eb8
commit
4b6e54a8cf
@ -28,7 +28,7 @@ class TestDAP_optimized(lldbdap_testcase.DAPTestCaseBase):
|
||||
parent_frame = self.dap_server.get_stackFrame(frameIndex=1)
|
||||
self.assertTrue(parent_frame["name"].endswith(" [opt]"))
|
||||
|
||||
@skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
|
||||
@skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
|
||||
@skipIfWindows
|
||||
def test_optimized_variable(self):
|
||||
"""Test optimized variable value contains error."""
|
||||
@ -50,9 +50,8 @@ class TestDAP_optimized(lldbdap_testcase.DAPTestCaseBase):
|
||||
value.startswith("<error:"),
|
||||
f"expect error for value: '{value}'",
|
||||
)
|
||||
error_msg = optimized_variable["$__lldb_extensions"]["error"]
|
||||
self.assertTrue(
|
||||
("could not evaluate DW_OP_entry_value: no parent function" in error_msg)
|
||||
or ("variable not available" in error_msg)
|
||||
("could not evaluate DW_OP_entry_value: no parent function" in value)
|
||||
or ("variable not available" in value)
|
||||
)
|
||||
self.continue_to_exit()
|
||||
|
@ -1,12 +1,10 @@
|
||||
"""
|
||||
Test lldb-dap setBreakpoints request
|
||||
Test lldb-dap variables request
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import dap_server
|
||||
import lldbdap_testcase
|
||||
from lldbsuite.test import lldbutil
|
||||
from lldbsuite.test.decorators import *
|
||||
from lldbsuite.test.lldbtest import *
|
||||
|
||||
@ -168,15 +166,6 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
|
||||
"type": "int",
|
||||
"value": "1",
|
||||
},
|
||||
"$__lldb_extensions": {
|
||||
"equals": {
|
||||
"value": "1",
|
||||
},
|
||||
"declaration": {
|
||||
"equals": {"line": 12, "column": 14},
|
||||
"contains": {"path": ["lldb-dap", "variables", "main.cpp"]},
|
||||
},
|
||||
},
|
||||
},
|
||||
"argv": {
|
||||
"equals": {"type": "const char **"},
|
||||
@ -196,10 +185,6 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
|
||||
},
|
||||
"x": {"equals": {"type": "int"}},
|
||||
}
|
||||
if enableAutoVariableSummaries:
|
||||
verify_locals["pt"]["$__lldb_extensions"] = {
|
||||
"equals": {"autoSummary": "{x:11, y:22}"}
|
||||
}
|
||||
|
||||
verify_globals = {
|
||||
"s_local": {"equals": {"type": "float", "value": "2.25"}},
|
||||
|
@ -540,11 +540,14 @@ public:
|
||||
Run(const protocol::ThreadsArguments &) const override;
|
||||
};
|
||||
|
||||
class VariablesRequestHandler : public LegacyRequestHandler {
|
||||
class VariablesRequestHandler
|
||||
: public RequestHandler<protocol::VariablesArguments,
|
||||
llvm::Expected<protocol::VariablesResponseBody>> {
|
||||
public:
|
||||
using LegacyRequestHandler::LegacyRequestHandler;
|
||||
using RequestHandler::RequestHandler;
|
||||
static llvm::StringLiteral GetCommand() { return "variables"; }
|
||||
void operator()(const llvm::json::Object &request) const override;
|
||||
llvm::Expected<protocol::VariablesResponseBody>
|
||||
Run(const protocol::VariablesArguments &) const override;
|
||||
};
|
||||
|
||||
class LocationsRequestHandler : public LegacyRequestHandler {
|
||||
|
@ -8,107 +8,37 @@
|
||||
|
||||
#include "DAP.h"
|
||||
#include "EventHelper.h"
|
||||
#include "Handler/RequestHandler.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "RequestHandler.h"
|
||||
#include "ProtocolUtils.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace lldb_dap::protocol;
|
||||
|
||||
namespace lldb_dap {
|
||||
|
||||
// "VariablesRequest": {
|
||||
// "allOf": [ { "$ref": "#/definitions/Request" }, {
|
||||
// "type": "object",
|
||||
// "description": "Variables request; value of command field is 'variables'.
|
||||
// Retrieves all child variables for the given variable reference. An
|
||||
// optional filter can be used to limit the fetched children to either named
|
||||
// or indexed children.", "properties": {
|
||||
// "command": {
|
||||
// "type": "string",
|
||||
// "enum": [ "variables" ]
|
||||
// },
|
||||
// "arguments": {
|
||||
// "$ref": "#/definitions/VariablesArguments"
|
||||
// }
|
||||
// },
|
||||
// "required": [ "command", "arguments" ]
|
||||
// }]
|
||||
// },
|
||||
// "VariablesArguments": {
|
||||
// "type": "object",
|
||||
// "description": "Arguments for 'variables' request.",
|
||||
// "properties": {
|
||||
// "variablesReference": {
|
||||
// "type": "integer",
|
||||
// "description": "The Variable reference."
|
||||
// },
|
||||
// "filter": {
|
||||
// "type": "string",
|
||||
// "enum": [ "indexed", "named" ],
|
||||
// "description": "Optional filter to limit the child variables to either
|
||||
// named or indexed. If ommited, both types are fetched."
|
||||
// },
|
||||
// "start": {
|
||||
// "type": "integer",
|
||||
// "description": "The index of the first variable to return; if omitted
|
||||
// children start at 0."
|
||||
// },
|
||||
// "count": {
|
||||
// "type": "integer",
|
||||
// "description": "The number of variables to return. If count is missing
|
||||
// or 0, all variables are returned."
|
||||
// },
|
||||
// "format": {
|
||||
// "$ref": "#/definitions/ValueFormat",
|
||||
// "description": "Specifies details on how to format the Variable
|
||||
// values."
|
||||
// }
|
||||
// },
|
||||
// "required": [ "variablesReference" ]
|
||||
// },
|
||||
// "VariablesResponse": {
|
||||
// "allOf": [ { "$ref": "#/definitions/Response" }, {
|
||||
// "type": "object",
|
||||
// "description": "Response to 'variables' request.",
|
||||
// "properties": {
|
||||
// "body": {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "variables": {
|
||||
// "type": "array",
|
||||
// "items": {
|
||||
// "$ref": "#/definitions/Variable"
|
||||
// },
|
||||
// "description": "All (or a range) of variables for the given
|
||||
// variable reference."
|
||||
// }
|
||||
// },
|
||||
// "required": [ "variables" ]
|
||||
// }
|
||||
// },
|
||||
// "required": [ "body" ]
|
||||
// }]
|
||||
// }
|
||||
void VariablesRequestHandler::operator()(
|
||||
const llvm::json::Object &request) const {
|
||||
llvm::json::Object response;
|
||||
FillResponse(request, response);
|
||||
llvm::json::Array variables;
|
||||
const auto *arguments = request.getObject("arguments");
|
||||
const auto variablesReference =
|
||||
GetInteger<uint64_t>(arguments, "variablesReference").value_or(0);
|
||||
const auto start = GetInteger<int64_t>(arguments, "start").value_or(0);
|
||||
const auto count = GetInteger<int64_t>(arguments, "count").value_or(0);
|
||||
/// Retrieves all child variables for the given variable reference.
|
||||
///
|
||||
/// A filter can be used to limit the fetched children to either named or
|
||||
/// indexed children.
|
||||
Expected<VariablesResponseBody>
|
||||
VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
|
||||
const uint64_t var_ref = arguments.variablesReference;
|
||||
const uint64_t count = arguments.count;
|
||||
const uint64_t start = arguments.start;
|
||||
bool hex = false;
|
||||
const auto *format = arguments->getObject("format");
|
||||
if (format)
|
||||
hex = GetBoolean(format, "hex").value_or(false);
|
||||
if (arguments.format)
|
||||
hex = arguments.format->hex;
|
||||
|
||||
if (lldb::SBValueList *top_scope =
|
||||
dap.variables.GetTopLevelScope(variablesReference)) {
|
||||
std::vector<Variable> variables;
|
||||
|
||||
if (lldb::SBValueList *top_scope = dap.variables.GetTopLevelScope(var_ref)) {
|
||||
// variablesReference is one of our scopes, not an actual variable it is
|
||||
// asking for the list of args, locals or globals.
|
||||
int64_t start_idx = 0;
|
||||
int64_t num_children = 0;
|
||||
|
||||
if (variablesReference == VARREF_REGS) {
|
||||
if (var_ref == VARREF_REGS) {
|
||||
// Change the default format of any pointer sized registers in the first
|
||||
// register set to be the lldb::eFormatAddressInfo so we show the pointer
|
||||
// and resolve what the pointer resolves to. Only change the format if the
|
||||
@ -128,7 +58,7 @@ void VariablesRequestHandler::operator()(
|
||||
}
|
||||
|
||||
num_children = top_scope->GetSize();
|
||||
if (num_children == 0 && variablesReference == VARREF_LOCALS) {
|
||||
if (num_children == 0 && var_ref == VARREF_LOCALS) {
|
||||
// Check for an error in the SBValueList that might explain why we don't
|
||||
// have locals. If we have an error display it as the sole value in the
|
||||
// the locals.
|
||||
@ -145,12 +75,11 @@ void VariablesRequestHandler::operator()(
|
||||
// errors are only set when there is a problem that the user could
|
||||
// fix, so no error will show up when you have no debug info, only when
|
||||
// we do have debug info and something that is fixable can be done.
|
||||
llvm::json::Object object;
|
||||
EmplaceSafeString(object, "name", "<error>");
|
||||
EmplaceSafeString(object, "type", "const char *");
|
||||
EmplaceSafeString(object, "value", var_err);
|
||||
object.try_emplace("variablesReference", (int64_t)0);
|
||||
variables.emplace_back(std::move(object));
|
||||
Variable var;
|
||||
var.name = "<error>";
|
||||
var.type = "const char *";
|
||||
var.value = var_err;
|
||||
variables.emplace_back(var);
|
||||
}
|
||||
}
|
||||
const int64_t end_idx = start_idx + ((count == 0) ? num_children : count);
|
||||
@ -165,7 +94,7 @@ void VariablesRequestHandler::operator()(
|
||||
}
|
||||
|
||||
// Show return value if there is any ( in the local top frame )
|
||||
if (variablesReference == VARREF_LOCALS) {
|
||||
if (var_ref == VARREF_LOCALS) {
|
||||
auto process = dap.target.GetProcess();
|
||||
auto selected_thread = process.GetSelectedThread();
|
||||
lldb::SBValue stop_return_value = selected_thread.GetStopReturnValue();
|
||||
@ -194,32 +123,35 @@ void VariablesRequestHandler::operator()(
|
||||
if (!variable.IsValid())
|
||||
break;
|
||||
|
||||
int64_t var_ref =
|
||||
const int64_t frame_var_ref =
|
||||
dap.variables.InsertVariable(variable, /*is_permanent=*/false);
|
||||
variables.emplace_back(CreateVariable(
|
||||
variable, var_ref, hex, dap.configuration.enableAutoVariableSummaries,
|
||||
variable, frame_var_ref, hex,
|
||||
dap.configuration.enableAutoVariableSummaries,
|
||||
dap.configuration.enableSyntheticChildDebugging,
|
||||
variable_name_counts[GetNonNullVariableName(variable)] > 1));
|
||||
}
|
||||
} else {
|
||||
// We are expanding a variable that has children, so we will return its
|
||||
// children.
|
||||
lldb::SBValue variable = dap.variables.GetVariable(variablesReference);
|
||||
lldb::SBValue variable = dap.variables.GetVariable(var_ref);
|
||||
if (variable.IsValid()) {
|
||||
const bool is_permanent =
|
||||
dap.variables.IsPermanentVariableReference(var_ref);
|
||||
auto addChild = [&](lldb::SBValue child,
|
||||
std::optional<std::string> custom_name = {}) {
|
||||
if (!child.IsValid())
|
||||
return;
|
||||
bool is_permanent =
|
||||
dap.variables.IsPermanentVariableReference(variablesReference);
|
||||
int64_t var_ref = dap.variables.InsertVariable(child, is_permanent);
|
||||
variables.emplace_back(CreateVariable(
|
||||
child, var_ref, hex, dap.configuration.enableAutoVariableSummaries,
|
||||
dap.configuration.enableSyntheticChildDebugging,
|
||||
/*is_name_duplicated=*/false, custom_name));
|
||||
const int64_t child_var_ref =
|
||||
dap.variables.InsertVariable(child, is_permanent);
|
||||
variables.emplace_back(
|
||||
CreateVariable(child, child_var_ref, hex,
|
||||
dap.configuration.enableAutoVariableSummaries,
|
||||
dap.configuration.enableSyntheticChildDebugging,
|
||||
/*is_name_duplicated=*/false, custom_name));
|
||||
};
|
||||
const int64_t num_children = variable.GetNumChildren();
|
||||
int64_t end_idx = start + ((count == 0) ? num_children : count);
|
||||
const int64_t end_idx = start + ((count == 0) ? num_children : count);
|
||||
int64_t i = start;
|
||||
for (; i < end_idx && i < num_children; ++i)
|
||||
addChild(variable.GetChildAtIndex(i));
|
||||
@ -233,10 +165,8 @@ void VariablesRequestHandler::operator()(
|
||||
addChild(variable.GetNonSyntheticValue(), "[raw]");
|
||||
}
|
||||
}
|
||||
llvm::json::Object body;
|
||||
body.try_emplace("variables", std::move(variables));
|
||||
response.try_emplace("body", std::move(body));
|
||||
dap.SendJSON(llvm::json::Value(std::move(response)));
|
||||
|
||||
return VariablesResponseBody{variables};
|
||||
}
|
||||
|
||||
} // namespace lldb_dap
|
||||
|
@ -120,6 +120,42 @@ DecodeMemoryReference(llvm::StringRef memoryReference) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool DecodeMemoryReference(const llvm::json::Value &v, llvm::StringLiteral key,
|
||||
lldb::addr_t &out, llvm::json::Path path,
|
||||
bool required) {
|
||||
const llvm::json::Object *v_obj = v.getAsObject();
|
||||
if (!v_obj) {
|
||||
path.report("expected object");
|
||||
return false;
|
||||
}
|
||||
|
||||
const llvm::json::Value *mem_ref_value = v_obj->get(key);
|
||||
if (!mem_ref_value) {
|
||||
if (!required)
|
||||
return true;
|
||||
|
||||
path.field(key).report("missing value");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<llvm::StringRef> mem_ref_str =
|
||||
mem_ref_value->getAsString();
|
||||
if (!mem_ref_str) {
|
||||
path.field(key).report("expected string");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<lldb::addr_t> addr_opt =
|
||||
DecodeMemoryReference(*mem_ref_str);
|
||||
if (!addr_opt) {
|
||||
path.field(key).report("malformed memory reference");
|
||||
return false;
|
||||
}
|
||||
|
||||
out = *addr_opt;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetStrings(const llvm::json::Object *obj,
|
||||
llvm::StringRef key) {
|
||||
std::vector<std::string> strs;
|
||||
@ -768,38 +804,6 @@ VariableDescription::VariableDescription(lldb::SBValue v,
|
||||
evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
|
||||
}
|
||||
|
||||
llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
|
||||
llvm::json::Object extensions;
|
||||
if (error)
|
||||
EmplaceSafeString(extensions, "error", *error);
|
||||
if (!value.empty())
|
||||
EmplaceSafeString(extensions, "value", value);
|
||||
if (!summary.empty())
|
||||
EmplaceSafeString(extensions, "summary", summary);
|
||||
if (auto_summary)
|
||||
EmplaceSafeString(extensions, "autoSummary", *auto_summary);
|
||||
|
||||
if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
|
||||
llvm::json::Object decl_obj;
|
||||
if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
|
||||
char path[PATH_MAX] = "";
|
||||
if (file.GetPath(path, sizeof(path)) &&
|
||||
lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) {
|
||||
decl_obj.try_emplace("path", std::string(path));
|
||||
}
|
||||
}
|
||||
|
||||
if (int line = decl.GetLine())
|
||||
decl_obj.try_emplace("line", line);
|
||||
if (int column = decl.GetColumn())
|
||||
decl_obj.try_emplace("column", column);
|
||||
|
||||
if (!decl_obj.empty())
|
||||
extensions.try_emplace("declaration", std::move(decl_obj));
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
std::string VariableDescription::GetResult(llvm::StringRef context) {
|
||||
// In repl context, the results can be displayed as multiple lines so more
|
||||
// detailed descriptions can be returned.
|
||||
@ -836,226 +840,6 @@ 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
|
||||
// can have a 'type' that is shown if space permits or when
|
||||
// hovering over the variable's name. An optional 'kind' is
|
||||
// used to render additional properties of the variable,
|
||||
// e.g. different icons can be used to indicate that a
|
||||
// variable is public or private. If the value is
|
||||
// structured (has children), a handle is provided to
|
||||
// retrieve the children with the VariablesRequest. If
|
||||
// the number of named or indexed children is large, the
|
||||
// numbers should be returned via the optional
|
||||
// 'namedVariables' and 'indexedVariables' attributes. The
|
||||
// client can use this optional information to present the
|
||||
// children in a paged UI and fetch them in chunks.",
|
||||
// "properties": {
|
||||
// "name": {
|
||||
// "type": "string",
|
||||
// "description": "The variable's name."
|
||||
// },
|
||||
// "value": {
|
||||
// "type": "string",
|
||||
// "description": "The variable's value. This can be a multi-line text,
|
||||
// e.g. for a function the body of a function."
|
||||
// },
|
||||
// "type": {
|
||||
// "type": "string",
|
||||
// "description": "The type of the variable's value. Typically shown in
|
||||
// the UI when hovering over the value."
|
||||
// },
|
||||
// "presentationHint": {
|
||||
// "$ref": "#/definitions/VariablePresentationHint",
|
||||
// "description": "Properties of a variable that can be used to determine
|
||||
// how to render the variable in the UI."
|
||||
// },
|
||||
// "evaluateName": {
|
||||
// "type": "string",
|
||||
// "description": "Optional evaluatable name of this variable which can
|
||||
// be passed to the 'EvaluateRequest' to fetch the
|
||||
// variable's value."
|
||||
// },
|
||||
// "variablesReference": {
|
||||
// "type": "integer",
|
||||
// "description": "If variablesReference is > 0, the variable is
|
||||
// structured and its children can be retrieved by
|
||||
// passing variablesReference to the VariablesRequest."
|
||||
// },
|
||||
// "namedVariables": {
|
||||
// "type": "integer",
|
||||
// "description": "The number of named child variables. The client can
|
||||
// use this optional information to present the children
|
||||
// in a paged UI and fetch them in chunks."
|
||||
// },
|
||||
// "indexedVariables": {
|
||||
// "type": "integer",
|
||||
// "description": "The number of indexed child variables. The client
|
||||
// can use this optional information to present the
|
||||
// children in a paged UI and fetch them in chunks."
|
||||
// },
|
||||
// "memoryReference": {
|
||||
// "type": "string",
|
||||
// "description": "A memory reference associated with this variable.
|
||||
// For pointer type variables, this is generally a
|
||||
// reference to the memory address contained in the
|
||||
// pointer. For executable data, this reference may later
|
||||
// be used in a `disassemble` request. This attribute may
|
||||
// be returned by a debug adapter if corresponding
|
||||
// capability `supportsMemoryReferences` is true."
|
||||
// },
|
||||
// "declarationLocationReference": {
|
||||
// "type": "integer",
|
||||
// "description": "A reference that allows the client to request the
|
||||
// location where the variable is declared. 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."
|
||||
// },
|
||||
// "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",
|
||||
// "properties": {
|
||||
// "declaration": {
|
||||
// "type": "object",
|
||||
// "description": "The source location where the variable was
|
||||
// declared. This value won't be present if no
|
||||
// declaration is available.
|
||||
// Superseded by `declarationLocationReference`",
|
||||
// "properties": {
|
||||
// "path": {
|
||||
// "type": "string",
|
||||
// "description": "The source file path where the variable was
|
||||
// declared."
|
||||
// },
|
||||
// "line": {
|
||||
// "type": "number",
|
||||
// "description": "The 1-indexed source line where the variable
|
||||
// was declared."
|
||||
// },
|
||||
// "column": {
|
||||
// "type": "number",
|
||||
// "description": "The 1-indexed source column where the variable
|
||||
// was declared."
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "value": {
|
||||
// "type": "string",
|
||||
// "description": "The internal value of the variable as returned by
|
||||
// This is effectively SBValue.GetValue(). The other
|
||||
// `value` entry in the top-level variable response
|
||||
// is, on the other hand, just a display string for
|
||||
// the variable."
|
||||
// },
|
||||
// "summary": {
|
||||
// "type": "string",
|
||||
// "description": "The summary string of the variable. This is
|
||||
// effectively SBValue.GetSummary()."
|
||||
// },
|
||||
// "autoSummary": {
|
||||
// "type": "string",
|
||||
// "description": "The auto generated summary if using
|
||||
// `enableAutoVariableSummaries`."
|
||||
// },
|
||||
// "error": {
|
||||
// "type": "string",
|
||||
// "description": "An error message generated if LLDB couldn't inspect
|
||||
// the variable."
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "required": [ "name", "value", "variablesReference" ]
|
||||
// }
|
||||
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
|
||||
bool format_hex, bool auto_variable_summaries,
|
||||
bool synthetic_child_debugging,
|
||||
bool is_name_duplicated,
|
||||
std::optional<std::string> custom_name) {
|
||||
VariableDescription desc(v, auto_variable_summaries, format_hex,
|
||||
is_name_duplicated, custom_name);
|
||||
llvm::json::Object object;
|
||||
EmplaceSafeString(object, "name", desc.name);
|
||||
EmplaceSafeString(object, "value", desc.display_value);
|
||||
|
||||
if (!desc.evaluate_name.empty())
|
||||
EmplaceSafeString(object, "evaluateName", desc.evaluate_name);
|
||||
|
||||
// If we have a type with many children, we would like to be able to
|
||||
// give a hint to the IDE that the type has indexed children so that the
|
||||
// request can be broken up in grabbing only a few children at a time. We
|
||||
// want to be careful and only call "v.GetNumChildren()" if we have an array
|
||||
// type or if we have a synthetic child provider producing indexed children.
|
||||
// We don't want to call "v.GetNumChildren()" on all objects as class, struct
|
||||
// and union types don't need to be completed if they are never expanded. So
|
||||
// we want to avoid calling this to only cases where we it makes sense to keep
|
||||
// performance high during normal debugging.
|
||||
|
||||
// If we have an array type, say that it is indexed and provide the number
|
||||
// of children in case we have a huge array. If we don't do this, then we
|
||||
// might take a while to produce all children at onces which can delay your
|
||||
// debug session.
|
||||
if (desc.type_obj.IsArrayType()) {
|
||||
object.try_emplace("indexedVariables", v.GetNumChildren());
|
||||
} else if (v.IsSynthetic()) {
|
||||
// For a type with a synthetic child provider, the SBType of "v" won't tell
|
||||
// us anything about what might be displayed. Instead, we check if the first
|
||||
// child's name is "[0]" and then say it is indexed. We call
|
||||
// GetNumChildren() only if the child name matches to avoid a potentially
|
||||
// expensive operation.
|
||||
if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
|
||||
llvm::StringRef first_child_name = first_child.GetName();
|
||||
if (first_child_name == "[0]") {
|
||||
size_t num_children = v.GetNumChildren();
|
||||
// If we are creating a "[raw]" fake child for each synthetic type, we
|
||||
// have to account for it when returning indexed variables.
|
||||
if (synthetic_child_debugging)
|
||||
++num_children;
|
||||
object.try_emplace("indexedVariables", num_children);
|
||||
}
|
||||
}
|
||||
}
|
||||
EmplaceSafeString(object, "type", desc.display_type_name);
|
||||
|
||||
// A unique variable identifier to help in properly identifying variables with
|
||||
// the same name. This is an extension to the VS protocol.
|
||||
object.try_emplace("id", var_ref);
|
||||
|
||||
if (v.MightHaveChildren())
|
||||
object.try_emplace("variablesReference", var_ref);
|
||||
else
|
||||
object.try_emplace("variablesReference", 0);
|
||||
|
||||
if (v.GetDeclaration().IsValid())
|
||||
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));
|
||||
|
||||
object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON());
|
||||
return llvm::json::Value(std::move(object));
|
||||
}
|
||||
|
||||
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {
|
||||
llvm::json::Object object;
|
||||
char unit_path_arr[PATH_MAX];
|
||||
|
@ -138,6 +138,30 @@ std::string EncodeMemoryReference(lldb::addr_t addr);
|
||||
std::optional<lldb::addr_t>
|
||||
DecodeMemoryReference(llvm::StringRef memoryReference);
|
||||
|
||||
/// Decodes a memory reference from the given json value.
|
||||
///
|
||||
/// \param[in] v
|
||||
/// A JSON value that we expected to contain the memory reference.
|
||||
///
|
||||
/// \param[in] key
|
||||
/// The key of the memory reference.
|
||||
///
|
||||
/// \param[out] out
|
||||
/// The memory address, if successfully decoded.
|
||||
///
|
||||
/// \param[in] path
|
||||
/// The path for reporting errors.
|
||||
///
|
||||
/// \param[in] required
|
||||
/// Indicates if the key is required to be present, otherwise report an error
|
||||
/// if the key is missing.
|
||||
///
|
||||
/// \return
|
||||
/// Returns \b true if the address was decoded successfully.
|
||||
bool DecodeMemoryReference(const llvm::json::Value &v, llvm::StringLiteral key,
|
||||
lldb::addr_t &out, llvm::json::Path path,
|
||||
bool required);
|
||||
|
||||
/// Extract an array of strings for the specified key from an object.
|
||||
///
|
||||
/// String values in the array will be extracted without any quotes
|
||||
@ -326,10 +350,6 @@ struct VariableDescription {
|
||||
bool format_hex = false, bool is_name_duplicated = false,
|
||||
std::optional<std::string> custom_name = {});
|
||||
|
||||
/// Create a JSON object that represents these extensions to the DAP variable
|
||||
/// response.
|
||||
llvm::json::Object GetVariableExtensionsJSON();
|
||||
|
||||
/// Returns a description of the value appropriate for the specified context.
|
||||
std::string GetResult(llvm::StringRef context);
|
||||
};
|
||||
@ -344,61 +364,6 @@ 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
|
||||
/// object:
|
||||
/// "name" - the name of the variable
|
||||
/// "value" - the value of the variable as a string
|
||||
/// "type" - the typename of the variable as a string
|
||||
/// "id" - a unique identifier for a value in case there are multiple
|
||||
/// variables with the same name. Other parts of the DAP
|
||||
/// protocol refer to values by name so this can help
|
||||
/// disambiguate such cases if a IDE passes this "id" value
|
||||
/// back down.
|
||||
/// "variablesReference" - Zero if the variable has no children,
|
||||
/// non-zero integer otherwise which can be used to expand
|
||||
/// the variable.
|
||||
/// "evaluateName" - The name of the variable to use in expressions
|
||||
/// as a string.
|
||||
///
|
||||
/// \param[in] v
|
||||
/// The LLDB value to use when populating out the "Variable"
|
||||
/// object.
|
||||
///
|
||||
/// \param[in] var_ref
|
||||
/// The variable reference. Used to identify the value, e.g.
|
||||
/// in the `variablesReference` or `declarationLocationReference`
|
||||
/// properties.
|
||||
///
|
||||
/// \param[in] format_hex
|
||||
/// If set to true the variable will be formatted as hex in
|
||||
/// the "value" key value pair for the value of the variable.
|
||||
///
|
||||
/// \param[in] auto_variable_summaries
|
||||
/// IF set to true the variable will create an automatic variable summary.
|
||||
///
|
||||
/// \param[in] is_name_duplicated
|
||||
/// Whether the same variable name appears multiple times within the same
|
||||
/// context (e.g. locals). This can happen due to shadowed variables in
|
||||
/// nested blocks.
|
||||
///
|
||||
/// As VSCode doesn't render two of more variables with the same name, we
|
||||
/// apply a suffix to distinguish duplicated variables.
|
||||
///
|
||||
/// \param[in] custom_name
|
||||
/// A provided custom name that is used instead of the SBValue's when
|
||||
/// creating the JSON representation.
|
||||
///
|
||||
/// \return
|
||||
/// A "Variable" JSON object with that follows the formal JSON
|
||||
/// definition outlined by Microsoft.
|
||||
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
|
||||
bool format_hex, bool auto_variable_summaries,
|
||||
bool synthetic_child_debugging,
|
||||
bool is_name_duplicated = false,
|
||||
std::optional<std::string> custom_name = {});
|
||||
|
||||
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
|
||||
|
||||
/// Create a runInTerminal reverse request object
|
||||
|
@ -531,6 +531,41 @@ json::Value toJSON(const ModulesResponseBody &MR) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Value &Param, VariablesArguments::VariablesFilter &VA,
|
||||
json::Path Path) {
|
||||
auto rawFilter = Param.getAsString();
|
||||
if (!rawFilter) {
|
||||
Path.report("expected a string");
|
||||
return false;
|
||||
}
|
||||
std::optional<VariablesArguments::VariablesFilter> filter =
|
||||
StringSwitch<std::optional<VariablesArguments::VariablesFilter>>(
|
||||
*rawFilter)
|
||||
.Case("indexed", VariablesArguments::eVariablesFilterIndexed)
|
||||
.Case("named", VariablesArguments::eVariablesFilterNamed)
|
||||
.Default(std::nullopt);
|
||||
if (!filter) {
|
||||
Path.report("unexpected value, expected 'named' or 'indexed'");
|
||||
return false;
|
||||
}
|
||||
|
||||
VA = *filter;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Value &Param, VariablesArguments &VA,
|
||||
json::Path Path) {
|
||||
json::ObjectMapper O(Param, Path);
|
||||
return O && O.map("variablesReference", VA.variablesReference) &&
|
||||
O.mapOptional("filter", VA.filter) &&
|
||||
O.mapOptional("start", VA.start) && O.mapOptional("count", VA.count) &&
|
||||
O.mapOptional("format", VA.format);
|
||||
}
|
||||
|
||||
json::Value toJSON(const VariablesResponseBody &VRB) {
|
||||
return json::Object{{"variables", VRB.variables}};
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Value &Params, WriteMemoryArguments &WMA,
|
||||
json::Path P) {
|
||||
json::ObjectMapper O(Params, P);
|
||||
|
@ -896,6 +896,54 @@ struct ModulesResponseBody {
|
||||
};
|
||||
llvm::json::Value toJSON(const ModulesResponseBody &);
|
||||
|
||||
/// Arguments for `variables` request.
|
||||
struct VariablesArguments {
|
||||
/// The variable for which to retrieve its children. The `variablesReference`
|
||||
/// must have been obtained in the current suspended state. See 'Lifetime of
|
||||
/// Object References' in the Overview section for details.
|
||||
uint64_t variablesReference;
|
||||
|
||||
enum VariablesFilter : unsigned {
|
||||
eVariablesFilterBoth = 0,
|
||||
eVariablesFilterIndexed = 1 << 0,
|
||||
eVariablesFilterNamed = 1 << 1,
|
||||
};
|
||||
|
||||
/// Filter to limit the child variables to either named or indexed. If
|
||||
/// omitted, both types are fetched.
|
||||
VariablesFilter filter = eVariablesFilterBoth;
|
||||
|
||||
/// The index of the first variable to return; if omitted children start at 0.
|
||||
///
|
||||
/// The attribute is only honored by a debug adapter if the corresponding
|
||||
/// capability `supportsVariablePaging` is true.
|
||||
uint64_t start = 0;
|
||||
|
||||
/// The number of variables to return. If count is missing or 0, all variables
|
||||
/// are returned.
|
||||
///
|
||||
/// The attribute is only honored by a debug adapter if the corresponding
|
||||
/// capability `supportsVariablePaging` is true.
|
||||
uint64_t count = 0;
|
||||
|
||||
/// Specifies details on how to format the Variable values.
|
||||
///
|
||||
/// The attribute is only honored by a debug adapter if the corresponding
|
||||
/// capability `supportsValueFormattingOptions` is true.
|
||||
std::optional<ValueFormat> format;
|
||||
};
|
||||
bool fromJSON(const llvm::json::Value &Param,
|
||||
VariablesArguments::VariablesFilter &VA, llvm::json::Path Path);
|
||||
bool fromJSON(const llvm::json::Value &, VariablesArguments &,
|
||||
llvm::json::Path);
|
||||
|
||||
/// Response to `variables` request.
|
||||
struct VariablesResponseBody {
|
||||
/// All (or a range) of variables for the given variable reference.
|
||||
std::vector<Variable> variables;
|
||||
};
|
||||
llvm::json::Value toJSON(const VariablesResponseBody &);
|
||||
|
||||
/// Arguments for `writeMemory` request.
|
||||
struct WriteMemoryArguments {
|
||||
/// Memory reference to the base location to which data should be written.
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Protocol/ProtocolTypes.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "ProtocolUtils.h"
|
||||
#include "lldb/lldb-defines.h"
|
||||
#include "lldb/lldb-types.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
@ -953,4 +954,71 @@ json::Value toJSON(const Module &M) {
|
||||
return result;
|
||||
}
|
||||
|
||||
json::Value toJSON(const VariablePresentationHint &VPH) {
|
||||
json::Object result{};
|
||||
|
||||
if (!VPH.kind.empty())
|
||||
result.insert({"kind", VPH.kind});
|
||||
if (!VPH.attributes.empty())
|
||||
result.insert({"attributes", VPH.attributes});
|
||||
if (!VPH.visibility.empty())
|
||||
result.insert({"visibility", VPH.visibility});
|
||||
if (VPH.lazy)
|
||||
result.insert({"lazy", VPH.lazy});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Value &Param, VariablePresentationHint &VPH,
|
||||
json::Path Path) {
|
||||
json::ObjectMapper O(Param, Path);
|
||||
return O && O.mapOptional("kind", VPH.kind) &&
|
||||
O.mapOptional("attributes", VPH.attributes) &&
|
||||
O.mapOptional("visibility", VPH.visibility) &&
|
||||
O.mapOptional("lazy", VPH.lazy);
|
||||
}
|
||||
|
||||
json::Value toJSON(const Variable &V) {
|
||||
json::Object result{{"name", V.name},
|
||||
{"variablesReference", V.variablesReference},
|
||||
{"value", V.value}};
|
||||
|
||||
if (!V.type.empty())
|
||||
result.insert({"type", V.type});
|
||||
if (V.presentationHint)
|
||||
result.insert({"presentationHint", *V.presentationHint});
|
||||
if (!V.evaluateName.empty())
|
||||
result.insert({"evaluateName", V.evaluateName});
|
||||
if (V.namedVariables)
|
||||
result.insert({"namedVariables", V.namedVariables});
|
||||
if (V.indexedVariables)
|
||||
result.insert({"indexedVariables", V.indexedVariables});
|
||||
if (V.memoryReference != LLDB_INVALID_ADDRESS)
|
||||
result.insert(
|
||||
{"memoryReference", EncodeMemoryReference(V.memoryReference)});
|
||||
if (V.declarationLocationReference)
|
||||
result.insert(
|
||||
{"declarationLocationReference", V.declarationLocationReference});
|
||||
if (V.valueLocationReference)
|
||||
result.insert({"valueLocationReference", V.valueLocationReference});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Value &Param, Variable &V, json::Path Path) {
|
||||
json::ObjectMapper O(Param, Path);
|
||||
return O && O.map("name", V.name) &&
|
||||
O.map("variablesReference", V.variablesReference) &&
|
||||
O.map("value", V.value) && O.mapOptional("type", V.type) &&
|
||||
O.mapOptional("presentationHint", *V.presentationHint) &&
|
||||
O.mapOptional("evaluateName", V.evaluateName) &&
|
||||
O.mapOptional("namedVariables", V.namedVariables) &&
|
||||
O.mapOptional("indexedVariables", V.indexedVariables) &&
|
||||
O.mapOptional("declarationLocationReference",
|
||||
V.declarationLocationReference) &&
|
||||
O.mapOptional("valueLocationReference", V.valueLocationReference) &&
|
||||
DecodeMemoryReference(Param, "memoryReference", V.memoryReference,
|
||||
Path, /*required=*/false);
|
||||
}
|
||||
|
||||
} // namespace lldb_dap::protocol
|
||||
|
@ -475,7 +475,7 @@ llvm::json::Value toJSON(const Thread &);
|
||||
/// Provides formatting information for a value.
|
||||
struct ValueFormat {
|
||||
/// Display the value in hex.
|
||||
std::optional<bool> hex;
|
||||
bool hex = false;
|
||||
};
|
||||
bool fromJSON(const llvm::json::Value &, ValueFormat &, llvm::json::Path);
|
||||
|
||||
@ -789,6 +789,137 @@ struct Module {
|
||||
};
|
||||
llvm::json::Value toJSON(const Module &);
|
||||
|
||||
/// Properties of a variable that can be used to determine how to render the
|
||||
/// variable in the UI.
|
||||
struct VariablePresentationHint {
|
||||
/// The kind of variable. Before introducing additional values, try to use the
|
||||
/// listed values.
|
||||
std::string kind;
|
||||
|
||||
/// Set of attributes represented as an array of strings. Before introducing
|
||||
/// additional values, try to use the listed values.
|
||||
std::vector<std::string> attributes;
|
||||
|
||||
/// Visibility of variable. Before introducing additional values, try to use
|
||||
/// the listed values.
|
||||
std::string visibility;
|
||||
|
||||
/// If true, clients can present the variable with a UI that supports a
|
||||
/// specific gesture to trigger its evaluation.
|
||||
///
|
||||
/// This mechanism can be used for properties that require executing code when
|
||||
/// retrieving their value and where the code execution can be expensive
|
||||
/// and/or produce side-effects. A typical example are properties based on a
|
||||
/// getter function.
|
||||
///
|
||||
/// Please note that in addition to the `lazy` flag, the variable's
|
||||
/// `variablesReference` is expected to refer to a variable that will provide
|
||||
/// the value through another `variable` request.
|
||||
bool lazy = false;
|
||||
};
|
||||
llvm::json::Value toJSON(const VariablePresentationHint &);
|
||||
bool fromJSON(const llvm::json::Value &, VariablePresentationHint &,
|
||||
llvm::json::Path);
|
||||
|
||||
/// A Variable is a name/value pair.
|
||||
///
|
||||
/// The `type` attribute is shown if space permits or when hovering over the
|
||||
/// variable's name.
|
||||
///
|
||||
/// The `kind` attribute is used to render additional properties of the
|
||||
/// variable, e.g. different icons can be used to indicate that a variable is
|
||||
/// public or private.
|
||||
///
|
||||
/// If the value is structured (has children), a handle is provided to retrieve
|
||||
/// the children with the `variables` request.
|
||||
///
|
||||
/// If the number of named or indexed children is large, the numbers should be
|
||||
/// returned via the `namedVariables` and `indexedVariables` attributes.
|
||||
///
|
||||
/// The client can use this information to present the children in a paged UI
|
||||
/// and fetch them in chunks.
|
||||
struct Variable {
|
||||
/// The variable's name.
|
||||
std::string name;
|
||||
|
||||
/// The variable's value.
|
||||
///
|
||||
/// This can be a multi-line text, e.g. for a function the body of a function.
|
||||
///
|
||||
/// For structured variables (which do not have a simple value), it is
|
||||
/// recommended to provide a one-line representation of the structured object.
|
||||
/// This helps to identify the structured object in the collapsed state when
|
||||
/// its children are not yet visible.
|
||||
///
|
||||
/// An empty string can be used if no value should be shown in the UI.
|
||||
std::string value;
|
||||
|
||||
/// The type of the variable's value. Typically shown in the UI when hovering
|
||||
/// over the value.
|
||||
///
|
||||
/// This attribute should only be returned by a debug adapter if the
|
||||
/// corresponding capability `supportsVariableType` is true.
|
||||
std::string type;
|
||||
|
||||
/// Properties of a variable that can be used to determine how to render the
|
||||
/// variable in the UI.
|
||||
std::optional<VariablePresentationHint> presentationHint;
|
||||
|
||||
/// The evaluatable name of this variable which can be passed to the
|
||||
/// `evaluate` request to fetch the variable's value.
|
||||
std::string evaluateName;
|
||||
|
||||
/// If `variablesReference` is > 0, the variable is structured and its
|
||||
/// children can be retrieved by passing `variablesReference` to the
|
||||
/// `variables` request as long as execution remains suspended. See 'Lifetime
|
||||
/// of Object References' in the Overview section for details.
|
||||
uint64_t variablesReference = 0;
|
||||
|
||||
/// The number of named child variables.
|
||||
///
|
||||
/// The client can use this information to present the children in a paged UI
|
||||
/// and fetch them in chunks.
|
||||
uint64_t namedVariables = 0;
|
||||
|
||||
/// The number of indexed child variables.
|
||||
///
|
||||
/// The client can use this information to present the children in a paged UI
|
||||
/// and fetch them in chunks.
|
||||
uint64_t indexedVariables = 0;
|
||||
|
||||
/// A memory reference associated with this variable.
|
||||
///
|
||||
/// For pointer type variables, this is generally a reference to the memory
|
||||
/// address contained in the pointer.
|
||||
///
|
||||
/// For executable data, this reference may later be used in a `disassemble`
|
||||
/// request.
|
||||
///
|
||||
/// This attribute may be returned by a debug adapter if corresponding
|
||||
/// capability `supportsMemoryReferences` is true.
|
||||
lldb::addr_t memoryReference = LLDB_INVALID_ADDRESS;
|
||||
|
||||
/// A reference that allows the client to request the location where the
|
||||
/// variable is declared. This should be present only if the adapter is likely
|
||||
/// to be able to resolve the location.
|
||||
///
|
||||
/// This reference shares the same lifetime as the `variablesReference`. See
|
||||
/// 'Lifetime of Object References' in the Overview section for details.
|
||||
uint64_t declarationLocationReference = 0;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This reference shares the same lifetime as the `variablesReference`. See
|
||||
/// 'Lifetime of Object References' in the Overview section for details.
|
||||
uint64_t valueLocationReference = 0;
|
||||
};
|
||||
llvm::json::Value toJSON(const Variable &);
|
||||
bool fromJSON(const llvm::json::Value &, Variable &, llvm::json::Path);
|
||||
|
||||
} // namespace lldb_dap::protocol
|
||||
|
||||
#endif
|
||||
|
@ -7,9 +7,11 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ProtocolUtils.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "LLDBUtils.h"
|
||||
|
||||
#include "lldb/API/SBDebugger.h"
|
||||
#include "lldb/API/SBDeclaration.h"
|
||||
#include "lldb/API/SBFormat.h"
|
||||
#include "lldb/API/SBMutex.h"
|
||||
#include "lldb/API/SBStream.h"
|
||||
@ -227,9 +229,9 @@ std::vector<protocol::Thread> GetThreads(lldb::SBProcess process,
|
||||
return threads;
|
||||
}
|
||||
|
||||
protocol::ExceptionBreakpointsFilter
|
||||
ExceptionBreakpointsFilter
|
||||
CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
|
||||
protocol::ExceptionBreakpointsFilter filter;
|
||||
ExceptionBreakpointsFilter filter;
|
||||
filter.filter = bp.GetFilter();
|
||||
filter.label = bp.GetLabel();
|
||||
filter.description = bp.GetLabel();
|
||||
@ -238,4 +240,68 @@ CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
Variable CreateVariable(lldb::SBValue v, int64_t var_ref, bool format_hex,
|
||||
bool auto_variable_summaries,
|
||||
bool synthetic_child_debugging, bool is_name_duplicated,
|
||||
std::optional<std::string> custom_name) {
|
||||
VariableDescription desc(v, auto_variable_summaries, format_hex,
|
||||
is_name_duplicated, custom_name);
|
||||
Variable var;
|
||||
var.name = desc.name;
|
||||
var.value = desc.display_value;
|
||||
var.type = desc.display_type_name;
|
||||
|
||||
if (!desc.evaluate_name.empty())
|
||||
var.evaluateName = desc.evaluate_name;
|
||||
|
||||
// If we have a type with many children, we would like to be able to
|
||||
// give a hint to the IDE that the type has indexed children so that the
|
||||
// request can be broken up in grabbing only a few children at a time. We
|
||||
// want to be careful and only call "v.GetNumChildren()" if we have an array
|
||||
// type or if we have a synthetic child provider producing indexed children.
|
||||
// We don't want to call "v.GetNumChildren()" on all objects as class, struct
|
||||
// and union types don't need to be completed if they are never expanded. So
|
||||
// we want to avoid calling this to only cases where we it makes sense to keep
|
||||
// performance high during normal debugging.
|
||||
|
||||
// If we have an array type, say that it is indexed and provide the number
|
||||
// of children in case we have a huge array. If we don't do this, then we
|
||||
// might take a while to produce all children at onces which can delay your
|
||||
// debug session.
|
||||
if (desc.type_obj.IsArrayType()) {
|
||||
var.indexedVariables = v.GetNumChildren();
|
||||
} else if (v.IsSynthetic()) {
|
||||
// For a type with a synthetic child provider, the SBType of "v" won't tell
|
||||
// us anything about what might be displayed. Instead, we check if the first
|
||||
// child's name is "[0]" and then say it is indexed. We call
|
||||
// GetNumChildren() only if the child name matches to avoid a potentially
|
||||
// expensive operation.
|
||||
if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
|
||||
llvm::StringRef first_child_name = first_child.GetName();
|
||||
if (first_child_name == "[0]") {
|
||||
size_t num_children = v.GetNumChildren();
|
||||
// If we are creating a "[raw]" fake child for each synthetic type, we
|
||||
// have to account for it when returning indexed variables.
|
||||
if (synthetic_child_debugging)
|
||||
++num_children;
|
||||
var.indexedVariables = num_children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (v.MightHaveChildren())
|
||||
var.variablesReference = var_ref;
|
||||
|
||||
if (v.GetDeclaration().IsValid())
|
||||
var.declarationLocationReference = PackLocation(var_ref, false);
|
||||
|
||||
if (ValuePointsToCode(v))
|
||||
var.valueLocationReference = PackLocation(var_ref, true);
|
||||
|
||||
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
|
||||
var.memoryReference = addr;
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
} // namespace lldb_dap
|
||||
|
@ -106,6 +106,48 @@ CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp);
|
||||
/// "2 MB").
|
||||
std::string ConvertDebugInfoSizeToString(uint64_t debug_size);
|
||||
|
||||
/// Create a protocol Variable for the given value.
|
||||
///
|
||||
/// \param[in] v
|
||||
/// The LLDB value to use when populating out the "Variable"
|
||||
/// object.
|
||||
///
|
||||
/// \param[in] var_ref
|
||||
/// The variable reference. Used to identify the value, e.g.
|
||||
/// in the `variablesReference` or `declarationLocationReference`
|
||||
/// properties.
|
||||
///
|
||||
/// \param[in] format_hex
|
||||
/// If set to true the variable will be formatted as hex in
|
||||
/// the "value" key value pair for the value of the variable.
|
||||
///
|
||||
/// \param[in] auto_variable_summaries
|
||||
/// If set to true the variable will create an automatic variable summary.
|
||||
///
|
||||
/// \param[in] synthetic_child_debugging
|
||||
/// Whether to include synthetic children when listing properties of the
|
||||
/// value.
|
||||
///
|
||||
/// \param[in] is_name_duplicated
|
||||
/// Whether the same variable name appears multiple times within the same
|
||||
/// context (e.g. locals). This can happen due to shadowed variables in
|
||||
/// nested blocks.
|
||||
///
|
||||
/// As VSCode doesn't render two of more variables with the same name, we
|
||||
/// apply a suffix to distinguish duplicated variables.
|
||||
///
|
||||
/// \param[in] custom_name
|
||||
/// A provided custom name that is used instead of the SBValue's when
|
||||
/// creating the JSON representation.
|
||||
///
|
||||
/// \return
|
||||
/// A Variable representing the given value.
|
||||
protocol::Variable CreateVariable(lldb::SBValue v, int64_t var_ref,
|
||||
bool format_hex, bool auto_variable_summaries,
|
||||
bool synthetic_child_debugging,
|
||||
bool is_name_duplicated,
|
||||
std::optional<std::string> custom_name = {});
|
||||
|
||||
} // namespace lldb_dap
|
||||
|
||||
#endif
|
||||
|
@ -7,9 +7,9 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "JSONUtils.h"
|
||||
#include "lldb/API/SBModule.h"
|
||||
#include "lldb/API/SBTarget.h"
|
||||
#include "lldb/lldb-defines.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
#include "llvm/Testing/Support/Error.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <optional>
|
||||
|
||||
@ -182,3 +182,66 @@ TEST(JSONUtilsTest, GetStrings_NestedArray) {
|
||||
ASSERT_EQ(result.size(), 1UL);
|
||||
EXPECT_EQ(result[0], "string");
|
||||
}
|
||||
|
||||
TEST(JSONUtilsTest, DecodeMemoryReference) {
|
||||
EXPECT_EQ(DecodeMemoryReference(""), std::nullopt);
|
||||
EXPECT_EQ(DecodeMemoryReference("123"), std::nullopt);
|
||||
EXPECT_EQ(DecodeMemoryReference("0o123"), std::nullopt);
|
||||
EXPECT_EQ(DecodeMemoryReference("0b1010101"), std::nullopt);
|
||||
EXPECT_EQ(DecodeMemoryReference("0x123"), 291u);
|
||||
|
||||
{
|
||||
addr_t addr = LLDB_INVALID_ADDRESS;
|
||||
json::Path::Root root;
|
||||
EXPECT_TRUE(DecodeMemoryReference(json::Object{{"mem_ref", "0x123"}},
|
||||
"mem_ref", addr, root,
|
||||
/*required=*/true));
|
||||
EXPECT_EQ(addr, 291u);
|
||||
}
|
||||
|
||||
{
|
||||
addr_t addr = LLDB_INVALID_ADDRESS;
|
||||
json::Path::Root root;
|
||||
EXPECT_TRUE(DecodeMemoryReference(json::Object{}, "mem_ref", addr, root,
|
||||
/*required=*/false));
|
||||
}
|
||||
|
||||
{
|
||||
addr_t addr = LLDB_INVALID_ADDRESS;
|
||||
json::Path::Root root;
|
||||
EXPECT_FALSE(DecodeMemoryReference(json::Value{"string"}, "mem_ref", addr,
|
||||
root,
|
||||
/*required=*/true));
|
||||
EXPECT_THAT_ERROR(root.getError(), FailedWithMessage("expected object"));
|
||||
}
|
||||
|
||||
{
|
||||
addr_t addr = LLDB_INVALID_ADDRESS;
|
||||
json::Path::Root root;
|
||||
EXPECT_FALSE(DecodeMemoryReference(json::Object{}, "mem_ref", addr, root,
|
||||
/*required=*/true));
|
||||
EXPECT_THAT_ERROR(root.getError(),
|
||||
FailedWithMessage("missing value at (root).mem_ref"));
|
||||
}
|
||||
|
||||
{
|
||||
addr_t addr = LLDB_INVALID_ADDRESS;
|
||||
json::Path::Root root;
|
||||
EXPECT_FALSE(DecodeMemoryReference(json::Object{{"mem_ref", 123}},
|
||||
"mem_ref", addr, root,
|
||||
/*required=*/true));
|
||||
EXPECT_THAT_ERROR(root.getError(),
|
||||
FailedWithMessage("expected string at (root).mem_ref"));
|
||||
}
|
||||
|
||||
{
|
||||
addr_t addr = LLDB_INVALID_ADDRESS;
|
||||
json::Path::Root root;
|
||||
EXPECT_FALSE(DecodeMemoryReference(json::Object{{"mem_ref", "123"}},
|
||||
"mem_ref", addr, root,
|
||||
/*required=*/true));
|
||||
EXPECT_THAT_ERROR(
|
||||
root.getError(),
|
||||
FailedWithMessage("malformed memory reference at (root).mem_ref"));
|
||||
}
|
||||
}
|
||||
|
@ -883,3 +883,120 @@ TEST(ProtocolTypesTest, ModulesResponseBody) {
|
||||
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
|
||||
EXPECT_EQ(pp(*expected), pp(response));
|
||||
}
|
||||
|
||||
TEST(ProtocolTypesTest, VariablePresentationHint) {
|
||||
VariablePresentationHint hint;
|
||||
hint.kind = "kind";
|
||||
hint.attributes = {"a", "b", "c"};
|
||||
hint.visibility = "public";
|
||||
hint.lazy = true;
|
||||
|
||||
const StringRef json = R"({
|
||||
"attributes": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"kind": "kind",
|
||||
"lazy": true,
|
||||
"visibility": "public"
|
||||
})";
|
||||
|
||||
EXPECT_EQ(pp(Value(hint)), json);
|
||||
EXPECT_THAT_EXPECTED(json::parse(json), HasValue(Value(hint)));
|
||||
}
|
||||
|
||||
TEST(ProtocolTypesTest, Variable) {
|
||||
Variable var;
|
||||
var.name = "var1";
|
||||
var.variablesReference = 42;
|
||||
var.value = "value";
|
||||
var.type = "type";
|
||||
|
||||
VariablePresentationHint hint;
|
||||
hint.kind = "kind";
|
||||
var.presentationHint = std::move(hint);
|
||||
var.evaluateName = "my_name";
|
||||
var.namedVariables = 7;
|
||||
var.indexedVariables = 7;
|
||||
var.memoryReference = 291u;
|
||||
var.declarationLocationReference = 24;
|
||||
var.valueLocationReference = 100;
|
||||
|
||||
const StringRef json = R"({
|
||||
"declarationLocationReference": 24,
|
||||
"evaluateName": "my_name",
|
||||
"indexedVariables": 7,
|
||||
"memoryReference": "0x123",
|
||||
"name": "var1",
|
||||
"namedVariables": 7,
|
||||
"presentationHint": {
|
||||
"kind": "kind"
|
||||
},
|
||||
"type": "type",
|
||||
"value": "value",
|
||||
"valueLocationReference": 100,
|
||||
"variablesReference": 42
|
||||
})";
|
||||
|
||||
EXPECT_EQ(pp(Value(var)), json);
|
||||
EXPECT_THAT_EXPECTED(json::parse(json), HasValue(Value(var)));
|
||||
}
|
||||
|
||||
TEST(ProtocolTypesTest, VariablesArguments) {
|
||||
llvm::Expected<VariablesArguments> expected = parse<VariablesArguments>(R"({
|
||||
"variablesReference": 42,
|
||||
"filter": "indexed",
|
||||
"start": 10,
|
||||
"count": 5,
|
||||
"format": {
|
||||
"hex": true
|
||||
}
|
||||
})");
|
||||
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
|
||||
EXPECT_EQ(expected->variablesReference, 42u);
|
||||
EXPECT_EQ(expected->filter, VariablesArguments::eVariablesFilterIndexed);
|
||||
EXPECT_EQ(expected->start, 10u);
|
||||
EXPECT_EQ(expected->count, 5u);
|
||||
EXPECT_EQ(expected->format->hex, true);
|
||||
|
||||
EXPECT_THAT_EXPECTED(
|
||||
parse<VariablesArguments>(R"({})"),
|
||||
FailedWithMessage("missing value at (root).variablesReference"));
|
||||
EXPECT_THAT_EXPECTED(
|
||||
parse<VariablesArguments>(
|
||||
R"({"variablesReference": 42, "filter": "my-filter"})"),
|
||||
FailedWithMessage(
|
||||
"unexpected value, expected 'named' or 'indexed' at (root).filter"));
|
||||
}
|
||||
|
||||
TEST(ProtocolTypesTest, VariablesResponseBody) {
|
||||
Variable var1;
|
||||
var1.name = "var1";
|
||||
var1.variablesReference = 42;
|
||||
var1.value = "<var1-value>";
|
||||
|
||||
Variable var2;
|
||||
var2.name = "var2";
|
||||
var2.variablesReference = 3;
|
||||
var2.value = "<var2-value>";
|
||||
|
||||
VariablesResponseBody response{{var1, var2}};
|
||||
|
||||
Expected<json::Value> expected = json::parse(R"({
|
||||
"variables": [
|
||||
{
|
||||
"name": "var1",
|
||||
"value": "<var1-value>",
|
||||
"variablesReference": 42
|
||||
},
|
||||
{
|
||||
"name": "var2",
|
||||
"value": "<var2-value>",
|
||||
"variablesReference": 3
|
||||
}
|
||||
]
|
||||
})");
|
||||
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
|
||||
EXPECT_EQ(pp(*expected), pp(response));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user