wallace updated this revision to Diff 315652.
wallace added a comment.

Addressed all comments:

- Now using a single file for all the communication. Did indeed simplified a 
lot of code
- Now adding timeouts to Send operations. This will help prevent the adaptor 
from undefinitely waiting for the launcher to read the data.
- I added more comments, specially in the part where the process attaches and 
resumes


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D93951

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/runInTerminal/TestVSCode_runInTerminal.py
  lldb/tools/lldb-vscode/CMakeLists.txt
  lldb/tools/lldb-vscode/FifoFiles.cpp
  lldb/tools/lldb-vscode/FifoFiles.h
  lldb/tools/lldb-vscode/JSONUtils.cpp
  lldb/tools/lldb-vscode/JSONUtils.h
  lldb/tools/lldb-vscode/Options.td
  lldb/tools/lldb-vscode/RunInTerminal.cpp
  lldb/tools/lldb-vscode/RunInTerminal.h
  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
@@ -384,12 +384,7 @@
             break;
           case lldb::eStateSuspended:
             break;
-          case lldb::eStateStopped: {
-            if (g_vsc.waiting_for_run_in_terminal) {
-              g_vsc.waiting_for_run_in_terminal = false;
-              g_vsc.request_in_terminal_cv.notify_one();
-            }
-          }
+          case lldb::eStateStopped:
             // Only report a stopped event if the process was not restarted.
             if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
               SendStdOutStdErr(process);
@@ -1441,47 +1436,64 @@
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
-void request_runInTerminal(const llvm::json::Object &launch_request,
-                           llvm::json::Object &launch_response) {
-  // We have already created a target that has a valid "program" path to the
-  // executable. We will attach to the next process whose name matches that
-  // of the target's.
+llvm::Error request_runInTerminal(const llvm::json::Object &launch_request) {
   g_vsc.is_attach = true;
   lldb::SBAttachInfo attach_info;
-  lldb::SBError error;
-  attach_info.SetWaitForLaunch(true, /*async*/ true);
-  g_vsc.target.Attach(attach_info, error);
 
-  llvm::json::Object reverse_request =
-      CreateRunInTerminalReverseRequest(launch_request);
+  llvm::Expected<std::shared_ptr<FifoFile>> comm_file_or_err =
+      CreateRunInTerminalCommFile();
+  if (!comm_file_or_err)
+    return comm_file_or_err.takeError();
+  FifoFile &comm_file = *comm_file_or_err.get();
+
+  RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.path);
+
+  llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
+      launch_request, g_vsc.debug_adaptor_path, comm_file.path);
   llvm::json::Object reverse_response;
   lldb_vscode::PacketStatus status =
       g_vsc.SendReverseRequest(reverse_request, reverse_response);
   if (status != lldb_vscode::PacketStatus::Success)
-    error.SetErrorString("Process cannot be launched by IDE.");
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "Process cannot be launched by the IDE. %s",
+                                   comm_channel.GetLauncherError().c_str());
 
-  if (error.Success()) {
-    // Wait for the attach stop event to happen or for a timeout.
-    g_vsc.waiting_for_run_in_terminal = true;
-    static std::mutex mutex;
-    std::unique_lock<std::mutex> locker(mutex);
-    g_vsc.request_in_terminal_cv.wait_for(locker, std::chrono::seconds(10));
+  if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
+    attach_info.SetProcessID(*pid);
+  else
+    return pid.takeError();
 
-    auto attached_pid = g_vsc.target.GetProcess().GetProcessID();
-    if (attached_pid == LLDB_INVALID_PROCESS_ID)
-      error.SetErrorString("Failed to attach to a process");
-    else
-      SendProcessEvent(Attach);
-  }
+  g_vsc.debugger.SetAsync(false);
+  lldb::SBError error;
+  g_vsc.target.Attach(attach_info, error);
 
-  if (error.Fail()) {
-    launch_response["success"] = llvm::json::Value(false);
-    EmplaceSafeString(launch_response, "message",
-                      std::string(error.GetCString()));
-  } else {
-    launch_response["success"] = llvm::json::Value(true);
-    g_vsc.SendJSON(CreateEventObject("initialized"));
-  }
+  if (error.Fail())
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "Failed to attach to the target process. %s",
+                                   comm_channel.GetLauncherError().c_str());
+  // This will notify the runInTerminal launcher that we attached.
+  // We have to make this async, as the function won't return until the launcher
+  // resumes and reads the data.
+  std::future<llvm::Error> did_attach_message_success =
+      comm_channel.NotifyDidAttach();
+
+  // We just attached to the runInTerminal launcher, which was waiting to be
+  // attached. We now resume it, so it can receive the didAttach notification
+  // and then perform the exec. Upon continuing, the debugger will stop the
+  // process right in the middle of the exec. To the user, what we are doing is
+  // transparent, as they will only be able to see the process since the exec,
+  // completely unaware of the preparatory work.
+  g_vsc.target.GetProcess().Continue();
+
+  // Now that the actual target is just starting (i.e. exec was just invoked),
+  // we return the debugger to its async state.
+  g_vsc.debugger.SetAsync(true);
+
+  // If sending the notification failed, the launcher should be dead by now and
+  // the async didAttach notification should have an error message, so we
+  // return it. Otherwise, everything was a success.
+  did_attach_message_success.wait();
+  return did_attach_message_success.get();
 }
 
 // "LaunchRequest": {
