aelitashen updated this revision to Diff 285725.
aelitashen added a comment.

Add a attacher thread to avoid race condition


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D84974

Files:
  lldb/test/API/tools/lldb-vscode/runInTerminal/Makefile
  lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
  lldb/test/API/tools/lldb-vscode/runInTerminal/main.cpp
  lldb/tools/lldb-vscode/VSCode.cpp
  lldb/tools/lldb-vscode/VSCode.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
@@ -343,7 +343,7 @@
   char buffer[1024];
   size_t count;
   while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0)
-  g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count));
+    g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count));
   while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0)
     g_vsc.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count));
 }
@@ -448,10 +448,10 @@
             if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) {
               body.try_emplace("reason", "new");
             } else if (event_mask &
-                        lldb::SBTarget::eBroadcastBitModulesUnloaded) {
+                       lldb::SBTarget::eBroadcastBitModulesUnloaded) {
               body.try_emplace("reason", "removed");
             } else if (event_mask &
-                        lldb::SBTarget::eBroadcastBitSymbolsLoaded) {
+                       lldb::SBTarget::eBroadcastBitSymbolsLoaded) {
               body.try_emplace("reason", "changed");
             }
             body.try_emplace("module", module_value);
@@ -873,7 +873,9 @@
 // "CompletionsRequest": {
 //   "allOf": [ { "$ref": "#/definitions/Request" }, {
 //     "type": "object",
-//     "description": "Returns a list of possible completions for a given caret position and text.\nThe CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true.",
+//     "description": "Returns a list of possible completions for a given caret
+//     position and text.\nThe CompletionsRequest may only be called if the
+//     'supportsCompletionsRequest' capability exists and is true.",
 //     "properties": {
 //       "command": {
 //         "type": "string",
@@ -892,19 +894,23 @@
 //   "properties": {
 //     "frameId": {
 //       "type": "integer",
-//       "description": "Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope."
+//       "description": "Returns completions in the scope of this stack frame.
+//       If not specified, the completions are returned for the global scope."
 //     },
 //     "text": {
 //       "type": "string",
-//       "description": "One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion."
+//       "description": "One or more source lines. Typically this is the text a
+//       user has typed into the debug console before he asked for completion."
 //     },
 //     "column": {
 //       "type": "integer",
-//       "description": "The character position for which to determine the completion proposals."
+//       "description": "The character position for which to determine the
+//       completion proposals."
 //     },
 //     "line": {
 //       "type": "integer",
-//       "description": "An optional line for which to determine the completion proposals. If missing the first line of the text is assumed."
+//       "description": "An optional line for which to determine the completion
+//       proposals. If missing the first line of the text is assumed."
 //     }
 //   },
 //   "required": [ "text", "column" ]
@@ -933,39 +939,51 @@
 // },
 // "CompletionItem": {
 //   "type": "object",
-//   "description": "CompletionItems are the suggestions returned from the CompletionsRequest.",
-//   "properties": {
+//   "description": "CompletionItems are the suggestions returned from the
+//   CompletionsRequest.", "properties": {
 //     "label": {
 //       "type": "string",
-//       "description": "The label of this completion item. By default this is also the text that is inserted when selecting this completion."
+//       "description": "The label of this completion item. By default this is
+//       also the text that is inserted when selecting this completion."
 //     },
 //     "text": {
 //       "type": "string",
-//       "description": "If text is not falsy then it is inserted instead of the label."
+//       "description": "If text is not falsy then it is inserted instead of the
+//       label."
 //     },
 //     "sortText": {
 //       "type": "string",
-//       "description": "A string that should be used when comparing this item with other items. When `falsy` the label is used."
+//       "description": "A string that should be used when comparing this item
+//       with other items. When `falsy` the label is used."
 //     },
 //     "type": {
 //       "$ref": "#/definitions/CompletionItemType",
-//       "description": "The item's type. Typically the client uses this information to render the item in the UI with an icon."
+//       "description": "The item's type. Typically the client uses this
+//       information to render the item in the UI with an icon."
 //     },
 //     "start": {
 //       "type": "integer",
-//       "description": "This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added.\nIf missing the text is added at the location specified by the CompletionsRequest's 'column' attribute."
+//       "description": "This value determines the location (in the
+//       CompletionsRequest's 'text' attribute) where the completion text is
+//       added.\nIf missing the text is added at the location specified by the
+//       CompletionsRequest's 'column' attribute."
 //     },
 //     "length": {
 //       "type": "integer",
