cimacmillan updated this revision to Diff 490077.
cimacmillan edited the summary of this revision.
cimacmillan added a comment.

Add handling for const and register cases. Setting watchpoint on SBValue rather 
than the 
address.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D140630/new/

https://reviews.llvm.org/D140630

Files:
  lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
  lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
  lldb/test/API/tools/lldb-vscode/breakpoint_data/Makefile
  
lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py
  lldb/test/API/tools/lldb-vscode/breakpoint_data/main.cpp
  lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/Makefile
  
lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/TestVSCode_setDataBreakpoints.py
  lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/main.cpp
  lldb/tools/lldb-vscode/CMakeLists.txt
  lldb/tools/lldb-vscode/VSCode.h
  lldb/tools/lldb-vscode/Watchpoint.cpp
  lldb/tools/lldb-vscode/Watchpoint.h
  lldb/tools/lldb-vscode/lldb-vscode.cpp

Index: lldb/tools/lldb-vscode/lldb-vscode.cpp
===================================================================
--- lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -41,6 +41,7 @@
 #include <set>
 #include <sstream>
 #include <thread>
+#include <unordered_set>
 #include <vector>
 
 #include "llvm/ADT/ArrayRef.h"
@@ -56,6 +57,8 @@
 #include "llvm/Support/PrettyStackTrace.h"
 #include "llvm/Support/raw_ostream.h"
 
+#include "lldb/API/SBMemoryRegionInfo.h"
+
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
 #include "OutputRedirector.h"
@@ -1541,6 +1544,8 @@
   body.try_emplace("supportsProgressReporting", true);
   // The debug adapter supports 'logMessage' in breakpoint.
   body.try_emplace("supportsLogPoints", true);