@@ -1556,12 +1568,6 @@
     return;
   }
 
-  if (GetBoolean(arguments, "runInTerminal", false)) {
-    request_runInTerminal(request, response);
-    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
-    return;
-  }
-
   // Instantiate a launch info instance for the target.
   auto launch_info = g_vsc.target.GetLaunchInfo();
 
@@ -1597,7 +1603,11 @@
 
   // Run any pre run LLDB commands the user specified in the launch.json
   g_vsc.RunPreRunCommands();
-  if (launchCommands.empty()) {
+
+  if (GetBoolean(arguments, "runInTerminal", false)) {
+    if (llvm::Error err = request_runInTerminal(request))
+      error.SetErrorString(llvm::toString(std::move(err)).c_str());
+  } else 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);
@@ -1616,10 +1626,11 @@
   }
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 
-  SendProcessEvent(Launch);
+  if (g_vsc.is_attach)
+    SendProcessEvent(Attach); // this happens when doing runInTerminal
+  else
+    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": {
@@ -2948,20 +2959,94 @@
   llvm::outs() << examples;
 }
 
-int main(int argc, char *argv[]) {
+// If --launch-target is provided, this instance of lldb-vscode becomes a
+// runInTerminal launcher. It will ultimately launch the program specified in
+// the --launch-target argument, which is the original program the user wanted
+// to debug. This is done in such a way that the actual debug adaptor can
+// place breakpoints at the beginning of the program.
+//
+// The launcher will communicate with the debug adaptor using a fifo file in the
+// directory specified in the --comm-file argument.
+//
+// Regarding the actual flow, this launcher will first notify the debug adaptor
+// of its pid. Then, the launcher will be in a pending state waiting to be
+// attached by the adaptor.
+//
+// Once attached and resumed, the launcher will exec and become the program
+// specified by --launch-target, which is the original target the
+// user wanted to run.
+//
+// In case of errors launching the target, a suitable error message will be
+// emitted to the debug adaptor.
+void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
+                               llvm::StringRef comm_file, char *argv[]) {
+#if defined(WIN_32)
+  llvm::errs() << "runInTerminal is not supported on Windows\n";
+  exit(EXIT_FAILURE);
+#else
+  RunInTerminalLauncherCommChannel comm_channel(comm_file);
+  if (llvm::Error err = comm_channel.NotifyPid()) {
+    llvm::errs() << llvm::toString(std::move(err)) << "\n";
+    exit(EXIT_FAILURE);
+  }
 
-  // Initialize LLDB first before we do anything.
-  lldb::SBDebugger::Initialize();
+  // We will wait to be attached with a timeout. We don't wait indefinitely
+  // using a signal to prevent being paused forever.
+
+  // This env var should be used only for tests.
+  const char *timeout_env_var = getenv("LLDB_VSCODE_RIT_TIMEOUT_IN_MS");
+  int timeout_in_ms =
+      timeout_env_var != nullptr ? atoi(timeout_env_var) : 20000;
+  if (llvm::Error err = comm_channel.WaitUntilDebugAdaptorAttaches(
+          std::chrono::milliseconds(timeout_in_ms))) {
+    llvm::errs() << llvm::toString(std::move(err)) << "\n";
+    exit(EXIT_FAILURE);
+  }
 
-  RegisterRequestCallbacks();
+  const char *target = target_arg.getValue();
+  execvp(target, argv);
 
-  int portno = -1;
+  std::string error = std::strerror(errno);
+  comm_channel.NotifyError(error);
+  llvm::errs() << error << "\n";
+  exit(EXIT_FAILURE);
+#endif
+}
+
+int main(int argc, char *argv[]) {
+  llvm::SmallString<256> program_path(argv[0]);
+  llvm::sys::fs::make_absolute(program_path);
+  g_vsc.debug_adaptor_path = program_path.str().str();
 
   LLDBVSCodeOptTable T;
   unsigned MAI, MAC;
   llvm::ArrayRef<const char *> ArgsArr = llvm::makeArrayRef(argv + 1, argc);
   llvm::opt::InputArgList input_args = T.ParseArgs(ArgsArr, MAI, MAC);
 
+  if (llvm::opt::Arg *target_arg = input_args.getLastArg(OPT_launch_target)) {
+    if (llvm::opt::Arg *comm_file = input_args.getLastArg(OPT_comm_file)) {
+      int target_args_pos = argc;
+      for (int i = 0; i < argc; i++)
+        if (strcmp(argv[i], "--launch-target") == 0) {
+          target_args_pos = i + 1;
+          break;
+        }
+      LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(),
+                                argv + target_args_pos);
+    } else {
+      llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be "
+                      "specified\n";
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  // Initialize LLDB first before we do anything.
+  lldb::SBDebugger::Initialize();
+
+  RegisterRequestCallbacks();
+
+  int portno = -1;
+
   if (input_args.hasArg(OPT_help)) {
     printHelp(T, llvm::sys::path::filename(argv[0]));
     return 0;
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ lldb/tools/lldb-vscode/VSCode.h
@@ -47,6 +47,7 @@
 #include "ExceptionBreakpoint.h"
 #include "FunctionBreakpoint.h"
 #include "IOStream.h"
+#include "RunInTerminal.h"
 #include "SourceBreakpoint.h"
 #include "SourceReference.h"
 
@@ -77,6 +78,7 @@
 };
 
 struct VSCode {
+  std::string debug_adaptor_path;
   InputStream input;
   OutputStream output;
   lldb::SBDebugger debugger;
@@ -104,7 +106,6 @@
   bool is_attach;
   uint32_t reverse_request_seq;
   std::map<std::string, RequestCallback> request_handlers;
-  std::condition_variable request_in_terminal_cv;
   bool waiting_for_run_in_terminal;
   // 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.
Index: lldb/tools/lldb-vscode/RunInTerminal.h
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/RunInTerminal.h
@@ -0,0 +1,129 @@
+//===-- RunInTerminal.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_RUNINTERMINAL_H
+#define LLDB_TOOLS_LLDB_VSCODE_RUNINTERMINAL_H
+
+#include "FifoFiles.h"
+
+#include <future>
+#include <thread>
+
+namespace lldb_vscode {
+
+enum RunInTerminalMessageKind {
+  eRunInTerminalMessageKindPID = 0,
+  eRunInTerminalMessageKindError,
+  eRunInTerminalMessageKindDidAttach,
+};
+
+struct RunInTerminalMessage;
+struct RunInTerminalMessagePid;
+struct RunInTerminalMessageError;
+struct RunInTerminalMessageDidAttach;
+
+struct RunInTerminalMessage {
+  RunInTerminalMessage(RunInTerminalMessageKind kind);
+
+  virtual ~RunInTerminalMessage() = default;
+
+  /// Serialize this object to JSON
+  virtual llvm::json::Value ToJSON() const = 0;
+
+  const RunInTerminalMessagePid *GetAsPidMessage() const;
+
+  const RunInTerminalMessageError *GetAsErrorMessage() const;
+
+  RunInTerminalMessageKind kind;
+};
+
+using RunInTerminalMessageUP = std::unique_ptr<RunInTerminalMessage>;
+
+struct RunInTerminalMessagePid : RunInTerminalMessage {
+  RunInTerminalMessagePid(lldb::pid_t pid);
+
+  llvm::json::Value ToJSON() const override;
+
+  lldb::pid_t pid;
+};
+
+struct RunInTerminalMessageError : RunInTerminalMessage {
+  RunInTerminalMessageError(llvm::StringRef error);
+
+  llvm::json::Value ToJSON() const override;
+
+  std::string error;
+};
+
+struct RunInTerminalMessageDidAttach : RunInTerminalMessage {
+  RunInTerminalMessageDidAttach();
+
+  llvm::json::Value ToJSON() const override;
+};
+
+class RunInTerminalLauncherCommChannel {
+public:
+  RunInTerminalLauncherCommChannel(llvm::StringRef comm_file);
+
+  /// Wait until the debug adaptor attaches.
+  ///
+  /// \param[in] timeout
+  ///     How long to wait to be attached.
+  //
+  /// \return
+  ///     An \a llvm::Error object in case of errors or if this operation times
+  ///     out.
+  llvm::Error WaitUntilDebugAdaptorAttaches(std::chrono::milliseconds timeout);
+
+  /// Notify the debug adaptor this process' pid.
+  ///
+  /// \return
+  ///     An \a llvm::Error object in case of errors or if this operation times
+  ///     out.
+  llvm::Error NotifyPid();
+
+  /// Notify the debug adaptor that there's been an error.
+  void NotifyError(llvm::StringRef error);
+
+private:
+  FifoFileIO m_io;
+};
+
+class RunInTerminalDebugAdapterCommChannel {
+public:
+  RunInTerminalDebugAdapterCommChannel(llvm::StringRef comm_file);
+
+  /// Notify the runInTerminal launcher that it was attached.
+  ///
+  /// \return
+  ///     A future indicated whether the runInTerminal launcher received the
+  ///     message correctly or not.
+  std::future<llvm::Error> NotifyDidAttach();
+
+  /// Fetch the pid of the runInTerminal launcher.
+  ///
+  /// \return
+  ///     An \a llvm::Error object in case of errors or if this operation times
+  ///     out.
+  llvm::Expected<lldb::pid_t> GetLauncherPid();
+
+  /// Fetch any errors emitted by the runInTerminal launcher or return a
+  /// default error message if a certain timeout if reached.
+  std::string GetLauncherError();
+
+private:
+  FifoFileIO m_io;
+};
+
+/// Create a fifo file used to communicate the debug adaptor with
+/// the runInTerminal launcher.
+llvm::Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile();
+
+} // namespace lldb_vscode
+
+#endif // LLDB_TOOLS_LLDB_VSCODE_RUNINTERMINAL_H
Index: lldb/tools/lldb-vscode/RunInTerminal.cpp
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/RunInTerminal.cpp
@@ -0,0 +1,167 @@
+//===-- RunInTerminal.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
+//
+//===----------------------------------------------------------------------===//
+
+#if !defined(WIN32)
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <chrono>
+#include <fstream>
+#include <future>
+#include <thread>
+
+#include "llvm/Support/FileSystem.h"
+
+#include "lldb/lldb-defines.h"
+
+#include "RunInTerminal.h"
+
+using namespace llvm;
+
+namespace lldb_vscode {
+
+const RunInTerminalMessagePid *RunInTerminalMessage::GetAsPidMessage() const {
+  return static_cast<const RunInTerminalMessagePid *>(this);
+}
+
+const RunInTerminalMessageError *
+RunInTerminalMessage::GetAsErrorMessage() const {
+  return static_cast<const RunInTerminalMessageError *>(this);
+}
+
+RunInTerminalMessage::RunInTerminalMessage(RunInTerminalMessageKind kind)
+    : kind(kind) {}
+
+RunInTerminalMessagePid::RunInTerminalMessagePid(lldb::pid_t pid)
+    : RunInTerminalMessage(eRunInTerminalMessageKindPID), pid(pid) {}
+
+json::Value RunInTerminalMessagePid::ToJSON() const {
+  return json::Object{{"kind", "pid"}, {"pid", static_cast<int64_t>(pid)}};
+}
+
+RunInTerminalMessageError::RunInTerminalMessageError(StringRef error)
+    : RunInTerminalMessage(eRunInTerminalMessageKindError), error(error) {}
+
+json::Value RunInTerminalMessageError::ToJSON() const {
+  return json::Object{{"kind", "error"}, {"value", error}};
+}
+
+RunInTerminalMessageDidAttach::RunInTerminalMessageDidAttach()
+    : RunInTerminalMessage(eRunInTerminalMessageKindDidAttach) {}
+
+json::Value RunInTerminalMessageDidAttach::ToJSON() const {
+  return json::Object{{"kind", "didAttach"}};
+}
+
+static Expected<RunInTerminalMessageUP>
+ParseJSONMessage(const json::Value &json) {
+  if (const json::Object *obj = json.getAsObject()) {
+    if (Optional<StringRef> kind = obj->getString("kind")) {
+      if (*kind == "pid") {
+        if (Optional<int64_t> pid = obj->getInteger("pid"))
+          return std::make_unique<RunInTerminalMessagePid>(
+              static_cast<lldb::pid_t>(*pid));
+      } else if (*kind == "error") {
+        if (Optional<StringRef> error = obj->getString("error"))
+          return std::make_unique<RunInTerminalMessageError>(*error);
+      } else if (*kind == "didAttach") {
+        return std::make_unique<RunInTerminalMessageDidAttach>();
+      }
+    }
+  }
+
+  return createStringError(inconvertibleErrorCode(),
+                           "Incorrect JSON message: " + JSONToString(json));
+}
+
+static Expected<RunInTerminalMessageUP>
+GetNextMessage(FifoFileIO &io, std::chrono::milliseconds timeout) {
+  if (Expected<json::Value> json = io.ReadJSON(timeout))
+    return ParseJSONMessage(*json);
+  else
+    return json.takeError();
+}
+
+static Error ToError(const RunInTerminalMessage &message) {
+  if (message.kind == eRunInTerminalMessageKindError)
+    return createStringError(inconvertibleErrorCode(),
+                             message.GetAsErrorMessage()->error);
+  return createStringError(inconvertibleErrorCode(),
+                           "Unexpected JSON message: " +
+                               JSONToString(message.ToJSON()));
+}
+
+RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel(
+    StringRef comm_file)
+    : m_io(comm_file, "debug adaptor") {}
+
+Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdaptorAttaches(
+    std::chrono::milliseconds timeout) {
+  if (Expected<RunInTerminalMessageUP> message =
+          GetNextMessage(m_io, timeout)) {
+    if (message.get()->kind == eRunInTerminalMessageKindDidAttach)
+      return Error::success();
+    else
+      return ToError(*message.get());
+  } else
+    return message.takeError();
+}
+
+Error RunInTerminalLauncherCommChannel::NotifyPid() {
+  return m_io.SendJSON(RunInTerminalMessagePid(getpid()).ToJSON());
+}
+
+void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
+  if (Error err = m_io.SendJSON(RunInTerminalMessageError(error).ToJSON(),
+                                std::chrono::seconds(2)))
+    llvm::errs() << llvm::toString(std::move(err)) << "\n";
+}
+
+RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel(
+    StringRef comm_file)
+    : m_io(comm_file, "runInTerminal launcher") {}
+
+std::future<Error> RunInTerminalDebugAdapterCommChannel::NotifyDidAttach() {
+  return std::async(std::launch::async, [&]() {
+    return m_io.SendJSON(RunInTerminalMessageDidAttach().ToJSON());
+  });
+}
+
+Expected<lldb::pid_t> RunInTerminalDebugAdapterCommChannel::GetLauncherPid() {
+  if (Expected<RunInTerminalMessageUP> message =
+          GetNextMessage(m_io, std::chrono::seconds(20))) {
+    if (message.get()->kind == eRunInTerminalMessageKindPID)
+      return message.get()->GetAsPidMessage()->pid;
+    return ToError(*message.get());
+  } else {
+    return message.takeError();
+  }
+}
+
+std::string RunInTerminalDebugAdapterCommChannel::GetLauncherError() {
+  // We know there's been an error, so a small timeout is enough.
+  if (Expected<RunInTerminalMessageUP> message =
+          GetNextMessage(m_io, std::chrono::seconds(1)))
+    return toString(ToError(*message.get()));
+  else
+    return toString(message.takeError());
+}
+
+Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile() {
+  SmallString<256> comm_file;
+  if (std::error_code EC = sys::fs::getPotentiallyUniqueTempFileName(
+          "lldb-vscode-run-in-terminal-comm", "", comm_file))
+    return createStringError(EC, "Error making unique file name for "
+                                 "runInTerminal communication files");
+
+  return CreateFifoFile(comm_file.str());
+}
+
+} // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/Options.td
===================================================================
--- lldb/tools/lldb-vscode/Options.td
+++ lldb/tools/lldb-vscode/Options.td
@@ -23,3 +23,14 @@
 def: Separate<["-"], "p">,
   Alias<port>,
   HelpText<"Alias for --port">;
+
+def launch_target: Separate<["--", "-"], "launch-target">,
+  MetaVarName<"<target>">,
+  HelpText<"Launch a target for the launchInTerminal request. Any argument "
+    "provided after this one will be passed to the target. The parameter "
+    "--comm-files-prefix must also be specified.">;
+
+def comm_file: Separate<["--", "-"], "comm-file">,
+  MetaVarName<"<file>">,
+  HelpText<"The fifo file used to communicate the with the debug adaptor"
+    "when using --launch-target.">;
Index: lldb/tools/lldb-vscode/JSONUtils.h
===================================================================
--- lldb/tools/lldb-vscode/JSONUtils.h
+++ lldb/tools/lldb-vscode/JSONUtils.h
@@ -449,11 +449,23 @@
 ///     The original launch_request object whose fields are used to construct
 ///     the reverse request object.
 ///
+/// \param[in] debug_adaptor_path
+///     Path to the current debug adaptor. It will be used to delegate the
+///     launch of the target.
+///
+/// \param[in] comm_file
+///     The fifo file used to communicate the with the target launcher.
+///
 /// \return
 ///     A "runInTerminal" JSON object that follows the specification outlined by
 ///     Microsoft.
 llvm::json::Object
-CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request);
+CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
+                                  llvm::StringRef debug_adaptor_path,
+                                  llvm::StringRef comm_file);
+
+/// Convert a given JSON object to a string.
+std::string JSONToString(const llvm::json::Value &json);
 
 } // namespace lldb_vscode
 
