Skip to content

Commit 6f5a9b3

Browse files
Debug support on the decompiled source code (#495)
* Debug support on the decompiled source code
1 parent 25dd38f commit 6f5a9b3

File tree

8 files changed

+219
-7
lines changed

8 files changed

+219
-7
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class DebugSettings {
4444
public int limitOfVariablesPerJdwpRequest = 100;
4545
public int jdwpRequestTimeout = 3000;
4646
public AsyncMode asyncJDWP = AsyncMode.OFF;
47+
public Switch debugSupportOnDecompiledSource = Switch.OFF;
4748

4849
public static DebugSettings getCurrent() {
4950
return current;
@@ -97,6 +98,13 @@ public static enum AsyncMode {
9798
OFF
9899
}
99100

101+
public static enum Switch {
102+
@SerializedName("on")
103+
ON,
104+
@SerializedName("off")
105+
OFF
106+
}
107+
100108
public static interface IDebugSettingChangeListener {
101109
public void update(DebugSettings oldSettings, DebugSettings newSettings);
102110
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
public class JavaBreakpointLocation {
1919
/**
20-
* The source line of the breakpoint or logpoint.
20+
* The line number in the source file.
21+
*/
22+
private int lineNumberInSourceFile = Integer.MIN_VALUE;
23+
/**
24+
* The line number in the class file.
2125
*/
2226
private int lineNumber;
2327
/**
@@ -110,4 +114,12 @@ public Types.BreakpointLocation[] availableBreakpointLocations() {
110114
public void setAvailableBreakpointLocations(Types.BreakpointLocation[] availableBreakpointLocations) {
111115
this.availableBreakpointLocations = availableBreakpointLocations;
112116
}
117+
118+
public int lineNumberInSourceFile() {
119+
return lineNumberInSourceFile == Integer.MIN_VALUE ? lineNumber : lineNumberInSourceFile;
120+
}
121+
122+
public void setLineNumberInSourceFile(int lineNumberInSourceFile) {
123+
this.lineNumberInSourceFile = lineNumberInSourceFile;
124+
}
113125
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,58 @@ public static String decodeURIComponent(String uri) {
310310
return uri;
311311
}
312312
}
313+
314+
/**
315+
* Find the mapped lines based on the given line number.
316+
*
317+
* The line mappings format is as follows:
318+
* - [i]: key
319+
* - [i+1]: value
320+
*/
321+
public static int[] binarySearchMappedLines(int[] lineMappings, int targetLine) {
322+
if (lineMappings == null || lineMappings.length == 0 || lineMappings.length % 2 != 0) {
323+
return null;
324+
}
325+
326+
final int MAX = lineMappings.length / 2 - 1;
327+
int low = 0;
328+
int high = MAX;
329+
int found = -1;
330+
while (low <= high) {
331+
int mid = low + (high - low) / 2;
332+
int actualMid = mid * 2;
333+
if (lineMappings[actualMid] == targetLine) {
334+
found = mid;
335+
break;
336+
}
337+
338+
if (lineMappings[actualMid] < targetLine) {
339+
low = mid + 1;
340+
} else {
341+
high = mid - 1;
342+
}
343+
}
344+
345+
if (found == -1) {
346+
return null;
347+
}
348+
349+
// Find the duplicates in the sorted array
350+
int left = found;
351+
while ((left - 1) >= 0 && lineMappings[(left - 1) * 2] == targetLine) {
352+
left--;
353+
}
354+
355+
int right = found;
356+
while ((right + 1) <= MAX && lineMappings[(right + 1) * 2] == targetLine) {
357+
right++;
358+
}
359+
360+
int[] values = new int[right - left + 1];
361+
for (int i = 0; i < values.length; i++) {
362+
values[i] = lineMappings[(left + i) * 2 + 1];
363+
}
364+
365+
return values;
366+
}
313367
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,26 @@ default String getJavaRuntimeVersion(String projectName) {
7676
*/
7777
List<MethodInvocation> findMethodInvocations(String uri, int line);
7878

79+
/**
80+
* Return the line mappings from the original line to the decompiled line.
81+
*
82+
* @param uri The uri
83+
* @return the line mappings from the original line to the decompiled line.
84+
*/
85+
default int[] getOriginalLineMappings(String uri) {
86+
return null;
87+
}
88+
89+
/**
90+
* Return the line mappings from the decompiled line to the original line.
91+
*
92+
* @param uri The uri
93+
* @return the line mappings from the decompiled line to the original line.
94+
*/
95+
default int[] getDecompiledLineMappings(String uri) {
96+
return null;
97+
}
98+
7999
public static class MethodInvocation {
80100
public String expression;
81101
public String methodName;

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import com.microsoft.java.debug.core.Configuration;
2626
import com.microsoft.java.debug.core.DebugException;
27+
import com.microsoft.java.debug.core.DebugSettings;
28+
import com.microsoft.java.debug.core.DebugSettings.Switch;
2729
import com.microsoft.java.debug.core.IBreakpoint;
2830
import com.microsoft.java.debug.core.IDebugSession;
2931
import com.microsoft.java.debug.core.IEvaluatableBreakpoint;
@@ -296,7 +298,8 @@ public static boolean handleEvaluationResult(IDebugAdapterContext context, Threa
296298
private Types.Breakpoint convertDebuggerBreakpointToClient(IBreakpoint breakpoint, IDebugAdapterContext context) {
297299
int id = (int) breakpoint.getProperty("id");
298300
boolean verified = breakpoint.getProperty("verified") != null && (boolean) breakpoint.getProperty("verified");
299-
int lineNumber = AdapterUtils.convertLineNumber(breakpoint.getLineNumber(), context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
301+
int lineNumber = AdapterUtils.convertLineNumber(breakpoint.sourceLocation().lineNumberInSourceFile(),
302+
context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
300303
return new Types.Breakpoint(id, verified, lineNumber, "");
301304
}
302305

@@ -318,6 +321,19 @@ private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Type
318321
} catch (NumberFormatException e) {
319322
hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition.
320323
}
324+
325+
if (DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON) {
326+
// Align the decompiled line with the original line.
327+
int[] lineMappings = sourceProvider.getDecompiledLineMappings(sourceFile);
328+
if (locations[i] != null && lineMappings != null) {
329+
int lineNumberInSourceFile = locations[i].lineNumber();
330+
int[] originalLines = AdapterUtils.binarySearchMappedLines(lineMappings, lineNumberInSourceFile);
331+
if (originalLines != null && originalLines.length > 0) {
332+
locations[i].setLineNumberInSourceFile(lineNumberInSourceFile);
333+
locations[i].setLineNumber(originalLines[0]);
334+
}
335+
}
336+
}
321337
breakpoints[i] = context.getDebugSession().createBreakpoint(locations[i], hitCount, sourceBreakpoints[i].condition,
322338
sourceBreakpoints[i].logMessage);
323339
if (sourceProvider.supportsRealtimeBreakpointVerification() && StringUtils.isNotBlank(locations[i].className())) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import org.apache.commons.lang3.StringUtils;
2626

2727
import com.microsoft.java.debug.core.AsyncJdwpUtils;
28+
import com.microsoft.java.debug.core.DebugSettings;
2829
import com.microsoft.java.debug.core.DebugUtility;
2930
import com.microsoft.java.debug.core.IBreakpoint;
31+
import com.microsoft.java.debug.core.DebugSettings.Switch;
3032
import com.microsoft.java.debug.core.adapter.AdapterUtils;
3133
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
3234
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
@@ -189,6 +191,14 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
189191
// display "Unknown Source" in the Call Stack View.
190192
clientSource = null;
191193
}
194+
} else if (DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON
195+
&& clientSource != null && clientSource.path != null) {
196+
// Align the original line with the decompiled line.
197+
int[] lineMappings = context.getProvider(ISourceLookUpProvider.class).getOriginalLineMappings(clientSource.path);
198+
int[] renderLines = AdapterUtils.binarySearchMappedLines(lineMappings, clientLineNumber);
199+
if (renderLines != null && renderLines.length > 0) {
200+
clientLineNumber = renderLines[0];
201+
}
192202
}
193203

194204
int clientColumnNumber = context.isClientColumnsStartAt1() ? 1 : 0;

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.eclipse.core.resources.IResource;
3737
import org.eclipse.core.resources.ResourcesPlugin;
3838
import org.eclipse.core.runtime.CoreException;
39+
import org.eclipse.core.runtime.NullProgressMonitor;
3940
import org.eclipse.core.runtime.URIUtil;
4041
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
4142
import org.eclipse.jdt.core.IBuffer;
@@ -60,14 +61,19 @@
6061
import org.eclipse.jdt.launching.IVMInstall;
6162
import org.eclipse.jdt.launching.JavaRuntime;
6263
import org.eclipse.jdt.launching.LibraryLocation;
64+
import org.eclipse.jdt.ls.core.internal.DecompilerResult;
6365
import org.eclipse.jdt.ls.core.internal.JDTUtils;
66+
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
67+
import org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager;
6468

6569
import com.microsoft.java.debug.BindingUtils;
6670
import com.microsoft.java.debug.BreakpointLocationLocator;
6771
import com.microsoft.java.debug.LambdaExpressionLocator;
6872
import com.microsoft.java.debug.core.Configuration;
6973
import com.microsoft.java.debug.core.DebugException;
74+
import com.microsoft.java.debug.core.DebugSettings;
7075
import com.microsoft.java.debug.core.JavaBreakpointLocation;
76+
import com.microsoft.java.debug.core.DebugSettings.Switch;
7177
import com.microsoft.java.debug.core.adapter.AdapterUtils;
7278
import com.microsoft.java.debug.core.adapter.Constants;
7379
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
@@ -303,10 +309,42 @@ private CompilationUnit asCompilationUnit(String uri) {
303309
} else {
304310
// For non-file uri (e.g. jdt://contents/rt.jar/java.io/PrintStream.class),
305311
// leverage jdt to load the source contents.
306-
ITypeRoot typeRoot = resolveClassFile(uri);
307-
if (typeRoot != null) {
308-
parser.setSource(typeRoot);
309-
astUnit = (CompilationUnit) parser.createAST(null);
312+
IClassFile typeRoot = resolveClassFile(uri);
313+
try {
314+
if (typeRoot != null && typeRoot.getSourceRange() != null) {
315+
parser.setSource(typeRoot);
316+
astUnit = (CompilationUnit) parser.createAST(null);
317+
} else if (typeRoot != null && DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON) {
318+
ContentProviderManager contentProvider = JavaLanguageServerPlugin.getContentProviderManager();
319+
try {
320+
String contents = contentProvider.getSource(typeRoot, new NullProgressMonitor());
321+
if (contents != null && !contents.isBlank()) {
322+
IJavaProject javaProject = typeRoot.getJavaProject();
323+
if (javaProject != null) {
324+
parser.setProject(javaProject);
325+
} else {
326+
parser.setEnvironment(new String[0], new String[0], null, true);
327+
/**
328+
* See the java doc for { @link ASTParser#setSource(char[]) },
329+
* the user need specify the compiler options explicitly.
330+
*/
331+
Map<String, String> javaOptions = JavaCore.getOptions();
332+
javaOptions.put(JavaCore.COMPILER_SOURCE, this.latestJavaVersion);
333+
javaOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, this.latestJavaVersion);
334+
javaOptions.put(JavaCore.COMPILER_COMPLIANCE, this.latestJavaVersion);
335+
javaOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED);
336+
parser.setCompilerOptions(javaOptions);
337+
}
338+
parser.setUnitName(typeRoot.getElementName());
339+
parser.setSource(contents.toCharArray());
340+
astUnit = (CompilationUnit) parser.createAST(null);
341+
}
342+
} catch (Exception e) {
343+
JavaLanguageServerPlugin.logException(e.getMessage(), e);
344+
}
345+
}
346+
} catch (JavaModelException e) {
347+
// ignore
310348
}
311349
}
312350
return astUnit;
@@ -533,4 +571,58 @@ private boolean isSameURI(String uri1, String uri2) {
533571
return false;
534572
}
535573
}
574+
575+
public int[] getOriginalLineMappings(String uri) {
576+
IClassFile classFile = resolveClassFile(uri);
577+
try {
578+
if (classFile == null) {
579+
return null;
580+
}
581+
582+
IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) classFile.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
583+
if (packageRoot != null && packageRoot.getSourceAttachmentPath() != null) {
584+
return null;
585+
}
586+
587+
if (classFile.getSourceRange() == null) {
588+
ContentProviderManager contentProvider = JavaLanguageServerPlugin.getContentProviderManager();
589+
try {
590+
DecompilerResult result = contentProvider.getSourceResult(classFile, new NullProgressMonitor());
591+
if (result != null) {
592+
return result.getOriginalLineMappings();
593+
}
594+
} catch (NoSuchMethodError e) {
595+
// ignore it if old language server version is installed.
596+
} catch (Exception e) {
597+
JavaLanguageServerPlugin.logException(e.getMessage(), e);
598+
}
599+
}
600+
} catch (JavaModelException e) {
601+
// ignore
602+
}
603+
return null;
604+
}
605+
606+
public int[] getDecompiledLineMappings(String uri) {
607+
IClassFile classFile = resolveClassFile(uri);
608+
try {
609+
if (classFile != null && classFile.getSourceRange() == null) {
610+
ContentProviderManager contentProvider = JavaLanguageServerPlugin.getContentProviderManager();
611+
try {
612+
DecompilerResult result = contentProvider.getSourceResult(classFile, new NullProgressMonitor());
613+
if (result != null) {
614+
return result.getDecompiledLineMappings();
615+
}
616+
} catch (NoSuchMethodError e) {
617+
// ignore it if old Java language server version is installed.
618+
} catch (Exception e) {
619+
JavaLanguageServerPlugin.logException(e.getMessage(), e);
620+
}
621+
}
622+
} catch (JavaModelException e) {
623+
// ignore
624+
}
625+
626+
return null;
627+
}
536628
}

com.microsoft.java.debug.target/com.microsoft.java.debug.tp.target

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<repository location="https://download.eclipse.org/jdtls/snapshots/repository/latest/"/>
3232
</location>
3333
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
34-
<repository location="https://download.eclipse.org/lsp4j/updates/releases/0.20.0/"/>
34+
<repository location="https://download.eclipse.org/lsp4j/updates/releases/0.21.0/"/>
3535
<unit id="org.eclipse.lsp4j.sdk.feature.group" version="0.0.0"/>
3636
</location>
3737
</locations>

0 commit comments

Comments
 (0)