+  // The debug adapter supports data breakpoints
+  body.try_emplace("supportsDataBreakpoints", true);
 
   response.try_emplace("body", std::move(body));
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
@@ -2117,6 +2122,365 @@
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
+static WatchpointType get_watchpoint_type_from_request(std::string type) {
+  if (type == "write") {
+    return WatchpointType::Write;
+  } else if (type == "read") {
+    return WatchpointType::Read;
+  } else if (type == "readWrite") {
+    return WatchpointType::ReadWrite;
+  }
+  std::string unknown_type_error = "Unknown watchpoint type: ";
+  unknown_type_error.append(type);
+  unknown_type_error.append(". Defaulting to ReadWrite.");
+  g_vsc.SendOutput(OutputType::Console, unknown_type_error);
+  return WatchpointType::ReadWrite;
+}
+
+static lldb::SBValue get_variable(std::string variable_name,
+                                  uint32_t variables_reference) {
+  bool is_duplicated_variable_name =
+      variable_name.find(" @") != llvm::StringRef::npos;
+  lldb::SBValue variable;
+
+  if (lldb::SBValueList *top_scope = GetTopLevelScope(variables_reference)) {
+    // variablesReference is one of our scopes, not an actual variable it is
+    // asking for a variable in locals or globals or registers
+    int64_t end_idx = top_scope->GetSize();
+    // Searching backward so that we choose the variable in closest scope
+    // among variables of the same name.
+    for (int64_t i = end_idx - 1; i >= 0; --i) {
+      lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
+      std::string local_variable = CreateUniqueVariableNameForDisplay(
+          curr_variable, is_duplicated_variable_name);
+      if (variable_name == local_variable) {
+        variable = curr_variable;
+        break;
+      }
+    }
+  } else {
+    // This is not under the globals or locals scope, so there are no duplicated
+    // names.
+
+    // We have a named item within an actual variable so we need to find it
+    // withing the container variable by name.
+    lldb::SBValue container = g_vsc.variables.GetVariable(variables_reference);
+    if (!container.IsValid()) {
+      return variable;
+    }
+
+    variable = container.GetChildMemberWithName(variable_name.data());
+    if (!variable.IsValid()) {
+      if (variable_name.size() > 0 && variable_name[0] == '[') {
+        llvm::StringRef index_str(std::move(variable_name.substr(1)));
+        uint64_t index = 0;
+        if (!index_str.consumeInteger(0, index)) {
+          if (index_str == "]")
+            variable = container.GetChildAtIndex(index);
+        }
+      }
+    }
+  }
+
+  return variable;
+}
+
+static std::optional<std::string> set_data_breakpoint(const llvm::json::Object *breakpoint) {
+  std::string breakpoint_id = GetString(breakpoint, "id").str();
+  std::string data_id = GetString(breakpoint, "dataId").str();
+  bool enabled = GetBoolean(breakpoint, "enabled", false);
+  WatchpointType watchpoint_type = get_watchpoint_type_from_request(
+      GetString(breakpoint, "accessType").str());
+
+  if (g_vsc.watchpoints.find(breakpoint_id) != g_vsc.watchpoints.end()) {
+    auto existing_watchpoint = g_vsc.watchpoints.at(breakpoint_id);
+    if (existing_watchpoint.is_enabled() != enabled)
+      existing_watchpoint.set_enabled(enabled);
+    return breakpoint_id;
+  }
+
+  auto delimiter = data_id.find('/');
+  if (delimiter == std::string::npos)
+    return std::nullopt;
+
+  auto variable_name = data_id.substr(0, delimiter);
+  auto variable_index_str = data_id.substr(delimiter + 1, data_id.length());
+  if (variable_name.size() == 0 || variable_index_str.size() == 0) 
+    return std::nullopt;
+
+  uint32_t variables_reference = stoul(variable_index_str);
+  lldb::SBValue variable = get_variable(variable_name, variables_reference);
+  if (!variable.IsValid())
+    return std::nullopt;
+
+  g_vsc.watchpoints.emplace(
+      breakpoint_id, Watchpoint(variable, enabled, watchpoint_type));
+  return breakpoint_id;
+}
+
+// "SetDataBreakpointsRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "Replaces all existing data breakpoints with new data
+//     breakpoints.\nTo clear all data breakpoints, specify an empty
+//     array.\nWhen a data breakpoint is hit, a `stopped` event (with reason
+//     `data breakpoint`) is generated.\nClients should only call this request
+//     if the corresponding capability `supportsDataBreakpoints` is true.",
+//     "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "setDataBreakpoints" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/SetDataBreakpointsArguments"
+//       }
+//     },
+//     "required": [ "command", "arguments"  ]
+//   }]
+// },
+// "SetDataBreakpointsArguments": {
+//   "type": "object",
+//   "description": "Arguments for `setDataBreakpoints` request.",
+//   "properties": {
+//     "breakpoints": {
+//       "type": "array",
+//       "items": {
+//         "$ref": "#/definitions/DataBreakpoint"
+//       },
+//       "description": "The contents of this array replaces all existing data
+//       breakpoints. An empty array clears all data breakpoints."
+//     }
+//   },
+//   "required": [ "breakpoints" ]
+// },
+// "SetDataBreakpointsResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to `setDataBreakpoints` request.\nReturned is
+//     information about each breakpoint created by this request.",
+//     "properties": {
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "breakpoints": {
+//             "type": "array",
+//             "items": {
+//               "$ref": "#/definitions/Breakpoint"
+//             },
+//             "description": "Information about the data breakpoints. The array
+//             elements correspond to the elements of the input argument
+//             `breakpoints` array."
+//           }
+//         },
+//         "required": [ "breakpoints" ]
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// }
+// "DataBreakpoint": {
+//   "type": "object",
+//   "description": "Properties of a data breakpoint passed to the
+//   `setDataBreakpoints` request.", "properties": {
+//     "dataId": {
+//       "type": "string",
+//       "description": "An id representing the data. This id is returned from
+//       the `dataBreakpointInfo` request."
+//     },
+//     "accessType": {
+//       "$ref": "#/definitions/DataBreakpointAccessType",
+//       "description": "The access type of the data."
+//     },
+//     "condition": {
+//       "type": "string",
+//       "description": "An expression for conditional breakpoints."
+//     },
+//     "hitCondition": {
+//       "type": "string",
+//       "description": "An expression that controls how many hits of the
+//       breakpoint are ignored.\nThe debug adapter is expected to interpret the
+//       expression as needed."
+//     }
+//   },
+//   "required": [ "dataId" ]
+// }
+void request_setDataBreakpoints(const llvm::json::Object &request) {
+  llvm::json::Object response;
+  lldb::SBError error;
+  FillResponse(request, response);
+  auto arguments = request.getObject("arguments");
+  auto breakpoints = arguments->getArray("breakpoints");
+
+  std::unordered_set<std::string> breakpoint_ids;
+  for (const auto &breakpoint : *breakpoints) {
+    auto id = set_data_breakpoint(breakpoint.getAsObject());
+    if (id.has_value())
+      breakpoint_ids.emplace(id.value());
+  }
+
+  // If the record of watchpoints has entries that aren't present in the
+  // request, it means it's been deleted
+  std::vector<std::string> breakpoints_to_delete;
+  for (auto entry : g_vsc.watchpoints) {
+    auto id = entry.first;
+    if (breakpoint_ids.find(id) == breakpoint_ids.end()) {
+      entry.second.set_enabled(false);
+      breakpoints_to_delete.push_back(id);
+    }
+  }
+  for (auto entry : breakpoints_to_delete) {
+    g_vsc.watchpoints.erase(entry);
+  }
+
+  g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+void populate_data_breakpoint_info(llvm::json::Object &response, std::optional<std::string> data_id, std::string description, bool read, bool write) {
+    llvm::json::Object body;
+    llvm::json::Array access_types;
+    if (data_id.has_value())
+      body.try_emplace("dataId", data_id.value());
+    else
+      body.try_emplace("dataId", nullptr);
+
+    body.try_emplace("description", description);
+    body.try_emplace("canPersist", false);
+
+    if (read) {
+      access_types.emplace_back("read");
+      if (write)
+        access_types.emplace_back("readWrite");
+    }
+    if (write)
+      access_types.emplace_back("write");
+
+    body.try_emplace("accessTypes", std::move(access_types));
+    response.try_emplace("body", std::move(body));
+}
+
+// "DataBreakpointInfoRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "Obtains information on a possible data breakpoint that
+//     could be set on an expression or variable.\nClients should only call this
+//     request if the corresponding capability `supportsDataBreakpoints` is
+//     true.", "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "dataBreakpointInfo" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/DataBreakpointInfoArguments"
+//       }
+//     },
+//     "required": [ "command", "arguments"  ]
+//   }]
+// },
+// "DataBreakpointInfoArguments": {
+//   "type": "object",
+//   "description": "Arguments for `dataBreakpointInfo` request.",
+//   "properties": {
+//     "variablesReference": {
+//       "type": "integer",
+//       "description": "Reference to the variable container if the data
+//       breakpoint is requested for a child of the container. The
+//       `variablesReference` must have been obtained in the current suspended
+//       state. See 'Lifetime of Object References' in the Overview section for
+//       details."
+//     },
+//     "name": {
+//       "type": "string",
+//       "description": "The name of the variable's child to obtain data
+//       breakpoint information for.\nIf `variablesReference` isn't specified,
+//       this can be an expression."
+//     },
+//     "frameId": {
+//       "type": "integer",
+//       "description": "When `name` is an expression, evaluate it in the scope
+//       of this stack frame. If not specified, the expression is evaluated in
+//       the global scope. When `variablesReference` is specified, this property
+//       has no effect."
+//     }
+//   },
+//   "required": [ "name" ]
+// },
+// "DataBreakpointInfoResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to `dataBreakpointInfo` request.",
+//     "properties": {
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "dataId": {
+//             "type": [ "string", "null" ],
+//             "description": "An identifier for the data on which a data
+//             breakpoint can be registered with the `setDataBreakpoints`
+//             request or null if no data breakpoint is available."
+//           },
+//           "description": {
+//             "type": "string",
+//             "description": "UI string that describes on what data the
+//             breakpoint is set on or why a data breakpoint is not available."
+//           },
+//           "accessTypes": {
+//             "type": "array",
+//             "items": {
+//               "$ref": "#/definitions/DataBreakpointAccessType"
+//             },
+//             "description": "Attribute lists the available access types for a
+//             potential data breakpoint. A UI client could surface this
+//             information."
+//           },
+//           "canPersist": {
+//             "type": "boolean",
+//             "description": "Attribute indicates that a potential data
+//             breakpoint could be persisted across sessions."
+//           }
+//         },
+//         "required": [ "dataId", "description" ]
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// }
+void request_dataBreakpointInfo(const llvm::json::Object &request) {
+  llvm::json::Object response;
+  lldb::SBError error;
+  std::stringstream data;
+  std::stringstream description;
+  FillResponse(request, response);
+  auto arguments = request.getObject("arguments");
+  auto name = GetString(arguments, "name");
+  auto variables_reference = arguments->getInteger("variablesReference");
+  lldb::SBValue variable =
+      get_variable(name.str(), variables_reference.value());
+
+  if (!variable.IsValid()) {
+    description << "variable '" << name.data() << "' is not a valid variable.";
+    populate_data_breakpoint_info(response, std::nullopt, description.str(), false, false);
+    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
+
+  auto v_address = variable.GetLoadAddress();
+  auto v_size = variable.GetByteSize();
+
+  if (v_address == LLDB_INVALID_ADDRESS) {
+    populate_data_breakpoint_info(response, std::nullopt, "Variable has an invalid load address. This could be because it is being stored in a register. Try recompiling your program without optimizations.", false, false);
+    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
+
+  lldb::SBMemoryRegionInfo region_info;
+  error = g_vsc.target.GetProcess().GetMemoryRegionInfo(v_address, region_info);
+  data << name.str() << "/" << variables_reference.value();
+  description << name.data() << ": at address " << std::hex << "0x" << v_address
+              << " with size " << std::dec << v_size;
+  populate_data_breakpoint_info(response, data.str(), description.str(), error.Success() && region_info.IsReadable(), error.Success() && region_info.IsWritable());
+  g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+}
+
 // "SetExceptionBreakpointsRequest": {
 //   "allOf": [ { "$ref": "#/definitions/Request" }, {
 //     "type": "object",
@@ -2762,7 +3126,6 @@
   const auto variablesReference =
       GetUnsigned(arguments, "variablesReference", 0);
   llvm::StringRef name = GetString(arguments, "name");
-  bool is_duplicated_variable_name = name.contains(" @");
 
   const auto value = GetString(arguments, "value");
   // Set success to false just in case we don't find the variable by name
@@ -2783,40 +3146,8 @@
   const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX);
   if (id_value != UINT64_MAX) {
     variable = g_vsc.variables.GetVariable(id_value);
-  } else if (lldb::SBValueList *top_scope =
-                 GetTopLevelScope(variablesReference)) {
-    // variablesReference is one of our scopes, not an actual variable it is
-    // asking for a variable in locals or globals or registers
-    int64_t end_idx = top_scope->GetSize();
-    // Searching backward so that we choose the variable in closest scope
-    // among variables of the same name.
-    for (int64_t i = end_idx - 1; i >= 0; --i) {
-      lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
-      std::string variable_name = CreateUniqueVariableNameForDisplay(
-          curr_variable, is_duplicated_variable_name);
-      if (variable_name == name) {
-        variable = curr_variable;
-        break;
-      }
-    }
   } else {
-    // This is not under the globals or locals scope, so there are no duplicated
-    // names.
-
-    // We have a named item within an actual variable so we need to find it
-    // withing the container variable by name.
-    lldb::SBValue container = g_vsc.variables.GetVariable(variablesReference);
-    variable = container.GetChildMemberWithName(name.data());
-    if (!variable.IsValid()) {
-      if (name.startswith("[")) {
-        llvm::StringRef index_str(name.drop_front(1));
-        uint64_t index = 0;
-        if (!index_str.consumeInteger(0, index)) {
-          if (index_str == "]")
-            variable = container.GetChildAtIndex(index);
-        }
-      }
-    }
+    variable = get_variable(name.str(), variablesReference);
   }
 
   if (variable.IsValid()) {
@@ -3079,6 +3410,10 @@
   g_vsc.RegisterRequestCallback("setFunctionBreakpoints",
                                 request_setFunctionBreakpoints);
   g_vsc.RegisterRequestCallback("setVariable", request_setVariable);
+  g_vsc.RegisterRequestCallback("setDataBreakpoints",
+                                request_setDataBreakpoints);
+  g_vsc.RegisterRequestCallback("dataBreakpointInfo",
+                                request_dataBreakpointInfo);
   g_vsc.RegisterRequestCallback("source", request_source);
   g_vsc.RegisterRequestCallback("stackTrace", request_stackTrace);
   g_vsc.RegisterRequestCallback("stepIn", request_stepIn);
Index: lldb/tools/lldb-vscode/Watchpoint.h
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/Watchpoint.h
@@ -0,0 +1,40 @@
+//===-- Watchpoint.h --------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_VSCODE_WATCHPOINT_H
+#define LLDB_TOOLS_LLDB_VSCODE_WATCHPOINT_H
+
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBValue.h"
+#include "lldb/API/SBWatchpoint.h"
+
+#include <cstdint>
+
+namespace lldb_vscode {
+
+enum class WatchpointType { Read, Write, ReadWrite };
+
+class Watchpoint {
+public:
+  Watchpoint(lldb::SBValue value, bool enabled, WatchpointType type);
+  void set_enabled(bool enabled);
+  bool is_enabled();
+
+private:
+  lldb::SBValue value;
+  bool enabled;
+  WatchpointType type;
+  bool watchpoint_active;
+  lldb::SBWatchpoint watchpoint;
+  lldb::SBError error;
+  void sync();
+};
+
+} // namespace lldb_vscode
+
+#endif
Index: lldb/tools/lldb-vscode/Watchpoint.cpp
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/Watchpoint.cpp
@@ -0,0 +1,52 @@
+//===-- Watchpoint.cpp ------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Watchpoint.h"
+#include "VSCode.h"
+
+namespace lldb_vscode {
+
+Watchpoint::Watchpoint(lldb::SBValue value, bool enabled, WatchpointType type)
+    : value(value), enabled(enabled), type(type), watchpoint_active(false) {
+  this->sync();
+}
+
+void Watchpoint::set_enabled(bool enabled) {
+  this->enabled = enabled;
+  this->sync();
+}
+
+bool Watchpoint::is_enabled() { return this->enabled; }
+
+void Watchpoint::sync() {
+  if (this->watchpoint_active) {
+    g_vsc.target.DeleteWatchpoint(this->watchpoint.GetID());
+    this->watchpoint_active = false;
+  }
+
+  if (!this->enabled)
+    return;
+
+  this->watchpoint =
+      this->value.Watch(true,
+                        this->type == WatchpointType::Read ||
+                            this->type == WatchpointType::ReadWrite,
+                        this->type == WatchpointType::Write ||
+                            this->type == WatchpointType::ReadWrite,
+                        this->error);
+
+  if (this->error.Success()) {
+    this->watchpoint_active = true;
+  } else {
+    std::string message = "Failed setting watchpoint: ";
+    message.append(this->error.GetCString());
+    g_vsc.SendOutput(OutputType::Console, message);
+  }
+}
+
+} // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ lldb/tools/lldb-vscode/VSCode.h
@@ -17,6 +17,7 @@
 #include <map>
 #include <set>
 #include <thread>
+#include <unordered_map>
 
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseSet.h"
@@ -53,6 +54,7 @@
 #include "RunInTerminal.h"
 #include "SourceBreakpoint.h"
 #include "SourceReference.h"
+#include "Watchpoint.h"
 
 #define VARREF_LOCALS (int64_t)1
 #define VARREF_GLOBALS (int64_t)2
@@ -136,6 +138,7 @@
   llvm::StringMap<SourceBreakpointMap> source_breakpoints;
   FunctionBreakpointMap function_breakpoints;
   std::vector<ExceptionBreakpoint> exception_breakpoints;
+  std::unordered_map<std::string, Watchpoint> watchpoints;
   std::vector<std::string> init_commands;
   std::vector<std::string> pre_run_commands;
   std::vector<std::string> exit_commands;
Index: lldb/tools/lldb-vscode/CMakeLists.txt
===================================================================
--- lldb/tools/lldb-vscode/CMakeLists.txt
+++ lldb/tools/lldb-vscode/CMakeLists.txt
@@ -35,6 +35,7 @@
   ProgressEvent.cpp
   RunInTerminal.cpp
   SourceBreakpoint.cpp
+  Watchpoint.cpp
   VSCode.cpp
 
   LINK_LIBS
Index: lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/main.cpp
@@ -0,0 +1,7 @@
+#include "stdio.h"
+
+int main() {
+  int num_a = 10;
+  int num_b = 20;
+  printf("%d", num_a + num_b);
+}
Index: lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/TestVSCode_setDataBreakpoints.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/TestVSCode_setDataBreakpoints.py
@@ -0,0 +1,47 @@
+"""
+Test lldb-vscode setDataBreakpoints request
+"""
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbvscode_testcase
+import os
+
+# Return first element in the array where func is true
+def array_find(array, func):
+    for i in array:
+        if func(i):
+            return i
+    return None
+
+class TestVSCode_setDataBreakpoints(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    def setUp(self):
+        lldbvscode_testcase.VSCodeTestCaseBase.setUp(self)
+        self.main_basename = 'main-copy.cpp'
+        self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_local_var(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        
+        # Break on the main function so we can see the variable scopes
+        response = self.vscode.request_setFunctionBreakpoints(['main'])
+        breakpoints = response['body']['breakpoints']
+        breakpoint_id = breakpoints[0]['id']
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([breakpoint_id])
+
+        # Get and verify the locals
+        locals = self.vscode.get_local_variables()
+        num_a = array_find(locals, lambda x: x['name'] == 'num_a')
+        self.assertIsNotNone(num_a)
+
+        # Get and verify the data breakpoint info
+        data_breakpoint_info = self.vscode.request_dataBreakpointInfo(
+            num_a['name'], 1
+        )
+        # num_a has no watchpoint access as it's stored in a register
+        self.assertEqual(data_breakpoint_info['body']['accessTypes'], [])
Index: lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/Makefile
@@ -0,0 +1,9 @@
+CXX_SOURCES := main-copy.cpp
+LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
+USE_LIBDL :=1
+CXXFLAGS_EXTRAS := -O3
+
+include Makefile.rules
+
+main-copy.cpp: main.cpp
+	cp -f $< $@
Index: lldb/test/API/tools/lldb-vscode/breakpoint_data/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/breakpoint_data/main.cpp
@@ -0,0 +1,20 @@
+#include "string.h"
+
+static int global_num = 20;
+const char *global_str = "hello world";
+const int global_const_num = 123;
+
+int main() {
+  int num_a = 10;
+  int num_b = 20; // num_a first
+  int *num_a_ptr = &num_a;
+  int *num_b_ptr = &num_b;
+
+  *num_a_ptr = *num_b_ptr * 20;
+  *num_b_ptr = *num_a_ptr / 10; // num_a second
+  global_num = 30;
+
+  int string_sum = 0; // global_num
+  for (int i = 0; i < strlen(global_str); i++)
+    string_sum += global_const_num; // global_const_num
+}
Index: lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py
@@ -0,0 +1,126 @@
+"""
+Test lldb-vscode setDataBreakpoints request
+"""
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbvscode_testcase
+import os
+
+# Return first element in the array where func is true
+def array_find(array, func):
+    for i in array:
+        if func(i):
+            return i
+    return None
+
+class TestVSCode_setDataBreakpoints(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    def setUp(self):
+        lldbvscode_testcase.VSCodeTestCaseBase.setUp(self)
+        self.main_basename = 'main-copy.cpp'
+        self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_global_var(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        
+        # Break on the main function so we can see the variable scopes
+        response = self.vscode.request_setFunctionBreakpoints(['main'])
+        breakpoints = response['body']['breakpoints']
+        breakpoint_id = breakpoints[0]['id']
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([breakpoint_id])
+
+        # Get and verify that the global_num is in scope
+        globals = self.vscode.get_global_variables()
+        global_num = array_find(globals, lambda x: x['name'] == 'global_num')
+        self.assertIsNotNone(global_num)
+
+        # Get and verify the data breakpoint info
+        data_breakpoint_info = self.vscode.request_dataBreakpointInfo(
+            global_num['name'], 2
+        )
+        self.assertEqual(data_breakpoint_info['body']['accessTypes'], ['read', 'readWrite', 'write'])
+
+        # Set the data breakpoint and verify watchpoint hit after continue
+        self.vscode.request_setDataBreakpoints(
+            'mockId',
+            data_breakpoint_info['body']['dataId']
+        )
+        self.vscode.request_continue()
+        expected_line = line_number('main.cpp', '// global_num')
+        self.verify_watchpoint_hit(expected_line)
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_local_var(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        
+        # Break on the main function so we can see the variable scopes
+        response = self.vscode.request_setFunctionBreakpoints(['main'])
+        breakpoints = response['body']['breakpoints']
+        breakpoint_id = breakpoints[0]['id']
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([breakpoint_id])
+
+        # Get and verify the locals
+        locals = self.vscode.get_local_variables()
+        num_a = array_find(locals, lambda x: x['name'] == 'num_a')
+        self.assertIsNotNone(num_a)
+
+        # Get and verify the data breakpoint info
+        data_breakpoint_info = self.vscode.request_dataBreakpointInfo(
+            num_a['name'], 1
+        )
+        self.assertEqual(data_breakpoint_info['body']['accessTypes'], ['read', 'readWrite', 'write'])
+
+        # Set the data breakpoint and verify breakpoint hit after continue
+        self.vscode.request_setDataBreakpoints(
+            'mockId',
+            data_breakpoint_info['body']['dataId']
+        )
+        self.vscode.request_continue()
+        expected_line = line_number('main.cpp', '// num_a first')
+        self.verify_watchpoint_hit(expected_line)
+
+        # In this example there is a second watchpoint to hit
+        self.vscode.request_continue()
+        expected_line = line_number('main.cpp', '// num_a second')
+        self.verify_watchpoint_hit(expected_line)
+
+    @skipIfWindows
+    @skipIfRemote
+    def global_const_num(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        
+        # Break on the main function so we can see the variable scopes
+        response = self.vscode.request_setFunctionBreakpoints(['main'])
+        breakpoints = response['body']['breakpoints']
+        breakpoint_id = breakpoints[0]['id']
+        self.vscode.request_continue()
+        self.verify_breakpoint_hit([breakpoint_id])
+
+        # Get and verify that the global_num is in scope
+        globals = self.vscode.get_global_variables()
+        global_const_num = array_find(globals, lambda x: x['name'] == 'global_const_num')
+        self.assertIsNotNone(global_const_num)
+
+        # Get and verify the data breakpoint info
+        data_breakpoint_info = self.vscode.request_dataBreakpointInfo(
+            global_const_num['name'], 2
+        )
+        self.assertEqual(data_breakpoint_info['body']['accessTypes'], ['read'])
+
+        # Set the data breakpoint and verify watchpoint hit after continue
+        self.vscode.request_setDataBreakpoints(
+            'mockId',
+            data_breakpoint_info['body']['dataId']
+        )
+        self.vscode.request_continue()
+        expected_line = line_number('main.cpp', '// global_const_num')
+        self.verify_watchpoint_hit(expected_line)
Index: lldb/test/API/tools/lldb-vscode/breakpoint_data/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/breakpoint_data/Makefile
@@ -0,0 +1,8 @@
+CXX_SOURCES := main-copy.cpp
+LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
+USE_LIBDL :=1
+
+include Makefile.rules
+
+main-copy.cpp: main.cpp
+	cp -f $< $@
Index: lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -827,6 +827,34 @@
         }
         return self.send_recv(command_dict)
 
+    def request_setDataBreakpoints(self, id, address, ):
+        breakpoint = {
+            'id': id,
+            'dataId': address,
+            'enabled': True
+        }
+        args_dict = {
+            'breakpoints': [ breakpoint ]
+        }
+        command_dict = {
+            'command': 'setDataBreakpoints',
+            'type': 'request',
+            'arguments': args_dict
+        }
+        return self.send_recv(command_dict)
+
+    def request_dataBreakpointInfo(self, name, variables_reference):
+        args_dict = {
+            'name': name,
+            'variablesReference': variables_reference
+        }
+        command_dict = {
+            'command': 'dataBreakpointInfo',
+            'type': 'request',
+            'arguments': args_dict
+        }
+        return self.send_recv(command_dict)
+
     def request_compileUnits(self, moduleId):
         args_dict = {'moduleId': moduleId}
         command_dict = {
Index: lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
+++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
@@ -93,6 +93,29 @@
                         return
         self.assertTrue(False, "breakpoint not hit")
 
+    def verify_watchpoint_hit(self, line_number):
+        '''Wait for the process we are debugging to stop, and verify we hit
+           any watchpoint at the line number.'''
+        stopped_events = self.vscode.wait_for_stopped()
+        for stopped_event in stopped_events:
+            if 'body' in stopped_event:
+                body = stopped_event['body']
+                if 'reason' not in body:
+                    continue
+                if body['reason'] != 'breakpoint':
+                    continue
+                if 'description' not in body:
+                    continue
+                description = body['description']
+                if 'watchpoint' not in description:
+                    continue
+
+                (_, line) = self.get_source_and_line(threadId=body['threadId'])
+                if line == line_number:
+                    return
+
+        self.assertTrue(False, "watchpoint not hit")
+
     def verify_stop_exception_info(self, expected_description):
         '''Wait for the process we are debugging to stop, and verify the stop
            reason is 'exception' and that the description matches
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to