Index: lldb/tools/lldb-vscode/JSONUtils.cpp
===================================================================
--- lldb/tools/lldb-vscode/JSONUtils.cpp
+++ lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -1001,7 +1001,9 @@
 /// See
 /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal
 llvm::json::Object
-CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request) {
+CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
+                                  llvm::StringRef debug_adaptor_path,
+                                  llvm::StringRef comm_file) {
   llvm::json::Object reverse_request;
   reverse_request.try_emplace("type", "request");
   reverse_request.try_emplace("command", "runInTerminal");
@@ -1012,10 +1014,13 @@
   run_in_terminal_args.try_emplace("kind", "integrated");
 
   auto launch_request_arguments = launch_request.getObject("arguments");
-  std::vector<std::string> args = GetStrings(launch_request_arguments, "args");
   // The program path must be the first entry in the "args" field
-  args.insert(args.begin(),
-              GetString(launch_request_arguments, "program").str());
+  std::vector<std::string> args = {
+      debug_adaptor_path.str(), "--comm-file", comm_file.str(),
+      "--launch-target", GetString(launch_request_arguments, "program").str()};
+  std::vector<std::string> target_args =
+      GetStrings(launch_request_arguments, "args");
+  args.insert(args.end(), target_args.begin(), target_args.end());
   run_in_terminal_args.try_emplace("args", args);
 
   const auto cwd = GetString(launch_request_arguments, "cwd");
@@ -1038,4 +1043,12 @@
   return reverse_request;
 }
 
