Skip to content

Commit 3f03453

Browse files
authored
fix: prefer sourcePaths before JDT source URIs (#629)
* Prefer sourcePaths before JDT source URIs When JDT source lookup returns a non-file URI, check debug sourcePaths before returning that URI to the client. Add a regression test covering cached JDT URI results being overridden by sourcePaths. * Address source path review feedback Use a null-safe SourceType comparison and convert sourcePaths matches to the client's expected path format. Extend regression coverage for URI clients and null source types.
1 parent 3adbc03 commit 3f03453

2 files changed

Lines changed: 126 additions & 11 deletions

File tree

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -273,19 +273,21 @@ public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedNa
273273
return result;
274274
});
275275

276-
Integer sourceReference = 0;
277276
String uri = source.getUri();
278277

279-
if (source.getType().equals(SourceType.REMOTE)) {
280-
sourceReference = context.createSourceReference(source.getUri());
281-
}
282-
283278
if (!StringUtils.isBlank(uri)) {
284279
// The Source.path could be a file system path or uri string.
285280
if (uri.startsWith("file:")) {
281+
int sourceReference = getSourceReference(source, context);
286282
String clientPath = AdapterUtils.convertPath(uri, context.isDebuggerPathsAreUri(), context.isClientPathsAreUri());
287283
return new Types.Source(sourceName, clientPath, sourceReference);
288284
} else {
285+
Types.Source sourceInSourcePaths = resolveSourceFromSourcePaths(sourceName, relativeSourcePath, context);
286+
if (sourceInSourcePaths != null) {
287+
return sourceInSourcePaths;
288+
}
289+
290+
int sourceReference = getSourceReference(source, context);
289291
// If the debugger returns uri in the Source.path for the StackTrace response, VSCode client will try to find a TextDocumentContentProvider
290292
// to render the contents.
291293
// 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
295297
}
296298
} else {
297299
// If the source lookup engine cannot find the source file, then lookup it in the source directories specified by user.
298-
String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath);
299-
if (absoluteSourcepath != null) {
300-
return new Types.Source(sourceName, absoluteSourcepath, sourceReference);
301-
} else {
302-
return null;
303-
}
300+
return resolveSourceFromSourcePaths(sourceName, relativeSourcePath, context);
304301
}
305302
}
306303

304+
private static int getSourceReference(Source source, IDebugAdapterContext context) {
305+
return SourceType.REMOTE == source.getType() ? context.createSourceReference(source.getUri()) : 0;
306+
}
307+
308+
private static Types.Source resolveSourceFromSourcePaths(String sourceName, String relativeSourcePath,
309+
IDebugAdapterContext context) {
310+
String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath);
311+
if (absoluteSourcepath == null) {
312+
return null;
313+
}
314+
315+
String clientPath = AdapterUtils.convertPath(absoluteSourcepath, false, context.isClientPathsAreUri());
316+
return new Types.Source(sourceName, clientPath, 0);
317+
}
318+
307319
private String formatMethodName(String methodName, List<String> argumentTypeNames, String fqn, boolean showContextClass, boolean showParameter) {
308320
StringBuilder formattedName = new StringBuilder();
309321
if (showContextClass) {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core.adapter.handler;
13+
14+
import static org.easymock.EasyMock.expect;
15+
import static org.junit.Assert.assertEquals;
16+
17+
import java.nio.charset.StandardCharsets;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.nio.file.Paths;
21+
22+
import org.easymock.EasyMockSupport;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.junit.rules.TemporaryFolder;
26+
27+
import com.microsoft.java.debug.core.adapter.DebugAdapterContext;
28+
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
29+
import com.microsoft.java.debug.core.adapter.ProviderContext;
30+
import com.microsoft.java.debug.core.adapter.Source;
31+
import com.microsoft.java.debug.core.adapter.SourceType;
32+
import com.microsoft.java.debug.core.protocol.Types;
33+
34+
public class StackTraceRequestHandlerTest extends EasyMockSupport {
35+
@Rule
36+
public TemporaryFolder tempFolder = new TemporaryFolder();
37+
38+
@Test
39+
public void convertDebuggerSourceShouldPreferSourcePathsOverCachedJdtUri() throws Exception {
40+
String fullyQualifiedName = "com.example.Foo";
41+
String sourceName = "Foo.java";
42+
String relativeSourcePath = Paths.get("com", "example", sourceName).toString();
43+
String jdtUri = "jdt://contents/foo.jar/com.example/Foo.java?=handle";
44+
Path sourceRoot = tempFolder.newFolder("sources").toPath();
45+
Path sourceFile = sourceRoot.resolve(relativeSourcePath);
46+
Files.createDirectories(sourceFile.getParent());
47+
Files.write(sourceFile, "package com.example; class Foo {}\n".getBytes(StandardCharsets.UTF_8));
48+
49+
ISourceLookUpProvider sourceProvider = createMock(ISourceLookUpProvider.class);
50+
expect(sourceProvider.getSource(fullyQualifiedName, relativeSourcePath))
51+
.andReturn(new Source(jdtUri, SourceType.LOCAL))
52+
.once();
53+
replayAll();
54+
55+
ProviderContext providerContext = new ProviderContext();
56+
providerContext.registerProvider(ISourceLookUpProvider.class, sourceProvider);
57+
DebugAdapterContext context = new DebugAdapterContext(null, providerContext);
58+
context.setSourcePaths(new String[0]);
59+
60+
Types.Source jdtSource = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName,
61+
relativeSourcePath, context);
62+
assertEquals(jdtUri, jdtSource.path);
63+
64+
context.setSourcePaths(new String[] { sourceRoot.toString() });
65+
Types.Source localSource = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName,
66+
relativeSourcePath, context);
67+
68+
assertEquals(sourceFile.toString(), localSource.path);
69+
assertEquals(0, localSource.sourceReference);
70+
71+
context.setClientPathsAreUri(true);
72+
Types.Source localUriSource = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName,
73+
sourceName, relativeSourcePath, context);
74+
75+
assertEquals(sourceFile.toUri().toString(), localUriSource.path);
76+
verifyAll();
77+
}
78+
79+
@Test
80+
public void convertDebuggerSourceShouldHandleSourceWithNullType() throws Exception {
81+
String fullyQualifiedName = "com.example.Bar";
82+
String sourceName = "Bar.java";
83+
Path sourceFile = tempFolder.newFile(sourceName).toPath();
84+
String sourceUri = sourceFile.toUri().toString();
85+
86+
ISourceLookUpProvider sourceProvider = createMock(ISourceLookUpProvider.class);
87+
expect(sourceProvider.getSource(fullyQualifiedName, sourceName))
88+
.andReturn(new Source(sourceUri, null))
89+
.once();
90+
replayAll();
91+
92+
ProviderContext providerContext = new ProviderContext();
93+
providerContext.registerProvider(ISourceLookUpProvider.class, sourceProvider);
94+
DebugAdapterContext context = new DebugAdapterContext(null, providerContext);
95+
96+
Types.Source source = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName,
97+
sourceName, context);
98+
99+
assertEquals(sourceFile.toString(), source.path);
100+
assertEquals(0, source.sourceReference);
101+
verifyAll();
102+
}
103+
}

0 commit comments

Comments
 (0)