-//       "description": "This value determines how many characters are overwritten by the completion text.\nIf missing the value 0 is assumed which results in the completion text being inserted."
+//       "description": "This value determines how many characters are
+//       overwritten by the completion text.\nIf missing the value 0 is assumed
+//       which results in the completion text being inserted."
 //     }
 //   },
 //   "required": [ "label" ]
 // },
 // "CompletionItemType": {
 //   "type": "string",
-//   "description": "Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them.",
-//   "enum": [ "method", "function", "constructor", "field", "variable", "class", "interface", "module", "property", "unit", "value", "enum", "keyword", "snippet", "text", "color", "file", "reference", "customcolor" ]
+//   "description": "Some predefined types for the CompletionItem. Please note
+//   that not all clients have specific icons for all of them.", "enum": [
+//   "method", "function", "constructor", "field", "variable", "class",
+//   "interface", "module", "property", "unit", "value", "enum", "keyword",
+//   "snippet", "text", "color", "file", "reference", "customcolor" ]
 // }
 void request_completions(const llvm::json::Object &request) {
   llvm::json::Object response;
@@ -992,9 +1010,7 @@
   lldb::SBStringList matches;
   lldb::SBStringList descriptions;
   g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
-    text.c_str(),
-    actual_column,
-    0, -1, matches, descriptions);
+      text.c_str(), actual_column, 0, -1, matches, descriptions);
   size_t count = std::min((uint32_t)100, matches.GetSize());
   targets.reserve(count);
   for (size_t i = 0; i < count; i++) {
@@ -1004,8 +1020,8 @@
     llvm::json::Object item;
 
     llvm::StringRef match_ref = match;
-    for(llvm::StringRef commit_point: {".", "->"}) {
-      if (match_ref.contains(commit_point)){
+    for (llvm::StringRef commit_point : {".", "->"}) {
+      if (match_ref.contains(commit_point)) {
         match_ref = match_ref.rsplit(commit_point).second;
       }
     }
@@ -1160,7 +1176,8 @@
     } else {
       SetValueForKey(value, body, "result");
       auto value_typename = value.GetType().GetDisplayTypeName();
-      EmplaceSafeString(body, "type", value_typename ? value_typename : NO_TYPENAME);
+      EmplaceSafeString(body, "type",
+                        value_typename ? value_typename : NO_TYPENAME);
       if (value.MightHaveChildren()) {
         auto variablesReference = VARIDX_TO_VARREF(g_vsc.variables.GetSize());
         g_vsc.variables.Append(value);
@@ -1229,8 +1246,8 @@
     if (module_id == curr_module.GetUUIDString()) {
       int num_units = curr_module.GetNumCompileUnits();
       for (int j = 0; j < num_units; j++) {
-        auto curr_unit = curr_module.GetCompileUnitAtIndex(j);\
-        units.emplace_back(CreateCompileUnit(curr_unit));\
+        auto curr_unit = curr_module.GetCompileUnitAtIndex(j);
+        units.emplace_back(CreateCompileUnit(curr_unit));
       }
       body.try_emplace("compileUnits", std::move(units));
       break;
@@ -1357,6 +1374,9 @@
     filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
   }
   body.try_emplace("exceptionBreakpointFilters", std::move(filters));
+  // The debug adapter supports launching a debugee in intergrated VSCode
+  // terminal.
+  body.try_emplace("supportsRunInTerminalRequest", true);
   // The debug adapter supports stepping back via the stepBack and
   // reverseContinue requests.
   body.try_emplace("supportsStepBack", false);
@@ -1416,6 +1436,77 @@
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
+void request_runInTerminal(const llvm::json::Object &request,
+                           llvm::json::Object &launch_response) {
+  // Fill out attach info
+  g_vsc.is_attach = true;
+  lldb::SBError error;
+  lldb::SBAttachInfo attach_info;
+  attach_info.SetWaitForLaunch(true, false);
+  // Manually set stopOnEntry as lldb cannot gather this info from the request
+  g_vsc.stop_at_entry = true;
+  auto attach_func = [&]() { g_vsc.target.Attach(attach_info, error); };
+  std::thread attacher(attach_func);
+
+  // Send reverse request for run in terminal
+  auto arguments = request.getObject("arguments");
+  llvm::json::Object reverseRequest;
+  reverseRequest.try_emplace("type", "request");
+  reverseRequest.try_emplace("command", "runInTerminal");
+  llvm::json::Object runInTerminalArgs;
+  runInTerminalArgs.try_emplace("kind", "integrated");
+  const auto cwd = GetString(arguments, "cwd");
+  if (!cwd.empty())
+    runInTerminalArgs.try_emplace("cwd", cwd);
+
+  std::vector<std::string> commands = GetStrings(arguments, "args");
+  commands.insert(commands.begin(),
+                  std::string(GetString(arguments, "program").data()));
+  runInTerminalArgs.try_emplace("args", commands);
+  std::vector<std::string> envVars = GetStrings(arguments, "env");
+  llvm::json::Object environment;
+  for (std::string envVar : envVars) {
+    size_t ind = envVar.find("=");
+    environment.try_emplace(envVar.substr(0, ind), envVar.substr(ind + 1));
+  }
+  runInTerminalArgs.try_emplace("env",
+                                llvm::json::Value(std::move(environment)));
+  reverseRequest.try_emplace("arguments",
+                             llvm::json::Value(std::move(runInTerminalArgs)));
+  llvm::json::Object reverseResponse;
+  lldb_vscode::VSCode::PacketStatus status =
+      g_vsc.SendReverseRequest(reverseRequest, reverseResponse);
+  if (status == lldb_vscode::VSCode::PacketStatus::Success) {
+    attacher.join();
+  } else {
+    // Reverse Request handshake fails
+    error.SetErrorString("Process cannot be launched by IDE.");
+  }
+
+  if (error.Success()) {
+    // IDE doesn't respond back the pid of the target, so lldb can only attach
+    // by process name, which is already set up in request_launch().
+    auto attached_pid = g_vsc.target.GetProcess().GetProcessID();
+    if (attached_pid == LLDB_INVALID_PROCESS_ID) {
+      error.SetErrorString("Failed to attach to a process");
+    }
+  }
+  if (error.Fail()) {
+    launch_response["success"] = llvm::json::Value(false);
+    EmplaceSafeString(launch_response, "message",
+                      std::string(error.GetCString()));
+    return;
+  }
+  if (error.Success()) {
+    SendProcessEvent(Attach);
+    launch_response["success"] = llvm::json::Value(true);
+  } else {
+    // Attach fails
+    launch_response["success"] = llvm::json::Value(false);
+  }
+  g_vsc.SendJSON(CreateEventObject("initialized"));
+}
+
 // "LaunchRequest": {
 //   "allOf": [ { "$ref": "#/definitions/Request" }, {
 //     "type": "object",
@@ -1488,64 +1579,69 @@
     return;
   }
 
-  // Instantiate a launch info instance for the target.
-  auto launch_info = g_vsc.target.GetLaunchInfo();
-
-  // Grab the current working directory if there is one and set it in the
-  // launch info.
-  const auto cwd = GetString(arguments, "cwd");
-  if (!cwd.empty())
-    launch_info.SetWorkingDirectory(cwd.data());
-
-  // Extract any extra arguments and append them to our program arguments for
-  // when we launch
-  auto args = GetStrings(arguments, "args");
-  if (!args.empty())
-    launch_info.SetArguments(MakeArgv(args).data(), true);
-
-  // Pass any environment variables along that the user specified.
-  auto envs = GetStrings(arguments, "env");
-  if (!envs.empty())
-    launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
-
-  auto flags = launch_info.GetLaunchFlags();
-
-  if (GetBoolean(arguments, "disableASLR", true))
-    flags |= lldb::eLaunchFlagDisableASLR;
-  if (GetBoolean(arguments, "disableSTDIO", false))
-    flags |= lldb::eLaunchFlagDisableSTDIO;
-  if (GetBoolean(arguments, "shellExpandArguments", false))
-    flags |= lldb::eLaunchFlagShellExpandArguments;
-  const bool detatchOnError = GetBoolean(arguments, "detachOnError", false);
-  launch_info.SetDetachOnError(detatchOnError);
-  launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
-                             lldb::eLaunchFlagStopAtEntry);
-
-  // Run any pre run LLDB commands the user specified in the launch.json
-  g_vsc.RunPreRunCommands();
-  if (launchCommands.empty()) {
-    // Disable async events so the launch will be successful when we return from
-    // the launch call and the launch will happen synchronously
-    g_vsc.debugger.SetAsync(false);
-    g_vsc.target.Launch(launch_info, error);
-    g_vsc.debugger.SetAsync(true);
+  if (GetBoolean(arguments, "launchInTerminal", false)) {
+    request_runInTerminal(request, response);
+    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
   } else {
-    g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
-    // The custom commands might have created a new target so we should use the
-    // selected target after these commands are run.
-    g_vsc.target = g_vsc.debugger.GetSelectedTarget();
-  }
+    // Instantiate a launch info instance for the target.
+    auto launch_info = g_vsc.target.GetLaunchInfo();
+
+    // Grab the current working directory if there is one and set it in the
+    // launch info.
+    const auto cwd = GetString(arguments, "cwd");
+    if (!cwd.empty())
+      launch_info.SetWorkingDirectory(cwd.data());
+
+    // Extract any extra arguments and append them to our program arguments for
+    // when we launch
+    auto args = GetStrings(arguments, "args");
+    if (!args.empty())
+      launch_info.SetArguments(MakeArgv(args).data(), true);
+
+    // Pass any environment variables along that the user specified.
+    auto envs = GetStrings(arguments, "env");
+    if (!envs.empty())
+      launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
+
+    auto flags = launch_info.GetLaunchFlags();
+
+    if (GetBoolean(arguments, "disableASLR", true))
+      flags |= lldb::eLaunchFlagDisableASLR;
+    if (GetBoolean(arguments, "disableSTDIO", false))
+      flags |= lldb::eLaunchFlagDisableSTDIO;
+    if (GetBoolean(arguments, "shellExpandArguments", false))
+      flags |= lldb::eLaunchFlagShellExpandArguments;
+    const bool detatchOnError = GetBoolean(arguments, "detachOnError", false);
+    launch_info.SetDetachOnError(detatchOnError);
+    launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
+                               lldb::eLaunchFlagStopAtEntry);
+
+    // Run any pre run LLDB commands the user specified in the launch.json
+    g_vsc.RunPreRunCommands();
+    if (launchCommands.empty()) {
+      // Disable async events so the launch will be successful when we return
+      // from the launch call and the launch will happen synchronously
+      g_vsc.debugger.SetAsync(false);
+      g_vsc.target.Launch(launch_info, error);
+      g_vsc.debugger.SetAsync(true);
+    } else {
+      g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
+      // The custom commands might have created a new target so we should use
+      // the selected target after these commands are run.
+      g_vsc.target = g_vsc.debugger.GetSelectedTarget();
+    }
 
-  if (error.Fail()) {
-    response["success"] = llvm::json::Value(false);
-    EmplaceSafeString(response, "message", std::string(error.GetCString()));
-  }
-  g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+    if (error.Fail()) {
+      response["success"] = llvm::json::Value(false);
+      EmplaceSafeString(response, "message", std::string(error.GetCString()));
+    }
 
-  SendProcessEvent(Launch);
-  g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized")));
-  // Reenable async events and start the event thread to catch async events.
-  // g_vsc.debugger.SetAsync(true);
+    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+    SendProcessEvent(Launch);
+    g_vsc.SendJSON(llvm::json::Value(CreateEventObject("initialized")));
+    // Reenable async events and start the event thread to catch async events.
+    // g_vsc.debugger.SetAsync(true);
+  }
 }
 
 // "NextRequest": {