+std::string JSONToString(const llvm::json::Value &json) {
+  std::string data;
+  llvm::raw_string_ostream os(data);
+  os << json;
+  os.flush();
+  return data;
+}
+
 } // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/FifoFiles.h
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/FifoFiles.h
@@ -0,0 +1,84 @@
+//===-- FifoFiles.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_FIFOFILES_H
+#define LLDB_TOOLS_LLDB_VSCODE_FIFOFILES_H
+
+#include "llvm/Support/Error.h"
+
+#include "JSONUtils.h"
+
+namespace lldb_vscode {
+
+/// Struct that controls the life of a fifo file in the filesystem.
+///
+/// The file is destroyed when the destructor is invoked.
+struct FifoFile {
+  FifoFile(llvm::StringRef path);
+
+  ~FifoFile();
+
+  std::string path;
+};
+
+/// Create a fifo file in the filesystem.
+///
+/// \param[in] path
+///     The path for the fifo file.
+///
+/// \return
+///     A \a std::shared_ptr<FifoFile> if the file could be created, or an
+///     \a llvm::Error in case of failures.
+llvm::Expected<std::shared_ptr<FifoFile>> CreateFifoFile(llvm::StringRef path);
+
+class FifoFileIO {
+public:
+  /// \param[in] fifo_file
+  ///     The path to an input fifo file that exists in the file system.
+  ///
+  /// \param[in] other_endpoint_name
+  ///     A human readable name for the other endpoint that will communicate
+  ///     using this file. This is used for error messages.
+  FifoFileIO(llvm::StringRef fifo_file, llvm::StringRef other_endpoint_name);
+
+  /// Read the next JSON object from the underlying input fifo file.
+  ///
+  /// The JSON object is expected to be a single line delimited with \a
+  /// std::endl.
+  ///
+  /// \return
+  ///     An \a llvm::Error object indicating the success or failure of this
+  ///     operation. Failures arise if the timeout is hit, the next line of text
+  ///     from the fifo file is not a valid JSON object, or is it impossible to
+  ///     read from the file.
+  llvm::Expected<llvm::json::Value> ReadJSON(std::chrono::milliseconds timeout);
+
+  /// Serialize a JSON object and write it to the underlying output fifo file.
+  ///
+  /// \param[in] json
+  ///     The JSON object to send. It will be printed as a single line delimited
+  ///     with \a std::endl.
+  ///
+  /// \param[in] timeout
+  ///     A timeout for how long we should until for the data to be consumed.
+  ///
+  /// \return
+  ///     An \a llvm::Error object indicating whether the data was consumed by
+  ///     a reader or not.
+  llvm::Error SendJSON(
+      const llvm::json::Value &json,
+      std::chrono::milliseconds timeout = std::chrono::milliseconds(20000));
+
+private:
+  std::string m_fifo_file;
+  std::string m_other_endpoint_name;
+};
+
+} // namespace lldb_vscode
+
+#endif // LLDB_TOOLS_LLDB_VSCODE_FIFOFILES_H
Index: lldb/tools/lldb-vscode/FifoFiles.cpp
===================================================================
--- /dev/null
+++ lldb/tools/lldb-vscode/FifoFiles.cpp
@@ -0,0 +1,91 @@
+//===-- FifoFiles.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
+//
+//===----------------------------------------------------------------------===//
+
+#if !defined(WIN32)
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <chrono>
+#include <fstream>
+#include <future>
+#include <thread>
+
+#include "llvm/Support/FileSystem.h"
+
+#include "lldb/lldb-defines.h"
+
+#include "FifoFiles.h"
+
+using namespace llvm;
+
+namespace lldb_vscode {
+
+FifoFile::FifoFile(StringRef path) : path(path) {}
+
+FifoFile::~FifoFile() {
+#if !defined(WIN32)
+  unlink(path.c_str());
+#endif
+};
+
+Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) {
+#if defined(WIN32)
+  return createStringError(inconvertibleErrorCode(), "Unimplemented");
+#else
+  if (int err = mkfifo(path.data(), 0600))
+    return createStringError(std::error_code(err, std::generic_category()),
+                             "Couldn't create fifo file: %s", path.data());
+  return std::make_shared<FifoFile>(path);
+#endif
+}
+
+FifoFileIO::FifoFileIO(StringRef fifo_file, StringRef other_endpoint_name)
+    : m_fifo_file(fifo_file), m_other_endpoint_name(other_endpoint_name) {}
+
+Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
+  // We use a pointer for this future, because otherwise its normal destructor
+  // would wait for the getline to end, rendering the timeout useless.
+  Optional<std::string> line;
+  std::future<void> *future =
+      new std::future<void>(std::async(std::launch::async, [&]() {
+        std::ifstream reader(m_fifo_file, std::ifstream::in);
+        std::string buffer;
+        std::getline(reader, buffer);
+        if (!buffer.empty())
+          line = buffer;
+      }));
+  if (future->wait_for(timeout) == std::future_status::timeout ||
+      !line.hasValue())
+    return createStringError(inconvertibleErrorCode(),
+                             "Timed out trying to get messages from the " +
+                                 m_other_endpoint_name);
+  delete future;
+  return json::parse(*line);
+}
+
+Error FifoFileIO::SendJSON(const json::Value &json,
+                           std::chrono::milliseconds timeout) {
+  bool done = false;
+  std::future<void> *future =
+      new std::future<void>(std::async(std::launch::async, [&]() {
+        std::ofstream writer(m_fifo_file, std::ofstream::out);
+        writer << JSONToString(json) << std::endl;
+        done = true;
+      }));
+  if (future->wait_for(timeout) == std::future_status::timeout || !done) {
+    return createStringError(inconvertibleErrorCode(),
+                             "Timed out trying to send messages to the " +
+                                 m_other_endpoint_name);
+  }
+  delete future;
+  return Error::success();
+}
+
+} // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/CMakeLists.txt
===================================================================
--- lldb/tools/lldb-vscode/CMakeLists.txt
+++ lldb/tools/lldb-vscode/CMakeLists.txt
@@ -27,10 +27,12 @@
   lldb-vscode.cpp
   BreakpointBase.cpp
   ExceptionBreakpoint.cpp
