diff --git a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py index 9cfa9b20f6051..3b769d2dd89ce 100644 --- a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py +++ b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py @@ -28,7 +28,7 @@ def test_stack_frame_name(self): 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 @@ def test_optimized_variable(self): value.startswith("> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "variables"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected + Run(const protocol::VariablesArguments &) const override; }; class LocationsRequestHandler : public LegacyRequestHandler { diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp index 19bcca2b22b9b..eba1da827257b 100644 --- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp @@ -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(arguments, "variablesReference").value_or(0); - const auto start = GetInteger(arguments, "start").value_or(0); - const auto count = GetInteger(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 +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; + + std::vector variables; - if (lldb::SBValueList *top_scope = - dap.variables.GetTopLevelScope(variablesReference)) { + 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", ""); - 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 = ""; + 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(); @@ -204,19 +133,20 @@ void VariablesRequestHandler::operator()( } 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()) { + bool is_permanent = dap.variables.IsPermanentVariableReference(var_ref); auto addChild = [&](lldb::SBValue child, std::optional 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)); + 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); @@ -233,10 +163,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 diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 553c52605c998..311e3fb9198ae 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -768,38 +768,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 +804,6 @@ std::pair 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 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]; diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 0424438ad5b72..1556872c6ae62 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -326,10 +326,6 @@ struct VariableDescription { bool format_hex = false, bool is_name_duplicated = false, std::optional 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 +340,6 @@ int64_t PackLocation(int64_t var_ref, bool is_value_location); /// Reverse of `PackLocation` std::pair 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 custom_name = {}); - llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit); /// Create a runInTerminal reverse request object diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index 83a205f118fc0..e840677cdebd5 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -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 filter = + StringSwitch>( + *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); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 1544815be9389..63f56fe36f148 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -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 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 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. diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index 9b5c9ef348ca4..8922d4b3e537e 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -953,4 +953,69 @@ 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.empty()) + result.insert({"memoryReference", 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("memoryReference", V.memoryReference) && + O.mapOptional("declarationLocationReference", + V.declarationLocationReference) && + O.mapOptional("valueLocationReference", V.valueLocationReference); +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index 2bb765e956256..2213b3e7cbee3 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -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 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 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 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. + std::string memoryReference; + + /// 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 diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp index f9e373db74618..e2e8f3c2df160 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.cpp +++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp @@ -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 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 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 = EncodeMemoryReference(addr); + + return var; +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h index d906d8e881158..a1f7ae0661914 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.h +++ b/lldb/tools/lldb-dap/ProtocolUtils.h @@ -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 custom_name = {}); + } // namespace lldb_dap #endif diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index b5cf06bd6f0b6..055e3c08d9577 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -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 = "0x123"; + 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 expected = parse(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(R"({})"), + FailedWithMessage("missing value at (root).variablesReference")); + EXPECT_THAT_EXPECTED( + parse( + 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 = ""; + + Variable var2; + var2.name = "var2"; + var2.variablesReference = 3; + var2.value = ""; + + VariablesResponseBody response{{var1, var2}}; + + Expected expected = json::parse(R"({ + "variables": [ + { + "name": "var1", + "value": "", + "variablesReference": 42 + }, + { + "name": "var2", + "value": "", + "variablesReference": 3 + } + ] + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(pp(*expected), pp(response)); +}