@@ -2080,7 +2176,7 @@
   // Disable any function breakpoints that aren't in the request_bps.
   // There is no call to remove function breakpoints other than calling this
   // function with a smaller or empty "breakpoints" list.
-  for (auto &pair: g_vsc.function_breakpoints) {
+  for (auto &pair : g_vsc.function_breakpoints) {
     auto request_pos = request_bps.find(pair.first());
     if (request_pos == request_bps.end()) {
       // This function breakpoint no longer exists delete it from LLDB
@@ -2098,7 +2194,7 @@
     }
   }
   // Remove any breakpoints that are no longer in our list
-  for (const auto &name: remove_names)
+  for (const auto &name : remove_names)
     g_vsc.function_breakpoints.erase(name);
 
   // Any breakpoints that are left in "request_bps" are breakpoints that
@@ -2814,39 +2910,35 @@
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
-const std::map<std::string, RequestCallback> &GetRequestHandlers() {
-#define REQUEST_CALLBACK(name)                                                 \
-  { #name, request_##name }
-  static std::map<std::string, RequestCallback> g_request_handlers = {
-      // VSCode Debug Adaptor requests
-      REQUEST_CALLBACK(attach),
-      REQUEST_CALLBACK(completions),
-      REQUEST_CALLBACK(continue),
-      REQUEST_CALLBACK(configurationDone),
-      REQUEST_CALLBACK(disconnect),
-      REQUEST_CALLBACK(evaluate),
-      REQUEST_CALLBACK(exceptionInfo),
-      REQUEST_CALLBACK(getCompileUnits),
-      REQUEST_CALLBACK(initialize),
-      REQUEST_CALLBACK(launch),
-      REQUEST_CALLBACK(next),
-      REQUEST_CALLBACK(pause),
-      REQUEST_CALLBACK(scopes),
-      REQUEST_CALLBACK(setBreakpoints),
-      REQUEST_CALLBACK(setExceptionBreakpoints),
-      REQUEST_CALLBACK(setFunctionBreakpoints),
-      REQUEST_CALLBACK(setVariable),
-      REQUEST_CALLBACK(source),
-      REQUEST_CALLBACK(stackTrace),
-      REQUEST_CALLBACK(stepIn),
-      REQUEST_CALLBACK(stepOut),
-      REQUEST_CALLBACK(threads),
-      REQUEST_CALLBACK(variables),
-      // Testing requests
-      REQUEST_CALLBACK(_testGetTargetBreakpoints),
-  };
-#undef REQUEST_CALLBACK
-  return g_request_handlers;
+const void RegisterRequestCallbacks() {
+  g_vsc.RegisterRequestCallback("attach", request_attach);
+  g_vsc.RegisterRequestCallback("completions", request_completions);
+  g_vsc.RegisterRequestCallback("continue", request_continue);
+  g_vsc.RegisterRequestCallback("configurationDone", request_configurationDone);
+  g_vsc.RegisterRequestCallback("disconnect", request_disconnect);
+  g_vsc.RegisterRequestCallback("evaluate", request_evaluate);
+  g_vsc.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
+  g_vsc.RegisterRequestCallback("getCompileUnits", request_getCompileUnits);
+  g_vsc.RegisterRequestCallback("initialize", request_initialize);
+  g_vsc.RegisterRequestCallback("launch", request_launch);
+  g_vsc.RegisterRequestCallback("next", request_next);
+  g_vsc.RegisterRequestCallback("pause", request_pause);
+  g_vsc.RegisterRequestCallback("scopes", request_scopes);
+  g_vsc.RegisterRequestCallback("setBreakpoints", request_setBreakpoints);
+  g_vsc.RegisterRequestCallback("setExceptionBreakpoints",
+                                request_setExceptionBreakpoints);
+  g_vsc.RegisterRequestCallback("setFunctionBreakpoints",
+                                request_setFunctionBreakpoints);
+  g_vsc.RegisterRequestCallback("setVariable", request_setVariable);
+  g_vsc.RegisterRequestCallback("source", request_source);
+  g_vsc.RegisterRequestCallback("stackTrace", request_stackTrace);
+  g_vsc.RegisterRequestCallback("stepIn", request_stepIn);
+  g_vsc.RegisterRequestCallback("stepOut", request_stepOut);
+  g_vsc.RegisterRequestCallback("threads", request_threads);
+  g_vsc.RegisterRequestCallback("variables", request_variables);
+  // Testing requests
+  g_vsc.RegisterRequestCallback("_testGetTargetBreakpoints",
+                                request__testGetTargetBreakpoints);
 }
 
 } // anonymous namespace
@@ -2874,7 +2966,7 @@
 }
 
 int main(int argc, char *argv[]) {
-
+  RegisterRequestCallbacks();
   // Initialize LLDB first before we do anything.
   lldb::SBDebugger::Initialize();
 
@@ -2920,48 +3012,17 @@
     g_vsc.output.descriptor =
         StreamDescriptor::from_file(fileno(stdout), false);
   }
-  auto request_handlers = GetRequestHandlers();
   uint32_t packet_idx = 0;
   while (!g_vsc.sent_terminated_event) {
-    std::string json = g_vsc.ReadJSON();
-    if (json.empty())
+    llvm::json::Object object;
+    lldb_vscode::VSCode::PacketStatus status = g_vsc.GetObject(object);
+    if (status == lldb_vscode::VSCode::PacketStatus::EndOfFile)
       break;
+    if (status != lldb_vscode::VSCode::PacketStatus::Success)
+      return 1; // Fatal error
 
-    llvm::StringRef json_sref(json);
-    llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
-    if (!json_value) {
-      auto error = json_value.takeError();
-      if (g_vsc.log) {
-        std::string error_str;
-        llvm::raw_string_ostream strm(error_str);
-        strm << error;
-        strm.flush();
-
-        *g_vsc.log << "error: failed to parse JSON: " << error_str << std::endl
-                   << json << std::endl;
-      }
+    if (!g_vsc.HandleObject(object))
       return 1;
-    }
-
-    auto object = json_value->getAsObject();
-    if (!object) {
-      if (g_vsc.log)
-        *g_vsc.log << "error: json packet isn't a object" << std::endl;
-      return 1;
-    }
-
-    const auto packet_type = GetString(object, "type");
-    if (packet_type == "request") {
-      const auto command = GetString(object, "command");
-      auto handler_pos = request_handlers.find(std::string(command));
-      if (handler_pos != request_handlers.end()) {
-        handler_pos->second(*object);
-      } else {
-        if (g_vsc.log)
-          *g_vsc.log << "error: unhandled command \"" << command.data() << std::endl;
-        return 1;
-      }
-    }
     ++packet_idx;
   }
 
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ lldb/tools/lldb-vscode/VSCode.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/JSON.h"
 #include "llvm/Support/raw_ostream.h"
 
 #include "lldb/API/SBAttachInfo.h"
@@ -61,6 +62,7 @@
 
 typedef llvm::DenseMap<uint32_t, SourceBreakpoint> SourceBreakpointMap;
 typedef llvm::StringMap<FunctionBreakpoint> FunctionBreakpointMap;
+typedef void (*RequestCallback)(const llvm::json::Object &command);
 enum class OutputType { Console, Stdout, Stderr, Telemetry };
 
 enum VSCodeBroadcasterBits { eBroadcastBitStopEventThread = 1u << 0 };
@@ -91,6 +93,14 @@
   bool sent_terminated_event;
   bool stop_at_entry;
   bool is_attach;
+  uint32_t reverse_request_seq = 0;
+  enum class PacketStatus {
+    Success = 0,
+    EndOfFile,
+    JSONMalformed,
+    JSONNotObject
+  };
+  std::map<std::string, RequestCallback> m_request_handlers;
   // Keep track of the last stop thread index IDs as threads won't go away
   // unless we send a "thread" event to indicate the thread exited.
   llvm::DenseSet<lldb::tid_t> thread_ids;
@@ -146,13 +156,21 @@
   ///
   /// \return
   ///     An SBTarget object.
-  lldb::SBTarget CreateTargetFromArguments(
-      const llvm::json::Object &arguments,
-      lldb::SBError &error);
+  lldb::SBTarget CreateTargetFromArguments(const llvm::json::Object &arguments,
+                                           lldb::SBError &error);
 
   /// Set given target object as a current target for lldb-vscode and start
   /// listeing for its breakpoint events.
   void SetTarget(const lldb::SBTarget target);
+
+  const std::map<std::string, RequestCallback> &GetRequestHandlers();
+
+  PacketStatus GetObject(llvm::json::Object &object);
+  bool HandleObject(const llvm::json::Object &object);
+  PacketStatus SendReverseRequest(llvm::json::Object &request,
+                                  llvm::json::Object &response);
+
+  void RegisterRequestCallback(std::string request, RequestCallback callback);
 };
 
 extern VSCode g_vsc;