+  FifoFiles.cpp
   FunctionBreakpoint.cpp
   IOStream.cpp
   JSONUtils.cpp
   LLDBUtils.cpp
+  RunInTerminal.cpp
   SourceBreakpoint.cpp
   VSCode.cpp
 
Index: lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
===================================================================
--- lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
+++ lldb/test/API/tools/lldb-vscode/runInTerminal/TestVSCode_runInTerminal.py
@@ -11,13 +11,17 @@
 import lldbvscode_testcase
 import time
 import os
+import subprocess
+import shutil
+import json
+from threading import Thread
 
 
 class TestVSCode_runInTerminal(lldbvscode_testcase.VSCodeTestCaseBase):
 
     mydir = TestBase.compute_mydir(__file__)
 
-    @skipUnlessDarwin
+    @skipIfWindows
     @skipIfRemote
     def test_runInTerminal(self):
         '''
@@ -26,7 +30,10 @@
         '''
         program = self.getBuildArtifact("a.out")
         source = 'main.c'
-        self.build_and_launch(program, stopOnEntry=True, runInTerminal=True, args=["foobar"], env=["FOO=bar"])
+        self.build_and_launch(
+            program, stopOnEntry=True, runInTerminal=True, args=["foobar"],
+            env=["FOO=bar"])
+
         breakpoint_line = line_number(source, '// breakpoint')
 
         self.set_source_breakpoints(source, [breakpoint_line])
@@ -46,3 +53,101 @@
         # We verify we were able to set the environment
         env = self.vscode.request_evaluate('foo')['body']['result']
         self.assertIn('bar', env)
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_runInTerminalInvalidTarget(self):
+        self.build_and_create_debug_adaptor()
+        response = self.launch(
+            "INVALIDPROGRAM", stopOnEntry=True, runInTerminal=True, args=["foobar"], env=["FOO=bar"], expectFailure=True)
+        self.assertFalse(response['success'])
+        self.assertIn("Could not create a target for a program 'INVALIDPROGRAM': unable to find executable",
+            response['message'])
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_missingArgInRunInTerminalLauncher(self):
+        proc = subprocess.run([self.lldbVSCodeExec,  "--launch-target", "INVALIDPROGRAM"],
+            capture_output=True, universal_newlines=True)
+        self.assertTrue(proc.returncode != 0)
+        self.assertIn('"--launch-target" requires "--comm-file" to be specified', proc.stderr)
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_FakeAttachedRunInTerminalLauncherWithInvalidProgram(self):
+        comm_file = os.path.join(self.getBuildDir(), "comm-file")
+        os.mkfifo(comm_file)
+
+        proc = subprocess.Popen(
+            [self.lldbVSCodeExec, "--comm-file", comm_file, "--launch-target", "INVALIDPROGRAM"],
+            universal_newlines=True, stderr=subprocess.PIPE)
+
+        with open(comm_file, "r") as file:
+            file.readline() # read the pid message
+
+        with open(comm_file, "w") as file:
+            file.write(json.dumps({"kind": "didAttach"}) + "\n")
+
+        with open(comm_file, "r") as file:
+            self.assertIn("No such file or directory", file.readline()) # read the error
+        
+        _, stderr = proc.communicate()
+        self.assertIn("No such file or directory", stderr)
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self):
+        comm_file = os.path.join(self.getBuildDir(), "comm-file")
+        os.mkfifo(comm_file)
+
+        proc = subprocess.Popen(
+            [self.lldbVSCodeExec, "--comm-file", comm_file, "--launch-target", "echo", "foo"],
+            universal_newlines=True, stdout=subprocess.PIPE)
+
+        with open(comm_file, "r") as file:
+            file.readline() # read the pid message
+
+        with open(comm_file, "w") as file:
+            file.write(json.dumps({"kind": "didAttach"}) + "\n")
+
+        stdout, _ = proc.communicate()
+        self.assertIn("foo", stdout)
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self):
+        comm_file = os.path.join(self.getBuildDir(), "comm-file")
+        os.mkfifo(comm_file)
+
+        proc = subprocess.Popen(
+            [self.lldbVSCodeExec, "--comm-file", comm_file, "--launch-target", "env"],
+            universal_newlines=True, stdout=subprocess.PIPE,
+            env={**os.environ, "FOO": "BAR"})
+        
+        with open(comm_file, "r") as file:
+            file.readline() # read the pid message
+
+        with open(comm_file, "w") as file:
+            file.write(json.dumps({"kind": "didAttach"}) + "\n")
+
+        stdout, _ = proc.communicate()
+        # We check the error message in both stderr and the error file
+        self.assertIn("FOO=BAR", stdout)
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_NonAttachedRunInTerminalLauncher(self):
+        comm_file = os.path.join(self.getBuildDir(), "comm-file")
+        os.mkfifo(comm_file)
+
+        proc = subprocess.Popen(
+            [self.lldbVSCodeExec, "--comm-file", comm_file, "--launch-target", "echo", "foo"],
+            universal_newlines=True, stderr=subprocess.PIPE,
+            env={**os.environ, "LLDB_VSCODE_RIT_TIMEOUT_IN_MS": "1000"})
+
+        with open(comm_file, "r") as file:
+            file.readline() # read the pid message
+        
+        _, stderr = proc.communicate()
+        # We check the error message in both stderr and the error file
+        self.assertIn("Timed out trying to get messages from the debug adaptor", stderr)
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
@@ -617,7 +617,7 @@
                        stopCommands=None, exitCommands=None,
                        terminateCommands=None ,sourcePath=None,
                        debuggerRoot=None, launchCommands=None, sourceMap=None,
