From 9be21a15d38e1d951c42533435a937a155b53254 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:40:49 +0000 Subject: [PATCH 1/4] Initial plan From c357f6f15be341e681b768521361bff807e7e3a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:48:14 +0000 Subject: [PATCH 2/4] Fix: Handle long executable paths in Windows process launching Add shortenWindowsCommandLine() to shorten all command line arguments that exceed Windows MAX_PATH limit, not just the working directory. This fixes issues where Eclipse is unable to launch processes with long executable paths on Windows. The fix uses Windows GetShortPathName API via Win32Handler to convert long paths to their 8.3 short form before passing them to ProcessBuilder or Runtime.exec(). Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- .../org/eclipse/debug/core/DebugPlugin.java | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/debug/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java b/debug/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java index 497aba4a8d9..06ffe1c8dd0 100644 --- a/debug/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java +++ b/debug/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java @@ -1006,6 +1006,7 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env public static Process exec(String[] cmdLine, File workingDirectory, String[] envp, boolean mergeOutput) throws CoreException { List factories = DebugPlugin.getDefault().getExecFactories(); Optional directory = shortenWindowsPath(workingDirectory); + String[] shortenedCmdLine = shortenWindowsCommandLine(cmdLine); Optional> envMap = Optional.ofNullable(envp).map(array -> { Map map = new LinkedHashMap<>(); for (String e : array) { @@ -1017,7 +1018,7 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env return Map.copyOf(map); }); for (ExecFactoryFacade holder : factories) { - Optional exec = holder.exec(cmdLine.clone(), directory, envMap, mergeOutput); + Optional exec = holder.exec(shortenedCmdLine.clone(), directory, envMap, mergeOutput); if (exec.isPresent()) { return exec.get(); } @@ -1029,7 +1030,7 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env // ProcessBuilder and Runtime.exec only the new option uses process // builder to not break existing caller of this method if (mergeOutput) { - ProcessBuilder pb = new ProcessBuilder(cmdLine); + ProcessBuilder pb = new ProcessBuilder(shortenedCmdLine); directory.ifPresent(pb::directory); pb.redirectErrorStream(mergeOutput); if (envMap.isPresent()) { @@ -1039,9 +1040,9 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env } return pb.start(); } else if (directory.isEmpty()) { - return Runtime.getRuntime().exec(cmdLine, envp); + return Runtime.getRuntime().exec(shortenedCmdLine, envp); } else { - return Runtime.getRuntime().exec(cmdLine, envp, directory.get()); + return Runtime.getRuntime().exec(shortenedCmdLine, envp, directory.get()); } } catch (IOException e) { Status status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_0, e); @@ -1083,6 +1084,50 @@ private static Optional shortenWindowsPath(File path) { return Optional.ofNullable(path); } + /** + * Shortens the command line elements if they exceed Windows MAX_PATH limit. + * This is necessary because Windows process creation APIs have problems with + * long paths, even when launching executables or passing file arguments. + * + * @param cmdLine the command line array + * @return the potentially shortened command line array + */ + private static String[] shortenWindowsCommandLine(String[] cmdLine) { + if (cmdLine == null || cmdLine.length == 0 || !Platform.OS.isWindows()) { + return cmdLine; + } + + String[] result = cmdLine.clone(); + boolean modified = false; + + // Check and shorten each path-like argument in the command line + // The first element is typically the executable path, which is most critical + for (int i = 0; i < result.length; i++) { + String arg = result[i]; + if (arg != null && arg.length() > WINDOWS_MAX_PATH) { + // Check if this looks like a file path + File file = new File(arg); + if (file.isAbsolute()) { + @SuppressWarnings("restriction") + String shortPath = org.eclipse.core.internal.filesystem.local.Win32Handler.getShortPathName(arg); + if (shortPath != null) { + result[i] = shortPath; + modified = true; + if (i == 0) { + // Log only for the executable (first argument) + logDebugMessage("Shortened executable path from " + arg.length() + " to " + shortPath.length() + " characters"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } else if (i == 0) { + // Only warn for the executable path, as that's the most critical + log(Status.warning("Executable path exceeds Window's MAX_PATH limit and shortening the path failed: " + arg)); //$NON-NLS-1$ + } + } + } + } + + return modified ? result : cmdLine; + } + /** * Returns whether this plug-in is in the process of * being shutdown. From 94f9f829b22fad6bcdc4fec2c8eee68d4d4aea8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:49:55 +0000 Subject: [PATCH 3/4] Add test for long executable paths on Windows Add testProcessLaunchWithLongExecutablePath() to verify that processes can be launched when the executable path exceeds Windows MAX_PATH limit. The test creates a directory with a very long path, copies java.exe to it, and verifies the executable can be launched successfully. Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- .../debug/tests/launching/LaunchTests.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java index 07cd3af313a..5a067704517 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java @@ -157,6 +157,33 @@ public void testProcessLaunchWithLongWorkingDirectory() throws CoreException, IO startProcessAndAssertOutputContains(List.of("java", "--version"), workingDirectory, true, "jdk"); } + @Test + public void testProcessLaunchWithLongExecutablePath() throws CoreException, IOException { + assumeTrue(Platform.OS.isWindows()); + + // Create a directory with a very long path + int rootLength = tempFolder.getRoot().toString().length(); + String subPathElementsName = "subfolder-with-relativly-long-name"; + String[] segments = Collections.nCopies((400 - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new); + File longPathDir = tempFolder.newFolder(segments); + assertTrue(longPathDir.toString().length() > 300); + + // Copy a system executable (java) to the long path + String javaHome = System.getProperty("java.home"); + File javaExe = new File(javaHome, "bin/java.exe"); + File longPathExe = new File(longPathDir, "java.exe"); + + // Copy the executable + java.nio.file.Files.copy(javaExe.toPath(), longPathExe.toPath()); + assertTrue(longPathExe.exists()); + String longExePath = longPathExe.getAbsolutePath(); + assertTrue("Executable path should exceed MAX_PATH", longExePath.length() > 258); + + // Launch the executable from the long path + startProcessAndAssertOutputContains(List.of(longExePath, "--version"), tempFolder.getRoot(), false, "jdk"); + startProcessAndAssertOutputContains(List.of(longExePath, "--version"), tempFolder.getRoot(), true, "jdk"); + } + private static void startProcessAndAssertOutputContains(List cmdLine, File workingDirectory, boolean mergeOutput, String expectedOutput) throws CoreException, IOException { Process process = DebugPlugin.exec(cmdLine.toArray(String[]::new), workingDirectory, null, mergeOutput); String output; From 34fc7b671f013270ca7ab1f6f94e73942731b6a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:52:37 +0000 Subject: [PATCH 4/4] Address code review feedback for test improvements - Fix spelling: 'relativly' -> 'relatively' - Add constants for magic numbers (WINDOWS_MAX_PATH, LONG_PATH_LENGTH_TARGET) - Use constants instead of hardcoded values for better maintainability Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- .../debug/tests/launching/LaunchTests.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java index 5a067704517..ec28429bad7 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java @@ -55,6 +55,18 @@ */ public class LaunchTests extends AbstractLaunchTest { + /** + * Windows MAX_PATH limit for file paths. + * See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation + */ + private static final int WINDOWS_MAX_PATH = 258; + + /** + * Target length for long path tests. This should be well above MAX_PATH to ensure + * the tests exercise the long path handling code. + */ + private static final int LONG_PATH_LENGTH_TARGET = 400; + private InvocationHandler handler; private Runnable readIsTerminatedTask; private Runnable readIsDisconnectedTask; @@ -146,8 +158,8 @@ public void testProcessLaunchWithLongWorkingDirectory() throws CoreException, IO assumeTrue(Platform.OS.isWindows()); int rootLength = tempFolder.getRoot().toString().length(); - String subPathElementsName = "subfolder-with-relativly-long-name"; - String[] segments = Collections.nCopies((400 - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new); + String subPathElementsName = "subfolder-with-relatively-long-name"; + String[] segments = Collections.nCopies((LONG_PATH_LENGTH_TARGET - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new); File workingDirectory = tempFolder.newFolder(segments); assertTrue(workingDirectory.toString().length() > 300); @@ -163,8 +175,8 @@ public void testProcessLaunchWithLongExecutablePath() throws CoreException, IOEx // Create a directory with a very long path int rootLength = tempFolder.getRoot().toString().length(); - String subPathElementsName = "subfolder-with-relativly-long-name"; - String[] segments = Collections.nCopies((400 - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new); + String subPathElementsName = "subfolder-with-relatively-long-name"; + String[] segments = Collections.nCopies((LONG_PATH_LENGTH_TARGET - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new); File longPathDir = tempFolder.newFolder(segments); assertTrue(longPathDir.toString().length() > 300); @@ -177,7 +189,7 @@ public void testProcessLaunchWithLongExecutablePath() throws CoreException, IOEx java.nio.file.Files.copy(javaExe.toPath(), longPathExe.toPath()); assertTrue(longPathExe.exists()); String longExePath = longPathExe.getAbsolutePath(); - assertTrue("Executable path should exceed MAX_PATH", longExePath.length() > 258); + assertTrue("Executable path should exceed MAX_PATH", longExePath.length() > WINDOWS_MAX_PATH); // Launch the executable from the long path startProcessAndAssertOutputContains(List.of(longExePath, "--version"), tempFolder.getRoot(), false, "jdk");