Author: Ebuka Ezike
Date: 2026-01-13T12:11:22Z
New Revision: c745e9b67dd1e549ae8b79bb994da8a77643d816

URL: 
https://github.com/llvm/llvm-project/commit/c745e9b67dd1e549ae8b79bb994da8a77643d816
DIFF: 
https://github.com/llvm/llvm-project/commit/c745e9b67dd1e549ae8b79bb994da8a77643d816.diff

LOG: [lldb-dap] Add testcases for stdio redirection on different console types. 
(#175048)

There are some bugs when launching in terminal with args and stdio
redirection.

- lldb-dap `--stdio` args is passed to the debuggee (should we change
this to use `--` to separate debuggee args from lldb-dap args, similar
to how we handle the `--client` args? ).

#### It also changes the behaviour of stdio redirection.
If a redirection is not specified, it uses to lldb default value. e.g.
```jsonc
"stdio": ["./stdin"]` 
// now becomes 
"stdio", ["./stdio", "./default_stdout", "./default_stderr"]
// instead of 
"stdio", ["./stdin", "./stdin", "./stdin"]
// took quite some time to figure out where my output is going to.
```

Fixes [#174445](https://github.com/llvm/llvm-project/issues/174445)

Other bug I noticed but should be in a different PR.
- debuggee args that contains newline are not properly escaped when sent
to the terminal.

Added: 
    lldb/test/API/tools/lldb-dap/launch/io/Makefile
    lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py
    lldb/test/API/tools/lldb-dap/launch/io/main.cpp

Modified: 
    lldb/packages/Python/lldbsuite/test/lldbtest.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
    lldb/tools/lldb-dap/Handler/RequestHandler.cpp
    lldb/tools/lldb-dap/JSONUtils.cpp
    lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
    lldb/tools/lldb-dap/README.md
    lldb/tools/lldb-dap/extension/package.json
    lldb/tools/lldb-dap/tool/lldb-dap.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py 
b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 6409e1f742fd6..55b527e3a9ce6 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -406,14 +406,25 @@ def terminate(self):
 
 class _LocalProcess(_BaseProcess):
     def __init__(self, trace_on):
-        self._proc = None
+        self._proc: Optional[Popen] = None
         self._trace_on = trace_on
         self._delayafterterminate = 0.1
 
     @property
     def pid(self):
+        assert self._proc is not None, "No process"
         return self._proc.pid
 
+    @property
+    def stdout(self):
+        assert self._proc is not None, "No process"
+        return self._proc.stdout
+
+    @property
+    def stderr(self):
+        assert self._proc is not None, "No process"
+        return self._proc.stderr
+
     def launch(self, executable, args, extra_env, **kwargs):
         env = None
         if extra_env:

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py 
b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 992e75244b67a..875cfb6c6b197 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -3,6 +3,7 @@
 # FIXME: remove when LLDB_MINIMUM_PYTHON_VERSION > 3.8
 from __future__ import annotations
 
+from typing import Protocol
 import argparse
 import binascii
 import dataclasses
@@ -42,7 +43,12 @@
 
 # See lldbtest.Base.spawnSubprocess, which should help ensure any processes
 # created by the DAP client are terminated correctly when the test ends.
-SpawnHelperCallback = Callable[[str, List[str], List[str]], subprocess.Popen]
+class SpawnHelperCallback(Protocol):
+    def __call__(
+        self, executable: str, args: List[str], extra_env: List[str], **kwargs
+    ) -> subprocess.Popen:
+        ...
+
 
 ## DAP type references
 
@@ -265,6 +271,7 @@ def __init__(
         self.module_events: List[Dict] = []
         self.sequence: int = 1
         self.output: Dict[str, str] = {}
+        self.reverse_process: Optional[subprocess.Popen] = None
 
         # debuggee state
         self.threads: Optional[dict] = None
@@ -525,8 +532,10 @@ def _handle_reverse_request(self, request: Request) -> 
None:
             assert self.spawn_helper is not None, "Not configured to spawn 
subprocesses"
             [exe, *args] = arguments["args"]
             env = [f"{k}={v}" for k, v in arguments.get("env", {}).items()]
-            proc = self.spawn_helper(exe, args, env)
-            body = {"processId": proc.pid}
+            self.reverse_process = self.spawn_helper(
+                exe, args, env, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+            )
+            body = {"processId": self.reverse_process.pid}
             self.send_packet(
                 {
                     "type": "response",

diff  --git a/lldb/test/API/tools/lldb-dap/launch/io/Makefile 
b/lldb/test/API/tools/lldb-dap/launch/io/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/launch/io/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules

diff  --git a/lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py 
b/lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py
new file mode 100644
index 0000000000000..ef189b9595eac
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py
@@ -0,0 +1,356 @@
+"""
+Test the redirection of stdio.
+There are three ways to launch the debuggee
+internalConsole, integratedTerminal and externalTerminal.
+
+For the three configurations, we test if we can read data
+from environments, stdin and cli arguments.
+
+NOTE: The testcases do not include all possible configurations of
+consoles, environments, stdin and cli arguments.
+"""
+
+from abc import abstractmethod
+from typing import IO
+import lldbdap_testcase
+from tempfile import NamedTemporaryFile
+
+from lldbsuite.test.decorators import (
+    skip,
+    skipIfBuildType,
+    skipIfRemote,
+    skipIfWindows,
+)
+
+
+class DAP_launchIO(lldbdap_testcase.DAPTestCaseBase):
+    """The class holds the implementation 
diff erent ways to redirect the debuggee I/O streams
+    which is configurable from the Derived classes.
+
+    Depending on the console type the output will be in 
diff erent places.
+    It also provides two abstract functions `_get_debuggee_stdout` and 
`_get_debuggee_stderr`
+    that provides the debuggee stdout and stderr.
+    """
+
+    def all_redirection(self, console: str, with_args: bool = False):
+        """Test all standard io redirection."""
+        self.build_and_create_debug_adapter()
+        program = self.getBuildArtifact("a.out")
+        input_text = "from stdin with redirection"
+        args_text = "string from argv"
+        program_args = [args_text] if with_args else None
+
+        with NamedTemporaryFile("wt") as stdin, NamedTemporaryFile(
+            "rt"
+        ) as stdout, NamedTemporaryFile("rt") as stderr:
+            stdin.write(input_text)
+            stdin.flush()
+            self.launch(
+                program,
+                stdio=[stdin.name, stdout.name, stderr.name],
+                console=console,
+                args=program_args,
+            )
+            self.continue_to_exit()
+
+            all_stdout = stdout.read()
+            all_stderr = stderr.read()
+
+            if with_args:
+                self.assertEqual(f"[STDOUT][FROM_ARGV]: {args_text}", 
all_stdout)
+                self.assertEqual(f"[STDERR][FROM_ARGV]: {args_text}", 
all_stderr)
+
+                self.assertNotIn(f"[STDOUT][FROM_ARGV]: {args_text}", 
all_stderr)
+                self.assertNotIn(f"[STDERR][FROM_ARGV]: {args_text}", 
all_stdout)
+
+            else:
+                self.assertEqual(f"[STDOUT][FROM_STDIN]: {input_text}", 
all_stdout)
+                self.assertEqual(f"[STDERR][FROM_STDIN]: {input_text}", 
all_stderr)
+
+                self.assertNotIn(f"[STDERR][FROM_STDIN]: {input_text}", 
all_stdout)
+                self.assertNotIn(f"[STDOUT][FROM_STDIN]: {input_text}", 
all_stderr)
+
+    def stdin_redirection(self, console: str, with_args: bool = False):
+        """Test only stdin redirection."""
+        self.build_and_create_debug_adapter()
+        program = self.getBuildArtifact("a.out")
+        input_text = "string from stdin"
+        args_text = "string from argv"
+        program_args = [args_text] if with_args else None
+
+        with NamedTemporaryFile("w+t") as stdin:
+            stdin.write(input_text)
+            stdin.flush()
+            self.launch(program, stdio=[stdin.name], console=console, 
args=program_args)
+            self.continue_to_exit()
+
+            stdout_text = self._get_debuggee_stdout()
+            stderr_text = self._get_debuggee_stderr()
+
+            if with_args:
+                self.assertIn(f"[STDOUT][FROM_ARGV]: {args_text}", stdout_text)
+                self.assertIn(f"[STDERR][FROM_ARGV]: {args_text}", stderr_text)
+            else:
+                self.assertIn(f"[STDOUT][FROM_STDIN]: {input_text}", 
stdout_text)
+                self.assertIn(f"[STDERR][FROM_STDIN]: {input_text}", 
stderr_text)
+
+    def stdout_redirection(self, console: str, with_env: bool = False):
+        """Test only stdout redirection."""
+        self.build_and_create_debug_adapter()
+        program = self.getBuildArtifact("a.out")
+
+        argv_text = "output with\n multiline"
+        # By default unix terminals the ONLCR flag is enabled. which replaces 
'\n' with '\r\n'
+        # see https://man7.org/linux/man-pages/man3/termios.3.html.
+        # This does not affect writing to normal files.
+        argv_replaced_text = argv_text.replace("\n", "\r\n")
+
+        program_args = [argv_text]
+        env_text = "string from env"
+        env = {"FROM_ENV": env_text} if with_env else {}
+
+        with NamedTemporaryFile("rt") as stdout:
+            self.launch(
+                program,
+                stdio=[None, stdout.name],
+                console=console,
+                args=program_args,
+                env=env,
+            )
+            self.continue_to_exit()
+
+            # check stdout
+            stdout_text = stdout.read()
+            stderr_text = self._get_debuggee_stderr()
+            if with_env:
+                self.assertIn(f"[STDOUT][FROM_ENV]: {env_text}", stdout_text)
+                self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text)
+
+                self.assertNotIn(f"[STDERR][FROM_ENV]: {env_text}", 
stdout_text)
+                self.assertNotIn(f"[STDOUT][FROM_ENV]: {env_text}", 
stderr_text)
+            else:
+                self.assertIn(f"[STDOUT][FROM_ARGV]: {argv_text}", stdout_text)
+
+                self.assertNotIn(
+                    f"[STDERR][FROM_ARGV]: {argv_replaced_text}", stdout_text
+                )
+                self.assertNotIn(f"[STDOUT][FROM_ARGV]: {argv_text}", 
stderr_text)
+
+            # check stderr
+            stderr_text = self._get_debuggee_stderr()
+            # FIXME: when using 'integrated' or 'external' terminal we do not 
correctly
+            # escape newlines that are sent to the terminal.
+            if console == "integratedConsole":
+                if with_env:
+                    self.assertNotIn(f"[STDOUT][FROM_ENV]: {env_text}", 
stderr_text)
+                    self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", 
stderr_text)
+                else:
+                    self.assertNotIn(
+                        f"[STDOUT][FROM_ARGV]: {argv_replaced_text}", 
stderr_text
+                    )
+                    self.assertIn(
+                        f"[STDERR][FROM_ARGV]: {argv_replaced_text}", 
stderr_text
+                    )
+
+    def stderr_redirection(self, console: str, with_env: bool = False):
+        """Test only stdout redirection."""
+        self.build_and_create_debug_adapter()
+        program = self.getBuildArtifact("a.out")
+
+        argv_text = "output with\n multiline"
+        # By default unix terminals the ONLCR flag is enabled. which replaces 
'\n' with '\r\n'
+        # see https://man7.org/linux/man-pages/man3/termios.3.html.
+        # This does not affect writing to normal files.
+        # Currently out test implementation for external and integrated 
Terminal does not run the
+        # program through a shell terminal.
+        argv_replaced_text = argv_text
+        if console == "internalConsole":
+            argv_replaced_text = argv_text.replace("\n", "\r\n")
+        program_args = [argv_text]
+        env_text = "string from env"
+        env = {"FROM_ENV": env_text} if with_env else {}
+
+        with NamedTemporaryFile("rt") as stderr:
+            self.launch(
+                program,
+                stdio=[None, None, stderr.name],
+                console=console,
+                args=program_args,
+                env=env,
+            )
+            self.continue_to_exit()
+            stdout_text = self._get_debuggee_stdout()
+            stderr_text = stderr.read()
+            if with_env:
+                self.assertIn(f"[STDOUT][FROM_ENV]: {env_text}", stdout_text)
+                self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text)
+            else:
+                self.assertIn(f"[STDOUT][FROM_ARGV]: {argv_replaced_text}", 
stdout_text)
+                self.assertIn(f"[STDERR][FROM_ARGV]: {argv_text}", stderr_text)
+
+    @abstractmethod
+    def _get_debuggee_stdout(self) -> str:
+        """Retrieves the standard output (stdout) from the debuggee process.
+
+        The default destination of the debuggee's stdout can vary based on how 
the debugger
+        was launched (either a debug console or a pseudo-terminal (pty)).
+        It requires subclasses to implement the specific mechanism for 
obtaining the stdout stream.
+        """
+        raise RuntimeError(f"NotImplemented for {self}")
+
+    @abstractmethod
+    def _get_debuggee_stderr(self) -> str:
+        """Retrieves the standard error (stderr) from the debuggee process.
+
+        The default destination of the debuggee's stderr can vary based on how 
the debugger
+        was launched (either a debug console or a pseudo-terminal (pty)).
+        It requires subclasses to implement the specific mechanism for 
obtaining the stderr stream.
+        """
+        raise RuntimeError(f"NotImplemented for {self}")
+
+
+@skipIfWindows
+class TestDAP_launchInternalConsole(DAP_launchIO):
+    console = "internalConsole"
+    __debuggee_stdout = None
+
+    # all redirection
+    def test_all_redirection(self):
+        self.all_redirection(console=self.console)
+
+    def test_all_redirection_with_args(self):
+        self.all_redirection(console=self.console, with_args=True)
+
+    # stdin
+    def test_stdin_redirection(self):
+        self.stdin_redirection(console=self.console)
+
+    def test_stdin_redirection_with_args(self):
+        self.stdin_redirection(console=self.console, with_args=True)
+
+    # stdout
+    def test_stdout_redirection(self):
+        self.stdout_redirection(console=self.console)
+
+    def test_stdout_redirection_with_env(self):
+        self.stdout_redirection(console=self.console, with_env=True)
+
+    # stderr
+    def test_stderr_redirection(self):
+        self.stderr_redirection(console=self.console)
+
+    def test_stderr_redirection_with_env(self):
+        self.stderr_redirection(console=self.console, with_env=True)
+
+    def _get_debuggee_stdout(self) -> str:
+        # self.get_stdout is not idempotent.
+        if self.__debuggee_stdout is None:
+            self.__debuggee_stdout = self.get_stdout()
+        return self.__debuggee_stdout
+
+    def _get_debuggee_stderr(self) -> str:
+        # NOTE: In internalConsole stderr writes to stdout.
+        return self._get_debuggee_stdout()
+
+
+@skipIfRemote
+@skipIfBuildType(["debug"])
+@skipIfWindows
+class TestDAP_launchIntegratedTerminal(DAP_launchIO):
+    console = "integratedTerminal"
+
+    # all redirection
+    def test_all_redirection(self):
+        self.all_redirection(console=self.console)
+
+    def test_all_redirection_with_args(self):
+        self.all_redirection(console=self.console, with_args=True)
+
+    # stdin
+    def test_stdin_redirection(self):
+        self.stdin_redirection(console=self.console)
+
+    def test_stdin_redirection_with_args(self):
+        self.stdin_redirection(console=self.console, with_args=True)
+
+    # stdout
+    def test_stdout_redirection(self):
+        self.stdout_redirection(console=self.console)
+
+    def test_stdout_redirection_with_env(self):
+        self.stdout_redirection(console=self.console, with_env=True)
+
+    # stderr
+    def test_stderr_redirection(self):
+        self.stderr_redirection(console=self.console)
+
+    def test_stderr_redirection_with_env(self):
+        self.stderr_redirection(console=self.console, with_env=True)
+
+    def _get_debuggee_stdout(self) -> str:
+        self.assertIsNotNone(
+            self.dap_server.reverse_process, "Expected a debuggee process."
+        )
+        proc_stdout: IO = self.dap_server.reverse_process.stdout
+        self.assertIsNotNone(proc_stdout)
+        return proc_stdout.read().decode()
+
+    def _get_debuggee_stderr(self) -> str:
+        self.assertIsNotNone(
+            self.dap_server.reverse_process, "Expected a debuggee process."
+        )
+        proc_stderr = self.dap_server.reverse_process.stderr
+        self.assertIsNotNone(proc_stderr)
+        return proc_stderr.read().decode()
+
+
+@skip  # NOTE: Currently there is no 
diff erence between internal and externalTerminal.
+@skipIfRemote
+@skipIfBuildType(["debug"])
+@skipIfWindows
+class TestDAP_launchExternalTerminal(DAP_launchIO):
+    console = "externalTerminal"
+
+    # all redirection
+    def test_all_redirection(self):
+        self.all_redirection(console=self.console)
+
+    def test_all_redirection_with_args(self):
+        self.all_redirection(console=self.console, with_args=True)
+
+    # stdin
+    def test_stdin_redirection(self):
+        self.stdin_redirection(console=self.console)
+
+    def test_stdin_redirection_with_args(self):
+        self.stdin_redirection(console=self.console, with_args=True)
+
+    # stdout
+    def test_stdout_redirection(self):
+        self.stdout_redirection(console=self.console)
+
+    def test_stdout_redirection_with_env(self):
+        self.stdout_redirection(console=self.console, with_env=True)
+
+    # stderr
+    def test_stderr_redirection(self):
+        self.stderr_redirection(console=self.console)
+
+    def test_stderr_redirection_with_env(self):
+        self.stderr_redirection(console=self.console, with_env=True)
+
+    def _get_debuggee_stdout(self) -> str:
+        self.assertIsNotNone(
+            self.dap_server.reverse_process, "Expected a debuggee process."
+        )
+        proc_stdout: IO = self.dap_server.reverse_process.stdout
+        self.assertIsNotNone(proc_stdout)
+        return proc_stdout.read().decode()
+
+    def _get_debuggee_stderr(self) -> str:
+        self.assertIsNotNone(
+            self.dap_server.reverse_process, "Expected a debuggee process."
+        )
+        proc_stderr = self.dap_server.reverse_process.stderr
+        self.assertIsNotNone(proc_stderr)
+        return proc_stderr.read().decode()

diff  --git a/lldb/test/API/tools/lldb-dap/launch/io/main.cpp 
b/lldb/test/API/tools/lldb-dap/launch/io/main.cpp
new file mode 100644
index 0000000000000..96a29421c78ea
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/launch/io/main.cpp
@@ -0,0 +1,24 @@
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char *argv[]) {
+  const bool use_stdin = argc <= 1;
+  const char *use_env = std::getenv("FROM_ENV");
+
+  if (use_env != nullptr) { // from environment variable
+    std::cout << "[STDOUT][FROM_ENV]: " << use_env;
+    std::cerr << "[STDERR][FROM_ENV]: " << use_env;
+
+  } else if (use_stdin) { // from standard in
+    std::string line;
+    std::getline(std::cin, line);
+    std::cout << "[STDOUT][FROM_STDIN]: " << line;
+    std::cerr << "[STDERR][FROM_STDIN]: " << line;
+
+  } else { // from argv
+    const char *first_arg = argv[1];
+    std::cout << "[STDOUT][FROM_ARGV]: " << first_arg;
+    std::cerr << "[STDERR][FROM_ARGV]: " << first_arg;
+  }
+  return 0;
+}

diff  --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp 
b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index b06a43d7421c1..5cb0055f034da 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -22,7 +22,9 @@
 #include "llvm/Support/raw_ostream.h"
 #include <mutex>
 
-#if !defined(_WIN32)
+#ifdef _WIN32
+#include "lldb/Host/windows/PosixApi.h"
+#else
 #include <unistd.h>
 #endif
 
@@ -61,25 +63,25 @@ static uint32_t SetLaunchFlag(uint32_t flags, bool flag,
 static void
 SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,
                    lldb::SBLaunchInfo &launch_info) {
-  size_t n = std::max(stdio.size(), static_cast<size_t>(3));
-  for (size_t i = 0; i < n; i++) {
-    std::optional<std::string> path;
-    if (stdio.size() <= i)
-      path = stdio.back();
-    else
-      path = stdio[i];
-    if (!path)
+  for (const auto &[idx, value_opt] : llvm::enumerate(stdio)) {
+    if (!value_opt)
       continue;
-    switch (i) {
+    const std::string &path = value_opt.value();
+    assert(!path.empty() && "paths should not be empty");
+
+    const int fd = static_cast<int>(idx);
+    switch (fd) {
     case 0:
-      launch_info.AddOpenFileAction(i, path->c_str(), true, false);
+      launch_info.AddOpenFileAction(STDIN_FILENO, path.c_str(), true, false);
       break;
     case 1:
+      launch_info.AddOpenFileAction(STDOUT_FILENO, path.c_str(), false, true);
+      break;
     case 2:
-      launch_info.AddOpenFileAction(i, path->c_str(), false, true);
+      launch_info.AddOpenFileAction(STDERR_FILENO, path.c_str(), false, true);
       break;
     default:
-      launch_info.AddOpenFileAction(i, path->c_str(), true, true);
+      launch_info.AddOpenFileAction(fd, path.c_str(), true, true);
       break;
     }
   }

diff  --git a/lldb/tools/lldb-dap/JSONUtils.cpp 
b/lldb/tools/lldb-dap/JSONUtils.cpp
index 8851af65d1c4f..5c33c6aa591a6 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -724,20 +724,24 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
     req_args.push_back("--debugger-pid");
     req_args.push_back(std::to_string(debugger_pid));
   }
-  req_args.push_back("--launch-target");
-  req_args.push_back(program.str());
+
   if (!stdio.empty()) {
-    req_args.push_back("--stdio");
+    req_args.emplace_back("--stdio");
+
     std::stringstream ss;
+    std::string_view delimiter;
     for (const std::optional<std::string> &file : stdio) {
+      ss << std::exchange(delimiter, ":");
       if (file)
         ss << *file;
-      ss << ":";
     }
-    std::string files = ss.str();
-    files.pop_back();
-    req_args.push_back(std::move(files));
+    req_args.push_back(ss.str());
   }
+
+  // WARNING: Any argument added after `launch-target` is passed to to the
+  // target.
+  req_args.emplace_back("--launch-target");
+  req_args.push_back(program.str());
   req_args.insert(req_args.end(), args.begin(), args.end());
   run_in_terminal_args.try_emplace("args", req_args);
 

diff  --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp 
b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index c3225f6ba0e35..1f8283eba86fa 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -296,7 +296,7 @@ bool fromJSON(const json::Value &Params, Console &C, 
json::Path P) {
 bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
               json::Path P) {
   json::ObjectMapper O(Params, P);
-  bool success =
+  const bool success =
       O && fromJSON(Params, LRA.configuration, P) &&
       O.mapOptional("noDebug", LRA.noDebug) &&
       O.mapOptional("launchCommands", LRA.launchCommands) &&
@@ -310,6 +310,13 @@ bool fromJSON(const json::Value &Params, 
LaunchRequestArguments &LRA,
       O.mapOptional("stdio", LRA.stdio) && parseEnv(Params, LRA.env, P);
   if (!success)
     return false;
+
+  for (std::optional<std::string> &io_path : LRA.stdio) {
+    // set empty paths to null.
+    if (io_path && llvm::StringRef(*io_path).trim().empty())
+      io_path.reset();
+  }
+
   // Validate that we have a well formed launch request.
   if (!LRA.launchCommands.empty() &&
       LRA.console != protocol::eConsoleInternal) {

diff  --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index 9e4fb83cebed0..8c7066a3b551b 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -265,7 +265,7 @@ contain the following key/value pairs:
 | **stopOnEntry**                   | boolean     |     | Whether to stop 
program immediately after launching.
 | **runInTerminal** (deprecated)    | boolean     |     | Launch the program 
inside an integrated terminal in the IDE. Useful for debugging interactive 
command line programs.
 | **console**                       | string      |     | Specify where to 
launch the program: internal console (`internalConsole`), integrated terminal 
(`integratedTerminal`) or external terminal (`externalTerminal`). Supported 
from lldb-dap 21.0 version.
-| **stdio**                         | [string]    |     | The stdio property 
specifies the redirection targets for the debuggee's stdio streams. A null 
value redirects a stream to the default debug terminal. String can be a path to 
file, named pipe or TTY device. If less than three values are provided, the 
list will be padded with the last value. Specifying more than three values will 
create additional file descriptors (4, 5, etc.). Supported from lldb-dap 22.0 
version.
+| **stdio**                         | [string]    |     | Redirects the 
debuggee's standard I/O (stdin, stdout, stderr) to a file path, named pipe, or 
TTY device.<br/>Use null to redirect a stream to the default debug 
terminal.<br/>The first three values map to stdin, stdout, and stderr 
respectively, Additional values create extra file descriptors (4, 5, 
etc.).<br/><br/>Example: `stdio: [null, "./output_file"]`, the debuggee uses 
the default terminal for `stdin` and `stderr`, but writes `stdout` to 
`./output_file`.<br/>Added in version 22.0.
 | **launchCommands**                | [string]    |     | LLDB commands 
executed to launch the program.
 
 For JSON configurations of `"type": "attach"`, the JSON configuration can 
contain

diff  --git a/lldb/tools/lldb-dap/extension/package.json 
b/lldb/tools/lldb-dap/extension/package.json
index f66b662160ad3..2a5104fc61dbe 100644
--- a/lldb/tools/lldb-dap/extension/package.json
+++ b/lldb/tools/lldb-dap/extension/package.json
@@ -643,7 +643,7 @@
                     "null"
                   ]
                 },
-                "description": "The stdio property specifies the redirection 
targets for the debuggee's stdio streams. A null value redirects a stream to 
the default debug terminal. String can be a path to file, named pipe or TTY 
device. If less than three values are provided, the list will be padded with 
the last value. Specifying more than three values will create additional file 
descriptors (4, 5, etc.).",
+                "description": "Redirects the debuggee's standard I/O (stdin, 
stdout, stderr) to a file path, named pipe, or TTY device.\nUse null to 
redirect a stream to the default debug terminal.\nThe first three values map to 
stdin, stdout, and stderr respectively.\nAdditional values create extra file 
descriptors (4, 5, etc.).\n\nExample: stdio: [null, \"./output_file\"], the 
debuggee uses the default terminal for stdin and stderr, but writes stdout to 
`./output_file`.\nAdded in version 22.0.",
                 "default": []
               },
               "timeout": {

diff  --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp 
b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 0119f4b1083a9..15c63543e86f5 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -286,11 +286,11 @@ static llvm::Error 
LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
 
   lldb_private::FileSystem::Initialize();
   if (!stdio.empty()) {
-    llvm::SmallVector<llvm::StringRef, 3> files;
-    stdio.split(files, ':');
-    while (files.size() < 3)
-      files.push_back(files.back());
-    if (llvm::Error err = SetupIORedirection(files))
+    constexpr size_t num_of_stdio = 3;
+    llvm::SmallVector<llvm::StringRef, num_of_stdio> stdio_files;
+    stdio.split(stdio_files, ':');
+    stdio_files.resize(std::max(num_of_stdio, stdio_files.size()));
+    if (llvm::Error err = SetupIORedirection(stdio_files))
       return err;
   } else if ((isatty(STDIN_FILENO) != 0) &&
              llvm::StringRef(getenv("TERM")).starts_with_insensitive("xterm")) 
{


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to