-                       runInTerminal=False):
+                       runInTerminal=False, expectFailure=False):
         args_dict = {
             'program': program
         }
@@ -665,9 +665,10 @@
         }
         response = self.send_recv(command_dict)
 
-        # Wait for a 'process' and 'initialized' event in any order
-        self.wait_for_event(filter=['process', 'initialized'])
-        self.wait_for_event(filter=['process', 'initialized'])
+        if not expectFailure:
+            # Wait for a 'process' and 'initialized' event in any order
+            self.wait_for_event(filter=['process', 'initialized'])
+            self.wait_for_event(filter=['process', 'initialized'])
         return response
 
     def request_next(self, threadId):
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
@@ -282,7 +282,8 @@
                trace=False, initCommands=None, preRunCommands=None,
                stopCommands=None, exitCommands=None, terminateCommands=None,
                sourcePath=None, debuggerRoot=None, launchCommands=None,
-               sourceMap=None, disconnectAutomatically=True, runInTerminal=False):
+               sourceMap=None, disconnectAutomatically=True, runInTerminal=False,
+               expectFailure=False):
         '''Sending launch request to vscode
         '''
 
@@ -317,7 +318,12 @@
             debuggerRoot=debuggerRoot,
             launchCommands=launchCommands,
             sourceMap=sourceMap,
-            runInTerminal=runInTerminal)
+            runInTerminal=runInTerminal,
+            expectFailure=expectFailure)
+
+        if expectFailure:
+            return response
+
         if not (response and response['success']):
             self.assertTrue(response['success'],
                             'launch failed (%s)' % (response['message']))
@@ -325,6 +331,7 @@
         # attached a runInTerminal process to finish initialization.
         if runInTerminal:
             self.vscode.request_configurationDone()
+        return response
 
 
     def build_and_launch(self, program, args=None, cwd=None, env=None,
@@ -340,7 +347,7 @@
         self.build_and_create_debug_adaptor()
         self.assertTrue(os.path.exists(program), 'executable must exist')
 
-        self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
+        return self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
                     disableSTDIO, shellExpandArguments, trace,
                     initCommands, preRunCommands, stopCommands, exitCommands,
                     terminateCommands, sourcePath, debuggerRoot, runInTerminal=runInTerminal)
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to