Index: lldb/tools/lldb-vscode/VSCode.cpp
===================================================================
--- lldb/tools/lldb-vscode/VSCode.cpp
+++ lldb/tools/lldb-vscode/VSCode.cpp
@@ -6,9 +6,9 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include <stdarg.h>
 #include <fstream>
 #include <mutex>
+#include <stdarg.h>
 
 #include "LLDBUtils.h"
 #include "VSCode.h"
@@ -16,9 +16,9 @@
 
 #if defined(_WIN32)
 #define NOMINMAX
-#include <windows.h>
 #include <fcntl.h>
 #include <io.h>
+#include <windows.h>
 #endif
 
 using namespace lldb_vscode;
@@ -41,9 +41,9 @@
       stop_at_entry(false), is_attach(false) {
   const char *log_file_path = getenv("LLDBVSCODE_LOG");
 #if defined(_WIN32)
-// Windows opens stdout and stdin in text mode which converts \n to 13,10
-// while the value is just 10 on Darwin/Linux. Setting the file mode to binary
-// fixes this.
+  // Windows opens stdout and stdin in text mode which converts \n to 13,10
+  // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
+  // fixes this.
   int result = _setmode(fileno(stdout), _O_BINARY);
   assert(result);
   result = _setmode(fileno(stdin), _O_BINARY);
@@ -54,8 +54,7 @@
     log.reset(new std::ofstream(log_file_path));
 }
 
