Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[lldb][lldb-dap] use the new protocol for setVariable requests. #137803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 6, 2025

Conversation

da-viper
Copy link
Contributor

// The "id" is the unique integer ID that is unique within the enclosing
// variablesReference. It is optionally added to any "interface Variable"
// objects to uniquely identify a variable within an enclosing
// variablesReference. It helps to disambiguate between two variables that
// have the same name within the same scope since the "setVariables" request
// only specifies the variable reference of the enclosing scope/variable, and
// the name of the variable. We could have two shadowed variables with the
// same name in "Locals" or "Globals". In our case the "id" absolute index
// of the variable within the dap.variables list.
const auto id_value =
    GetInteger<uint64_t>(arguments, "id").value_or(UINT64_MAX);
if (id_value != UINT64_MAX) {

I dropped this part because. variables that have the same name has a @path suffix on both of them.
and the setVariableArguments does not have a field called id.

@llvmbot
Copy link
Member

llvmbot commented Apr 29, 2025

@llvm/pr-subscribers-lldb

Author: Ebuka Ezike (da-viper)

Changes
// The "id" is the unique integer ID that is unique within the enclosing
// variablesReference. It is optionally added to any "interface Variable"
// objects to uniquely identify a variable within an enclosing
// variablesReference. It helps to disambiguate between two variables that
// have the same name within the same scope since the "setVariables" request
// only specifies the variable reference of the enclosing scope/variable, and
// the name of the variable. We could have two shadowed variables with the
// same name in "Locals" or "Globals". In our case the "id" absolute index
// of the variable within the dap.variables list.
const auto id_value =
    GetInteger&lt;uint64_t&gt;(arguments, "id").value_or(UINT64_MAX);
if (id_value != UINT64_MAX) {

I dropped this part because. variables that have the same name has a @<!-- -->path suffix on both of them.
and the setVariableArguments does not have a field called id.


Full diff: https://github.com/llvm/llvm-project/pull/137803.diff

6 Files Affected:

  • (modified) lldb/tools/lldb-dap/Handler/RequestHandler.h (+7-4)
  • (modified) lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp (+52-154)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp (+31)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.h (+69)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp (+6)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolTypes.h (+7)
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index fa3d76ed4a125..37cc902e1c98e 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -430,17 +430,20 @@ class ScopesRequestHandler : public LegacyRequestHandler {
   void operator()(const llvm::json::Object &request) const override;
 };
 
-class SetVariableRequestHandler : public LegacyRequestHandler {
+class SetVariableRequestHandler final
+    : public RequestHandler<protocol::SetVariableArguments,
+                            llvm::Expected<protocol::SetVariableResponseBody>> {
 public:
-  using LegacyRequestHandler::LegacyRequestHandler;
+  using RequestHandler::RequestHandler;
   static llvm::StringLiteral GetCommand() { return "setVariable"; }
   FeatureSet GetSupportedFeatures() const override {
     return {protocol::eAdapterFeatureSetVariable};
   }
-  void operator()(const llvm::json::Object &request) const override;
+  llvm::Expected<protocol::SetVariableResponseBody>
+  Run(const protocol::SetVariableArguments &args) const override;
 };
 
-class SourceRequestHandler
+class SourceRequestHandler final
     : public RequestHandler<protocol::SourceArguments,
                             llvm::Expected<protocol::SourceResponseBody>> {
 public:
diff --git a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
index c48bcd84c9ddc..eab5d69deb529 100644
--- a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
@@ -13,168 +13,66 @@
 
 namespace lldb_dap {
 
-// "SetVariableRequest": {
-//   "allOf": [ { "$ref": "#/definitions/Request" }, {
-//     "type": "object",
-//     "description": "setVariable request; value of command field is
-//     'setVariable'. Set the variable with the given name in the variable
-//     container to a new value.", "properties": {
-//       "command": {
-//         "type": "string",
-//         "enum": [ "setVariable" ]
-//       },
-//       "arguments": {
-//         "$ref": "#/definitions/SetVariableArguments"
-//       }
-//     },
-//     "required": [ "command", "arguments"  ]
-//   }]
-// },
-// "SetVariableArguments": {
-//   "type": "object",
-//   "description": "Arguments for 'setVariable' request.",
-//   "properties": {
-//     "variablesReference": {
-//       "type": "integer",
-//       "description": "The reference of the variable container."
-//     },
-//     "name": {
-//       "type": "string",
-//       "description": "The name of the variable."
-//     },
-//     "value": {
-//       "type": "string",
-//       "description": "The value of the variable."
-//     },
-//     "format": {
-//       "$ref": "#/definitions/ValueFormat",
-//       "description": "Specifies details on how to format the response value."
-//     }
-//   },
-//   "required": [ "variablesReference", "name", "value" ]
-// },
-// "SetVariableResponse": {
-//   "allOf": [ { "$ref": "#/definitions/Response" }, {
-//     "type": "object",
-//     "description": "Response to 'setVariable' request.",
-//     "properties": {
-//       "body": {
-//         "type": "object",
-//         "properties": {
-//           "value": {
-//             "type": "string",
-//             "description": "The new value of the variable."
-//           },
-//           "type": {
-//             "type": "string",
-//             "description": "The type of the new value. Typically shown in the
-//             UI when hovering over the value."
-//           },
-//           "variablesReference": {
-//             "type": "number",
-//             "description": "If variablesReference is > 0, the new value is
-//             structured and its children can be retrieved by passing
-//             variablesReference to the VariablesRequest."
-//           },
-//           "namedVariables": {
-//             "type": "number",
-//             "description": "The number of named child variables. The client
-//             can use this optional information to present the variables in a
-//             paged UI and fetch them in chunks."
-//           },
-//           "indexedVariables": {
-//             "type": "number",
-//             "description": "The number of indexed child variables. The client
-//             can use this optional information to present the variables in a
-//             paged UI and fetch them in chunks."
-//           },
-//           "valueLocationReference": {
-//             "type": "integer",
-//             "description": "A reference that allows the client to request the
-//             location where the new value is declared. For example, if the new
-//             value is function pointer, the adapter may be able to look up the
-//             function's location. This should be present only if the adapter
-//             is likely to be able to resolve the location.\n\nThis reference
-//             shares the same lifetime as the `variablesReference`. See
-//             'Lifetime of Object References' in the Overview section for
-//             details."
-//           }
-//         },
-//         "required": [ "value" ]
-//       }
-//     },
-//     "required": [ "body" ]
-//   }]
-// }
-void SetVariableRequestHandler::operator()(
-    const llvm::json::Object &request) const {
-  llvm::json::Object response;
-  FillResponse(request, response);
-  llvm::json::Array variables;
-  llvm::json::Object body;
-  const auto *arguments = request.getObject("arguments");
-  // This is a reference to the containing variable/scope
-  const auto variablesReference =
-      GetInteger<uint64_t>(arguments, "variablesReference").value_or(0);
-  llvm::StringRef name = GetString(arguments, "name").value_or("");
+/// Set the variable with the given name in the variable container to a new
+/// value. Clients should only call this request if the corresponding capability
+/// `supportsSetVariable` is true.
+///
+/// If a debug adapter implements both `setVariable` and `setExpression`,
+/// a client will only use `setExpression` if the variable has an evaluateName
+/// property.
+llvm::Expected<protocol::SetVariableResponseBody>
+SetVariableRequestHandler::Run(
+    const protocol::SetVariableArguments &args) const {
+  const auto args_name = llvm::StringRef(args.name);
 
-  const auto value = GetString(arguments, "value").value_or("");
-  // Set success to false just in case we don't find the variable by name
-  response.try_emplace("success", false);
+  constexpr llvm::StringRef return_value_name = "(Return Value)";
+  if (args_name == return_value_name)
+    return llvm::make_error<DAPError>(
+        "cannot change the value of the return value");
 
-  lldb::SBValue variable;
+  lldb::SBValue variable =
+      dap.variables.FindVariable(args.variablesReference, args_name);
 
-  // The "id" is the unique integer ID that is unique within the enclosing
-  // variablesReference. It is optionally added to any "interface Variable"
-  // objects to uniquely identify a variable within an enclosing
-  // variablesReference. It helps to disambiguate between two variables that
-  // have the same name within the same scope since the "setVariables" request
-  // only specifies the variable reference of the enclosing scope/variable, and
-  // the name of the variable. We could have two shadowed variables with the
-  // same name in "Locals" or "Globals". In our case the "id" absolute index
-  // of the variable within the dap.variables list.
-  const auto id_value =
-      GetInteger<uint64_t>(arguments, "id").value_or(UINT64_MAX);
-  if (id_value != UINT64_MAX) {
-    variable = dap.variables.GetVariable(id_value);
-  } else {
-    variable = dap.variables.FindVariable(variablesReference, name);
-  }
+  if (!variable.IsValid())
+    return llvm::make_error<DAPError>("could not find variable in scope");
+
+  lldb::SBError error;
+  const bool success = variable.SetValueFromCString(args.value.c_str(), error);
+  if (!success)
+    return llvm::make_error<DAPError>(error.GetCString());
+
+  VariableDescription desc(variable,
+                           dap.configuration.enableAutoVariableSummaries);
 
-  if (variable.IsValid()) {
-    lldb::SBError error;
-    bool success = variable.SetValueFromCString(value.data(), error);
-    if (success) {
-      VariableDescription desc(variable,
-                               dap.configuration.enableAutoVariableSummaries);
-      EmplaceSafeString(body, "value", desc.display_value);
-      EmplaceSafeString(body, "type", desc.display_type_name);
+  auto body = protocol::SetVariableResponseBody{};
+  body.value = desc.display_value;
+  body.type = desc.display_type_name;
+
+  // We don't know the index of the variable in our dap.variables
+  // so always insert a new one to get its variablesReference.
+  // is_permanent is false because debug console does not support
+  // setVariable request.
+  const int64_t new_var_ref =
+      dap.variables.InsertVariable(variable, /*is_permanent=*/false);
+  if (variable.MightHaveChildren()) {
+    body.variablesReference = new_var_ref;
+    if (desc.type_obj.IsArrayType())
+      body.indexedVariables = variable.GetNumChildren();
+    else
+      body.namedVariables = variable.GetNumChildren();
 
-      // We don't know the index of the variable in our dap.variables
-      // so always insert a new one to get its variablesReference.
-      // is_permanent is false because debug console does not support
-      // setVariable request.
-      int64_t new_var_ref =
-          dap.variables.InsertVariable(variable, /*is_permanent=*/false);
-      if (variable.MightHaveChildren())
-        body.try_emplace("variablesReference", new_var_ref);
-      else
-        body.try_emplace("variablesReference", 0);
-      if (lldb::addr_t addr = variable.GetLoadAddress();
-          addr != LLDB_INVALID_ADDRESS)
-        body.try_emplace("memoryReference", EncodeMemoryReference(addr));
-      if (ValuePointsToCode(variable))
-        body.try_emplace("valueLocationReference", new_var_ref);
-    } else {
-      EmplaceSafeString(body, "message", std::string(error.GetCString()));
-    }
-    response["success"] = llvm::json::Value(success);
   } else {
-    response["success"] = llvm::json::Value(false);
+    body.variablesReference = 0;
   }
 
-  response.try_emplace("body", std::move(body));
-  dap.SendJSON(llvm::json::Value(std::move(response)));
+  if (lldb::addr_t addr = variable.GetLoadAddress();
+      addr != LLDB_INVALID_ADDRESS)
+    body.memoryReference = EncodeMemoryReference(addr);
+
+  if (ValuePointsToCode(variable))
+    body.valueLocationReference = new_var_ref;
+
+  return body;
 }
 
 } // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 61fea66490c30..d9ffc0c04e134 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -261,6 +261,37 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
          parseEnv(Params, LRA.env, P) && parseTimeout(Params, LRA.timeout, P);
 }
 
+bool fromJSON(const llvm::json::Value &Params, SetVariableArguments &SVA,
+              llvm::json::Path P) {
+  json::ObjectMapper O(Params, P);
+  return O && O.map("variablesReference", SVA.variablesReference) &&
+         O.map("name", SVA.name) && O.map("value", SVA.value) &&
+         O.mapOptional("format", SVA.format);
+}
+
+llvm::json::Value toJSON(const SetVariableResponseBody &SVR) {
+  json::Object Body{{"value", SVR.value}};
+  if (SVR.type.has_value())
+    Body.insert({"type", SVR.type});
+
+  if (SVR.variablesReference.has_value())
+    Body.insert({"variablesReference", SVR.variablesReference});
+
+  if (SVR.namedVariables.has_value())
+    Body.insert({"namedVariables", SVR.namedVariables});
+
+  if (SVR.indexedVariables.has_value())
+    Body.insert({"indexedVariables", SVR.indexedVariables});
+
+  if (SVR.memoryReference.has_value())
+    Body.insert({"memoryReference", SVR.memoryReference});
+
+  if (SVR.valueLocationReference.has_value())
+    Body.insert({"valueLocationReference", SVR.valueLocationReference});
+
+  return llvm::json::Value(std::move(Body));
+}
+
 bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) {
   json::ObjectMapper O(Params, P);
   return O && O.map("source", SA.source) &&
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 33f93cc38799a..3c39e9b278d5d 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -294,6 +294,75 @@ bool fromJSON(const llvm::json::Value &, LaunchRequestArguments &,
 /// field is required.
 using LaunchResponseBody = VoidResponse;
 
+/// Arguments for `setVariable` request.
+struct SetVariableArguments {
+  /// The reference of the variable container. 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;
+
+  /// The name of the variable in the container.
+  std::string name;
+
+  /// The value of the variable.
+  std::string value;
+
+  /// Specifies details on how to format the response value.
+  ValueFormat format;
+};
+bool fromJSON(const llvm::json::Value &, SetVariableArguments &,
+              llvm::json::Path);
+
+/// Response to `setVariable` request.
+struct SetVariableResponseBody {
+
+  /// The new value of the variable.
+  std::string value;
+
+  /// The type of the new value. Typically shown in the UI when hovering over
+  /// the value.
+  std::optional<std::string> type;
+
+  /// If `variablesReference` is > 0, the new value 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.
+  ///
+  /// If this property is included in the response, any `variablesReference`
+  /// previously associated with the updated variable, and those of its
+  /// children, are no longer valid.
+  std::optional<uint64_t> variablesReference;
+
+  /// The number of named child variables.
+  /// The client can use this information to present the variables in a paged
+  /// UI and fetch them in chunks.
+  /// The value should be less than or equal to 2147483647 (2^31-1).
+  std::optional<uint64_t> namedVariables;
+
+  /// The number of indexed child variables.
+  /// The client can use this information to present the variables in a paged
+  /// UI and fetch them in chunks.
+  /// The value should be less than or equal to 2147483647 (2^31-1).
+  std::optional<uint64_t> indexedVariables;
+
+  /// A memory reference to a location appropriate for this result.
+  /// For pointer type eval results, this is generally a reference to the
+  /// memory address contained in the pointer.
+  /// This attribute may be returned by a debug adapter if corresponding
+  /// capability `supportsMemoryReferences` is true.
+  std::optional<std::string> memoryReference;
+
+  /// A reference that allows the client to request the location where the new
+  /// value is declared. For example, if the new value is function pointer, the
+  /// adapter may be able to look up the function's location. This should be
+  /// present only if the adapter is likely to be able to resolve the location.
+  ///
+  /// This reference shares the same lifetime as the `variablesReference`. See
+  /// 'Lifetime of Object References' in the Overview section for details.
+  std::optional<uint64_t> valueLocationReference;
+};
+llvm::json::Value toJSON(const SetVariableResponseBody &);
+
 /// Arguments for `source` request.
 struct SourceArguments {
   /// Specifies the source content to load. Either `source.path` or
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index e64998c4ca488..8f4defb6fd2ea 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -254,4 +254,10 @@ bool fromJSON(const llvm::json::Value &Params, SteppingGranularity &SG,
   return true;
 }
 
+bool fromJSON(const llvm::json::Value &Params, ValueFormat &VF,
+              llvm::json::Path P) {
+  json::ObjectMapper O(Params, P);
+  return O && O.mapOptional("hex", VF.hex);
+}
+
 } // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 54941f24efbd9..ae1ba90d1666a 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -322,6 +322,13 @@ enum SteppingGranularity : unsigned {
 bool fromJSON(const llvm::json::Value &, SteppingGranularity &,
               llvm::json::Path);
 
+/// Provides formatting information for a value.
+struct ValueFormat {
+  /// Display the value in hex.
+  std::optional<bool> hex;
+};
+bool fromJSON(const llvm::json::Value &, ValueFormat &, llvm::json::Path);
+
 } // namespace lldb_dap::protocol
 
 #endif

@da-viper da-viper requested a review from ashgti April 29, 2025 15:07
llvm::Expected<protocol::SetVariableResponseBody>
SetVariableRequestHandler::Run(
const protocol::SetVariableArguments &args) const {
const auto args_name = llvm::StringRef(args.name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: How about using namespace llvm at line ~13ish and we drop llvm:: here and below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems more common to have the llvm:: prefix scope as I could not find any other place that it was dropped.

/// The client can use this information to present the variables in a paged
/// UI and fetch them in chunks.
/// The value should be less than or equal to 2147483647 (2^31-1).
std::optional<uint64_t> namedVariables;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have noticed that in lldb-dap we use uint64_t for values like this and a few others in the spec that have ranges. I think in VSCode these are just javascript numbers and we may run into problems one day if we have a value over 2^53-1 (See Number.MAX_SAFE_INTEGER ).

We may not need to change this now, but I've been working if we should constrain some keys to smaller sizes just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can used the default uint64_t for everything except the ones that have a constraint such as this one.

ie uint64_t for numbers, int32_t for integers., and if constrained use what it wants.

what do you think?

The content part is encoded using utf-8.
integers defined in the protocol (JSON schema type integer) may be represented as 32 bit signed integers,
although` some properties disallow negative values. numbers in the protocol (JSON schema type number) may
be represented as 64 bit floating point numbers.

From https://microsoft.github.io/debug-adapter-protocol/overview

@da-viper da-viper requested a review from ashgti April 30, 2025 23:02
Copy link
Contributor

@ashgti ashgti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@da-viper da-viper merged commit e8adb3a into llvm:main May 6, 2025
10 checks passed
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
…#137803)

```cpp

// The "id" is the unique integer ID that is unique within the enclosing
// variablesReference. It is optionally added to any "interface
Variable"
  // objects to uniquely identify a variable within an enclosing
// variablesReference. It helps to disambiguate between two variables
that
// have the same name within the same scope since the "setVariables"
request
// only specifies the variable reference of the enclosing
scope/variable, and
// the name of the variable. We could have two shadowed variables with
the
// same name in "Locals" or "Globals". In our case the "id" absolute
index
  // of the variable within the dap.variables list.
  const auto id_value =
      GetInteger<uint64_t>(arguments, "id").value_or(UINT64_MAX);
  if (id_value != UINT64_MAX) {
```
I dropped this part because. variables that have the same name has a ` @path` suffix on both of them. 
and the setVariableArguments does not have a field called `id`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants