diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java index 6f54bcedb..cc4dd4ece 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java @@ -273,19 +273,21 @@ public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedNa return result; }); - Integer sourceReference = 0; String uri = source.getUri(); - if (source.getType().equals(SourceType.REMOTE)) { - sourceReference = context.createSourceReference(source.getUri()); - } - if (!StringUtils.isBlank(uri)) { // The Source.path could be a file system path or uri string. if (uri.startsWith("file:")) { + int sourceReference = getSourceReference(source, context); String clientPath = AdapterUtils.convertPath(uri, context.isDebuggerPathsAreUri(), context.isClientPathsAreUri()); return new Types.Source(sourceName, clientPath, sourceReference); } else { + Types.Source sourceInSourcePaths = resolveSourceFromSourcePaths(sourceName, relativeSourcePath, context); + if (sourceInSourcePaths != null) { + return sourceInSourcePaths; + } + + int sourceReference = getSourceReference(source, context); // If the debugger returns uri in the Source.path for the StackTrace response, VSCode client will try to find a TextDocumentContentProvider // to render the contents. // Language Support for Java by Red Hat extension has already registered a jdt TextDocumentContentProvider to parse the jdt-based uri. @@ -295,15 +297,25 @@ public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedNa } } else { // If the source lookup engine cannot find the source file, then lookup it in the source directories specified by user. - String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath); - if (absoluteSourcepath != null) { - return new Types.Source(sourceName, absoluteSourcepath, sourceReference); - } else { - return null; - } + return resolveSourceFromSourcePaths(sourceName, relativeSourcePath, context); } } + private static int getSourceReference(Source source, IDebugAdapterContext context) { + return SourceType.REMOTE == source.getType() ? context.createSourceReference(source.getUri()) : 0; + } + + private static Types.Source resolveSourceFromSourcePaths(String sourceName, String relativeSourcePath, + IDebugAdapterContext context) { + String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath); + if (absoluteSourcepath == null) { + return null; + } + + String clientPath = AdapterUtils.convertPath(absoluteSourcepath, false, context.isClientPathsAreUri()); + return new Types.Source(sourceName, clientPath, 0); + } + private String formatMethodName(String methodName, List argumentTypeNames, String fqn, boolean showContextClass, boolean showParameter) { StringBuilder formattedName = new StringBuilder(); if (showContextClass) { diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandlerTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandlerTest.java new file mode 100644 index 000000000..ae1198374 --- /dev/null +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandlerTest.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2026 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import static org.easymock.EasyMock.expect; +import static org.junit.Assert.assertEquals; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.easymock.EasyMockSupport; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import com.microsoft.java.debug.core.adapter.DebugAdapterContext; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.ProviderContext; +import com.microsoft.java.debug.core.adapter.Source; +import com.microsoft.java.debug.core.adapter.SourceType; +import com.microsoft.java.debug.core.protocol.Types; + +public class StackTraceRequestHandlerTest extends EasyMockSupport { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void convertDebuggerSourceShouldPreferSourcePathsOverCachedJdtUri() throws Exception { + String fullyQualifiedName = "com.example.Foo"; + String sourceName = "Foo.java"; + String relativeSourcePath = Paths.get("com", "example", sourceName).toString(); + String jdtUri = "jdt://contents/foo.jar/com.example/Foo.java?=handle"; + Path sourceRoot = tempFolder.newFolder("sources").toPath(); + Path sourceFile = sourceRoot.resolve(relativeSourcePath); + Files.createDirectories(sourceFile.getParent()); + Files.write(sourceFile, "package com.example; class Foo {}\n".getBytes(StandardCharsets.UTF_8)); + + ISourceLookUpProvider sourceProvider = createMock(ISourceLookUpProvider.class); + expect(sourceProvider.getSource(fullyQualifiedName, relativeSourcePath)) + .andReturn(new Source(jdtUri, SourceType.LOCAL)) + .once(); + replayAll(); + + ProviderContext providerContext = new ProviderContext(); + providerContext.registerProvider(ISourceLookUpProvider.class, sourceProvider); + DebugAdapterContext context = new DebugAdapterContext(null, providerContext); + context.setSourcePaths(new String[0]); + + Types.Source jdtSource = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName, + relativeSourcePath, context); + assertEquals(jdtUri, jdtSource.path); + + context.setSourcePaths(new String[] { sourceRoot.toString() }); + Types.Source localSource = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName, + relativeSourcePath, context); + + assertEquals(sourceFile.toString(), localSource.path); + assertEquals(0, localSource.sourceReference); + + context.setClientPathsAreUri(true); + Types.Source localUriSource = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, + sourceName, relativeSourcePath, context); + + assertEquals(sourceFile.toUri().toString(), localUriSource.path); + verifyAll(); + } + + @Test + public void convertDebuggerSourceShouldHandleSourceWithNullType() throws Exception { + String fullyQualifiedName = "com.example.Bar"; + String sourceName = "Bar.java"; + Path sourceFile = tempFolder.newFile(sourceName).toPath(); + String sourceUri = sourceFile.toUri().toString(); + + ISourceLookUpProvider sourceProvider = createMock(ISourceLookUpProvider.class); + expect(sourceProvider.getSource(fullyQualifiedName, sourceName)) + .andReturn(new Source(sourceUri, null)) + .once(); + replayAll(); + + ProviderContext providerContext = new ProviderContext(); + providerContext.registerProvider(ISourceLookUpProvider.class, sourceProvider); + DebugAdapterContext context = new DebugAdapterContext(null, providerContext); + + Types.Source source = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName, + sourceName, context); + + assertEquals(sourceFile.toString(), source.path); + assertEquals(0, source.sourceReference); + verifyAll(); + } +}