-VSCode::~VSCode() {
-}
+VSCode::~VSCode() {}
 
 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
   auto pos = source_map.find(sourceReference);
@@ -232,8 +231,8 @@
   va_start(args, format);
   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
   va_end(args);
-  SendOutput(o, llvm::StringRef(buffer,
-                                std::min<int>(actual_length, sizeof(buffer))));
+  SendOutput(
+      o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
 }
 
 int64_t VSCode::GetNextSourceReference() {
@@ -313,9 +312,9 @@
   RunLLDBCommands("Running terminateCommands:", terminate_commands);
 }
 
-lldb::SBTarget VSCode::CreateTargetFromArguments(
-    const llvm::json::Object &arguments,
-    lldb::SBError &error) {
+lldb::SBTarget
+VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments,
+                                  lldb::SBError &error) {
   // Grab the name of the program we need to debug and create a target using
   // the given program as an argument. Executable file can be a source of target
   // architecture and platform, if they differ from the host. Setting exe path
@@ -330,18 +329,15 @@
   llvm::StringRef platform_name = GetString(arguments, "platformName");
   llvm::StringRef program = GetString(arguments, "program");
   auto target = this->debugger.CreateTarget(
-    program.data(),
-    target_triple.data(),
-    platform_name.data(),
-    true, // Add dependent modules.
-    error
-  );
+      program.data(), target_triple.data(), platform_name.data(),
+      true, // Add dependent modules.
+      error);
 
   if (error.Fail()) {
     // Update message if there was an error.
     error.SetErrorStringWithFormat(
-        "Could not create a target for a program '%s': %s.",
-        program.data(), error.GetCString());
+        "Could not create a target for a program '%s': %s.", program.data(),
+        error.GetCString());
   }
 
   return target;
