| 1 | //===-- VariablesRequestHandler.cpp ---------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "DAP.h" |
| 10 | #include "EventHelper.h" |
| 11 | #include "JSONUtils.h" |
| 12 | #include "RequestHandler.h" |
| 13 | |
| 14 | namespace lldb_dap { |
| 15 | |
| 16 | // "VariablesRequest": { |
| 17 | // "allOf": [ { "$ref": "#/definitions/Request" }, { |
| 18 | // "type": "object", |
| 19 | // "description": "Variables request; value of command field is 'variables'. |
| 20 | // Retrieves all child variables for the given variable reference. An |
| 21 | // optional filter can be used to limit the fetched children to either named |
| 22 | // or indexed children.", "properties": { |
| 23 | // "command": { |
| 24 | // "type": "string", |
| 25 | // "enum": [ "variables" ] |
| 26 | // }, |
| 27 | // "arguments": { |
| 28 | // "$ref": "#/definitions/VariablesArguments" |
| 29 | // } |
| 30 | // }, |
| 31 | // "required": [ "command", "arguments" ] |
| 32 | // }] |
| 33 | // }, |
| 34 | // "VariablesArguments": { |
| 35 | // "type": "object", |
| 36 | // "description": "Arguments for 'variables' request.", |
| 37 | // "properties": { |
| 38 | // "variablesReference": { |
| 39 | // "type": "integer", |
| 40 | // "description": "The Variable reference." |
| 41 | // }, |
| 42 | // "filter": { |
| 43 | // "type": "string", |
| 44 | // "enum": [ "indexed", "named" ], |
| 45 | // "description": "Optional filter to limit the child variables to either |
| 46 | // named or indexed. If ommited, both types are fetched." |
| 47 | // }, |
| 48 | // "start": { |
| 49 | // "type": "integer", |
| 50 | // "description": "The index of the first variable to return; if omitted |
| 51 | // children start at 0." |
| 52 | // }, |
| 53 | // "count": { |
| 54 | // "type": "integer", |
| 55 | // "description": "The number of variables to return. If count is missing |
| 56 | // or 0, all variables are returned." |
| 57 | // }, |
| 58 | // "format": { |
| 59 | // "$ref": "#/definitions/ValueFormat", |
| 60 | // "description": "Specifies details on how to format the Variable |
| 61 | // values." |
| 62 | // } |
| 63 | // }, |
| 64 | // "required": [ "variablesReference" ] |
| 65 | // }, |
| 66 | // "VariablesResponse": { |
| 67 | // "allOf": [ { "$ref": "#/definitions/Response" }, { |
| 68 | // "type": "object", |
| 69 | // "description": "Response to 'variables' request.", |
| 70 | // "properties": { |
| 71 | // "body": { |
| 72 | // "type": "object", |
| 73 | // "properties": { |
| 74 | // "variables": { |
| 75 | // "type": "array", |
| 76 | // "items": { |
| 77 | // "$ref": "#/definitions/Variable" |
| 78 | // }, |
| 79 | // "description": "All (or a range) of variables for the given |
| 80 | // variable reference." |
| 81 | // } |
| 82 | // }, |
| 83 | // "required": [ "variables" ] |
| 84 | // } |
| 85 | // }, |
| 86 | // "required": [ "body" ] |
| 87 | // }] |
| 88 | // } |
| 89 | void VariablesRequestHandler::operator()( |
| 90 | const llvm::json::Object &request) const { |
| 91 | llvm::json::Object response; |
| 92 | FillResponse(request, response); |
| 93 | llvm::json::Array variables; |
| 94 | const auto *arguments = request.getObject(K: "arguments" ); |
| 95 | const auto variablesReference = |
| 96 | GetInteger<uint64_t>(obj: arguments, key: "variablesReference" ).value_or(u: 0); |
| 97 | const auto start = GetInteger<int64_t>(obj: arguments, key: "start" ).value_or(u: 0); |
| 98 | const auto count = GetInteger<int64_t>(obj: arguments, key: "count" ).value_or(u: 0); |
| 99 | bool hex = false; |
| 100 | const auto *format = arguments->getObject(K: "format" ); |
| 101 | if (format) |
| 102 | hex = GetBoolean(obj: format, key: "hex" ).value_or(u: false); |
| 103 | |
| 104 | if (lldb::SBValueList *top_scope = |
| 105 | dap.variables.GetTopLevelScope(variablesReference)) { |
| 106 | // variablesReference is one of our scopes, not an actual variable it is |
| 107 | // asking for the list of args, locals or globals. |
| 108 | int64_t start_idx = 0; |
| 109 | int64_t num_children = 0; |
| 110 | |
| 111 | if (variablesReference == VARREF_REGS) { |
| 112 | // Change the default format of any pointer sized registers in the first |
| 113 | // register set to be the lldb::eFormatAddressInfo so we show the pointer |
| 114 | // and resolve what the pointer resolves to. Only change the format if the |
| 115 | // format was set to the default format or if it was hex as some registers |
| 116 | // have formats set for them. |
| 117 | const uint32_t addr_size = dap.target.GetProcess().GetAddressByteSize(); |
| 118 | lldb::SBValue reg_set = dap.variables.registers.GetValueAtIndex(idx: 0); |
| 119 | const uint32_t num_regs = reg_set.GetNumChildren(); |
| 120 | for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) { |
| 121 | lldb::SBValue reg = reg_set.GetChildAtIndex(idx: reg_idx); |
| 122 | const lldb::Format format = reg.GetFormat(); |
| 123 | if (format == lldb::eFormatDefault || format == lldb::eFormatHex) { |
| 124 | if (reg.GetByteSize() == addr_size) |
| 125 | reg.SetFormat(lldb::eFormatAddressInfo); |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | num_children = top_scope->GetSize(); |
| 131 | if (num_children == 0 && variablesReference == VARREF_LOCALS) { |
| 132 | // Check for an error in the SBValueList that might explain why we don't |
| 133 | // have locals. If we have an error display it as the sole value in the |
| 134 | // the locals. |
| 135 | |
| 136 | // "error" owns the error string so we must keep it alive as long as we |
| 137 | // want to use the returns "const char *" |
| 138 | lldb::SBError error = top_scope->GetError(); |
| 139 | const char *var_err = error.GetCString(); |
| 140 | if (var_err) { |
| 141 | // Create a fake variable named "error" to explain why variables were |
| 142 | // not available. This new error will help let users know when there was |
| 143 | // a problem that kept variables from being available for display and |
| 144 | // allow users to fix this issue instead of seeing no variables. The |
| 145 | // errors are only set when there is a problem that the user could |
| 146 | // fix, so no error will show up when you have no debug info, only when |
| 147 | // we do have debug info and something that is fixable can be done. |
| 148 | llvm::json::Object object; |
| 149 | EmplaceSafeString(obj&: object, key: "name" , str: "<error>" ); |
| 150 | EmplaceSafeString(obj&: object, key: "type" , str: "const char *" ); |
| 151 | EmplaceSafeString(obj&: object, key: "value" , str: var_err); |
| 152 | object.try_emplace(K: "variablesReference" , Args: (int64_t)0); |
| 153 | variables.emplace_back(A: std::move(object)); |
| 154 | } |
| 155 | } |
| 156 | const int64_t end_idx = start_idx + ((count == 0) ? num_children : count); |
| 157 | |
| 158 | // We first find out which variable names are duplicated |
| 159 | std::map<std::string, int> variable_name_counts; |
| 160 | for (auto i = start_idx; i < end_idx; ++i) { |
| 161 | lldb::SBValue variable = top_scope->GetValueAtIndex(idx: i); |
| 162 | if (!variable.IsValid()) |
| 163 | break; |
| 164 | variable_name_counts[GetNonNullVariableName(value&: variable)]++; |
| 165 | } |
| 166 | |
| 167 | // Show return value if there is any ( in the local top frame ) |
| 168 | if (variablesReference == VARREF_LOCALS) { |
| 169 | auto process = dap.target.GetProcess(); |
| 170 | auto selected_thread = process.GetSelectedThread(); |
| 171 | lldb::SBValue stop_return_value = selected_thread.GetStopReturnValue(); |
| 172 | |
| 173 | if (stop_return_value.IsValid() && |
| 174 | (selected_thread.GetSelectedFrame().GetFrameID() == 0)) { |
| 175 | auto renamed_return_value = stop_return_value.Clone(new_name: "(Return Value)" ); |
| 176 | int64_t return_var_ref = 0; |
| 177 | |
| 178 | if (stop_return_value.MightHaveChildren() || |
| 179 | stop_return_value.IsSynthetic()) { |
| 180 | return_var_ref = dap.variables.InsertVariable(variable: stop_return_value, |
| 181 | /*is_permanent=*/false); |
| 182 | } |
| 183 | variables.emplace_back(A: CreateVariable( |
| 184 | v: renamed_return_value, var_ref: return_var_ref, format_hex: hex, |
| 185 | auto_variable_summaries: dap.configuration.enableAutoVariableSummaries, |
| 186 | synthetic_child_debugging: dap.configuration.enableSyntheticChildDebugging, is_name_duplicated: false)); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | // Now we construct the result with unique display variable names |
| 191 | for (auto i = start_idx; i < end_idx; ++i) { |
| 192 | lldb::SBValue variable = top_scope->GetValueAtIndex(idx: i); |
| 193 | |
| 194 | if (!variable.IsValid()) |
| 195 | break; |
| 196 | |
| 197 | int64_t var_ref = |
| 198 | dap.variables.InsertVariable(variable, /*is_permanent=*/false); |
| 199 | variables.emplace_back(A: CreateVariable( |
| 200 | v: variable, var_ref, format_hex: hex, auto_variable_summaries: dap.configuration.enableAutoVariableSummaries, |
| 201 | synthetic_child_debugging: dap.configuration.enableSyntheticChildDebugging, |
| 202 | is_name_duplicated: variable_name_counts[GetNonNullVariableName(value&: variable)] > 1)); |
| 203 | } |
| 204 | } else { |
| 205 | // We are expanding a variable that has children, so we will return its |
| 206 | // children. |
| 207 | lldb::SBValue variable = dap.variables.GetVariable(var_ref: variablesReference); |
| 208 | if (variable.IsValid()) { |
| 209 | auto addChild = [&](lldb::SBValue child, |
| 210 | std::optional<std::string> custom_name = {}) { |
| 211 | if (!child.IsValid()) |
| 212 | return; |
| 213 | bool is_permanent = |
| 214 | dap.variables.IsPermanentVariableReference(var_ref: variablesReference); |
| 215 | int64_t var_ref = dap.variables.InsertVariable(variable: child, is_permanent); |
| 216 | variables.emplace_back(A: CreateVariable( |
| 217 | v: child, var_ref, format_hex: hex, auto_variable_summaries: dap.configuration.enableAutoVariableSummaries, |
| 218 | synthetic_child_debugging: dap.configuration.enableSyntheticChildDebugging, |
| 219 | /*is_name_duplicated=*/false, custom_name)); |
| 220 | }; |
| 221 | const int64_t num_children = variable.GetNumChildren(); |
| 222 | int64_t end_idx = start + ((count == 0) ? num_children : count); |
| 223 | int64_t i = start; |
| 224 | for (; i < end_idx && i < num_children; ++i) |
| 225 | addChild(variable.GetChildAtIndex(idx: i)); |
| 226 | |
| 227 | // If we haven't filled the count quota from the request, we insert a new |
| 228 | // "[raw]" child that can be used to inspect the raw version of a |
| 229 | // synthetic member. That eliminates the need for the user to go to the |
| 230 | // debug console and type `frame var <variable> to get these values. |
| 231 | if (dap.configuration.enableSyntheticChildDebugging && |
| 232 | variable.IsSynthetic() && i == num_children) |
| 233 | addChild(variable.GetNonSyntheticValue(), "[raw]" ); |
| 234 | } |
| 235 | } |
| 236 | llvm::json::Object body; |
| 237 | body.try_emplace(K: "variables" , Args: std::move(variables)); |
| 238 | response.try_emplace(K: "body" , Args: std::move(body)); |
| 239 | dap.SendJSON(json: llvm::json::Value(std::move(response))); |
| 240 | } |
| 241 | |
| 242 | } // namespace lldb_dap |
| 243 | |