@@ -359,11 +355,83 @@
     listener.StartListeningForEvents(this->broadcaster,
                                      eBroadcastBitStopEventThread);
     listener.StartListeningForEvents(
-      this->target.GetBroadcaster(),
-      lldb::SBTarget::eBroadcastBitModulesLoaded |
-          lldb::SBTarget::eBroadcastBitModulesUnloaded |
-          lldb::SBTarget::eBroadcastBitSymbolsLoaded);                                
+        this->target.GetBroadcaster(),
+        lldb::SBTarget::eBroadcastBitModulesLoaded |
+            lldb::SBTarget::eBroadcastBitModulesUnloaded |
+            lldb::SBTarget::eBroadcastBitSymbolsLoaded);
+  }
+}
+
+VSCode::PacketStatus VSCode::GetObject(llvm::json::Object &object) {
+  std::string json = ReadJSON();
+  if (json.empty())
+    return PacketStatus::EndOfFile;
+
+  llvm::StringRef json_sref(json);
+  llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
+  if (!json_value) {
+    auto error = json_value.takeError();
+    if (log) {
+      std::string error_str;
+      llvm::raw_string_ostream strm(error_str);
+      strm << error;
+      strm.flush();
+      *log << "error: failed to parse JSON: " << error_str << std::endl
+           << json << std::endl;
+    }
+    return PacketStatus::JSONMalformed;
   }
+  object = *json_value->getAsObject();
+  if (!json_value->getAsObject()) {
+    if (log)
+      *log << "error: json packet isn't a object" << std::endl;
+    return PacketStatus::JSONNotObject;
+  }
+  return PacketStatus::Success;
+}
+
+bool VSCode::HandleObject(const llvm::json::Object &object) {
+  const auto packet_type = GetString(object, "type");
+  if (packet_type == "request") {
+    const auto command = GetString(object, "command");
+    auto handler_pos = m_request_handlers.find(std::string(command));
+    if (handler_pos != m_request_handlers.end()) {
+      handler_pos->second(object);
+      return true; // Success
+    } else {
+      if (log)
+        *log << "error: unhandled command \"" << command.data() << std::endl;
+      return false; // Fail
+    }
+  }
+  return false;
+}
+
+VSCode::PacketStatus VSCode::SendReverseRequest(llvm::json::Object &request,
+                                                llvm::json::Object &response) {
+  // Put the right "seq" into the packet here, so we don't have to do it from
+  // where we send the reverse request.
+  request.try_emplace("seq", ++reverse_request_seq);
+  SendJSON(llvm::json::Value(std::move(request)));
+  bool got_response = false;
+  while (!got_response) {
+    PacketStatus status = GetObject(response);
+    const auto packet_type = GetString(response, "type");
+    if (packet_type == "response") {
+      if (status == PacketStatus::Success) {
+        return status;
+        // Not our response, we got another packet
+        HandleObject(response);
+      } else {
+        return status;
+      }
+    }
+  }
+}
+
+void VSCode::RegisterRequestCallback(std::string request,
+                                     RequestCallback callback) {
+  m_request_handlers[request] = callback;
 }
 
 } // namespace lldb_vscode
Index: lldb/test/API/tools/lldb-vscode/runInTerminal/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/runInTerminal/main.cpp
@@ -0,0 +1,9 @@
+
+int multiply(int x, int y) {
+  return x * y; // breakpoint 1
+}
+
+int main(int argc, char const *argv[]) {
+  int result = multiply(argc, 20);
+  return result < 0;
+}
Index: lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
@@ -0,0 +1,70 @@
+"""
+Test lldb-vscode setBreakpoints request
+"""
+
+from __future__ import print_function
+
+import unittest2
+import vscode
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbvscode_testcase
+
+
+class TestVSCode_console(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def check_lldb_command(self, lldb_command, contains_string, assert_msg):
+        response = self.vscode.request_evaluate('`%s' % (lldb_command))
+        output = response['body']['result']
+        self.assertTrue(contains_string in output,
+                        ("""Verify %s by checking the command output:\n"""
+                         """'''\n%s'''\nfor the string: "%s" """ % (
+                         assert_msg, output, contains_string)))
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_scopes_variables_setVariable_evaluate(self):
+        '''
+            Tests that the "scopes" request causes the currently selected
+            thread and frame to be updated. There are no DAP packets that tell
+            lldb-vscode which thread and frame are selected other than the
+            "scopes" request. lldb-vscode will now select the thread and frame
+            for the latest "scopes" request that it receives.
+
+            The LLDB command interpreter needs to have the right thread and
+            frame selected so that commands executed in the debug console act
+            on the right scope. This applies both to the expressions that are
+            evaluated and the lldb commands that start with the backtick
+            character.
+        '''
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        source = 'main.cpp'
+        breakpoint1_line = line_number(source, '// breakpoint 1')
+        lines = [breakpoint1_line]
+        # Set breakpoint in the thread function so we can step the threads
+        breakpoint_ids = self.set_source_breakpoints(source, lines)
+        self.assertTrue(len(breakpoint_ids) == len(lines),
+                        "expect correct number of breakpoints")
+        self.continue_to_breakpoints(breakpoint_ids)
+        # Cause a "scopes" to be sent for frame zero which should update the
+        # selected thread and frame to frame 0.
+        self.vscode.get_local_variables(frameIndex=0)
+        # Verify frame #0 is selected in the command interpreter by running
+        # the "frame select" command with no frame index which will print the
+        # currently selected frame.
+        self.check_lldb_command("frame select", "frame #0",
+                                "frame 0 is selected")
+
+        # Cause a "scopes" to be sent for frame one which should update the
+        # selected thread and frame to frame 1.
+        self.vscode.get_local_variables(frameIndex=1)
+        # Verify frame #1 is selected in the command interpreter by running
+        # the "frame select" command with no frame index which will print the
+        # currently selected frame.
+
+        self.check_lldb_command("frame select", "frame #1",
+                                "frame 1 is selected")
Index: lldb/test/API/tools/lldb-vscode/runInTerminal/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-vscode/runInTerminal/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to